J'ai essayé de concevoir un module qui me permettra de modifier les réponses esclaves sélectionnées sur un bus I2C. Voici la configuration de bus d'origine (les tractions et les connexions d'alimentation ne sont pas affichées pour plus de clarté:
Il n'y a que 2 appareils sur ce bus et ce n'est qu'à 100 kHz. Un contrôleur MCU (maître I2C) et le lecteur de carte RFID (esclave I2C) NXP PN512. Je ne peux pas modifier le micrologiciel du contrôleur ni changer les transactions du bus I2C. La bonne partie est que le contrôleur envoie seulement 2 types de transactions:
Master (Write Register) - <s><address+W><register number><data><p>
Master (Read Register) - <s><address+W><register number><p><s><address+R><data><p>
Ce que je veux faire, c'est remplacer les octets de données sélectionnés lors de la lecture du registre maître par mes propres octets. Je peux envoyer les numéros de registre que le MCU veut lire sur mon PC via UART (921.6kbaud). Je peux les traiter en C / C ++ ou Python là-bas. Lorsque je reçois le numéro de registre dont la valeur doit être remplacée, je peux renvoyer un faux octet à mon appareil et il se chargera de le renvoyer au contrôleur en remplaçant la réponse de la carte d'origine.
Au début, j'ai divisé le bus I2C en deux bus:
J'ai essayé Arduino Nano et plus tard un CPLD en utilisant l'étirement de l'horloge. Le matériel ATmega328 I2C faisant face au contrôleur MCU n'a pas pu suivre car parfois la séquence de démarrage a été générée plus tôt que 5us après le cycle d'arrêt précédent. Donc, de temps en temps, l'AVR effectuait une transaction de lecture. Le CPLD pouvait gérer la vitesse d'arrêt / démarrage, il s'est avéré que l'étirement du bus était désactivé dans le MCU.
J'ai eu l'idée que je pouvais "prédire" la lecture du registre maître en détectant une écriture sur un seul octet car je suis sûr qu'elle est suivie d'une lecture. Il semble que j'ai eu assez de temps pendant l'écriture d'adresse du cycle de lecture suivant pour introduire l'octet de l'esclave. Cela n'a pas vraiment fonctionné. Les transactions de bus semblaient bien au début (environ les 5 premières secondes) mais ensuite le contrôleur cessait toutes les communications sur le bus comme s'il détectait qu'il ne parlait pas directement pour lire la balise.
Le lecteur de carte peut également générer des interruptions vers le maître. Les IRQ sont basés sur un temporisateur ou un événement. J'ai attribué le problème au retard que j'introduisais par nature dans le bus. Je me suis peut-être trompé, mais j'ai trouvé une autre conception «zéro retard».
L'idée est que je ne peux que couper la ligne SDA et laisser la ligne SCL connectée entre le maître et l'esclave. De cette façon, je peux toujours remplacer les octets sur la ligne de données dans les deux sens. La conception s'est avérée plus compliquée car je dois contrôler la direction de la ligne SDA en fonction du cycle du bus. Voici le code VHDL qui gère les transactions de bus et envoie des octets hexadécimaux via UART à l'ordinateur. La réception d'octets de l'ordinateur n'est pas encore implémentée:
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;
Vous trouverez ci-dessous les transitions de bus capturées avec le CPLD contrôlant la ligne SDA.
S'inscrire écrire:
S'inscrire lire:
Vous pouvez voir quelques problèmes lorsque la direction du bus change. Cela est dû aux différences de synchronisation entre le CPLD changeant la direction du bus et le lecteur de carte générant un ACK. Le niveau ACK semble stable sur le front montant du SCL. Autant que je sache, c'est tout ce dont vous avez besoin.
Avec cette chose en place, le contrôleur se comporte de la même manière qu'avec les bus divisés suspendant toute activité de bus en quelques secondes. Je teste également ce w Arduino qui se moque de ce MCU et génère du trafic de bus pour moi et il semble que l'Arduino se fige également de temps en temps. Donc je suppose que je peux avoir une sorte de problème avec la machine d'état VHDL où, dans certaines conditions, je suis coincé dans un état sans issue. Des idées?
la source
There's only 2 devices on this bus running at 100kHz
et ensuiteThe hardware I2C was a slave and a bit banged I2C was a master on the card reader bus at 1Mbps
. Pourquoi y a-t-il deux bus? Pourquoi le besoin du bus à grande vitesse? Fournissez un croquis de votre conception initiale et essayez de clarifier votre question.Réponses:
Je pense que tenter des hacks cutsey comme vous l'avez été pose des problèmes, avec exactement le type de symptômes que vous rencontrez. Vous essayez essentiellement de tricher et espérez ne pas vous faire prendre.
La seule chose que vous n'avez pas essayé, selon votre description, est une émulation complète de ce lecteur de carte. Vous n'avez pas vraiment expliqué ce qu'il fait exactement et à quel point c'est compliqué, mais à en juger par ce que le maître envoie, ce n'est pas si compliqué.
Utilisez un microcontrôleur avec une capacité esclave IIC matérielle. C'est connecté au maître. Le firmware émule le lecteur de carte. Étant donné que la seule chose que le maître lit jamais est une séquence de registres, l'autre partie du micrologiciel communique complètement de manière asynchrone avec le lecteur de carte pour en obtenir des informations et les contrôler. Cela signifie également que les lignes de réinitialisation et d'IRQ sont également séparées.
Si c'est bien fait, cela doit fonctionner, car il n'y a pas de tricherie. Le lecteur de carte voit un contrôleur lui envoyer des commandes et lire exactement comment il était destiné à être utilisé. Cela comprend la réponse aux événements IRQ.
Le maître pense qu'il parle directement à un vrai lecteur de carte parce que vous émulez toutes ses opérations comme la vraie chose, y compris le comportement de réinitialisation et d'IRQ.
Cela peut sembler plus de travail que certains brouillages rapides et sales d'un octet différent sur le piratage du bus, mais comme vous l'avez constaté, ce n'est pas si rapide et peut toujours avoir des problèmes de synchronisation. Avec une émulation complète, toutes les contraintes de synchronisation sont levées. Si votre émulation n'a pas encore rattrapé quelque chose que le lecteur de cartes a fait, alors elle agit pour le maître comme si elle ne s'était pas encore produite. Vous prétendez que rien de nouveau ne s'est produit jusqu'à ce que votre émulation soit prête à répondre à l'événement sous tous ses aspects.
Cela signifie que vous avez vraiment deux parties asynchrones du firmware: l'émulation IIC du lecteur présenté au maître et un pilote de lecteur de carte complet qui vous permet de garder tout son état en direct dans votre mémoire interne.
Puisque vous ne trichez pas, cela doit fonctionner s'il est bien fait. Le seul problème au niveau du système est qu'il y aura un certain retard dans la vue principale et les actions du lecteur de cartes par rapport au système existant. Cela ne sonne pas comme un gros problème pour un "lecteur de carte", et compte tenu de ce retard serait probablement de 10 secondes de millisecondes au pire. Cela ne devrait certainement pas être perceptible à l'échelle du temps humain.
Notez que la communication entre votre émulateur et le lecteur de carte n'est pas limitée aux 100 kbits / s actuellement utilisés. Vous devez l'exécuter aussi vite que le lecteur de carte et votre matériel le permettent. Après tout, sur ce lien, vous serez le maître, vous possédez donc l'horloge. Encore une fois, avec une architecture de micrologiciel appropriée et des tâches asynchrones, cela ne devrait pas avoir d'importance. En fait, votre pilote communiquera probablement plus souvent et obtiendra plus de données du lecteur de cartes que le maître n'en obtient de votre émulateur.
la source
Je dirais que vous êtes sur la bonne voie avec un Arduino Nano comme MITM, bien que je pense que ce serait mieux avec deux.
Le NXP-PN512 fonctionnera à une vitesse d'horloge de 3,4 MHz, je suggère donc que vous puissiez utiliser quelque chose de l'ordre de 1,5 à 2 MHz pour le MCU de droite qui parle au Reader.
Étant donné que le MCU de gauche est réglé sur 100 kHz, une fois que vous avez reconnu tous les octets de transaction (adresse / registre-WR), vous pouvez le copier sur un bus parallèle 8 bits (ou même plus large) entre les MCU et envoyer les commandes au lecteur dans moins d'une heure d'horloge sur le canal I2C à vitesse lente. La réception égale d'un octet du lecteur est réalisée en moins d'une heure d'horloge sur le bus lent, ce qui donne le temps nécessaire pour configurer l'octet de réponse.
Je suppose ici que vous devrez peut-être traduire plusieurs octets en tant qu'identifiant NFC et pas seulement une conversion d'octet par octet (ce qui nécessite moins de temps).
Le problème majeur que je verrais alors est que si vous avez besoin de sérialiser plusieurs octets vers / depuis le PC pour mapper vos modifications, le timing devient encore plus critique. S'il y avait un moyen de construire votre algorithme / table de changement de mappage dans le MCU de gauche, cela semblerait une meilleure approche, bien que la résolution d'un mappage d'identifiants multi-bye soit toujours le plus grand défi.
Si je me trompe et que vous avez juste besoin de mapper, disons un seul octet d'identification de carte, cela pourrait fonctionner.
Lors de vos premiers tests avec l'Arduino, avez-vous vérifié que toutes les interruptions étaient désactivées (au moins seulement TWI utilisé)? Si vous ne l'avez pas fait, cela pourrait avoir gâché votre timing.
la source