• 0

PMOD MIC3 SPI interface driver code


Question

I am trying to implement my pmod mic3 on Basys3 board. My code seems true but I couldn't read any data. 

I mentioned my code in below.

There are 3 modules: SPI_master, SPI_master_withCS, mic3

SPI_Master.vhd

------------------------------------------------------------------------------/
-- Description: SPI (Serial Peripheral Interface) Master
--              Creates master based on input configuration.
--              Sends a byte one bit at a time on MOSI
--              Will also receive byte data one bit at a time on MISO.
--              Any data on input byte will be shipped out on MOSI.
--
--              To kick-off transaction, user must pulse i_TX_DV.
--              This module supports multi-byte transmissions by pulsing
--              i_TX_DV and loading up i_TX_Byte when o_TX_Ready is high.
--
--              This module is only responsible for controlling Clk, MOSI, 
--              and MISO.  If the SPI peripheral requires a chip-select, 
--              this must be done at a higher level.
--
-- Note:        i_Clk must be at least 2x faster than i_SPI_Clk
--
-- Generics:    SPI_MODE, can be 0, 1, 2, or 3.  See above.
--              Can be configured in one of 4 modes:
--              Mode | Clock Polarity (CPOL/CKP) | Clock Phase (CPHA)
--               0   |             0             |        0
--               1   |             0             |        1
--               2   |             1             |        0
--               3   |             1             |        1
--              More: https:--en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Mode_numbers
--              CLKS_PER_HALF_BIT - Sets frequency of o_SPI_Clk.  o_SPI_Clk is
--              derived from i_Clk.  Set to integer number of clocks for each
--              half-bit of SPI data.  E.g. 100 MHz i_Clk, CLKS_PER_HALF_BIT = 2
--              would create o_SPI_CLK of 25 MHz.  Must be >= 2
-- tsclk = 20MHz
-- fpga clock =100MHz
------------------------------------------------------------------------------/

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity SPI_Master is
  generic (
    SPI_MODE          : integer := 2;
    CLKS_PER_HALF_BIT : integer := 5
    );
  port (
   -- Control/Data Signals,
   i_Rst_L : in std_logic;        -- FPGA Reset
   i_Clk   : in std_logic;        -- FPGA Clock
   
   -- TX (MOSI) Signals
   i_TX_Byte   : in std_logic_vector(11 downto 0);   -- Byte to transmit on MOSI
   i_TX_DV     : in std_logic;          -- Data Valid Pulse with i_TX_Byte
   o_TX_Ready  : inout std_logic;         -- Transmit Ready for next byte
   
   -- RX (MISO) Signals
   o_RX_DV   : out std_logic;    -- Data Valid pulse (1 clock cycle)
   o_RX_Byte : out std_logic_vector(11 downto 0);   -- Byte received on MISO

   -- SPI Interface
   o_SPI_Clk  : out std_logic;
   i_SPI_MISO : in  std_logic;
   o_SPI_MOSI : out std_logic
   );
end entity SPI_Master;

architecture RTL of SPI_Master is

  -- SPI Interface (All Runs at SPI Clock Domain)
  signal w_CPOL : std_logic;     -- Clock polarity
  signal w_CPHA : std_logic;     -- Clock phase

  signal r_SPI_Clk_Count : integer range 0 to CLKS_PER_HALF_BIT*2-1;
  signal r_SPI_Clk       : std_logic;
  signal r_SPI_Clk_Edges : integer range 0 to 16;
  signal r_Leading_Edge  : std_logic;
  signal r_Trailing_Edge : std_logic;
  signal r_TX_DV         : std_logic;
  signal r_TX_Byte       : std_logic_vector(11 downto 0);

  signal r_RX_Bit_Count : unsigned(2 downto 0);
  signal r_TX_Bit_Count : unsigned(2 downto 0);

