Jump to content
  • 0

pmodAD2 pmodDA1 problem with two simultaneous channels


iratidance

Question

Dear All,

 

I am trying to get two signals in the pmodAD1 simultaneously and send the data through the two channels in the pmodDA2 simultaneously. I got the code working for a single channel and I tried to modify it for two of them. However, even though the compilation does not give me any error, I do not see any signal out of the pmod in the oscilloscope.

 

Please, find attached the piece of code. Any help or suggestion will be really appreciate it!

 

Thanks a lot!

2channel main.txt

2channel pins.txt

Link to comment
Share on other sites

22 answers to this question

Recommended Posts

I couldn't help myself, so I ammened it into something that could be functionally similar, and fixes a few glitches/gotchas, the most important being that the SYNC and CS signals shouldn't be aligned to SCLK clock edges.

 

It is also self recovers from errors (including single bit upsets!) so requires no reset signal, and you can tweak the length of SCLK and the sampling cycle. It uses 25% less FPGA resources, and clicks about 25% faster (Fmax of 253MHz vs 201MHz on Spartan 3E, when constrained for either a 250MHz or 200MHz clock).. 

 

However.... (and a big however) I don't have the hardware to test it on, but it looks OK in simulation, it might still be buggy!

 

It might however give you some ideas - you will need to add in your filter, of course!

library ieee;
use ieee.std_logic_1164.ALL;
use ieee.std_logic_unsigned.ALL;
use ieee.numeric_std.all;
 
entity adc_dac is
    port(   CLK   : in std_logic;   -- 100MHz clock
            CS    : out std_logic;  -- chip select for ADC(active low)
            SCLK  : out std_logic;  -- ADC
            DIN_0 : in std_logic;   -- ADC
            DIN_1 : in std_logic;   -- ADC
 
            SYNC   : out std_logic; -- SYNC for DAC
            SCLK2  : out std_logic; -- DAC
            DOUT_0 : out std_logic; -- DAC
            DOUT_1 : out std_logic   --DAC
            );
end adc_dac;
 
