Schnittstelle eines MCP23S17 (SPI) mit einem FPGA

Ich arbeite mit einem MCP23S17 SPI I/O-Expander-Chip in einem VHDL-Projekt auf meinem Basys 2 .

Auf den ersten Blick dachte ich, dies sei nur eine einfache SPI-Schnittstelle, bei der ich die Chipauswahl auf niedrig stelle und sie mir die Daten auf der MISO-Leitung liefert, aber es sieht so aus, als wäre sie mit den erforderlichen Befehlen und Initialisierungen etwas komplizierter.

Ich habe einige Setup-Bits ("0100" & "000" & "1") hinzugefügt, die einmal auf der MOSI-Leitung erscheinen, wenn Sie versuchen, Daten zu lesen. aber es hat sich nichts geändert. Es scheint viele Register zu geben, um Einstellungen zu speichern, aber ich habe keine Ahnung, wie ich diese einstellen soll.

SPI-Steuerbyte-Format

Hier ist ein Diagramm, wie ich alles angeschlossen habe. Die Test-E/A dient nur dazu, sicherzustellen, dass ich einige bekannte Bits habe, die angezeigt werden sollten, wenn die Transaktion erfolgreich ist. Ich werde die B-Seite des Chips verwenden. Wenn also etwas Besonderes passieren muss, um das zu lesen, dann erklären Sie es bitte.Chip-Setup

Was muss passieren, um Daten vom Chip zu lesen?

Hier ist das SPI-Modul (SPI.vhd), das ich bisher geschrieben habe.

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;

Ich habe nicht viele Artikel gefunden, die über diesen Chip mit Code sprechen. Ich habe einige Arduino-Sachen gefunden, aber sie verwenden alle die SPI-Bibliothek, die nicht hilft zu erklären, was genau passiert. Hier sind die wenigen Links, die ich gefunden habe:

Bearbeiten:

In Ordnung, nachdem ich an dem gearbeitet habe, was Dave Tweed gesagt hat. Ich kann die Befehle auf MOSI senden und produzieren, aber auf der MISO-Leitung kommt nichts zurück. Denken Sie daran, dass das FPGA die Daten erhalten sollte und ich einen Logikanalysator habe, der die Bits anzeigt, wenn etwas herauskommt und mein FPGA-Code falsch ist.

CS:   1111000000000000000000000000
MOSI: xxxx0100aaa10000110000000000
MISO: xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Hier ist die Simulation in ISim. **Dies gibt keine Daten auf MISO zurück, da es sich nur um eine Simulation ohne Chip handelt, um tatsächlich die richtigen Daten zurückzusenden.*ISim-Ergebnisse

Und von einem Logikanalysator in der realen Welt:Ergebnisse des Logikanalysators

Hier ist der Update-SPI.vhd-Modulcode:

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;

Antworten (2)

Der MCP23S17 ist eigentlich für den Anschluss an einen Mikrocontroller gedacht. Ich habe es erfolgreich in einem Blackfin-basierten Projekt eingesetzt. Es verfügt über eine Reihe interner Register, genau wie die GPIO-Ports eines typischen Mikrocontrollers. Jeder 8-Bit-Port hat ein Richtungsregister, ein Eingangsregister und ein Ausgangsregister sowie Register für Eingangspolarität und Interrupt-on-Change. Es gibt auch ein globales Konfigurationsregister.

Beim Einschalten werden standardmäßig alle Eingänge verwendet. Wenn das alles ist, was Sie brauchen, müssen Sie nur eine Zustandsmaschine erstellen, die die beiden Eingangsregister liest. Beachten Sie, dass Sie für jeden Lesezyklus sowohl ein Chip-Adress-Byte als auch ein Register-Adress-Byte bereitstellen müssen.

Außerdem müssen Sie sich darüber im Klaren sein, dass dieser Chip die funky Eigenschaft hat, zwei verschiedene Adresszuordnungen für die Register zu haben, abhängig von der Einstellung des "BANK"-Bits. Studieren Sie diesen Teil sorgfältig; es ist ziemlich verwirrend.

Das BANK-Bit ist beim Einschalten Null, sodass die beiden gewünschten Register GPIOA und GPIOB an den Adressen 12 bzw. 13 zu finden sind. Um beide zu lesen, müssen Sie daher zwei 24-Takt-SPI-Zyklen durchführen:

CS:   1111000000000000000000000000111111110000000000000000000000001111
MOSI: xxxx0100aaa10000110000000000xxxxxxxx0100aaa10000110100000000xxxx
MISO: xxxx0000000000000000AAAAAAAAxxxxxxxx0000000000000000BBBBBBBBxxxx
  • "aaa" steht für die Chipadresse.
  • "AAAAAAAA" steht für die Daten von Port A
  • "BBBBBBBB" repräsentiert die Daten von Port B

Beachten Sie, dass alles MSB-first ist.

Können Sie mir ein Beispiel für den Versuch geben, A- und B-Seiten zu lesen (was ich MOSI runterpumpen sollte)? Muss ich diesen Befehl jedes Mal ausführen, wenn ich die Daten haben möchte?
Ich habe meinen Code aktualisiert, damit diese Befehle korrekt ausgegeben werden können (funktioniert in der Simulation und in der realen Welt - Logikanalysator). Sie können meine Ergebnisse in der OP sehen. Obwohl ich diese Befehle auf MOSI sende, kommt nichts auf MISO zurück.
Sie verwenden die Chipadresse aaa=000. Sind Sie sicher, dass die entsprechenden Pins (A2, A1, A0) am 23S17 geerdet sind? Außerdem scheinen sich die MOSI-Daten an der steigenden Flanke der Uhr zu ändern. Sie sollten die Daten an der fallenden Flanke ändern, da die Setup- und Haltezeiten des 23S17 relativ zur steigenden Flanke sind.
Ja, alle A-Pins sind geerdet. Keine Änderung beim Wechsel auf fallende Flanke: i.imgur.com/ua54TsF.png

Haben Sie Ihr VHDL simuliert, um zu überprüfen, ob es das tut, was Sie erwarten?

Ihr FPGA sollte als SPI-Master fungieren, erzeugt jedoch kein SPI-CLK-Signal. Der VHDL-Prozess wird auch von demselben SPI CLK-Signal (sck) getaktet, und da dieses Signal keinen Takt hat, tut Ihr Prozess nichts.

Der Takt wird auf dem Top-Level-Modul generiert und dann in das SPI-Modul sowie die spi_sck-Leitung zum Chip eingespeist. Die Simulation sieht ok aus. SW0 geht auf Low und die Setup-Bits springen heraus. i.imgur.com/irxxmrg.png
Dieses Simulationsbild beweist nicht, dass Ihr SPI-Master auf dem FPGA funktioniert. Tatsächlich zeigt es nicht einmal, dass es 8 Bits richtig verschiebt, nach 4 Bits geht Mosi auf 'U'. In meinen Augen sieht Ihr VHDL logisch falsch aus. Ich denke, Sie brauchen einen umfassenderen Prüfstand, um zu beweisen, dass Ihr SPI-Master funktioniert.
Ich sehe, es scheint die ersten 4 korrekt zu verschieben und der Debug zeigt, dass es nach 8 Takten eingerichtet ist, aber es scheinen tatsächlich 4 Bits zu fehlen. Sie können die setupBits im obigen Code sehen: variable setupBits: std_logic_vector(7 downto 0) := "0100" & address & "1";
Zusätzlicher Hinweis, es gibt eine Errata für diesen Teil, auf die Sie vielleicht achten sollten, IIRC wirkt sich auf die Adresse aus, die Sie verwenden müssen.