begin

  -- CPOL: Clock Polarity
  -- CPOL=0 means clock idles at 0, leading edge is rising edge.
  -- CPOL=1 means clock idles at 1, leading edge is falling edge.
  w_CPOL <= '1' when (SPI_MODE = 2) or (SPI_MODE = 3) else '0';

  -- CPHA: Clock Phase
  -- CPHA=0 means the "out" side changes the data on trailing edge of clock
  --              the "in" side captures data on leading edge of clock
  -- CPHA=1 means the "out" side changes the data on leading edge of clock
  --              the "in" side captures data on the trailing edge of clock
  w_CPHA <= '1' when (SPI_MODE = 1) or (SPI_MODE = 3) else '0';

  -- Purpose: Generate SPI Clock correct number of times when DV pulse comes
  Edge_Indicator : process (i_Clk, i_Rst_L)
  begin
    if i_Rst_L = '0' then
      o_TX_Ready      <= '0';
      r_SPI_Clk_Edges <= 0;
      r_Leading_Edge  <= '0';
      r_Trailing_Edge <= '0';
      r_SPI_Clk       <= w_CPOL; -- assign default state to idle state
      r_SPI_Clk_Count <= 0;
    elsif falling_edge(i_Clk) then

      -- Default assignments
      r_Leading_Edge  <= '0';
      r_Trailing_Edge <= '0';
      
      if i_TX_DV = '1' then
        o_TX_Ready      <= '0';
        r_SPI_Clk_Edges <= 16;  -- Total # edges in one byte ALWAYS 16
      elsif r_SPI_Clk_Edges > 0 then
        o_TX_Ready <= '0';
        
        if r_SPI_Clk_Count = CLKS_PER_HALF_BIT*2-1 then
          r_SPI_Clk_Edges <= r_SPI_Clk_Edges - 1;
          r_Trailing_Edge <= '1';
          r_SPI_Clk_Count <= 0;
          r_SPI_Clk       <= not r_SPI_Clk;
        elsif r_SPI_Clk_Count = CLKS_PER_HALF_BIT-1 then
          r_SPI_Clk_Edges <= r_SPI_Clk_Edges - 1;
          r_Leading_Edge  <= '1';
          r_SPI_Clk_Count <= r_SPI_Clk_Count + 1;
          r_SPI_Clk       <= not r_SPI_Clk;
        else
          r_SPI_Clk_Count <= r_SPI_Clk_Count + 1;
        end if;
      else
        o_TX_Ready <= '1';
      end if;
    end if;
  end process Edge_Indicator;

         
  -- Purpose: Register i_TX_Byte when Data Valid is pulsed.
  -- Keeps local storage of byte in case higher level module changes the data
  Byte_Reg : process (i_Clk, i_Rst_L)
  begin
    if i_Rst_L = '0' then
      r_TX_Byte <= X"000";
      r_TX_DV   <= '0';
    elsif falling_edge(i_clk) then
      r_TX_DV <= i_TX_DV; -- 1 clock cycle delay
      if i_TX_DV = '1' then
        r_TX_Byte <= i_TX_Byte;
      end if;
    end if;
  end process Byte_Reg;


  -- Purpose: Generate MOSI data
  -- Works with both CPHA=0 and CPHA=1
  MOSI_Data : process (i_Clk, i_Rst_L)
  begin
    if i_Rst_L = '0' then
      o_SPI_MOSI     <= '0';
      r_TX_Bit_Count <= "111";          -- Send MSB first
    elsif falling_edge(i_Clk) then
      -- If ready is high, reset bit counts to default
      if o_TX_Ready = '1' then
        r_TX_Bit_Count <= "111";

      -- Catch the case where we start transaction and CPHA = 0
      elsif (r_TX_DV = '1' and w_CPHA = '0') then
        o_SPI_MOSI     <= r_TX_Byte(7);
        r_TX_Bit_Count <= "110";        -- 6
      elsif (r_Leading_Edge = '1' and w_CPHA = '1') or (r_Trailing_Edge = '1' and w_CPHA = '0') then
        r_TX_Bit_Count <= r_TX_Bit_Count - 1;
        o_SPI_MOSI     <= r_TX_Byte(to_integer(r_TX_Bit_Count));
      end if;
    end if;
  end process MOSI_Data;


  -- Purpose: Read in MISO data.
  MISO_Data : process (i_Clk, i_Rst_L)
  begin
    if i_Rst_L = '0' then
      o_RX_Byte      <= X"000";
      o_RX_DV        <= '0';
      r_RX_Bit_Count <= "111";          -- Starts at 7
    elsif falling_edge(i_Clk) then
      -- Default Assignments
      o_RX_DV <= '0';

      if o_TX_Ready = '1' then -- Check if ready, if so reset count to default
        r_RX_Bit_Count <= "111";        -- Starts at 7
      elsif (r_Leading_Edge = '1' and w_CPHA = '0') or (r_Trailing_Edge = '1' and w_CPHA = '1') then
        o_RX_Byte(to_integer(r_RX_Bit_Count)) <= i_SPI_MISO;  -- Sample data
        r_RX_Bit_Count <= r_RX_Bit_Count - 1;
        if r_RX_Bit_Count = "000" then
          o_RX_DV <= '1';   -- Byte done, pulse Data Valid
        end if;
      end if;
    end if;
  end process MISO_Data;
  
  
  -- Purpose: Add clock delay to signals for alignment.
  SPI_Clock : process (i_Clk, i_Rst_L)
  begin
    if i_Rst_L = '0' then
      o_SPI_Clk  <= w_CPOL;
    elsif falling_edge(i_Clk) then
      o_SPI_Clk <= r_SPI_Clk;
    end if;
  end process SPI_Clock;
  
