ADC readout and USB2.0 data transfer with an FPGA

Recently I bought the Altera DE0 Development and Education FPGA board from Terasic. This board has a Cyclone III FPGA and some periphery on board. Also two expansion header to connect own hardware are brought out. The board gives me finally the chance to also do some FPGA developments at home. Even if it’s not the most powerful FPGA it is enough to play around and has an affordable price of around 100$.
As probably a lot of people have realized I like to play with ADCs and read them out into the computer. It’s my kind of “Hallo World” program when playing with electronics. I’m also trying to increase the speed of my readout when moving from one architecture to the next one. Or maybe for faster readout I need to change architecture :P.
The good part with the FPGA is that one has a real time platform and can avoid some hassle one gets when for example using a microprocessor like a Raspberry Pi running an operations system (see Raspberry Pi as an Oscilloscope @ 10 MSPS).
The DE0 Development board does not contain a USB interface for data read out. The only option to read out the data is to use the serial interface or the JTAG interface. Both are not very fast.
Therefore I decided to equip my FPGA with a USB 2.0 interface to be able to transfer with high speed the data into the PC.
This is not only important for me for this ADC project but also for some other projects I have in mind for the future.

As a USB 2.0 chip I picked the FT232H Single Channel Hi-Speed USB to Multipurpose UART/FIFO IC from FTDI (Future Technology Devices International). The chip provides a USB2.0 interface to several protocols like RS232, FT1248, SPI, FIFO …
I bought the development board UM232H for this chip which is a tiny board one can put on a breakout board.

Since I want to interface a fast ADC I took the AD9057 from Analog Devices which is available for different speed up to 80 MSPS. I have the 40 MSPS version to begin with. The ADC has a resolution of 8 bits.

Since ADC, USB controller and FPGA need to be connected I developed some circuits to do so.

Starting with the USB controller, which was put on a break out board and interfaced via some level shifter/buffer with the FPGA. The USB controller is run in FiFo mode which requires some setup in the EEPROM.

FPGA_USB_Interface

As can be seen in the drawing a 74HC4050 buffer was used to buffer RXF, TXE, RD, WR, OE and the 60 MHz clock from the USB interface and a 74HC245 bi direction level shifter was used to buffer the 8 bit for the data transfer line. A bidirectional buffer in needed since the 8 pins are used as an in and output.
I have the buffer and level shifter there to protect the FPGA since the 3.3 V of the FPGA and the 3.3V of the USB interface are not the same source. I don’t want to put on the FPGA pins the 3.3V of the USB controller, so it’s for protection.
And in fact I discovered that the 60 MHz signal pin is higher than 3.3 V when idle.
So the buffers are strongly recommended!

The next step was the design of the ADC PCB. The ADC comes as a SMD and therefore needs to be soldered. For this the CAD program Eagle was used and a PCB was created and etched.

FPGA_ADC_Sch FPGA_ADC_PCB

This gives also the possibility to make a nice flat cable contact in order to connect nicely with the FPGA board. It should be noted that there is a 50 Ohm termination which can be connected by a jumper and is optionally. With unconnected jumper the input has a high impedance, with connected jumper there is 50 Ohm termination. This connection is probably not optimal but it is an easy option to switch impedance.

Etched and soldered the PCB looks like this:

FPGA_ADC2

FPGA_ADC1
FPGA_ADC3

With this all hardware is available to be connected to the FPGA.
What is missing is the design code for the FPGA connecting all the parts together. The code is written in VHDL.
Which will be described in the following. For the design the Altera Quartus II development framework was used and a control part for the USB device and for the ADC were designed. All parts are glued together forming a picture like this:

FPGA_ADC_Schematic

Several parts can be found in the schematic:
FreqDiv: This is a frequency divider to produce the clock frequency for the ADC from the local clock (50 MHz) on the D0 board.

ADCRead: This basically reads the 8 bits from the ADC on every raising edge of the clock.

FiFo: This is a FiFo from the Altera IP MegaWizard. It has two separate clock inputs, one for the write clock and one for the read clock. The FiFo is needed at this stage since we need to synchronize two different clock domains. We have on the writing side the clock of the ADC and on the reading side we have the clock of the USB controller (60 MHz) see data sheet for further information.

