Ich habe versucht, ein Modul zu entwerfen, mit dem ich ausgewählte Slave-Antworten auf einem I2C-Bus ändern kann. Hier ist die ursprüngliche Buskonfiguration (die Klimmzüge und Stromanschlüsse sind aus Gründen der Übersichtlichkeit nicht dargestellt:
Es gibt nur 2 Geräte auf diesem Bus und es ist nur 100kHz. Eine Controller-MCU (I2C-Master) und der RFID-Kartenleser (I2C-Slave) NXP PN512. Ich kann die Controller-Firmware oder die I2C-Bustransaktionen nicht ändern. Der gute Teil ist, dass der Controller nur zwei Arten von Transaktionen sendet:
Master (Write Register) - <s><address+W><register number><data><p>
Master (Read Register) - <s><address+W><register number><p><s><address+R><data><p>
Ich möchte ausgewählte Datenbytes während des Lesens des Master-Registers durch meine eigenen Bytes ersetzen. Ich kann die Registernummern, die die MCU lesen möchte, über UART (921,6 kBaud) an meinen PC senden. Ich kann sie dort in C / C ++ oder Python verarbeiten. Wenn ich die Registernummer erhalte, deren Wert ersetzt werden muss, kann ich ein gefälschtes Byte an mein Gerät zurücksenden, das sich darum kümmert, es an den Controller zurückzusenden, wobei die ursprüngliche Kartenantwort ersetzt wird.
Zuerst habe ich den I2C-Bus in zwei Busse aufgeteilt:
Ich habe Arduino Nano und später eine CPLD mit Clock Stretching ausprobiert. Die dem MCU-Controller zugewandte ATmega328-Hardware I2C konnte nicht mithalten, da die Startsequenz manchmal früher als 5us nach dem vorherigen Stoppzyklus generiert wurde. Ab und zu machte der AVR eine Lesetransaktion. Die CPLD konnte mit der Stopp- / Startgeschwindigkeit umgehen, bei der sich herausstellte, dass die Busdehnung in der MCU deaktiviert war.
Ich hatte die Idee, dass ich das Lesen des Master-Registers durch Erkennen eines einzelnen Byte-Schreibvorgangs "vorhersagen" kann, da ich sicher bin, dass darauf ein Lesevorgang folgt. Es scheint, dass ich während des folgenden Lesezyklus-Adressschreibens genug Zeit hatte, um das Byte vom Slave einzubringen. Das hat nicht ganz funktioniert. Die Bustransaktionen schienen am Anfang in Ordnung zu sein (ca. die ersten 5 Sekunden), aber dann stellte der Controller die gesamte Kommunikation auf dem Bus ein, als ob er feststellte, dass er nicht direkt mit dem Lesen von Tags spricht.
Der Kartenleser kann auch Interrupts für den Master erzeugen. Die IRQs basieren auf Timern oder Ereignissen. Ich schrieb das Problem der Verzögerung zu, die ich von Natur aus im Bus eingeführt hatte. Ich hätte mich vielleicht geirrt, aber ich habe mir ein anderes "Zero-Delay" -Design ausgedacht.
Die Idee ist, dass ich nur die SDA-Leitung unterbrechen und die SCL-Leitung zwischen Master und Slave angeschlossen lassen kann. Auf diese Weise kann ich immer noch Bytes auf der Datenleitung in beide Richtungen ersetzen. Das Design erwies sich als komplizierter, da ich die SDA-Leitungsrichtung basierend auf dem Buszyklus steuern muss. Hier ist der VHDL-Code, der die Bustransaktionen verarbeitet und Hex-Bytes über UART an den Computer sendet. Das Empfangen von Bytes vom Computer ist noch nicht implementiert:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity I2C_Sniffer is
port (
clk : in std_logic;
scl_master : in std_logic;
sda_master : inout std_logic;
sda_slave : inout std_logic;
tx : out std_logic
);
end entity I2C_Sniffer;
architecture arch of I2C_Sniffer is
signal clkDiv: std_logic_vector(7 downto 0) := (others => '0');
type I2C_STATE is (I2C_IDLE, I2C_MASTER_WRITE, I2C_SLAVE_ACK, I2C_MASTER_READ, I2C_MASTER_ACK);
signal i2cState: I2C_STATE := I2C_IDLE;
type I2C_BUS_DIR is (MASTER_TO_SLAVE, SLAVE_TO_MASTER);
signal i2cBusDir: I2C_BUS_DIR := MASTER_TO_SLAVE;
signal i2cRxData: std_logic_vector(7 downto 0);
signal i2cCntr: integer range 0 to 8 := 0;
signal i2cAddr: std_logic := '1';
signal i2cCmd: std_logic := '0';
signal scl_d: std_logic := '1';
signal scl: std_logic := '1';
signal sda_d: std_logic := '1';
signal sda: std_logic := '1';
--Strobes for SCL edges and Start/Stop bits
signal start_strobe : std_logic := '0';
signal stop_strobe : std_logic := '0';
signal scl_rising_strobe : std_logic := '0';
signal scl_falling_strobe : std_logic := '0';
type UART_STATE is (UART_IDLE, UART_START, UART_DATA, UART_STOP);
signal uartState: UART_STATE := UART_IDLE;
signal uartTxRdy: std_logic := '0';
signal uartTxData: std_logic_vector(7 downto 0);
signal uartCntr: integer range 0 to 8 := 0;
begin
CLK_DIV: process (clk)
begin
if rising_edge(clk) then
clkDiv <= std_logic_vector(unsigned(clkDiv) + 1);
end if;
end process;
I2C_STROBES: process (clk)
begin
if rising_edge(clk) then
--Pipelined SDA and SCL signals
scl_d <= scl_master;
scl <= scl_d;
scl_rising_strobe <= '0';
if scl = '0' and scl_d = '1' then
scl_rising_strobe <= '1';
end if;
scl_falling_strobe <= '0';
if scl = '1' and scl_d = '0' then
scl_falling_strobe <= '1';
end if;
if i2cBusDir = MASTER_TO_SLAVE then
sda_d <= sda_master;
sda <= sda_d;
else
sda_d <= sda_slave;
sda <= sda_d;
end if;
start_strobe <= '0';
if sda_d = '0' and sda = '1' and scl = '1' and scl_d = '1' then
start_strobe <= '1';
end if;
stop_strobe <= '0';
if sda_d = '1' and sda = '0' and scl = '1' and scl_d = '1' then
stop_strobe <= '1';
end if;
end if;
end process;
BUS_DIR: process(sda_master, sda_slave, i2cBusDir)
begin
if i2cBusDir = MASTER_TO_SLAVE then
sda_slave <= sda_master;
sda_master <= 'Z';
else
sda_master <= sda_slave;
sda_slave <= 'Z';
end if;
end process;
I2C: process(clk)
begin
if rising_edge(clk) then
uartTxRdy <= '0';
case i2cState is
when I2C_IDLE =>
i2cBusDir <= MASTER_TO_SLAVE;
if start_strobe = '1' then
i2cAddr <= '1';
i2cCntr <= 0;
i2cState <= I2C_MASTER_WRITE;
end if;
-- Master Write (Address/Data)
when I2C_MASTER_WRITE =>
i2cBusDir <= MASTER_TO_SLAVE;
if stop_strobe = '1' then
i2cState <= I2C_IDLE;
uartTxData <= "00001010";
uartTxRdy <= '1';
end if;
if scl_rising_strobe = '1' then
if i2cCntr <= 7 then
i2cRxData(7 - i2cCntr) <= sda;
i2cCntr <= i2cCntr + 1;
end if;
end if;
if i2cCntr = 4 then
case i2cRxData(7 downto 4) is
when "0000" => uartTxData <= "00110000"; --0
when "0001" => uartTxData <= "00110001"; --1
when "0010" => uartTxData <= "00110010"; --2
when "0011" => uartTxData <= "00110011"; --3
when "0100" => uartTxData <= "00110100"; --4
when "0101" => uartTxData <= "00110101"; --5
when "0110" => uartTxData <= "00110110"; --6
when "0111" => uartTxData <= "00110111"; --7
when "1000" => uartTxData <= "00111000"; --8
when "1001" => uartTxData <= "00111001"; --9
when "1010" => uartTxData <= "01000001"; --A
when "1011" => uartTxData <= "01000010"; --B
when "1100" => uartTxData <= "01000011"; --C
when "1101" => uartTxData <= "01000100"; --D
when "1110" => uartTxData <= "01000101"; --E
when "1111" => uartTxData <= "01000110"; --F
when others => uartTxData <= "00111111"; --?
end case;
uartTxRdy <= '1';
end if;
if i2cCntr = 8 then
case i2cRxData(3 downto 0) is
when "0000" => uartTxData <= "00110000"; --0
when "0001" => uartTxData <= "00110001"; --1
when "0010" => uartTxData <= "00110010"; --2
when "0011" => uartTxData <= "00110011"; --3
when "0100" => uartTxData <= "00110100"; --4
when "0101" => uartTxData <= "00110101"; --5
when "0110" => uartTxData <= "00110110"; --6
when "0111" => uartTxData <= "00110111"; --7
when "1000" => uartTxData <= "00111000"; --8
when "1001" => uartTxData <= "00111001"; --9
when "1010" => uartTxData <= "01000001"; --A
when "1011" => uartTxData <= "01000010"; --B
when "1100" => uartTxData <= "01000011"; --C
when "1101" => uartTxData <= "01000100"; --D
when "1110" => uartTxData <= "01000101"; --E
when "1111" => uartTxData <= "01000110"; --F
when others => uartTxData <= "00111111"; --?
end case;
uartTxRdy <= '1';
end if;
if i2cCntr = 8 then
if scl_falling_strobe = '1' then
i2cState <= I2C_SLAVE_ACK;
if i2cAddr = '1' then
i2cCmd <= i2cRxData(0);
i2cAddr <= '0';
end if;
end if;
end if;
when I2C_SLAVE_ACK =>
i2cBusDir <= SLAVE_TO_MASTER;
if scl_falling_strobe = '1' then
i2cCntr <= 0;
if i2cCmd = '0' then
i2cState <= I2C_MASTER_WRITE;
else
i2cState <= I2C_MASTER_READ;
end if;
end if;
when I2C_MASTER_READ =>
i2cBusDir <= SLAVE_TO_MASTER;
if stop_strobe = '1' then
i2cState <= I2C_IDLE;
uartTxData <= "00001010";
uartTxRdy <= '1';
end if;
if scl_rising_strobe = '1' then
if i2cCntr <= 7 then
i2cRxData(7 - i2cCntr) <= sda;
i2cCntr <= i2cCntr + 1;
end if;
end if;
if i2cCntr = 4 then
case i2cRxData(7 downto 4) is
when "0000" => uartTxData <= "00110000"; --0
when "0001" => uartTxData <= "00110001"; --1
when "0010" => uartTxData <= "00110010"; --2
when "0011" => uartTxData <= "00110011"; --3
when "0100" => uartTxData <= "00110100"; --4
when "0101" => uartTxData <= "00110101"; --5
when "0110" => uartTxData <= "00110110"; --6
when "0111" => uartTxData <= "00110111"; --7
when "1000" => uartTxData <= "00111000"; --8
when "1001" => uartTxData <= "00111001"; --9
when "1010" => uartTxData <= "01000001"; --A
when "1011" => uartTxData <= "01000010"; --B
when "1100" => uartTxData <= "01000011"; --C
when "1101" => uartTxData <= "01000100"; --D
when "1110" => uartTxData <= "01000101"; --E
when "1111" => uartTxData <= "01000110"; --F
when others => uartTxData <= "00111111"; --?
end case;
uartTxRdy <= '1';
end if;
if i2cCntr = 8 then
case i2cRxData(3 downto 0) is
when "0000" => uartTxData <= "00110000"; --0
when "0001" => uartTxData <= "00110001"; --1
when "0010" => uartTxData <= "00110010"; --2
when "0011" => uartTxData <= "00110011"; --3
when "0100" => uartTxData <= "00110100"; --4
when "0101" => uartTxData <= "00110101"; --5
when "0110" => uartTxData <= "00110110"; --6
when "0111" => uartTxData <= "00110111"; --7
when "1000" => uartTxData <= "00111000"; --8
when "1001" => uartTxData <= "00111001"; --9
when "1010" => uartTxData <= "01000001"; --A
when "1011" => uartTxData <= "01000010"; --B
when "1100" => uartTxData <= "01000011"; --C
when "1101" => uartTxData <= "01000100"; --D
when "1110" => uartTxData <= "01000101"; --E
when "1111" => uartTxData <= "01000110"; --F
when others => uartTxData <= "00111111"; --?
end case;
uartTxRdy <= '1';
end if;
if i2cCntr = 8 and scl_falling_strobe = '1' then
i2cState <= I2C_MASTER_ACK;
end if;
when I2C_MASTER_ACK =>
i2cBusDir <= MASTER_TO_SLAVE;
if scl_falling_strobe = '1' then
i2cCntr <= 0;
end if;
if stop_strobe = '1' then
i2cState <= I2C_IDLE;
uartTxData <= "00001010"; -- \n
uartTxRdy <= '1';
end if;
end case;
end if;
end process;
UART: process (clk, clkDiv(1), uartTxRdy)
begin
if rising_edge(clk) then
case uartState is
when UART_IDLE =>
if uartTxRdy = '1' then
uartState <= UART_START;
end if;
when UART_START =>
if clkDiv(1 downto 0) = "00" then
tx <= '0';
uartState <= UART_DATA;
uartCntr <= 0;
end if;
when UART_DATA =>
if clkDiv(1 downto 0) = "00" then
if uartCntr <= 7 then
uartCntr <= uartCntr + 1;
tx <= uartTxData(uartCntr);
else
tx <= '1';
uartState <= UART_STOP;
end if;
end if;
when UART_STOP =>
if clkDiv(1 downto 0) = "00" then
tx <= '1';
uartState <= UART_IDLE;
end if;
end case;
end if;
end process;
end architecture arch;
Unten sind die Busübergänge aufgeführt, die mit der CPLD erfasst wurden, die die SDA-Leitung steuert.
Registrieren schreiben:
Register lesen:
Sie können einige Störungen sehen, wenn sich die Busrichtung ändert. Dies wird durch die zeitlichen Unterschiede zwischen der CPLD, die die Busrichtung ändert, und dem Kartenleser verursacht, der eine ACK erzeugt. Der ACK-Pegel scheint an der ansteigenden Flanke der SCL stabil zu sein. Soweit ich weiß, ist das alles was du brauchst.
Mit dieser Funktion verhält sich der Controller genauso wie mit den geteilten Bussen, die die Busaktivität innerhalb weniger Sekunden unterbrechen. Ich teste auch das Arduino, das diese MCU verspottet und Busverkehr für mich erzeugt, und es sieht so aus, als ob Arduino auch ab und zu einfriert. Ich glaube, ich habe ein Problem mit der VHDL-Zustandsmaschine, bei der ich unter bestimmten Umständen in einem Zustand ohne Ausweg stecken bleibe. Irgendwelche Ideen?
quelle
There's only 2 devices on this bus running at 100kHz
und dannThe hardware I2C was a slave and a bit banged I2C was a master on the card reader bus at 1Mbps
. Warum gibt es zwei Busse? Warum braucht man den Hochgeschwindigkeitsbus? Stellen Sie eine Skizze Ihres ursprünglichen Entwurfs bereit und versuchen Sie, Ihre Frage zu klären.Antworten:
Ich denke, dass der Versuch, Cutsey-Hacks wie Sie zu versuchen, um Ärger bittet, mit genau den Symptomen, auf die Sie stoßen. Sie versuchen im Grunde zu betrügen und hoffen, dass Sie nicht erwischt werden.
Das einzige, was Sie Ihrer Beschreibung nach nicht ausprobiert haben, ist eine vollständige Emulation dieser Kartenleser-Sache. Sie haben nicht wirklich erklärt, was genau es tut und wie kompliziert es ist, aber nach dem zu urteilen, was der Meister sendet, ist es nicht so kompliziert.
Verwenden Sie einen Mikrocontroller mit Hardware-IIC-Slave-Fähigkeit. Das ist mit dem Master verbunden. Die Firmware emuliert den Kartenleser. Da der Master nur eine Folge von Registern liest, kommuniziert der andere Teil der Firmware vollständig asynchron mit dem Kartenleser, um Informationen von ihm abzurufen und zu steuern. Dies bedeutet auch, dass die Reset- und IRQ-Leitungen ebenfalls getrennt sind.
Wenn es richtig gemacht wird, muss dies funktionieren, da kein Betrug stattfindet. Der Kartenleser sieht einen Controller, der ihm Befehle sendet und genau liest, wie er verwendet werden soll. Dies beinhaltet die Reaktion auf IRQ-Ereignisse.
Der Master glaubt, dass er direkt mit einem echten Kartenleser spricht, da Sie alle seine Vorgänge genau wie das Original emulieren, einschließlich des Zurücksetzens und des IRQ-Verhaltens.
Dies mag nach mehr Arbeit klingen als ein schnelles und schmutziges Stören eines anderen Bytes auf den Bus-Hack, aber wie Sie festgestellt haben, ist dies nicht so schnell und kann immer zu Zeitproblemen führen. Bei einer vollständigen Emulation werden alle zeitlichen Einschränkungen aufgehoben. Wenn Ihre Emulation noch nicht etwas erreicht hat, was der Kartenleser getan hat, verhält sie sich gegenüber dem Master so, als wäre es noch nicht geschehen. Sie tun im Grunde so, als wäre nichts Neues passiert, bis Ihre Emulation bereit ist, in allen Aspekten auf das Ereignis zu reagieren.
Dies bedeutet, dass Sie wirklich zwei asynchrone Teile der Firmware haben: Die IIC-Emulation des Lesegeräts, die dem Master präsentiert wird, und einen vollständigen Kartenlesertreiber, mit dem Sie den gesamten Status in Ihrem internen Speicher erhalten können.
Da Sie nicht schummeln, muss dies funktionieren, wenn es richtig gemacht wird. Das einzige Problem auf Systemebene besteht darin, dass der Master Kartenleseraktionen etwas länger sieht und verursacht als das vorhandene System. Für einen "Kartenleser" klingt dies nicht nach einer großen Sache, und wenn man bedenkt, dass diese Verzögerung im schlimmsten Fall 10 Sekunden Millisekunden betragen würde. Es sollte auf menschlicher Zeitskala sicherlich nicht wahrnehmbar sein.
Beachten Sie, dass die Kommunikation zwischen Ihrem Emulator und dem Kartenleser nicht auf die derzeit verwendeten 100 kbit / s beschränkt ist. Sie sollten dies so schnell ausführen, wie es der Kartenleser und Ihre Hardware zulassen. Auf diesem Link bist du schließlich der Meister, also besitzt du die Uhr. Auch bei einer ordnungsgemäßen Firmware-Architektur und asynchronen Aufgaben sollte dies keine Rolle spielen. In der Tat wird Ihr Treiber wahrscheinlich häufiger kommunizieren und mehr Daten vom Kartenleser erhalten, als der Master von Ihrem Emulator erhält.
quelle
Ich würde vorschlagen, dass Sie mit einem Arduino Nano als MITM auf dem richtigen Weg waren, obwohl ich denke, dass es mit zwei am besten wäre.
Der NXP-PN512 wird mit einer Taktrate von 3,4 MHz betrieben, daher würde ich vorschlagen, dass Sie für die rechte MCU, die mit dem Reader spricht, etwas in der Größenordnung von 1,5 bis 2 MHz verwenden können.
Da die linke MCU auf 100 kHz eingestellt ist, können Sie Transaktionsbytes (Adresse / Register-WR), sobald Sie sie erkannt haben, über einen 8-Bit-Parallelbus (oder noch breiter) zwischen den MCUs kopieren und die Befehle an den Leser senden weniger als eine Uhrzeit auf dem langsamen I2C-Kanal. Gleichermaßen wird das Empfangen eines Bytes vom Lesegerät in weniger als einer Taktzeit auf dem langsamen Bus erreicht, was ausreichend Zeit zum Einrichten des Antwortbytes gibt.
Ich gehe hier davon aus, dass Sie möglicherweise tatsächlich mehrere Bytes als NFC-ID übersetzen müssen und nicht nur eine einzelne Byte-für-Byte-Konvertierung (was weniger Zeit erfordert).
Das Hauptproblem, das ich dann sehen würde, ist, dass das Timing noch kritischer wird, wenn Sie mehrere Bytes zum / vom PC serialisieren müssen, um Ihre Änderungen abzubilden. Wenn es eine Möglichkeit gäbe, Ihren Mapping-Änderungsalgorithmus / Ihre Mapping-Änderungstabelle in die linke MCU zu integrieren, wäre dies ein besserer Ansatz, obwohl das Lösen eines Multi-Bye-Identifier-Mappings immer noch die größte Herausforderung darstellt.
Wenn ich falsch liege und Sie nur ein einzelnes Kartenidentifizierungsbyte zuordnen müssen, funktioniert dies möglicherweise.
Haben Sie bei Ihren frühen Tests mit dem Arduino sichergestellt, dass alle Interrupts ausgeschaltet sind (zumindest wird nur TWI verwendet)? Wenn Sie dies nicht getan haben, hat dies möglicherweise Ihr Timing beeinträchtigt.
quelle