end architecture RTL;

SPI_master_withCS.vhd

-------------------------------------------------------------------------------
-- Description: SPI (Serial Peripheral Interface) Master
--              With single chip-select (AKA Slave Select) capability
--
--              Supports arbitrary length byte transfers.
-- 
--              Instantiates a SPI Master and adds single CS.
--              If multiple CS signals are needed, will need to use different
--              module, OR multiplex the CS from this at a higher level.
--
-- Note:        i_Clk must be at least 2x faster than i_SPI_Clk
--
-- Parameters:  SPI_MODE, can be 0, 1, 2, or 3.  See above.
--              Can be configured in one of 4 modes:
--              Mode | Clock Polarity (CPOL/CKP) | Clock Phase (CPHA)
--               0   |             0             |        0
--               1   |             0             |        1
--               2   |             1             |        0
--               3   |             1             |        1
--
--              CLKS_PER_HALF_BIT - Sets frequency of o_SPI_Clk.  o_SPI_Clk is
--              derived from i_Clk.  Set to integer number of clocks for each
--              half-bit of SPI data.  E.g. 100 MHz i_Clk, CLKS_PER_HALF_BIT = 2
--              would create o_SPI_CLK of 25 MHz.  Must be >= 2
--
--              MAX_BYTES_PER_CS - Set to the maximum number of bytes that
--              will be sent during a single CS-low pulse.
-- 
--              CS_INACTIVE_CLKS - Sets the amount of time in clock cycles to
--              hold the state of Chip-Selct high (inactive) before next 
--              command is allowed on the line.  Useful if chip requires some
--              time when CS is high between trasnfers.
-------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity spi_master_cs is
  generic (
    SPI_MODE          : integer := 2;
    CLKS_PER_HALF_BIT : integer := 5;
    MAX_BYTES_PER_CS  : integer := 2;
    CS_INACTIVE_CLKS  : integer := 1
    );
  port (
   -- Control/Data Signals,
   i_Rst_L : in std_logic;     -- FPGA Reset
   i_Clk   : in std_logic;     -- FPGA Clock
   
   -- TX (MOSI) Signals
   i_TX_Count : in  std_logic_vector;  -- # bytes per CS low
   i_TX_Byte  : in  std_logic_vector(11 downto 0);  -- Byte to transmit on MOSI
   i_TX_DV    : in  std_logic;     -- Data Valid Pulse with i_TX_Byte
   o_TX_Ready : out std_logic;     -- Transmit Ready for next byte
   
   -- RX (MISO) Signals
   o_RX_Count : inout std_logic_vector(11 downto 0);  -- Index RX byte
   o_RX_DV    : inout std_logic;  -- Data Valid pulse (1 clock cycle)
   o_RX_Byte  : out std_logic_vector(11 downto 0);   -- Byte received on MISO

   -- SPI Interface
   o_SPI_Clk  : out std_logic;
   i_SPI_MISO : in  std_logic;
   o_SPI_CS_n : out std_logic
   );
