Eu tenho tentado projetar um módulo que permita modificar respostas de escravos selecionadas em um barramento I2C. Aqui está a configuração do barramento original (as conexões pull-ups e de energia não são mostradas para maior clareza:
Existem apenas 2 dispositivos neste barramento e são apenas 100kHz. Um controlador MCU (mestre I2C) e o leitor de cartão RFID (escravo I2C) NXP PN512. Não consigo modificar o firmware do controlador ou alterar as transações de barramento I2C. A parte boa é que o Controller envia apenas 2 tipos de transações:
Master (Write Register) - <s><address+W><register number><data><p>
Master (Read Register) - <s><address+W><register number><p><s><address+R><data><p>
O que eu quero fazer é substituir os bytes de dados selecionados durante a leitura do registro mestre pelos meus próprios bytes. Posso enviar os números de registro que o MCU deseja ler para o meu PC pelo UART (921.6kbaud). Eu posso processá-los em C / C ++ ou Python lá. Quando recebo o número de registro cujo valor precisa ser substituído, posso enviar um byte falso de volta ao meu dispositivo e ele cuidará de enviá-lo de volta ao controlador, substituindo a resposta do cartão original.
No começo, dividi o barramento I2C em dois:
Eu tentei o Arduino Nano e depois um CPLD usando o alongamento do relógio. O hardware ATmega328 I2C voltado para o controlador MCU não pôde acompanhar, pois às vezes a sequência de início era gerada antes de 5us após o ciclo de parada anterior. Então, de vez em quando, o AVR fazia NAK uma transação de leitura. O CPLD podia lidar com a velocidade de parada / partida, pois o alongamento do barramento estava desativado no MCU.
Tive uma idéia de que eu posso "prever" a leitura do registro mestre detectando uma gravação de um byte, pois tenho certeza de que é seguida por uma leitura. Parece que tive tempo suficiente durante a gravação do endereço do ciclo de leitura a seguir para obter o byte do escravo. Isso não deu certo. As transações do barramento pareciam boas no início (aproximadamente 5 primeiros segundos), mas o controlador estava interrompendo todas as comunicações no barramento como se detectasse que não está falando diretamente com a leitura de tags.
O leitor de cartão também pode gerar interrupções para o mestre. Os IRQs são baseados em um cronômetro ou evento. Atribuí o problema ao atraso que eu estava introduzindo inerentemente no ônibus. Eu poderia estar errado, mas criei outro design de "atraso zero".
A idéia é que eu só posso interromper a linha SDA e deixar a linha SCL conectada entre o mestre e o escravo. Dessa forma, ainda posso substituir bytes na linha de dados em qualquer direção. O design provou ser mais complicado, pois tenho que controlar a direção da linha SDA com base no ciclo do barramento. Aqui está o código VHDL que lida com as transações de barramento e envia bytes hexadecimais pelo UART para o computador. O recebimento de bytes do computador ainda não foi implementado:
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;
Abaixo estão as transações de ônibus capturadas com o CPLD controlando a linha SDA.
Registrar escrever:
Registre-se lido:
Você pode ver algumas falhas quando a direção do ônibus muda. Isso é causado pelas diferenças de tempo entre o CPLD mudar a direção do barramento e o leitor de cartão que gera um ACK. O nível de ACK parece estável na borda ascendente do SCL. Tanto quanto sei, é tudo o que você precisa.
Com isso, o controlador se comporta da mesma maneira que com os barramentos divididos suspendendo qualquer atividade de barramentos em alguns segundos. Também testo o w Arduino que zomba desse MCU e gera tráfego de ônibus para mim e parece que o Arduino também congela de vez em quando. Então, acho que posso ter algum tipo de problema com a máquina de estado VHDL, em que, sob algumas condições, fico presa em um estado sem saída. Alguma ideia?
fonte
There's only 2 devices on this bus running at 100kHz
e depoisThe hardware I2C was a slave and a bit banged I2C was a master on the card reader bus at 1Mbps
. Por que existem dois ônibus? Por que a necessidade do ônibus de alta velocidade? Faça um esboço do seu design inicial e tente esclarecer sua pergunta.Respostas:
Acho que tentar hackers cutsey como você está pedindo problemas, com exatamente o tipo de sintomas nos quais você está se deparando. Você está basicamente tentando trapacear e espero que não seja pego.
A única coisa que você não tentou, de acordo com sua descrição, é uma emulação completa dessa coisa de leitor de cartão. Você realmente não explicou exatamente o que faz e como é complicado, mas, a julgar pelo que o mestre está enviando, não é tão complicado.
Use um microcontrolador com capacidade de escravo IIC de hardware. Isso está conectado ao mestre. O firmware emula o leitor de cartão. Como a única coisa que o mestre lê é uma sequência de registros, a outra parte do firmware se comunica completamente de forma assíncrona com o leitor de cartão para obter informações e controlá-lo. Isso também significa que as linhas de redefinição e IRQ também são separadas.
Se bem feito, isso tem que funcionar, pois não há trapaça. O leitor de cartão vê um controlador enviando comandos e fazendo leituras exatamente como ele deveria ser usado. Isso inclui responder a eventos de IRQ.
O mestre acha que está falando diretamente com um leitor de cartão real, porque você emula todas as suas operações exatamente como a coisa real, incluindo o comportamento de redefinição e IRQ.
Isso pode parecer mais trabalhoso do que algum bloqueio rápido e sujo de um byte diferente no hack do barramento, mas como você descobriu, isso não é tão rápido e pode sempre ter alguns problemas de tempo. Com uma emulação completa, todas as restrições de tempo são levantadas. Se sua emulação ainda não alcançou algo que o leitor de cartão fez, ela age com o mestre como se ainda não tivesse acontecido. Você basicamente finge que nada de novo aconteceu até que sua emulação esteja pronta para responder ao evento em todos os aspectos.
Isso significa que você realmente tem duas partes assíncronas do firmware: a emulação da IIC do leitor apresentada ao mestre e um driver completo do leitor de cartão que permite manter todo o seu estado ativo na memória interna.
Como você não está trapaceando, isso deve funcionar se for feito corretamente. O único problema no nível do sistema é que haverá algum atraso no mestre vendo e causando ações do leitor de cartão do que o sistema existente. Isso não soa muito para um "leitor de cartão" e, considerando esse atraso, provavelmente seria 10s de milissegundos na pior das hipóteses. Certamente não deve ser perceptível em uma escala de tempo humana.
Observe que a comunicação entre o emulador e o leitor de cartão não se limita aos 100 kbits / s usados atualmente. Você deve executá-lo o mais rápido que o leitor de cartão e seu hardware permitirem. Afinal, nesse link você será o mestre, e será o dono do relógio. Novamente, com arquitetura de firmware adequada e tarefas assíncronas, isso não deve importar. De fato, seu driver provavelmente se comunicará com mais frequência e obterá mais dados do leitor de cartão do que o mestre obtém do emulador.
fonte
Eu sugiro que você esteja no caminho certo com um Arduino Nano como o MITM, embora eu ache que seria melhor com dois.
O NXP-PN512 funcionará na velocidade de clock de 3,4 Mhz, por isso sugiro que você use algo da ordem de 1,5 - 2 MHz para o MCU do lado direito conversando com o Reader.
Como o MCU esquerdo está definido em 100 kHz, depois de reconhecer os bytes de transação (endereço / registrador-WR), você pode copiá-lo em um barramento paralelo de 8 bits (ou mais amplo) entre os MCUs e enviar os comandos ao leitor em menos de uma hora no canal I2C de baixa velocidade. Igualmente, o recebimento de um byte do leitor é alcançado em menos de um relógio no barramento lento, proporcionando tempo suficiente para configurar o byte de resposta.
Estou assumindo aqui que você pode realmente precisar converter vários bytes como um ID NFC e não apenas uma conversão de byte a byte (que requer menos tempo).
O principal problema que eu veria então é que, se você precisar serializar vários bytes de / para o PC para mapear suas alterações, o tempo se tornará ainda mais crítico. Se houvesse uma maneira de criar seu algoritmo / tabela de alteração de mapeamento no MCU do lado esquerdo, isso pareceria uma abordagem melhor, embora resolver um mapeamento de identificador de vários bytes ainda seja o maior desafio.
Se eu estiver errado e você apenas precisar mapear, digamos, um único byte de identificador de cartão, isso poderá funcionar.
Nos seus primeiros testes com o Arduino, você garantiu que todas as interrupções foram desativadas (pelo menos apenas o TWI está sendo usado)? Caso contrário, isso pode ter interferido no seu tempo.
fonte