Connection of Serial ADC to FPGA
The ADC (Analog to Digital Converter) can be interfaced to FPGA/ASIC in a different way depending on the output interface. This post gives an overview of the different interfaces available in ADC interfacing. On modern ADC, when the sampling rate is below 10 Msample/sec the ADCs often implement a serial interface to provide sampled data to the user. The serial interface is a little bit complex w.r.t. a parallel interface but the use of serial protocol reduces the number of wires and allows interfacing the ADC to a microprocessor like Arduino or Raspberry Pi.
In the serial interface, the serial clock provided by the device connected to the ADC is also used as ADC sampling clock.
In this post, we will see an example of how to interface the TI ADC128S022 used in the Altera DE0-nano Board
The ADC128S022 device is a low-power, eight-channel CMOS 12-bit analog-to-digital converter specified for conversion throughput rates of 50 ksps to 200 ksps. The converter is based on a successive approximation register architecture with an internal track-and-hold circuit. It can be configured to accept up to eight input signals at inputs IN0 through IN7.
The output serial data is straight binary and is compatible with several standards, such as SPI, QSPI, MICROWIRE, and many common DSP serial interfaces.
ADC Serial Protocol
The ADC serial protocol is a simple SPI protocol.
During the first 3 cycles of SCLK, the ADC is in track mode, acquiring the input voltage. For the next 13 SCLK cycles the conversion is accomplished and the data is clocked out. The SCLK falling edges 1 through 4 clocks out leading zeros while falling edges 5 through 16 clocks out the conversion result, MSB first. If there is more than one conversion in a frame (continuous conversion mode as in the figure), the ADC will re-enter the track mode on the falling edge of SCLK after the N*16th rising edge of SCLK and re-enter the hold/convert mode on the N*16+4th falling edge of SCLK. “N” is an integer value. For further information on SPI ADC128S022 protocol here you can find the datasheet.
ADC128S022 controller VHDL implementation
In order to convert and read from the ADC the sampled data the protocol of Figure2 shall be implemented. On DE0-nano, the board clock is 50 MHz.
The ADC128S022 can work from 0.8 MHz to 3.2 MHz. Using the board clock we can generate the maximum sampling clock as 50/16 = 3.125 MHz.
In this example, the VHDL code of the serial ADC is implemented using 5 main blocks divided into 5 VHDL processes.
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity adc_serial_control is generic( CLK_DIV : integer := 100 ); -- input clock divider to generate output serial clock; o_sclk frequency = i_clk/(CLK_DIV) port ( i_clk : in std_logic; i_rstb : in std_logic; i_conv_ena : in std_logic; -- enable ADC convesion i_adc_ch : in std_logic_vector(2 downto 0); -- ADC channel 0-7 o_adc_data_valid : out std_logic; -- conversion valid pulse o_adc_ch : out std_logic_vector(2 downto 0); -- ADC converted channel o_adc_data : out std_logic_vector(11 downto 0); -- adc parallel data -- ADC serial interface o_sclk : out std_logic; o_ss : out std_logic; o_mosi : out std_logic; i_miso : in std_logic); end adc_serial_control; architecture rtl of adc_serial_control is constant C_N : integer := 16; signal r_counter_clock : integer range 0 to CLK_DIV; signal r_sclk_rise : std_logic; signal r_sclk_fall : std_logic; signal r_counter_clock_ena : std_logic; signal r_counter_data : integer range 0 to C_N-1; signal r_tc_counter_data : std_logic; signal r_conversion_running : std_logic; -- enable serial data protocol signal r_miso : std_logic; signal r_conv_ena : std_logic; -- enable ADC convesion signal r_adc_ch : std_logic_vector(2 downto 0); -- ADC converted channel signal r_adc_data : std_logic_vector(11 downto 0); -- adc parallel data begin -------------------------------------------------------------------- -- FSM p_conversion_control : process(i_clk,i_rstb) begin if(i_rstb='0') then r_conv_ena <= '0'; r_conversion_running <= '0'; r_counter_clock_ena <= '0'; elsif(rising_edge(i_clk)) then r_conv_ena <= i_conv_ena; if(r_conv_ena='1') then r_conversion_running <= '1'; elsif(r_conv_ena='0') and (r_tc_counter_data='1') then -- terminate current conversion r_conversion_running <= '0'; end if; r_counter_clock_ena <= r_conversion_running; -- enable clock divider end if; end process p_conversion_control; p_counter_data : process(i_clk,i_rstb) begin if(i_rstb='0') then r_counter_data <= 0; r_tc_counter_data <= '0'; elsif(rising_edge(i_clk)) then if(r_counter_clock_ena='1') then if(r_sclk_rise='1') then -- count data @ o_sclk rising edge if(r_counter_data<C_N-1) then r_counter_data <= r_counter_data + 1; r_tc_counter_data <= '0'; else r_counter_data <= 0; r_tc_counter_data <= '1'; end if; else r_tc_counter_data <= '0'; end if; else r_counter_data <= 0; r_tc_counter_data <= '0'; end if; end if; end process p_counter_data; -- Serial Input Process p_serial_input : process(i_clk,i_rstb) begin if(i_rstb='0') then r_miso <= '0'; r_adc_ch <= (others=>'0'); r_adc_data <= (others=>'0'); elsif(rising_edge(i_clk)) then r_miso <= i_miso; if(r_tc_counter_data='1') then r_adc_ch <= i_adc_ch; -- strobe new end if; case r_counter_data is when 4 => r_adc_data(11) <= r_miso; when 5 => r_adc_data(10) <= r_miso; when 6 => r_adc_data( 9) <= r_miso; when 7 => r_adc_data( 8) <= r_miso; when 8 => r_adc_data( 7) <= r_miso; when 9 => r_adc_data( 6) <= r_miso; when 10 => r_adc_data( 5) <= r_miso; when 11 => r_adc_data( 4) <= r_miso; when 12 => r_adc_data( 3) <= r_miso; when 13 => r_adc_data( 2) <= r_miso; when 14 => r_adc_data( 1) <= r_miso; when 15 => r_adc_data( 0) <= r_miso; when others => NULL; end case; end if; end process p_serial_input; -- SERIAL Output process p_serial_output : process(i_clk,i_rstb) begin if(i_rstb='0') then o_ss <= '1'; o_mosi <= '1'; o_sclk <= '1'; o_adc_data_valid <= '0'; o_adc_ch <= (others=>'0'); o_adc_data <= (others=>'0'); elsif(rising_edge(i_clk)) then o_ss <= not r_conversion_running; if(r_tc_counter_data='1') then o_adc_ch <= r_adc_ch; -- update current conversion o_adc_data <= r_adc_data; end if; o_adc_data_valid <= r_tc_counter_data; if(r_counter_clock_ena='1') then -- sclk = '1' by default if(r_sclk_rise='1') then o_sclk <= '1'; elsif(r_sclk_fall='1') then o_sclk <= '0'; end if; else o_sclk <= '1'; end if; if(r_sclk_fall='1') then case r_counter_data is when 2 => o_mosi <= r_adc_ch(2); when 3 => o_mosi <= r_adc_ch(1); when 4 => o_mosi <= r_adc_ch(0); when others => NULL; end case; end if; end if; end process p_serial_output; -- CLOCK divider p_counter_clock : process(i_clk,i_rstb) begin if(i_rstb='0') then r_counter_clock <= 0; r_sclk_rise <= '0'; r_sclk_fall <= '0'; elsif(rising_edge(i_clk)) then if(r_counter_clock_ena='1') then if(r_counter_clock=(CLK_DIV/2)-1) then -- firse edge = fall r_counter_clock <= r_counter_clock + 1; r_sclk_rise <= '0'; r_sclk_fall <= '1'; elsif(r_counter_clock=(CLK_DIV-1)) then r_counter_clock <= 0; r_sclk_rise <= '1'; r_sclk_fall <= '0'; else r_counter_clock <= r_counter_clock + 1; r_sclk_rise <= '0'; r_sclk_fall <= '0'; end if; else r_counter_clock <= 0; r_sclk_rise <= '0'; r_sclk_fall <= '0'; end if; end if; end process p_counter_clock; end rtl;
In the control logic process “p_conversion_control” the clock generation is enabled using an input control signal “i_conv_ena“. When the conversion is disabled, the control logic will complete the current conversion before stopping the process.
The process “p_counter_data” counts the current clock rising edge and controls the data conversion. The counter is used as a sequencer of the current conversion state.
In the process “p_serial_input” the input ADC channel is sampled before starting the conversion and provided on the serial output data line to the ADC. The “r_counter_data” internal counter will demux the input serial data into a parallel register. At the end of the current, the received “r_adc_data” will be stored in the output parallel 12-bit register “o_adc_data”
The process “p_serial_output” will drive the serial clock output and serial data output to the ADC.
In the serial clock generator process ”p_counter_clock” the serial clock is generated by the division of the board clock. The clock generation is disabled if no conversion is required.
The video shows how to implement the serial ADC VHDL code controller on the DE0-nano Altera board.
Serial ADC controller VHDL code Simulation
In the simulation windows, the board clock is 50 MHz (20 ns). The Clock division factor is 16 so the generated serial clock will be 3.125 MHz (i.e.320 ns)
Serial ADC controller VHDL code implementation on FPGA
The DE0-nano provides 8 LEDs. In order to verify the ADC controller VHDL code, the 8 LEDs of the board are connected to the 8 ADC MSB, so we can use the led to “read” the ADC converted values.
The ADC channel is selected using the switch SW 3..1 that will select the serial ADC input channel 0 to 7. The Serial ADC conversion is controlled using SW4 that can enable/disable the ADC conversion.
If you appreciate this post, please help us to share it with your friend.
To contact us, please write to: surf.vhdl@gmail.com
We appreciate any of your comments, please post below:
tnx…..
Hi
I want to implement a spi for interface between ADC and FPGA. I read your code. but i have problem. would you please help me?
o_ss must active after one O_sclk that ADC start to sent new data. but this code just enable o_ss one time.
how can i active o_ss after one o_sclk?
just modify the VHDL code accordingly
Hi I’m typing run your codes on de1-Soc board. Can you please help me to identify the pin planer ?
you can check the user manual of the board, then update the pinout file
Hi! I’m kinda start my first fpga project, could you help me how to update the pinout file please? thankyou
you can take a look here
https://surf-vhdl.teachable.com
How to implement adc output in pwm vhdl code for max-10 fpga? Could you help me, please…
What do you mean with
“adc output in pwm”?
Hi,I am trying to write a simulation for my ADC interface,do you have a test bench for this ?
send me an email
Hi, could you please send me the testbench of the controller?
Hi, Thanks a lot. Could you please send me the test bench?
Hi
Can you please explain what is the difference between ADC and ADC interface in figure 1.
How can I interface AD7476 with fpga? Can you please explain the process.
in this post, I deal with serial ADC.
I mean, the interfacing between the ADC and FPGA is a serial interface. Figure 1 reports the typical serial interface
Hi, Thanks a lot. Could you please send me the test bench?
ali.a.alizadeh87@gmail.com
hi can you please send me the test bench?
johnzafeirakis4444@gmail.com
Hi Surf VHDL,
Thanks for posting this. I recently started working with an ADC (ADS1675 from TI, 24 bit Resolution and 4 MSPS) and configured it with DE10 Nano. This ADC produces serial output at up to 96MHz clock. I need to capture this output with the FPGA on DE10 nano and transfer it then to Computer. It will be very much appreciated if you bit advice on doing it. What would be the best way of doing it?
Thanks in Advance
you can store into the board RAM and then download on the PC using a USB or UART connection.
You can use the
https://www.ftdichip.com/Products/Modules/DevelopmentModules.htm#FT4232H-56_Mini
to implement a USB-UART connection.
of course you need to write the code on your PC and implement a sort of communication protocol
P.S.
All of this stuffs are in my course
https://surf-vhdl.teachable.com/p/start-learning-vhdl-using-fpga
Hello there,
Could you please send me the test bench?
email: dhossain@crimson.ua.edu
Thank You
Thank you so much for this great explaination.
I would like if you could send me the test bench?
Here is my email: isamgharib@gmail.com
Best regards
Thank you so much for this excellent explaination!
I would really appreciate if you could send me the test bench
Here is my email: juanf.ramros@gmail.com
Best regards
Thank you so much for the great explanation.
Could you please send me the TB?
My Email:
ah.jouya@gmail.com
Best regards
Thank you so much for this great explaination.
I would like if you could send me the test bench?
Here is my email: himashini.m@gmail.com
Could you send me the test bench?
email : himasshini.m@gmail.com
Hi , cloud you please send me the test bench
My email : Ahmad7rb90@gmail.com
hi can you please send me the test bench?
jrlin20@yahoo.com
Thank You
Hello, thanks for this lesson. Very informative. Could you please send me the test bench?
My email is onistephen1@gmail.com
Thank you
First of all, thank you so much for this lesson
I would like if you could send me the test bench?
Here is my email: bolshoi.booze1999@gmail.com
Thank you
Hello,
First of all, thank you so much for this work.
I would like if you could send me the test bench?
and thank u
Thank you so much or this sharing. It helps a lot in order for me to understand more about spi. Apart from that, can I have the .xdc file or .ucf file for this project? You can email me at m.ariffrosman@gmail.com. Thank you so much
I am a bit confuse where you route the adc output to leds?
I need testbench code can u send me.
Hello,
thank you so much for your effort to explain all.
Could you please send me the TB?
Hello !
In first I want to thanks you a lot for your work and your help.
I analyse your code and simulate it, but there is just a little problem, the first conversion doesn’t take in account the channel specified.
The problem is that you output the channel in the end of conversion, so the first one is negliged….
Do you have any idea to change simply the code without have more signals ?
Anyway, thanks again for everything ! 🙂
I apologize, I just don’t take time for reading correclty the datasheet of ADC to see that the channel data on DIN is for the next frame of conversion.
Hello !
I am looking for some code that uses the ADC from the DE0-Nano board and that reads data from the 8 analog input channels by multiplexing. The langage could be Verilog or VHDL or even C code.
Any idea ?
Thanks in advance…
Mathieu Winger
Hi, thanks for this sharing!
Could you please send me the testbench?