FiFo_USB_CONTROL: Does the interfacing between the USB controller and the FIFO, in order to tell the FiFo when to read from it, to check if data is available and to interface the State Machine of the USB interface.

The state machine is probably the most important part of the design.

-----------------------------------------------------------------------------
-- Title           : FT245 control
-----------------------------------------------------------------------------
-- Author          : Daniel Pelikan
-- Date Created    : 20-07-2014
-----------------------------------------------------------------------------
-- Description     : State Machine for the FT245 fifo interface
--							
--
-----------------------------------------------------------------------------
-- Copyright 2014. All rights reserved
-----------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity ft245_control is
--  generic (
--    g_Variable : integer := 10    
--    );
	port 
	(
      FT_CLK   : in std_logic;                     -- 60 MHz FT232H clock
      TXE      : in std_logic;                     -- Can TX
      RXF      : in std_logic;                     -- Can RX
      RD      : out std_logic;                     -- Read -- low to be able to read
      --SIWU      : out std_logic;                     -- Send Immediate / WakeUp
      OE      : out std_logic;                     -- Output enable, high to write to USB
      WR      : out std_logic;                  -- FIFO Buffer Write Enable, low to write to usb
      FT_INOUT      : inout std_logic_vector(7 downto 0);   -- Bidirectional FIFO data
		
	
		data_av	:	in std_logic;	-- data is available to be written to USB
		data_in	:	in std_logic_vector(7 downto 0);
		data_out	:	out std_logic_vector(7 downto 0);
		reset		: 	in std_logic;	-- low for reset
		read_data : in std_logic -- put high if you want to read data
		
	);

end entity;

architecture MOORE of ft245_control is

--	signal tmp : std_logic := 0 ;
--	constant const    : std_logic_vector(3 downto 0) := "1000";



type state_type is (SM_Idle,SM_Next_isWrite,SM_Write,SM_Next_isRead, SM_Read);
signal current_state, next_state : state_type;

signal counter : std_logic_vector (3 downto 0) := "0000";
signal int_RD, int_WR, int_OE : std_logic;
signal int_FT_INOUT : std_logic_vector(7 downto 0) := "00000000";
signal countup : std_logic :='0';


begin
	P0:process(current_state,data_av,TXE,RXF,read_data)
	begin
		case current_state is
		
			when SM_Idle =>	
				if(data_av='1' and TXE ='0') then -- what happens if read is also high?
					next_state<=SM_Next_isWrite;
					
				elsif (read_data='1' and RXF = '0') then
					next_state<=SM_Next_isRead;
					
				else
					next_state<=SM_Idle;
				end if;
				
			when SM_Next_isWrite =>
					next_state<=SM_Write;
					
			when SM_Write =>
					
				if(data_av='1' and TXE ='0' and read_data='0') then
					next_state<=SM_Write;
				else
					next_state<=SM_Idle;
				end if;
			
			when SM_Next_isRead =>
				next_state<=SM_Read;
				
			when SM_Read =>
				-- put here a mode to stay in read mode if more data is available
				next_state<=SM_Idle;

			when others => next_state<= SM_Idle;
		end case;
	end process;

	
	P1: process(FT_CLK,reset)
	begin
		if reset='0' then
			current_state<=SM_Idle;	
		elsif rising_edge(FT_CLK) then
				current_state<=next_state;
				if (countup='1') then
					counter<=counter+1;
					--int_FT_INOUT<="0011" & counter;--data_in;
				end if;
				int_FT_INOUT<=data_in;
			
		end if;
	end process;

	P2:process(current_state,counter,FT_INOUT,TXE,RXF)
	begin
		
		
		case current_state is
			when SM_Idle=> 				int_RD<='1';
												int_OE<='1';
												int_WR<='1';
												countup<='0';
												

							
			when SM_Next_isWrite => 	int_RD<='1';
												int_OE<='1';
												int_WR<='1';
												countup<='0';												
												--int_FT_INOUT<="0011" & counter;--data_in;
												
												
			when SM_Write => 				int_RD<='1';
												int_OE<='1';
												countup<='1';
												if(TXE ='0' and data_av='1') then 
													int_WR<='0'; -- the WR low can be used to trigger that the next byte is load into data_in
												else 
													int_WR<='1';
												end if;	
												
												
			when SM_Next_isRead => 		int_RD<='1';
												int_OE<='0';
												int_WR<='1';
												countup<='0';
												--int_FT_INOUT <= (others =>'Z');
												--data_out <= FT_INOUT;
												
												
			
			when SM_Read =>  				
												int_OE<='0';
												int_WR<='1';
												countup<='0';
												--int_FT_INOUT <= (others =>'Z');
												if(RXF = '0') then
													int_RD<='0';
												else	
													int_RD<='1';
												end if;	
												--data_out <= FT_INOUT;
							
							
		end case;
	end process;
	
	--Make available to the outside
	RD<=int_RD;
	WR<=int_WR;
	OE<=int_OE;
	FT_INOUT<=int_FT_INOUT;
	data_out <= FT_INOUT;
