Interfaz de un MCP23S17 (SPI) con un FPGA

Estoy trabajando con un chip expansor de E/S SPI MCP23S17 en un proyecto VHDL en mi Basys 2 .

A primera vista, pensé que esto era solo una interfaz SPI simple donde puse la selección de chip baja y me dará los datos en la línea MISO, pero parece que es un poco más complicado con los comandos y la inicialización necesaria.

Agregué algunos bits de configuración ("0100" y "000" y "1") que aparecen en la línea MOSI una vez cuando intenta leer datos. pero nada ha cambiado. Parece que hay muchos registros para mantener la configuración, pero no tengo ni idea de cómo configurarlos.

Formato de byte de control SPI

Aquí hay un diagrama de cómo lo tengo todo conectado. La E/S de prueba es solo para asegurarme de que tengo algunos bits conocidos que deberían aparecer si la transacción se realiza correctamente. Usaré el lado B del chip, así que si algo especial necesita suceder para leer eso, explíquelo.Configuración de chips

¿Qué debe suceder para leer los datos del chip?

Aquí está el módulo SPI (SPI.vhd) que he escrito hasta ahora.

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

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;

-- Uncomment the following library declaration if instantiating
-- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity SPI is
    Generic (   
        dataWidthN : positive := 8
    );
    port(
        sck: in std_logic; -- clock
        mosi: out std_logic; -- data going into slave
        miso: in std_logic; -- data coming out of slave
        cs: in std_logic; -- chip select

        address: in std_logic_vector(2 downto 0); -- 0 - 7

        data: out std_logic_vector(dataWidthN-1 downto 0);

        debug: out std_logic_vector(1 downto 0)
    );
end SPI;

architecture Behavioral of SPI is
    signal data_reg : STD_LOGIC_VECTOR (dataWidthN-1 downto 0);

begin

    data <= data_reg;

    process (sck)
        variable isSetup: std_logic := '0';
        variable setupBits: std_logic_vector(7 downto 0) := "0100" & address & "1";
        variable setupBitCount: natural := 0;
    begin
        if rising_edge(sck) then  -- rising edge of SCK
            if (cs = '0') then -- SPI CS must be selected

                if (isSetup = '0' and setupBitCount < 7) then
                    mosi <= setupBits(7-setupBitCount);
                    setupBitCount := setupBitCount + 1;
                else
                    isSetup := '1';
                    setupBitCount := 0;
                end if;

                if isSetup = '1' then
                    debug <= "11";

                    -- shift serial data into dat_reg on each rising edge
                    -- of SCK, MSB first
                    data_reg <= data_reg(dataWidthN-2 downto 0) & miso;
                else
                    debug <= "10";
                end if;

            end if;
        end if;
    end process;

end Behavioral;

No he encontrado muchos artículos hablando de este chip usando código. Encontré algunas cosas de Arduino, pero todas usan la biblioteca SPI que no ayuda a explicar qué está sucediendo exactamente. Aquí están los pocos enlaces que he encontrado:

Editar:

Muy bien, después de trabajar en lo que dijo Dave Tweed. Puedo enviar y producir los comandos en MOSI, pero no vuelve nada en la línea MISO. Tenga en cuenta que el FPGA debe obtener los datos y tengo un analizador lógico que mostrará los bits si sale algo y mi código FPGA está mal.

CS:   1111000000000000000000000000
MOSI: xxxx0100aaa10000110000000000
MISO: xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Aquí está la simulación en ISim. **Esto no devolverá datos en MISO porque es solo una simulación sin chip para enviar datos correctos.*resultados ISim

Y de un analizador lógico en el mundo real:Resultados del analizador lógico

Aquí está el código de actualización del módulo SPI.vhd:

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

entity SPI is
    Generic (   
        dataWidthN : integer := 8
    );
    port(
        sck: in std_logic; -- clock
        mosi: out std_logic; -- data going into slave
        miso: in std_logic; -- data coming out of slave
        cs: in std_logic; -- chip select

        address: in std_logic_vector(2 downto 0); -- 0 - 7

        data: out std_logic_vector(dataWidthN-1 downto 0);

        debug: out std_logic_vector(1 downto 0)
    );
end SPI;

architecture Behavioral of SPI is
    type state_type is (idle, s_readSetup, s_read);

    signal data_reg : STD_LOGIC_VECTOR (dataWidthN-1 downto 0);
