This chapter is only applicable to the Papilio One board when used with the LogicStart MegaWing, as the Basys2 does not include any ADC functionality - it is still a useful read as it shows how simple peripherals can be to interface to.
Unlike other projects so far, I’ve included the full code for the module, giving some sort of reference implementation that can be used to verify your own design.
The ADC on the LogicStart is an eight-channel 12-bit ADC, with a serial interface compatible with the Serial Peripheral Interface Bus ("SPI") standard. The reference voltage for the ADC is 3.3V, giving a resolution of about 0.8mV.
The official SPI bus specifications uses four logic signals. They are called:
-
SCLK: serial clock (output from master);
-
MOSI; SIMO: master output, slave input (output from master);
-
MISO; SOMI: master input, slave output (output from slave);
-
SS: slave select (active low, output from master).
But for this design I’m following the names used in the datasheet - which are named from the perspective of the slave device:
-
CS; Chip Select
-
DIN; Data In
-
DOUT; Data Out
-
SCLK; Serial Clock
To read channel 0 of the ADC it is pretty simple:
-
Hold DIN low (this ensures that you read channel 0)
-
Hold CS high while the ADC is idle
-
Lower CS when you are ready to convert a sample
-
Send 16 clock pulses with a frequency somewhere between 8MHz and 16MHz
-
Raise CS when finished
The data bits will be available on DOUT for clock pulses 4 through 16.
Reading a different channel is a little harder - you need to give the ADC the bits to select the channel for the next sample on clock pulses 2, 3 and 4. These bits are sent in MSB first order.
This sounds simple enough, but as ever the difficulty is in the details. To make this work the setup and holdup times must be factored in:
-
CS must go low a few ns before the SCLK line drops for the first time
-
The DOUT signal transitions just after the rising edge of the SCLK signal. For reliable results it needs to be sampled in the middle of the clock pulse
-
The DIN signal must be given enough time to be stable before the SCLK falls
I decided that the easiest way to do this is to run a counter at the 32MHz clock of the crystal, then the gross timings for the signals are:
-
the SCLK signal is generated from bit 2 of a counter running at the system clock of 32MHz
-
bits 3 through 6 indicate what bit of the frame we are on
-
if bit 7 or over are set, then CS is held high
-
data is sampled when the lowest two bits are "10"
To ensure that I don’t have any setup and holdup time issues with the interface, a shift register is used to delay the SCLK signal by one cycle, and a second shift register is used to delay DIN by three clocks. This ensures that CS and DIN have plenty of setup and holdup time.
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity AtoD is
port
(
clk : IN std_logic;
-- user interface
switches : IN std_logic_vector(2 downto 0);
leds : OUT std_logic_vector(7 downto 0);
-- Signals to the ADC
ADC_CS_N : OUT std_logic;
ADC_SCLK : OUT std_logic;
ADC_DIN : OUT std_logic;
ADC_DOUT : IN std_logic
);
end entity;
architecture rtl of AtoD is
-- Counter - the lowest 6 bits are used to control signals to the ADC.
-- The rest are used to activate the ADC when 0
signal counter : std_logic_vector(22 downto 0) := (others =>'0');
-- shift registers fo delay output signals
signal clk_shiftreg : std_logic_vector( 1 downto 0) := (others =>'0');
signal dataout_shiftreg : std_logic_vector( 2 downto 0) := (others =>'0');
-- shift register to collect incoming bits
signal datain_shiftreg : std_logic_vector(11 downto 0) := (others =>'0');
-- register to hold the current channel
signal channel_hold : std_logic_vector( 2 downto 0) := (others =>'0');
signal adc_active : std_logic;
begin
-- set outoging signals
adc_din <= dataout_shiftreg(2);
adc_sclk <= clk_shiftreg(1);
with counter(22 downto 6) select adc_active <= '1' when "00000000000000000",
'0' when others;
process (clk)
begin
if rising_edge(clk) then
-- A small shift register delays the clk by one cycle (31.25ns) to ensure timings are met.
clk_shiftreg(1) <= clk_shiftreg(0);
-- Including adc_cs_n in a clocked process to ensure that it is adc_cs is implemented in a flipflop
adc_cs_n <= not(adc_active);
if adc_active = '1' then
clk_shiftreg(0) <= counter(1);
else
clk_shiftreg(0) <= '1';
end if;
-- This controls where we send out the address to the ADC (bits 2,3 and 4 of the stream)
-- we use a short shift register to ensure that the ADC_DOUT transistions are delayed
-- 31 ns or so from the clk transitions
dataout_shiftreg(2 downto 1) <= dataout_shiftreg(1 downto 0);
if adc_active = '1' then
case counter(5 downto 2) is
when "0010" => dataout_shiftreg(0) <= channel_hold(2);
when "0011" => dataout_shiftreg(0) <= channel_hold(1);
when "0100" => dataout_shiftreg(0) <= channel_hold(0);
when others => dataout_shiftreg(0) <= '0';
end case;
-- As counter(2) is used used to generate sclk, this test ensures that we
-- capture bits right in the middle of the clock pulse
if counter(5 downto 0) = "000000" then
channel_hold <= switches;
end if;
if counter(1 downto 0) = "11" then
datain_shiftreg <= datain_shiftreg(10 downto 0) & adc_dout;
end if;
-- When we have captured the last bit it is the time to update the output.
if counter(5 downto 0) = "111111" then
-- Normally you would grab "datain_shiftreg(10 downto 0) & adc_dout" for 12 bits
LEDs <= datain_shiftreg(10 downto 3);
end if;
else
dataout_shiftreg(0) <= '0';
end if;
counter <= counter+1;
end if;
end process;
end rtl;
Constraints for the Papilio One board: ~~~~~~~~~~~~ The constraints required to implement the interface are:
NET LEDs(7) LOC = "P5"; NET LEDs(6) LOC = "P9"; NET LEDs(5) LOC = "P10"; NET LEDs(4) LOC = "P11"; NET LEDs(3) LOC = "P12"; NET LEDs(2) LOC = "P15"; NET LEDs(1) LOC = "P16"; NET LEDs(0) LOC = "P17"; NET switches(2) LOC = "P2"; NET switches(1) LOC = "P3"; NET switches(0) LOC = "P4"; NET ADC_CS_N LOC="P70"; NET ADC_SCLK LOC="P86"; NET ADC_DOUT LOC="P79"; NET ADC_DIN LOC="P84"; NET "clk" LOC="P89" | IOSTANDARD=LVCMOS25 | PERIOD=31.25ns;
-
Modify the above project to output all 12 bits, and display the value on the Seven Segment display in hex.
A jumper wire with a 100 Ohm resistor is useful for testing, but only test using the GND, 2.5V and 3.3V signals - connecting the ADC to 5V will damage it! Another option is to use one of the colour channels on the VGA socket, giving you a range of sixteen test values.
-
If you multiply the value received by 129/16, you have a range of 0 to 33016 - very close to 10,000*Vin. The multiplication is easy to do in logic, but can you convert the resulting binary back to decimal to display on the seven segment display? One easy way would be to build a decimal counter, that counts up to the sampled value.