end entity spi_master_cs;

architecture RTL of spi_master_cs is
  type t_SM_CS is (IDLE, TRANSFER, CS_INACTIVE);
  signal r_SM_CS : t_SM_CS;
  signal r_CS_n : std_logic;
  signal r_CS_Inactive_Count : integer range 0 to CS_INACTIVE_CLKS;
  signal r_TX_Count : integer range 0 to MAX_BYTES_PER_CS + 1;
  signal w_Master_Ready : std_logic;
begin
  -- Instantiate Master
  SPI_Master_1 : entity work.spi_master
    generic map (
      SPI_MODE          => SPI_MODE,
      CLKS_PER_HALF_BIT => CLKS_PER_HALF_BIT)
    port map (
      -- Control/Data Signals,
      i_Rst_L    => i_Rst_L,            -- FPGA Reset
      i_Clk      => i_Clk,              -- FPGA Clock
      -- TX (MOSI) Signals
      i_TX_Byte  => i_TX_Byte,          -- Byte to transmit
      i_TX_DV    => i_TX_DV,            -- Data Valid pulse
      o_TX_Ready => w_Master_Ready,     -- Transmit Ready for Byte
      -- RX (MISO) Signals
      o_RX_DV    => o_RX_DV,            -- Data Valid pulse
      o_RX_Byte  => o_RX_Byte,          -- Byte received on MISO
      -- SPI Interface
      o_SPI_Clk  => o_SPI_Clk, 
      i_SPI_MISO => i_SPI_MISO
      );
  -- Purpose: Control CS line using State Machine

  SM_CS :process(i_Clk, i_Rst_L)
  begin
    if i_Rst_L = '0' then
      r_SM_CS             <= IDLE;
      r_CS_n              <= '1';   -- Resets to high
      r_TX_Count          <= 0;
      r_CS_Inactive_Count <= CS_INACTIVE_CLKS;
    elsif falling_edge(i_Clk) then

      case r_SM_CS is
        when IDLE =>
          if r_CS_n = '1' and i_TX_DV = '1' then -- Start of transmission
            r_TX_Count <= to_integer(unsigned(i_TX_Count) - 1); -- Register TX Count
            r_CS_n     <= '0';       -- Drive CS low
            r_SM_CS    <= TRANSFER;   -- Transfer bytes
          end if;

        when TRANSFER =>
          -- Wait until SPI is done transferring do next thing
          if w_Master_Ready = '1' then
            if r_TX_Count > 0 then
              if i_TX_DV = '1' then
                r_TX_Count <= r_TX_Count - 1;
              end if;
            else
              r_CS_n              <= '1'; -- we done, so set CS high
              r_CS_Inactive_Count <= CS_INACTIVE_CLKS;
              r_SM_CS             <= CS_INACTIVE;
            end if;
          end if;
          
        when CS_INACTIVE =>
          if r_CS_Inactive_Count > 0 then
            r_CS_Inactive_Count <= r_CS_Inactive_Count - 1;
          else
            r_SM_CS <= IDLE;
          end if;

        when others => 
          r_CS_n  <= '1'; -- we done, so set CS high
          r_SM_CS <= IDLE;
      end case;
    end if;
  end process SM_CS; 


  -- Purpose: Keep track of RX_Count
  RX_COUNT : process (i_Clk)
  begin
    if falling_edge(i_Clk) then
      if r_CS_n = '1' then
        o_RX_Count <= std_logic_vector(to_unsigned(0, o_RX_Count'length));
      elsif o_RX_DV = '1' then
        o_RX_Count <= std_logic_vector(unsigned(o_RX_Count) + 1);
      end if;
    end if;
  end process RX_COUNT;

  o_SPI_CS_n <= r_CS_n;

  o_TX_Ready <= '1' when i_TX_DV /= '1' and ((r_SM_CS = IDLE) or (r_SM_CS = TRANSFER and w_Master_Ready = '1' and r_TX_Count > 0)) else '0'; 

end architecture RTL;

mic3.vhd

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity mic3 is
  port (
    -- Control/Data Signals,
    i_Switch_1 : in std_logic; -- Used as FPGA Reset
    i_Clk      : in std_logic; -- FPGA Clock
    
    -- PMOD SPI Interface
    o_SPI_Clk  : out std_logic;
    i_SPI_MISO : in std_logic;
    o_SPI_CS_n : out std_logic;
    
    -- Output
    r_ADC_Word : out std_logic_vector(11 downto 0)
    );
end entity mic3;

architecture RTL of mic3 is

  -- CPOL = 1 and CPHA = 1, based on ADC part ADC08S021
  constant SPI_MODE : integer := 2;

  -- Go Board operates at 25 MHz, so divide by 10 to get to 2.5 MHz.
  -- This is clocks per half bit, so divide 10 by 2 to get 5.
  constant CLKS_PER_HALF_BIT : integer := 5;

  -- Number of clock cycles to leave CS high after transaction is done
  constant CS_INACTIVE_CLKS : integer := 100;

  signal w_Rst_L : std_logic;

  -- SPI Signals
  signal w_Master_RX_Count : std_logic_vector(11 downto 0);
  signal w_Master_RX_DV    : std_logic;
  signal w_Master_RX_Byte  : std_logic_vector(11 downto 0);
  signal w_Master_TX_Ready : std_logic;
  signal r_Master_TX_DV    : std_logic;

  signal w_LED_Enable  : std_logic;
  signal w_Ambient_Val : std_logic_vector(11 downto 0);


begin  -- architecture RTL
      
  w_Rst_L <= not i_Switch_1;  -- switch defaults to low.

  SPI_Master_With_Single_CS_1: entity work.spi_master_cs
    generic map (
      SPI_MODE          => SPI_MODE,
      CLKS_PER_HALF_BIT => CLKS_PER_HALF_BIT,
      MAX_BYTES_PER_CS  => 2,           -- always 2 bytes per CS
      CS_INACTIVE_CLKS  => CS_INACTIVE_CLKS)
    port map (
      i_Rst_L    => w_Rst_L,            -- FPGA Reset (push button)
      i_Clk      => i_Clk,              -- FPGA Clock
      -- TX (MOSI Signals)
      i_TX_Count => x"10",               -- Always 2 bytes per CS
      i_TX_Byte  => x"000",              -- Can stuff with zeros
      i_TX_DV    => r_Master_TX_DV,     -- Data valid pulse with i_TX_Byte
      o_TX_Ready => w_Master_TX_Ready,  -- Transmit ready for byte
      -- RX (MISO) Signals
      o_RX_Count => w_Master_RX_Count,  -- Index of RX'd byte
      o_RX_DV    => w_Master_RX_DV,     -- Data valid pulse (1 clock cycle)
      o_RX_Byte  => w_Master_RX_Byte,   -- Byte received on MISO
      -- SPI Interface
      o_SPI_Clk  => o_SPI_Clk,
      i_SPI_MISO => i_SPI_MISO,
      o_SPI_CS_n => o_SPI_CS_n
      );

  -- Purpose: Handle read requests from ADC.
  -- Just request data as often as possible
  -- Also creates simple counter to count continuously.
  process (i_Clk) is
  begin
    if falling_edge(i_Clk) then
      r_Master_TX_DV <= w_Master_TX_Ready;
    end if;
  end process;

   -- Purpose: Handle data being read back from SPI
   -- Pack up response into a single 8-bit value for ambient light.
  process (i_Clk) is
  begin
    if falling_edge(i_Clk) then
      if w_Master_RX_DV = '1' then
        if w_Master_RX_Count = "00" then
          r_ADC_Word <= w_Master_RX_Byte;
        end if;
      end if;
    end if;
  end process;

end architecture RTL;

Link to post
Share on other sites

0 answers to this question

Recommended Posts

There have been no answers to this question yet

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now