begin

    data <= data_reg;

    spi_read: process (sck)
        variable transactionComplete: std_logic := '0';
        variable setupBits: std_logic_vector(15 downto 0);
        variable setupCmdBitCount: natural := 0;  -- setup command is 16 in length
        variable readCmdBitCount: natural := 0;  -- A command is same as dataWidthN

        variable currState: state_type := idle;
    begin
        setupBits := "0100" & address & "1" & "00001100";

        if falling_edge(sck) then  -- rising edge of SCK

            case currState is
            when s_readSetup =>
                if (cs = '0') then -- SPI CS must be selected
                    debug <= "10";

                    mosi <= setupBits(setupBits'length-1-setupCmdBitCount);

                    setupCmdBitCount := setupCmdBitCount + 1;

                    -- Move to the next state
                    if setupCmdBitCount >= setupBits'length then
                        setupCmdBitCount := 0;
                        currState := s_read;
                    end if;

                else
                    currState := idle;
                end if;

            when s_read =>
                if (cs = '0') then -- SPI CS must be selected
                    debug <= "11";

                    -- shift serial data into dat_reg on each rising edge
                    -- of SCK, MSB first
                    data_reg <= data_reg(dataWidthN-2 downto 0) & miso;

                    readCmdBitCount := readCmdBitCount + 1;

                    if readCmdBitCount >= data'length then
                        readCmdBitCount := 0;
                        transactionComplete := '1';
                        currState := idle;
                    end if;

                else
                    currState := idle;
                end if;

            -- Idle state: if the state is unknown then we just go idle
            when others =>
                debug <= "00";

                setupCmdBitCount := 0;
                readCmdBitCount := 0;
                mosi <= '0';

                if cs = '0' and transactionComplete = '0'  then
                    mosi <= setupBits(setupBits'length-1-setupCmdBitCount);
                    setupCmdBitCount := setupCmdBitCount + 1;

                    currState := s_readSetup;

                elsif cs = '1' and transactionComplete = '1' then
                    transactionComplete := '0';
                end if;

            end case;

        end if;



    end process;

end Behavioral;

Respuestas (2)

El MCP23S17 realmente está diseñado para conectarse a un microcontrolador. Lo he usado con éxito en un proyecto basado en Blackfin. Tiene varios registros internos, al igual que los puertos GPIO en un microcontrolador típico. Cada puerto de 8 bits tiene un registro de dirección, un registro de entrada y un registro de salida, además de registros para la polaridad de entrada y cambio de interrupción. También hay un registro de configuración global.

De forma predeterminada, todas las entradas se activan, por lo que si eso es todo lo que necesita, solo necesita crear una máquina de estado que lea los dos registros de entrada. Tenga en cuenta que debe proporcionar un byte de dirección de chip y luego un byte de dirección de registro para cada ciclo de lectura.

Además, debe tener en cuenta que este chip tiene la peculiar característica de tener dos mapas de direcciones diferentes para los registros, según la configuración del bit "BANCO". Estudie esta parte cuidadosamente; es bastante confuso.

El bit BANK es cero en el encendido, por lo que los dos registros que desea, GPIOA y GPIOB, se encuentran en las direcciones 12 y 13, respectivamente. Por lo tanto, para leerlos a ambos, debe realizar dos ciclos SPI de 24 horas:

CS:   1111000000000000000000000000111111110000000000000000000000001111
MOSI: xxxx0100aaa10000110000000000xxxxxxxx0100aaa10000110100000000xxxx
MISO: xxxx0000000000000000AAAAAAAAxxxxxxxx0000000000000000BBBBBBBBxxxx
  • "aaa" representa la dirección del chip.
  • "AAAAAAAA" representa los datos del puerto A
  • "BBBBBBBB" representa los datos del puerto B

Tenga en cuenta que todo es MSB primero.

¿Podría darme un ejemplo de tratar de leer los lados A y B (lo que debería bombear MOSI)? ¿Necesito hacer este comando cada vez que quiero los datos?
He actualizado mi código para que pueda canalizar esos comandos correctamente (funciona en simulación y en el mundo real: analizador lógico). Puedes ver mis resultados en la op. Aunque estoy enviando estos comandos en MOSI, nada vuelve en MISO.
Está utilizando la dirección de chip aaa=000. ¿Está seguro de que los pines correspondientes (A2, A1, A0) en el 23S17 están conectados a tierra? Además, parece que los datos MOSI cambian en el flanco ascendente del reloj. Debería cambiar los datos en el flanco descendente, ya que los tiempos de configuración y espera de 23S17 son relativos al flanco ascendente.
Sí, todos los pines A están conectados a tierra. Sin cambios al cambiar a flanco descendente: i.imgur.com/ua54TsF.png

¿Ha simulado su VHDL para verificar que está haciendo lo que espera?

Su FPGA debería actuar como el maestro SPI pero no genera la señal SPI CLK. El proceso VHDL también está cronometrado desde la misma señal SPI CLK (sck) y debido a que no hay reloj en esta señal, su proceso no hace nada.

El reloj se genera en el módulo de nivel superior y luego se alimenta al módulo SPI, así como a la línea spi_sck que va al chip. La simulación se ve bien. SW0 baja y los bits de configuración saltan. i.imgur.com/irxxmrg.png
Esa imagen sim no prueba que su maestro SPI en el FPGA esté funcionando. De hecho, ni siquiera lo muestra desplazando 8 bits correctamente, después de 4 bits, mosi dice 'U'. A mis ojos, su VHDL parece lógicamente incorrecto. Creo que necesita un banco de pruebas más completo para demostrar que su maestro SPI está funcionando.
Ya veo, parece cambiar los primeros 4 correctamente y la depuración muestra que está configurado después de 8 relojes, pero parece que faltan 4 bits. Puede ver los bits de configuración en el código anterior: bits de configuración variables: std_logic_vector (7 hasta 0) := "0100" & dirección & "1";
Nota adicional, hay una errata para esta parte a la que quizás desee prestar atención, IIRC afecta la dirección que necesita usar.