architecture Behavioral of adc_dac is    
    signal data_0 : unsigned(11 downto 0) := (others=>'0');
    signal data_1 : unsigned(11 downto 0) := (others=>'0');
    
    -----------------------------------------------------------------------------
    -- You can control the sampling frequency with the length of 
    -- sequncer_shift_reg and ce_sr.
    --
    -- F(sclk) =F(clk)/(2*(ce_sr'length+1))
    --
    -- Sampling freqency is F(sclk)/ (sequncer_shift_reg'length+1)
    --
    -- with 100MHz and ce_sr being four bits long SCLK is 10MHz.
    -- with sequncer_shift_reg of 19 bits, that gives a sample rate of 0.5MHz
    -----------------------------------------------------------------------------
    signal ce_sr               : std_logic_vector(3 downto 0) := (others=>'X');    
    signal sequncer_shift_reg  : std_logic_vector(18 downto 0) := (others=>'X');
 
    signal clock_state         : std_logic := 'X';
    signal clock_enable        : std_logic := 'X';
    signal din0_shift_reg      : std_logic_vector(15 downto 0) := (others=>'X');
    signal din1_shift_reg      : std_logic_vector(15 downto 0) := (others=>'X');
    signal dout0_shift_reg     : std_logic_vector(15 downto 0) := (others=>'X');
    signal dout1_shift_reg     : std_logic_vector(15 downto 0) := (others=>'X');
 
begin
    -----------------------------------
    -- Generate the clock enable signal
    -- Change the length of ce_sr to 
    -- change the clock speed
    -----------------------------------
    clock_divide : process(CLK)
        begin
             if rising_edge(CLK) then
                --------------------------------------
                -- Self-recovering in case of a glitch
                --------------------------------------
                if unsigned(ce_sr) = 0 then
                  ce_sr <= ce_sr(ce_sr'high-1 downto 0) & '1';
                  clock_enable <= '1';
                else
                  ce_sr <= ce_sr(ce_sr'high-1 downto 0) & '0';
                  clock_enable <= '0';
               end if;
            end if;
        end process clock_divide;
        
    main : process (CLK)
        begin
            if rising_edge(CLK) then
               if clock_enable = '1' then
                  if clock_state = '0' then
                     -- Things to do on the rising edge of the clock.
                     -----------------------------------------------------------------
                     -- Capture the bits coming in from the ADC
                     -----------------------------------------------------------------
                     if sequncer_shift_reg(16) = '1' then
                        -- At this point we have a complete sample that 
                        -- can be fed intothe DSP pipeline
                        data_0 <= unsigned(din0_shift_reg(11 downto 0));
                        data_1 <= unsigned(din1_shift_reg(11 downto 0));
                     end if;
                     din0_shift_reg <= din0_shift_reg(din0_shift_reg'high-1 downto 0) & din_0;
                     din1_shift_reg <= din1_shift_reg(din1_shift_reg'high-1 downto 0) & din_1;
   
                     -----------------------------------------------------------------
                     -- sending the bits out to the DAC
                     -----------------------------------------------------------------
                     dout_0 <= dout0_shift_reg(dout0_shift_reg'high);
                     dout_1 <= dout1_shift_reg(dout1_shift_reg'high);
                     -- Either move new bits into the shift register or move them along
                     if sequncer_shift_reg(0) = '1' then
                        -- This is where you moe the output of the filter into the DAC 
                        -- Shift register
                        dout0_shift_reg <= "0000" & std_logic_vector(data_0);
                        dout1_shift_reg <= "0000" & std_logic_vector(data_1);
                     else
                        dout0_shift_reg <= dout0_shift_reg(dout0_shift_reg'high-1 downto 0) & '0';
                        dout1_shift_reg <= dout1_shift_reg(dout1_shift_reg'high-1 downto 0) & '0';
                     end if;
   
                     -----------------------------------------------------------------
                     -- And lastly update the sequencing shift register
                     -- Self-recovering in case of a glitch
                     -----------------------------------------------------------------
                     if unsigned(sequncer_shift_reg) = 0 then
                        sequncer_shift_reg <= sequncer_shift_reg(sequncer_shift_reg'high-1 downto 0) & '1'; 
                     else
                        sequncer_shift_reg <= sequncer_shift_reg(sequncer_shift_reg'high-1 downto 0) & '0'; 
                     end if;
                     -- Output rising clock edges
                     sclk        <= '1';
                     sclk2       <= '1';
                     clock_state <= '1';
                  else
                     -- Output falling clock edges
                     sclk        <= '0';
                     sclk2       <= '0';
                     clock_state <= '0';
                  end if;
               end if;
 
               -----------------------------------------------------------------
               -- A special kludge to get CS to rise and fall while SCLK 
               -- is high on the DAC. This ensures setup and hold times are met.
               -----------------------------------------------------------------
               if ce_sr(ce_sr'length/2) = '1' and clock_state = '1' then
                  if sequncer_shift_reg(0) = '1' then
                     cs <= '1';
                  elsif sequncer_shift_reg(1) = '1' then
                     cs <= '0';
                  end if;
               end if;
               -----------------------------------------------------------------
               -- A special kludge to get SYNC to fall while SCLK is low 
               -- on the ADC This ensures setup and hold times are met.
               -----------------------------------------------------------------
               if ce_sr(ce_sr'length/2) = '1' and clock_state = '0' then
                  if sequncer_shift_reg(17) = '1' then
                     sync <= '1';
                  elsif sequncer_shift_reg(1) = '1' then
                     sync <= '0';
                  end if;
               end if;
            end if;
        end process main;
end Behavioral;
Link to comment
Share on other sites

Today I sorted out my PMOD collection, and found that I did in fact have a PmodAD1... 

So I've hooked it up to my Basys3 with a generic "works with Arduino" analog joystick, and output the top eight bits of data_0 & data_1 to the LEDs. At least the ADC portion works ok.

Link to comment
Share on other sites

Hi @Rohith R,

I was able to get both channel one and channel two working. I was not able to get channel 3 and 4 working with the time I had to work with this project. I have altered your xdc to be more accurate and am only using the leds for the data i want to look at.  Here is a link to your project with my editing.

cheers,

Jon 

Link to comment
Share on other sites

Hi Jpeyron, 

  • Currently planning to use 2 channels in Pmod AD2 
  • Mapped 4 bits<11-8> of channel 1 to four user LEDs<LD0-LD3> and 4 bits of channel 2 <11-8> to another 4 user LEDs<LD4-LD7> for testing purpose. 
  • Project on Zedboard

Attached

1. project file

2. 1st pictures (when 5V is supplied to channel 1 and 0 V is applied to channel 2 )

3. 2nd picture (when 0V is supplied to channel 1 and 5 V is applied to channel 2 ) 

 

PmodAD2_project.zip

The problem is All LEDs are turned ON when channel 1 is given 5V and Channel 2 is given 0V 

But actually  LEDs <LD0-LD3> should be ON and rest LEDs <LD4-LD7> should be OFF for above condition. 

Link to comment
Share on other sites

Hi JColvin, 

In regards to above question of MOEZ, I performed a 4 read operation (total 4 byte) for 2 channel output. I could see only one channel ouput.Could you please clarify me ? 

Spoiler



------------------------------------------------------------------------
--	pmodAD2_ctrl.vhd  -- Frame for TWI system for PmodAD2 controller
------------------------------------------------------------------------
-- Author: Luke Renaud 
--	Copyright 2011 Digilent, Inc.
------------------------------------------------------------------------
-- Module description
--		This module takes in the standard system clock interfaces, and
--		uses them to control the five transfer TWI sequence for interf-
--		acing with the PmodAD2. The TWI interface itself is handled by
--		a module writen by an Digilent Romaina.
--	
--		To interface with the module, the address and data is provided
--		and the STB line is brought high for at least one full
--		TWI clock pulse. The done signal is brought high by the module
--		when the system is ready for the next byte of a multibyte transfer
--		or when it's time to force a new transfer. Keep STB or MSG high
--		to continue transfering data, bring them low if you want the
--		system to stop transmitting. More details can be found in the TWICtl
--		file itself.
--
--		In this use, I write the configuration to the device, force a new
--		transfer and then perform a dual read operation to bring in the
--		value that the PmodAD2 has sampled from the PmodDA1.
--
--  Inputs:
--		mainClk		50MHz System Clock for the TWI module
--		SDA_mst		Inout for TWI Data pin
--		rst			Main reset control
--
--  Outputs:
--		wData0		Returned Analog Value
--		SCL_mst		TWI Clock

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

Library UNISIM;
use UNISIM.vcomponents.all;

entity pmodAD2_ctrl is
    Port ( mainClk	: in	STD_LOGIC;
           SDA_mst	: inout	STD_LOGIC;
           SCL_mst	: inout	STD_LOGIC;
           wData0	: out	STD_LOGIC_VECTOR (15 downto 0);
           wData1	: out	STD_LOGIC_VECTOR (15 downto 0);
           rst		: in	STD_LOGIC);
end pmodAD2_ctrl;

architecture Behavioral of pmodAD2_ctrl is

	------------------------------------------------------------------------
	-- Component Declarations
	------------------------------------------------------------------------
	component TWICtl is
		generic (CLOCKFREQ : natural); -- input CLK frequency in MHz
		Port (	    MSG_I			: in	STD_LOGIC; --new message
					STB_I			: in	STD_LOGIC; --strobe
					A_I			: in	STD_LOGIC_VECTOR (7 downto 0); --address input bus
					D_I			: in	STD_LOGIC_VECTOR (7 downto 0); --data input bus
					D_O			: out	STD_LOGIC_VECTOR (7 downto 0); --data output bus
					DONE_O		: out	STD_LOGIC; --done status signal
					ERR_O			: out	STD_LOGIC; --error status
					CLK			: in	std_logic;	-- Input Clock
					SRST			: in	std_logic; -- Reset
					
					SDA			: inout	std_logic; --TWI SDA
					SCL			: inout	std_logic); --TWI SCL
	end component;

	------------------------------------------------------------------------
	-- General control and timing signals
	------------------------------------------------------------------------
	signal fMessage		    : STD_LOGIC;
	signal fDoTransmit   	: STD_LOGIC;
	signal fDoRead			: STD_LOGIC;
	signal currentAddr	    : STD_LOGIC_VECTOR(7 downto 0);
	signal fDone			: STD_LOGIC;
	
	constant addrAD2		: STD_LOGIC_VECTOR(6 downto 0) := "0101000";
	constant writeCfg		: STD_LOGIC_VECTOR(7 downto 0) := "11110000";
	
	signal waitCount : integer := 0;
	
	type	state_AD2_reader is (stDone, stWait, stGo, stConfig, stRead1, stRead2, stRead3, stRead4);
	signal stMain : state_AD2_reader := stDone;

	------------------------------------------------------------------------
	-- Data path signals
	------------------------------------------------------------------------
	signal curResponse	: STD_LOGIC_VECTOR(7 downto 0);

------------------------------------------------------------------------
-- Implementation
------------------------------------------------------------------------

begin
	
	-- Tie together the bus
	I2CBus: TWICtl
		generic map (CLOCKFREQ => 100) -- System clock in MHz
		Port Map (
		MSG_I =>			fMessage,
		STB_I =>			fDoTransmit,
		A_I =>			currentAddr, -- Address with read/write bit
		D_I =>			writeCfg, -- The one and only output byte
		D_O =>			curResponse,
		DONE_O =>		fDone,
		ERR_O =>			open,
		CLK =>			mainClk,
		SRST =>			rst,
		SDA =>			SDA_mst,
		SCL =>			SCL_mst);

	-- The address is constant except for the read flag
	currentAddr <= addrAD2(6 downto 0) & fDoRead; -- The address register is the address with the read/write bit
	-- The read flag is set when we aren't writing the config state.
	with stMain select
		fDoRead <=	'0' when stConfig,
						'0' when stGo,
						'1' when others;
						
	-- We want to drive the outputs to the TWI interface like we have pull up resistors attached.
	-- So when controller indicates we're high Z, attach the signal to a weak high signal instead.
	PULLUP_SDA: PULLUP PORT MAP ( O=>SDA_mst );
	PULLUP_SCL: PULLUP PORT MAP ( O=>SCL_mst );

	process(mainClk)
	begin
		-- Reset control
		if rising_edge(mainClk) then
			if (rst = '1') then
				stMain <= stWait;
				fDoTransmit <= '0';
				fMessage <= '0';
				waitCount <= 0;
			else
				case stMain is
					-- When the TWI controller we're using is brought out of a reset state
					-- it requires a great deal of time to startup and make sure that the line
					-- we're trying to use is indeed idle. This routine waits for it because it
					-- does not contain a line ready type signal to let us start using it.
					when stWait =>
						waitCount <= waitCount + 1;
						if (waitCount = 2000) then
							stMain <= stGo;
						else
							stMain <= stWait;
						end if;
					-- Pulse the transmit lines for the first TX packet. 
					when stGo => 
						fDoTransmit <= '1';
						fMessage <= '0';
						stMain <= stConfig;
					when stConfig =>
						fMessage <= '0';
						fDoTransmit <= '0';
						
						-- Hold the configuration state until we're done sending.
						if (fDone = '1') then
							-- Send the proper pins high for the state change
							stMain <= stRead1;
						else
							-- Force the pins low now that we're actually in the state.
							fMessage <= '0';
							stMain <= stConfig;
						end if;
		
					-- This state manages setting the device to read the 16-bits
					-- from the PmodAD2. The first byte (MSB) is read in this state
					-- then the I2C controller will inidicate fDone. Once this happens
					-- we pulse the transmit line to tell the controller we want to
					-- move onto getting the next byte.
                              
					when stRead1 =>
						fMessage <= '1';
						fDoTransmit <= '1';
							
						if (fDone = '1') then
							-- Send the proper pins high for the state change
							stMain <= stRead2;
							wData0(15 downto 8) <= curResponse(7 downto 0);
						else
							-- Force the pins low now that we're actually in the state.
							stMain <= stRead1;
						end if;

					-- This state manages recieving the second byte from the PmodAD2 over the
					-- I2C line. To do this the transmit message line is pulsed high for a
					-- moment to indicate that we keep transmitting on the same packet stream.
					
					
					
					when stRead2 =>
						-- Keep the fDoTransmit pin asserted to indicate multibyte transmit.
						fMessage <= '1';
                        fDoTransmit <= '1';

						if (fDone = '1') then
							stMain <= stRead3;
							wData0(7 downto 0) <= curResponse(7 downto 0);
						else
							stMain <= stRead2;
						end if;
						
					
                    
                                            when stRead3 =>
                                                fMessage <= '1';
                                                fDoTransmit <= '1';
                                                    
                                                if (fDone = '1') then
                                                    -- Send the proper pins high for the state change
                                                    stMain <= stRead4;
                                                    wData1(15 downto 8) <= curResponse(7 downto 0);
                                                else
                                                    -- Force the pins low now that we're actually in the state.
                                                    stMain <= stRead3;
                                                end if;
                        
                                            -- This state manages recieving the second byte from the PmodAD2 over the
                                            -- I2C line. To do this the transmit message line is pulsed high for a
                                            -- moment to indicate that we keep transmitting on the same packet stream.
                                            
                                            
                                            
                                            when stRead4 =>
                                                -- Keep the fDoTransmit pin asserted to indicate multibyte transmit.
                                                fMessage <= '0';
                                                fDoTransmit <= '1';
                        
                                                if (fDone = '1') then
                                                    stMain <= stDone;
                                                    wData1(7 downto 0) <= curResponse(7 downto 0);
                                                else
                                                    stMain <= stRead4;
                                                end if;
                                                			
					
					
					
						
					when others =>
						-- TX pins stay low once we're done
						fDoTransmit <= '0';
						fMessage <= '0';
						stMain <= stDone;
				end case;
			end if;
		end if;			
	end process;
	
	
	

	
	
	

end Behavioral;


 

 

Link to comment
Share on other sites

Hi moez,

The datasheet does not actually say that you will get four simultaneous channels of conversion for the AD7991 (which is on the PmodAD2). Rather, if you maintain the default setting of all of the channels enabled, the chip will instead cycle through each channel individually (as indicated in Tables 10 and 11 in the datasheet). So you will need to perform four reads in a row to have the module perform a conversion on each of it's channels.

Let me know if you have any more questions.

Thanks,
JColvin

Link to comment
Share on other sites

i attempt to use PMOD AD2 on simultaneous channels, with the configure registre  writeCfg as mentionned in code Nexys3_AD2_DA1_source

such as to select  is performed by for MSB  bits with underlying register, for example for channel in0  

constant writeCfg   "0001  0000";

for channel in1:  writeCfg = "0010 0000";  and so on

if we set writeCfg = 11110000 to obtain four simultaneous channels convertion, asn it mentionne in datasheet of 7999 but no signe of life, i have onle channel in0 however i tested each channel,  separatley. thank for help

pmodAD2_ctrl.vhd

Link to comment
Share on other sites

Apart from the serial clocks being slower than the comments suggest, The testbench shows a typo here 

   ...
   elsif (cnt_0 > 3 and cnt_0 < 16 and cnt_1 > 3 and cnt_1 < 6) then
  ....

cnt_1 gets stuck at 6 and never increments, and everything stops  - that last test should be against 16 and not6.

 

It also shows the entire sample/process/output cycle taking 6.24us,- a sampling rate of approx 160 kHz. You might have designed it for that, but it is quite low compared to what the devices can achieve.- they will work up to about 1M Samples/S.

 

You might get problems as the two ADCs are only active for a small faction of the time. Say the anti-alias filters are tuned as a 500kHz low-pass, and you are sampling at 160KHz, then you will have all the aliases of frequencies from 80kHz to 500kHz messing up your system.

 

I think in practice the filters automagically adjust for the serial clock frequency being used (the slower the clock, the lower the cutoff), but having the ADC active for 1/4th of the time may upset that too...

 

Have a go at adding the test-bench and running it up. It looks interesting and offer quite a lot of insight into what is going on!

Mike

Link to comment
Share on other sites

Hey Guys!

 

Thanks a lot for your kind interest. Unfortunately I do not think it is a matter of the cycles because the code works for a single input. Please, find it attached.

 

On the other hand, I am new with Vivado (used to use ISE) and when running the simulation it gives me a compile error, suggesting to check on the tcl, but I dont even find where that is.

 

What does actually the testbench show?

 

Thanks!

 

Ira

adc_dac_single_channel.txt

Link to comment
Share on other sites

Here is the test bench I'm using

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
ENTITY tb_adc_dac IS
END tb_adc_dac;
 
ARCHITECTURE behavior OF tb_adc_dac IS 
    COMPONENT adc_dac
    PORT(
         CLK : IN  std_logic;
         CS : OUT  std_logic;
         SYNC : OUT  std_logic;
         DIN_0 : IN  std_logic;
         DIN_1 : IN  std_logic;
         DOUT_0 : OUT  std_logic;
         DOUT_1 : OUT  std_logic;
         SCLK : OUT  std_logic;
         SCLK2 : OUT  std_logic;
         reset : IN  std_logic
        );
    END COMPONENT;
   --Inputs
   signal CLK : std_logic := '0';
   signal DIN_0 : std_logic := '0';
   signal DIN_1 : std_logic := '0';
   signal reset : std_logic := '0';
 
  --Outputs
   signal CS : std_logic;
   signal SYNC : std_logic;
   signal DOUT_0 : std_logic;
   signal DOUT_1 : std_logic;
   signal SCLK : std_logic;
   signal SCLK2 : std_logic;
 
   signal tristate_sr : std_logic_vector(15 downto 0);
   signal adc_data    : std_logic_vector(15 downto 0);
   -- Clock period definitions
   constant CLK_period : time := 10 ns;
   constant SCLK_period : time := 10 ns;
 
BEGIN
----------------------------------------
-- Cheap and nasty simulation of the ADC
----------------------------------------
   Din_0       <= adc_data(adc_data'high) when tristate_sr(tristate_sr'high) = '1' and cs ='0' else 'Z';
   Din_1       <= adc_data(adc_data'high) when tristate_sr(tristate_sr'high) = '1' and cs ='0' else 'Z'; 
 p: process(sclk)
   begin
      if falling_edge(sclk) then
         if cs = '1' then
            adc_data <= x"0FFF";
            tristate_sr <= (others => '1');
         else
            adc_data <= (others => 'X');
            adc_data    <= adc_data(adc_data'high-1 downto 0) & adc_data(adc_data'high) after 25 ns;
            tristate_sr <= tristate_sr(tristate_sr'high-1 downto 0) & '0' after 25 ns;
         end if; 
      end if;
   end process;
-- Instantiate the Unit Under Test (UUT)
   uut: adc_dac PORT MAP (
          CLK    => CLK,
          CS     => CS,
          SYNC   => SYNC,
          DIN_0  => DIN_0,
          DIN_1  => DIN_1,
          DOUT_0 => DOUT_0,
          DOUT_1 => DOUT_1,
          SCLK   => SCLK,
          SCLK2  => SCLK2,
          reset  => reset
        );
 
   -- Clock process definitions
   CLK_process :process
   begin
      CLK <= '0';
      wait for CLK_period/2;
      CLK <= '1';
      wait for CLK_period/2;
   end process;
END;
Link to comment
Share on other sites

What happens to cnt_0 and cnt_1 when they are 6, and your FSM is in write_data state?

 

Also, you are doing

- Read ADC for 16 cycles

- Process for one cycle

- Write DAC for 16 cycles.

 

Why not do this?

0:  Read ADC(0), write DAC(15), calculate new output value.

1:  Read ADC(1), write DAC(0)

2:  Read ADC(2), write DAC(1)

3:  Read ADC(3), write DAC(2)

16: Read ADC(15), write DAC(14) 

 

Rather than processing 606k samples/sec you can then process 1.25 M Samples per sec (using your 20MHz Clk.)

Link to comment
Share on other sites

I did a little sim...

                if (clkdiv = 5) then -- divide 100MHz by 5
                    risingedge <= risingedge xor '1';
                    newclk <= newclk xor '1';
                    clkdiv <= 0;
                else
                    clkdiv <= clkdiv + 1;
                end if;

Is actually dividing by 6.

 

I'm just building a little test bench that looks like the ADC to see what is going on, and will report back.

Link to comment
Share on other sites

Hi iratidance,

 

I think I see your issue. In your 2channel main under the Write Data for your FSM you have the line

 

 

elsif (cnt_0 > 3 and cnt_0 < 16 and cnt_1 > 3 and cnt_1 < 6)

 

where your cnt_1 does not go all the way up to the 16 bits.

 

Hope this helps!

 

Thanks,

JColvin

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...