end MOORE;


The state Machine was tested for writing to the USB controller only, so I’m not 100% sure if the read is working or not.

On the PC side a readout can be performed by using the official libftd2xx library from FTDI or by using the open source library libftdi which comes with most Linux distributions.
I use the libftdi open source library since I have a better performance when reading from the USB interface. I could read up to 44 MBytes/second when reading from the interface.
The library comes with a nice example for performance testing:
stream_test.c
After compilation the code can be run with the command
stream_test -n out.txt
which will write the read data into the out.txt file and will show the speed of readout.
It can be necessary to modify the code


if (ftdi_usb_open_desc(ftdi, 0x0403, 0x6014, descstring, NULL) < 0)

in order to set the right device. This is also a good starting point for further development.

With all devices in place everything looks like:

FPGA_ALL

In order to test if the ADC is working properly a sine wave was generated by using a DAC and an Arduino and send to the ADC connected with the FPGA. The sine wave has approximately a amplitude of 1Vpp which is the range of the ADC.
After readout from the FPGA the bytes from the ADC are dumped in a file. Then each bytes is converted into an integer and plot by gnuplot. Since the ADC is 8 bit the sine wave oscillates between 0 and 255.

FPGA_ADC_Plot

FPGA_ADC_Plot2

The described code can be found on GitHub
https://github.com/digibird1/FPGA_ADC

It can be checked out with:

git clone https://github.com/digibird1/FPGA_ADC

6 comments on “ADC readout and USB2.0 data transfer with an FPGA
  1. Bharat says:

    I am trying to develop the VHDL code for ltc2314 to FPGA (spartan 6) to USB (FTDI mini module) to LabVIEW (FTDI driver). I am just starting FPGA coding not easier to grasp your state machine. Would it be possible for you to send me commented version of the code. I will acknowledge your help if I am successfull in finishing my board develpment
    Thanks & best regards
    Yogita

  2. Reza says:

    Hi. Great job. I have some questions. I would be really thankful for your instructions. How do you calculate the ADC clk rare? why FreqDiv, how much is the ADC clk speed?
    Thank you.

    • digibird1 says:

      G21 is the 50 MHz clock pin connected to the oscillator. The clock is divided to change the sample rate of the ADC. This is done by simply holding a logic low for a certain amount of 50 MHZ pulses and a logic high for a certain amount. Simple counting. By this you can divide the clock. I think in the pic the clock of the ADC is 25 MHz had just a quick look in the code.

      • Reza says:

        Thanks for answers. How do you calculate the sample rate? by default the sample rate of AD9057 are 40, 60 and 80 MSPS. What is your desired sample rate in this project? Does it work based on one sample per one hz? Could you please help me with your calculation.
        And one more, in your vhdl code:
        g_DIV : integer := 50000
        .
        .
        .
        elsif(rising_edge(clk)) then –syncrone reset
        –if(nrst_in=’0′) then
        –tmp<=x"00000000"; — on reset set to 0
        –q_out<='0';
        –else
        tmp<= tmp + 2;
        –end if;

        if(tmp=g_DIV) then –we devide
        tmp<=0;
        q_out<=not q_out;
        end if;

        What is the output frequency? Is adc sampling rate equal to this?
        Thanks

  3. Reza says:

    And what is test switches for (Test[7..0])?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: