He estado tratando de diseñar un módulo que me permita modificar las respuestas esclavas seleccionadas en un bus I2C. Aquí está la configuración original del bus (los pull-ups y las conexiones de alimentación no se muestran para mayor claridad:
Solo hay 2 dispositivos en este bus y solo tiene 100 kHz. Un controlador MCU (maestro I2C) y el lector de tarjetas RFID (esclavo I2C) NXP PN512. No puedo modificar el firmware del controlador o cambiar las transacciones del bus I2C. Lo bueno es que el controlador envía solo 2 tipos de transacciones:
Master (Write Register) - <s><address+W><register number><data><p>
Master (Read Register) - <s><address+W><register number><p><s><address+R><data><p>
Lo que quiero hacer es reemplazar los bytes de datos seleccionados durante la lectura del registro maestro con mis propios bytes. Puedo enviar los números de registro que el MCU quiere leer a mi PC a través de UART (921.6kbaudios). Puedo procesarlos en C / C ++ o Python allí. Cuando recibo el número de registro cuyo valor necesita ser reemplazado, puedo enviar un byte falso de regreso a mi dispositivo y se encargará de enviarlo de regreso al controlador reemplazando la respuesta original de la tarjeta.
Al principio dividí el bus I2C en dos buses:
Probé Arduino Nano y luego un CPLD usando el estiramiento del reloj. El hardware I2C de ATmega328 que enfrenta el controlador MCU no pudo seguir el ritmo ya que a veces la secuencia de inicio se generó antes de 5us después del ciclo de parada anterior. Entonces, de vez en cuando, el AVR estaba haciendo una transacción de lectura. El CPLD podía manejar la velocidad de parada / arranque, resultó que el estiramiento del bus estaba desactivado en la MCU.
Se me ocurrió una idea de que puedo "predecir" la lectura del registro maestro al detectar una escritura de un solo byte, ya que estoy seguro de que es seguida por una lectura. Parece que tuve suficiente tiempo durante el siguiente ciclo de lectura de escritura de dirección para traer el byte del esclavo. Eso no funcionó del todo. Las transacciones del bus parecían estar bien al principio (aproximadamente los primeros 5 segundos), pero luego el controlador interrumpió todas las comunicaciones en el bus como si detectara que no está hablando directamente con la lectura de etiqueta.
El lector de tarjetas también puede generar interrupciones al maestro. Las IRQ son un temporizador o evento basado. Atribuí el problema a la demora que introduje inherentemente en el autobús. Puede que me haya equivocado, pero se me ocurrió otro diseño de "retraso cero".
La idea es que solo puedo romper la línea SDA y dejar la línea SCL conectada entre el maestro y el esclavo. De esta manera, todavía puedo reemplazar bytes en la línea de datos en cualquier dirección. El diseño resultó ser más complicado ya que tengo que controlar la dirección de la línea SDA en función del ciclo del bus. Aquí está el código VHDL que maneja las transacciones del bus y envía bytes hexadecimales sobre UART a la computadora. La recepción de bytes desde la computadora aún no está implementada:
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;
A continuación se muestran las transiciones de bus capturadas con el CPLD que controla la línea SDA.
Registrarse escribir:
Registrarse leer:
Puede ver algunos problemas técnicos cuando cambia la dirección del autobús. Esto se debe a las diferencias de tiempo entre el CPLD que cambia la dirección del bus y el lector de tarjetas que genera un ACK. El nivel de ACK parece ser estable en el borde ascendente de la SCL. Por lo que sé, eso es todo lo que necesitas.
Con esto en su lugar, el controlador se comporta de la misma manera que con los buses divididos que suspenden cualquier actividad del bus en unos pocos segundos. También pruebo que w Arduino se burla de esa MCU y genera tráfico de autobús para mí y parece que Arduino también se congela de vez en cuando. Así que supongo que puedo tener algún tipo de problema con la máquina de estado VHDL donde, en algunas condiciones, me quedo atascado en un estado sin salida. ¿Algunas ideas?
fuente
There's only 2 devices on this bus running at 100kHz
y luegoThe hardware I2C was a slave and a bit banged I2C was a master on the card reader bus at 1Mbps
. ¿Por qué hay dos autobuses? ¿Por qué la necesidad del autobús de alta velocidad? Proporcione un boceto de su diseño inicial e intente aclarar su pregunta.Respuestas:
Creo que intentar hackear cutsey como lo has estado haciendo es pedir problemas, con exactamente el tipo de síntomas que estás teniendo. Básicamente estás tratando de hacer trampa y espero que no te atrapen.
Lo único que no ha probado, según su descripción, es una emulación completa de este lector de tarjetas. Realmente no has explicado qué hace exactamente y qué tan complicado es, pero a juzgar por lo que el maestro está enviando no es tan complicado.
Utilice un microcontrolador con capacidad de esclavo IIC de hardware. Eso está conectado con el maestro. El firmware emula el lector de tarjetas. Como lo único que lee el maestro es una secuencia de registros, la otra parte del firmware se comunica de forma completamente asíncrona con el lector de tarjetas para obtener información y controlarla. Esto también significa que las líneas de reinicio e IRQ también están separadas.
Si se hace bien, esto tiene que funcionar, ya que no hay trampas. El lector de tarjetas ve un controlador que le envía comandos y hace lecturas exactamente de la forma en que se utilizaron. Esto incluye responder a eventos IRQ.
El maestro cree que está hablando directamente con un lector de tarjetas real porque emula todas sus operaciones como si fueran reales, incluido el comportamiento de reinicio e IRQ.
Esto puede parecer más trabajo que algo rápido y sucio al atascar un byte diferente en el truco del autobús, pero como descubrió, eso no es tan rápido y siempre puede tener algunos problemas de sincronización. Con una emulación completa, se eliminan todas las restricciones de tiempo. Si su emulación aún no ha alcanzado algo que el lector de tarjetas haya hecho, entonces actúa ante el maestro como si aún no hubiera sucedido. Básicamente finges que no ha sucedido nada nuevo hasta que tu emulación esté lista para responder al evento en todos los aspectos.
Esto significa que realmente tiene dos partes asincrónicas del firmware: la emulación IIC del lector presentada al maestro y un controlador de lector de tarjetas completo que le permite mantener vivo todo su estado en su memoria interna.
Como no estás haciendo trampa, esto tiene que funcionar si se hace bien. El único problema a nivel del sistema es que habrá un retraso en la vista maestra y causará acciones del lector de tarjetas que el sistema existente. Esto no suena como un gran problema para un "lector de tarjetas", y teniendo en cuenta este retraso probablemente sería de 10 segundos en el peor de los casos. Ciertamente no debería ser notable en una escala de tiempo humana.
Tenga en cuenta que la comunicación entre su emulador y el lector de tarjetas no se limita a los 100 kbits / s utilizados actualmente. Debe ejecutarlo tan rápido como lo permita el lector de tarjetas y su hardware. Después de todo, en ese enlace serás el maestro, así que eres el dueño del reloj. Nuevamente, con una arquitectura de firmware adecuada y tareas asincrónicas, esto no debería importar. De hecho, su controlador probablemente se comunicará con más frecuencia y obtendrá más datos del lector de tarjetas que el maestro de su emulador.
fuente
Sugeriría que estabas en el camino correcto con un Arduino Nano como MITM, aunque creo que sería mejor con dos.
El NXP-PN512 funcionará a una velocidad de reloj de 3.4 Mhz, por lo que le sugiero que use algo del orden de 1.5 - 2 MHz para la MCU de la derecha que habla con el Lector.
Como la MCU de la izquierda está configurada a 100 kHz, una vez que haya reconocido los bytes de transacción (dirección / registro-WR) puede copiarla en un bus paralelo de 8 bits (o incluso más ancho) entre las MCU y enviar los comandos al lector en menos de una hora en el canal I2C de baja velocidad. Igualmente, se recibe un byte del lector en menos de un tiempo de reloj en el bus lento, dando tiempo suficiente para configurar el byte de respuesta.
Supongo que es posible que deba traducir varios bytes como una ID NFC y no solo una conversión byte a byte (que requiere menos tiempo).
El problema principal que vería entonces es que si necesita serializar múltiples bytes hacia / desde la PC para mapear sus cambios, el tiempo se vuelve aún más crítico. Si hubiera una manera de construir su algoritmo / tabla de cambio de mapeo en la MCU de la izquierda, eso parecería un mejor enfoque, aunque resolver un mapeo de identificador de bye sigue siendo el mayor desafío.
Si me equivoco y solo necesita asignar un solo byte identificador de tarjeta, entonces esto podría funcionar.
En sus primeras pruebas con el Arduino, ¿se aseguró de que todas las interrupciones estuvieran apagadas (al menos solo TWI en uso)? Si no lo hizo, entonces esto puede haber alterado su tiempo.
fuente