Engenharia reversa de um microcontrolador desconhecido





Gravata complicada



Fundo ...



Como parte do meu trabalho de  engenharia reversa de etiquetas de preços eletrônicas eInk,  encontrei um problema interessante. Uma empresa específica (Samsung Electro Mechanics / SoluM) deixou de usar chips de terceiros, cuja origem consegui identificar (Marvell 88MZ100), para um novo chip, que passou a usar com suas etiquetas de preços de próxima geração.



Parecia que se tratava de um chip próprio, desenvolvido pela empresa para esse fim. Assumir a engenharia reversa de tal coisa é um assunto morto. Um amigo me deu algumas etiquetas de preços com essas fichas - para mexer. Descobriu-se que são de dois tipos: um com display segmentado em e-ink e outro com display gráfico convencional em e-ink. O chip principal em ambos os modelos é o mesmo, então a primeira coisa que fiz foi com um monitor segmentado, por ser mais simples e mais fácil lidar com um sistema desconhecido usando-o. Não ficou totalmente claro por onde começar, mas, claro, essas são as tarefas que são sempre as mais interessantes! 



Estudar





É tolice tentar resolver um quebra-cabeça de palavras cruzadas sem ler as perguntas. É tão tolo quanto fazer engenharia reversa em um dispositivo sem primeiro coletar todas as informações que já estão disponíveis sobre ele. Então, o que sabemos inicialmente? O protocolo de transferência de dados sem fio é provavelmente o mesmo de costume, já que nenhuma empresa deseja migrar para um novo ou oferecer suporte a dois protocolos para seus clientes ao mesmo tempo, realizando a migração lentamente. O protocolo antigo era semelhante ao ZigBee de 2,4 GHz, então o novo provavelmente é o mesmo. Aqui está uma foto do tabuleiro de ambos os lados.





Então, o que vemos? Primeiro, um bom exemplo de otimização de custos. Eles laminaram a tela e-ink diretamente no PCB! Quem precisa de um painel traseiro de vidro condutor quando há um PCB? O painel frontal é feito de plástico condutor. Mas não é importante.



Duas antenas são visíveis, ambas, a julgar por seu tamanho - em 2,4 GHz. Como esperado, já que os dispositivos da geração anterior também tinham duas antenas de 2,4 GHz. Vemos dois chips. Grande e pequeno. O grande (denominado "SEM9010") aparentemente tem muitos contatos indo para o display e nenhum para as antenas. Obviamente, este é um controlador de exibição.



O pequeno (designado "SEM9110") parece ser o cérebro responsável por todas as operações. Ele é conectado às antenas, ao cristal de temporização e aos pontos-chave que são óbvios aqui para a programação de fábrica.



Existem 12 pads aqui: um está conectado ao terminal positivo da bateria, um ao terra, o propósito dos outros 10 é um mistério. Procurando o nome do chip online, não encontro nada útil - definitivamente o seu próprio desenvolvimento. Mas quem projeta seu próprio chip para uma aplicação tão simples? Talvez apenas uma reformulação da marca? Aproveitados, estamos trabalhando!



Curiosamente, a Pesquisa de Imagens do Google ajudou aqui. Acontece que essa ferramenta é útil para engenharia reversa. Nesse caso, ele nos traz a esta pepita. (cópia arquivada  aqui  para posteridade). Esta é uma pergunta do StackExchange - querendo saber como essas etiquetas eletrônicas de prateleira funcionam. A pergunta é interessante porque na foto postada aqui, a placa de circuito impresso parece quase  idêntica à  nossa. Os chips também são exatamente iguais, mas os rótulos neles são diferentes! A placa provavelmente foi feita antes que SoluM começasse a mudar a marca desses chips.



O chip que presumi ser o controlador de vídeo está rotulado  SSD1623L2



. Na verdade, é um controlador de display segmentado de tinta eletrônica que suporta até 96 segmentos. Pesquisando online, encontro a  folha de dados da versão 0.1 de pré-lançamento (cópia arquivada  aqui para a posteridade). É bom! Se eles soubessem como fazer isso, eles poderiam pegar um código que ele entende e, assim que vermos esse código, isso é tudo!



Acontece que o microcontrolador principal é ZBS242



. OK. Não estou familiarizado com este microcontrolador. Vamos pesquisar um pouco mais na Internet - e as pesquisas nos levam ao link  (cópia do arquivo  aqui para a posteridade), que também menciona a mesma resposta do StackExchange. A página é coreana, mas mostra que este chip tem um núcleo 8051, bem como um equipamento periférico bastante previsível: UART, SPI, I2C, ADC, DAC, comparador, sensor de temperatura, PWM de 5 canais, controlador triac de 3 canais , Transmissor IR, função key scan, função RF-Wake, espaçamento da antena, rádio compatível com ZigBiee e MAC. A imagem mostra que também existe um oscilador RC interno de 32 kHz, que, conforme declarado, pode consumir apenas 1 uA no modo de hibernação. Acho que foi essa empresa que fez nosso chip para a Samsung. Interessante ...



Vamos olhar as fotos e descobrir que o cristal SEM9110 que nos intrigou também foi  feito à queima-roupa  (cópia de arquivo  aqui  para a posteridade). Afirma-se que é ZBS243. Acho que isso significa que há toda uma família de chips aqui: o ZBS24x. Muito interessante.



Temos um tópico!





Depois de abrir outra tag de segmento, continuamos a nos alegrar com a notícia: o cabeçalho de programação está assinado em letras douradas claras e legíveis! O cabeçote parece ter um SPI, UART, pino de reinicialização, fonte de alimentação, aterramento e um pino chamado “teste”, provavelmente usado para entrar no modo de teste de fábrica. Tudo é mais curioso e curioso.





É lógico que o representante mais antigo da família hipotética ZBS24x será designado "ZBS240". Talvez a busca por tal consulta nos dê algo interessante? Procurando por "ZBS240" e filtrando a escória, encontramos outra página interessante em coreano  (cópia arquivada  aqui  para a posteridade). Parece que essa empresa cria programadores de grupo sob demanda personalizados. Depois de dar uma olhada em seu site, encontramos um manual  (cópia de arquivo  aqui para a posteridade) em seu dispositivo de programação, e podemos até baixar um utilitário para um PC funcionar com tal dispositivo. Este utilitário ainda possui uma ferramenta para atualizar o firmware do dispositivo. Procurei ver se era possível adivinhar a partir dessas informações como programar o dispositivo, mas o firmware estava criptografado. Aparentemente, o utilitário do lado do PC está apenas enviando dados pela porta serial USB, portanto, também não há informações úteis aqui. Triste ... 



Depois de pesquisar um pouco mais, encontramos uma página ainda mais interessante  (cópia arquivada  aqui para a posteridade). O que é? Está em promoção?!? Definitivamente não é mais, certo? Acabei de escrever para esta empresa pedindo sabonete, só para garantir. Silêncio ... Em um gesto de desespero, perguntei a um amigo de Hong Kong se ele conhecia alguém na Coréia que pudesse entrar em contato com esses caras, já que o site deles mostra que eles só aceitam uma transferência de um banco coreano como pagamento. Fiquei surpreso quando ele bateu para trás e disse, de fato, ele poderia me conseguir este dispositivo através de um intermediário encontrado na Coreia! Poucos dias depois, o dispositivo foi entregue pela DHL!



Você pode alcançá-lo!



Como entrar em contato com ele



Trabalho! Posso ler o chip e escrever nele. Levei um tempo para pesquisar a ferramenta de programação. Aparentemente, o chip tem 64 KB de memória flash e um "bloco de informações" de 1 KB, que acredito ser usado para armazenar valores de calibração, endereços MAC e similares. Consegui interceptar alguns dos rastros, armado com o maravilhoso  analisador lógico Saleae Logic , observando o programador fazer seu trabalho. Você pode baixar minhas descobertas aqui . Neste arquivo você encontrará vestígios de leitura, apagamento e escrita nos espaços INFOBLOCK e CODE. Na verdade, o protocolo é MUITO  simples! A frequência do clock pode ser qualquer coisa de 100 kHz a 8 MHz.



Protocolo ISP: corte até o osso 



Tudo começa com a configuração das linhas para o estado desejado: SCLK inferior, MOSI superior, RESET superior, SS superior. Essa condição é mantida por 20 ms. Em seguida, o RESET é reduzido em 32 ms. Então, pelo menos 4 clocks do processador são enviados para a linha SCK a 500 kHz. Em seguida, há outro atraso de 10 ms até que RESET seja pressionado. Agora você pode definir um atraso de 100 ms antes de iniciar a comunicação. Depois disso, qualquer número de transações pode ser feito. Algumas regras básicas: deve haver pelo menos 5us entre SS descendo e enviando um byte, pelo menos 2us entre o final do byte e SS subindo, e o menor período que o SS pode gastar é 2,5us. Portanto, cada byte é enviado no estado: SS está inativo, um byte é enviado no modo SPI 0, SS está ativo. Sim, claro,SS inverte para cada byte.



Todas as transações têm de três a quatro bytes. O primeiro byte indica o tipo de transação, o bit mais baixo especifica a direção da transação: zero significa escrita no dispositivo, um significa leitura no dispositivo. Os comandos 0x02



/ 0x03



 são usados ​​para iniciar sessões de comunicação. O programador envia uma escrita de três bytes:  02 BA A5



e então lê, primeiro enviando o comando de leitura e "endereço":, o  03 BA



mestre envia FF



enquanto recebe A5



. Se funcionar, a comunicação é estabelecida.



Comandos  0x12



/ 0x13



 são usados ​​para ler / gravar registros de propósito especial (SFRs) na CPU (achei isso mais difícil, mas neste caso a ordem não é tão importante). Para selecionar INFOBLOCK, SFR  0xD8



 deve ser definido como 0x80



, para selecionar a área de flash principal, deve ser definido como 0x00



. Para escrever o valor de vv no registro rr, são necessários dados SPI  12 rr vv



. Para ter certeza de que o valor foi lido, ele pode ser lido enviando primeiro um comando de leitura e um "endereço":  13 rr



após o qual o mestre envia  FF



enquanto recebe  vv



.



É fácil ler a memória flash. Para fazer isso, aplique 0x09



, um comando de quatro bytes. Após o byte de comando, o endereço é enviado, primeiro o byte alto, depois o byte baixo. O mestre então envia  FF



, enquanto recebe o byte que foi lido. Bem, sim. Um comando separado é necessário para ler cada byte. Escrever também é fácil. Para isso, o comando é usado 0x08



. Este é um comando de quatro bytes. Após o byte de comando, o endereço é enviado, primeiro o byte superior, depois o inferior e, em seguida, o byte a ser escrito. Um comando separado também é necessário para gravar cada byte. Certifique-se de apagar antes de gravar. Para INFOBLOCK erase, que requer apenas uma sequência de 4 Bytes:  48 00 00 00



. O apagamento da memória flash principal é feito por meio do comando 88 00 00 00



.



Então agora você sabe o suficiente para programar trivialmente seu ZBS24x!



Ir trabalhar!







Primer para 8051



Se você já está familiarizado com o 8051, pode pular  esta seção com segurança  .



O 8051  é um  microcontrolador antigo projetado pela Intel na antiguidade . É um incômodo terrível de se trabalhar, mas ainda é usado com frequência porque é barato de licenciar (na verdade, gratuito). Qual é o problema? O 8051 possui vários espaços de memória separados. CODE



 É a área da memória reservada para o código. Seu tamanho máximo é 64 KB (endereço de 16 bits). Nos designs mais modernos, é a memória flash. O código pode ler bytes a partir daqui usando uma instrução especial movc



 ("MOVe do código").  XRAM



 É uma memória "externa". Ou seja, externo ao núcleo. Você pode armazenar várias coisas nele, mas é quase inútil para qualquer outra coisa. Assim: as únicas operações que podem ser realizadas nesta memória são a escrita e a leitura. Seu tamanho máximo é 64 KB (endereço de 16 bits). Como funciona a memória de endereço de um endereço de 8 bits com um endereço de 16 bits? Acontece que é muito lento. O comando movx



 ("MOVe to / from eXternal") acessa este tipo de memória, mas como você especifica um endereço de 16 bits? Para isso, DPTR



 utiliza-se um registro especial denominado  ("Data PoinTeR"), bem como para trabalhar com uma instrução movc



DPTR



 consiste em um registro superior  DPH



 e um registro inferior  DPL



... Consequentemente, escrevendo metade do endereço para cada um deles, você pode endereçar a memória externa e a memória de código. Como você pode imaginar, esse processo começa a escorregar rapidamente, pois, por exemplo, para copiar uma seção da memória externa para a memória externa, você precisará embaralhar repetidamente os valores entre DPL



 e  DPH



. Por esse motivo, algumas das versões mais avançadas do 8051 têm muitos registros  DPTR



, mas não todos, e nem todos são implementados da mesma maneira.



A Intel adicionou uma maneira mais rápida de acessar um subconjunto de memória externa. Nesse caso, a ideia é usar registradores R0



 e  R1



 como registradores de ponteiro. Mas eles têm 8 bits, de onde vêm os outros 8 bits do endereço? Eles são de um registro P2



 (que também controla a porta 2 para os pinos GPIO). Obviamente, essa prática atrapalha o uso da porta 2 para ... você sabe ... GPIO. Existem maneiras de amenizar essa situação, mas não estou falando sobre isso agora. Portanto, a quantidade de memória disponível para nós é limitada a 256 bytes (a menos que você altere dinamicamente a porta 2, o que provavelmente você não deseja fazer). Normalmente, essa memória é chamada PDATA



. Acessos de memória semelhantes também são feitos usando uma instrução  movx



. Em seguida temos SFR



- vários registros de configuração com os quais os periféricos são configurados. Esta área de memória só pode ser acessada diretamente. A situação é a seguinte: o endereço deve ser codificado diretamente na instrução, não haverá acesso por nenhum registrador de ponteiro. Existem 128 bytes SFR



. A tabela a seguir mostra as listas SFR



disponíveis de acordo com o padrão 8051. As caixas cinza contêm SFR



quais bits podem ser acessados ​​individualmente usando comandos bit a bit. Isso é útil ao atribuir pinos de porta atômicos ou ao ativar / desativar fontes de interrupção ou ao verificar alguns status.



A memória interna do 8051 é um pouco complicada. Em todos os 8051s modernos, é de 256 bytes. Os últimos 128 bytes  0x80-0xff



 estão disponíveis  apenas  indiretamente através dos registradores  R0



 e  R1



, mas, ao contrário da situação com a memória externa, agora não apenas a leitura e a gravação estão disponíveis para nós. Podemos realizar um aumento de um ( inc



rement), uma redução de um ( dec



rement), adição ( add



) e a maioria das outras operações esperadas. Na verdade, TODA  a RAM interna é acessada indiretamente através desses registradores de ponteiro. 128 bytes mais baixos 0x00-0x7f



 também disponível diretamente (o endereço é codificado diretamente na própria instrução, assim como ao trabalhar com SFR



. 16 bytes de memória no intervalo 0x20-0x2f



 também são endereçáveis ​​por bits usando instruções de processamento de bits. É conveniente armazenar variáveis ​​para valores booleanos em esta parte. Os 32 bytes mais baixos  0x00-0x1f



 constituem 4 registos de bancos  R0



... R7



No registo de estado  PSW



 existem bits que permitem seleccionar o banco que está a ser utilizado, mas na realidade, visto que normalmente existe uma falta de memória interna, o código usa principalmente apenas um banco de memória.



O 8051 é uma máquina projetada principalmente para funcionar com um único operando. Ou seja: na maioria das operações, a bateria é usada como uma das fontes e, possivelmente, como destino. Os registros também podem ser usados ​​para muitas (mas não todas) operações, e algumas operações permitem acesso indireto à RAM interna, conforme descrito acima. A pilha é um upstream vazio, endereçável SFR



, é chamada  sp



 e está localizada apenas na RAM interna, seu tamanho máximo é limitado a 256 bytes, mas na realidade é muito menor. 



Qualquer imagem do 8051 ROM começa com uma tabela de vetores que contém saltos para o código inicial que você deseja executar, bem como os manipuladores de interrupção. Em 8051, historicamente, o vetor de redefinição está localizado em 0x0000



, e os manipuladores de interrupção começam no endereço 0x0003



 e, em seguida, a cada 8 bytes. Como a instrução reti



 só é usada para retornar de interrupções, ela pode ser usada para detectar facilmente se uma função específica é um tratador de interrupção.



Preencha o seu canal de compilador C com tudo isso e dê uma tragada! 



Existe um compilador C adequado para esta arquitetura: Keil's C51. Mas não é barato. Também existe um compilador de código aberto: SDCC . É mais ou menos, mas grátis. Ao fazer este projeto, encontrei apenas dois grandes bugs nele, que só poderiam ser superados ignorando; não é ruim para um projeto de código aberto.



Vamos começar a análise



void prvTxBitbang(u8 val)
                  __naked {
  __asm__(
    "  setb  PSW.5       \n"
    "  jbc   _EA, 00004$ \n"
    "  clr   PSW.5       \n"
    "00004$:             \n"
    "  clr   C           \n"
    "  mov   A, DPL      \n"
    "  rlc   A           \n"
    "  mov   DPL, A      \n"
    "  mov   A, #0xff    \n"
    "  rlc   A           \n"
    "  mov   DPH, A      \n"
    "  mov   B, #11      \n"
    "00001$:             \n"
    "  mov   A, DPH      \n"
    "  rrc   A           \n"
    "  mov   DPH, A      \n"
    "  mov   A, DPL      \n"
    "  rrc   A           \n"
    "  mov   DPL, A      \n"
    "  jnc   00002$      \n"
    "  setb  _P1_0       \n"
    "  sjmp  00003$      \n"
    "00002$:             \n"
    "  clr   _P1_0       \n"
    "  nop               \n"
    "  nop               \n"
    "00003$:             \n" 
    "  nop               \n"
    "  nop               \n"
    "  nop               \n"
    "  djnz  B, 00001$   \n"
    "  mov   C, PSW.5    \n"
    "  mov   _EA, C      \n"
    "  ret               \n"
  );  }

      
      





É fácil começar com a configuração GPIO. Como regra, você encontrará vários bits correspondentes, que serão configurados ou apagados em vários registradores em sequência. Isso é lógico, pois ao ativar ou desativar, geralmente você tem que usar o pino como uma função (do GPIO), defini-lo como uma entrada ou saída e definir ou ler seu valor. Você deve encontrar esse tipo de código no início do trabalho. Vamos ver o que há aí ... descobrimos que o padrão registra P0



P1



P2



 na verdade usamos dessa forma, como lidar com os registradores GPIO. Observando quais registros são escritos em torno deles e o que então acontece com os bits neles (sejam eles lidos (entrada) ou gravados (saída)), podemos assumir que os registros  AD



AE



AF



 Está projetado para "a função" - e parece que GPIO, que são definidos os bits correspondente não são utilizados como GPIO, e todos GPIO, efectivamente utilizado como um GPIO, começar a trabalhar assim só depois de um bit correspondente em um desses registros será apagado. Eu os nomeei, PxFUNC



onde x é o número da porta. Então podemos concluir que  B9



BA



BB



 controlar a direção. Sempre que um bit é definido em um deles, o GPIO correspondente é apenas lido e, quando o bit é apagado, o GPIO correspondente é somente gravação. Portanto, entendemos que esses registros controlam a direção do GPIO. Eu os nomeei  PxDIR



onde x é o número da porta. Então agora, em teoria, eu poderia controlar o GPIO. Se eu soubesse qual deles faz o quê ... 



Decidi tentar todos eles em uma linha até encontrar aquele que controla o "pad de TESTE" no cabeçote de programação, ou talvez os pads URX e UTX. Enfim, na verdade ... descobri que a porta 1 pino 0 ( P1.0



) é "TEST",  P0.6



 isso é "UTX" e  P0.7



 isso é "URX". Tendo um GPIO controlado, você pode simplificar sua vida, mas apenas enquanto puder lidar com a depuração trocando GPIOs diferentes, e até se cansar disso. Eu tive tempo para praticar isso! 



Temos printf!



Usei essa função para transformar o painel de "TESTE" em uma porta serial 8n1 normal usando o método bit-bang e coletei a saída usando meu analisador lógico. Eu brinquei com ele até que ele forneceu a taxa de transmissão que meu USB para cabo adaptador serial poderia suportar. Eu já tinha uma implementação de printf em assembler 8051. Por uma hora, eu pratiquei a saída de linhas de depuração complexas dessa porta serial improvisada. Não é um mau começo, definitivamente, esta é a única maneira de agir para avançar com eficácia! 



Neste ponto, eu exibi na janela os valores de todos SFR



, para pelo menos navegar quais são esses valores. Ainda havia alguns problemas com pesquisas futuras. Para começar, o watchdog timer (WDT) parecia estar configurado por padrão e zerado o chip após um segundo de execução, então todos os meus experimentos tiveram que caber em um segundo ou menos. Eu ainda não sabia como operar o WDT, então aguentei essa limitação por um tempo. Seja como for, um segundo é muitos ciclos! 



Expandindo o acesso



Agora que consegui executar o código de maneira confiável e gerar os resultados, decidi descobrir onde estão os controles de tique. Quase todos os registradores têm pelo menos um registrador que controla diferentes velocidades (pelo menos a velocidade da CPU) e outro registrador que controla a taxa de clock (ou reset) de vários módulos. Eles geralmente são encontrados assim: o primeiro geralmente é registrado MUITO  cedo na carga inicial, e depois disso quase não é tocado (se o for). O segundo geralmente tem um bit definido (ciclos de clock) ou um pouco limpo antes de começarmos a configurar um periférico. Não sabemos onde os vários periféricos estão configurados, mas geralmente o conjunto  SFR



com números semelhantes corresponde a um dispositivo periférico. Então vamos ver. Definitivamente não é um caso, se encaixam nessa descrição: B7



. Vemos que ele é definido um bit de cada vez, antes que vários SFR



com números semelhantes sejam gravados  , e os bits nele serão apagados depois que as chamadas para vários SFR



com números semelhantes pararem. Também vemos que é inicialmente registrado como  0x2F



, portanto, aqui estamos lidando com periféricos que são incluídos antecipadamente. Uma vez que os bits parecem estar definidos  antes do que consideramos como periféricos de inicialização, chamarei este registrador CLKEN



... Eu brinquei com a alteração dos bits neste registro e parecia que nada aconteceu quando eles foram apagados. Em princípio, isso é lógico, já que não uso nenhum periférico.



Outro registro escrito próximo (o código literário geralmente inicializa todas as operações do relógio juntas), que então não é reescrito, é este 8E



. Ele escreve para  0x21



. Sugeri que pode estar relacionado à velocidade. Eu experimentei. Aparentemente, os 4 bits menos significativos não são refletidos de forma alguma no trabalho, então não tenho ideia de por que eles estão configurados em  0b0001



, mas os próximos três bits, provavelmente, mudam a velocidade da CPU de forma bastante significativa (pelo que posso julgar pela velocidade do meu UART, sujeito à deriva). O bit mais significativo pareceu alterar um pouco a frequência, presumi que seja o responsável por alternar entre o circuito RC interno e o cristal externo. Três bits, que supus funcionarem como um divisor de frequência, definem a velocidade do clock para parecer igual 16M / (1 + )



. Eu nomeei este registro CLKSPEED



. Consequentemente, a velocidade mais alta é alcançada no valor 0x01



e a mais baixa em  0xf1







Fazendo os cronômetros funcionarem



Muitos fabricantes baseiam-se em todos os tipos de coisas no 8051, portanto, há muito pouca padronização aqui. No entanto, a maioria não toca no equipamento normal do 8051, como o temporizador 0 e o temporizador 1. Observe: esta não é uma regra prática. Por exemplo, a TI altera significativamente os temporizadores em seus chips da série CC. Notei que neste chip, os registradores que normalmente deveriam configurar os temporizadores 8051 padrão parecem acontecer próximos, e o manipulador de interrupção nº 1 parece afetá-los também. É possível? Temporizadores padrão? Eu tentei e funcionou. Completamente padrão, aparentemente exatamente igual à especificação original. Eu verifiquei o registro  CLKEN



 e encontrei aquele bit 0 (máscara  0x01



) para fazer os temporizadores funcionarem. Confirmado que o registro padrão IEN0



 também funciona como esperado, e que os números 1 e 3 realmente conduzem interrupções para o temporizador 0 e temporizador 1! Os temporizadores parecem funcionar exatamente a 1/12 de 16 MHz, exatamente como seria esperado em um 8051 padrão operando a 16 MHz. Até agora, não descobri como alterar essa frequência. O que sabemos agora revela registros  TL0



TH0



TL1



TH1



TMOD



TCON



! Agora temos temporizadores de precisão funcionando!



Não tive preguiça de verificar se o temporizador 2 está realmente implementado no padrão 8052 (sequência de 8051). Não, não é. 



Ou talvez UART?



void uartInit(void) {
    // 
    CLKEN |= 0x20;
 
    //  
    P0FUNC |= (1 << 6) | (1 << 7);
    P0DIR &=~ (1 << 6);
    P0DIR |= (1 << 7);
 
    // 
    UARTBRGH = 0x00;
    UARTBRGL = 0x89;
    UARTSTA = 0x12;
}
 
void uartTx(u8 ch) {
    while (UARTSTA_1));
    UARTSTA_1 = 0;
    UARTBUF = ch;
}

      
      





Havia várias linhas no módulo OTA. Faz sentido que eles se relacionem com algo, certo? Talvez uma porta serial de depuração? Isso iria bem com uma placa que tem os pontos-chave "UTX" e "URX". Este código era um pouco complicado, mas parecia que estava armazenando bytes em algum tipo de buffer. O código definitivamente parecia um buffer de anel padrão. Eu olhei onde este buffer está sendo lido. Descobriu-se que estava no manipulador da interrupção # 0. Oooh, interessante. Poderia ser um manipulador de interrupção UART? O código parecia verificar o bit # 1 em uma área que se assemelhava a um registrador de status (registrador  98



) e, se fosse definido, lia um byte de nosso buffer de anel e o gravava em um registrador 99



... Se outro bit (# 0) foi definido no registro de status mencionado acima, então ele leu o registro  99



 e inseriu o resultado em ... outro buffer circular. Bem, isso está muito de acordo com o que eu esperaria de um manipulador de interrupção UART! O que faremos a seguir? 



Cada buffer circular possui dois ponteiros, um para leitura e outro para escrita. Faz sentido que eles sejam inicializados antes que o buffer seja usado para qualquer coisa. Portanto, se descobrirmos onde esses índices são inicializados, provavelmente encontraremos onde o UART está instalado, certo? Definitivamente se parece com isso. Nessa função, que inicializa o UART, vemos que GPIO  P0.6



 e  P0.7



definido no modo de função,  P0.7



 é colocado na entrada e  P0.6



 - na saída. Mais dois registradores:  9A



 e  9B



 são escritos com  0x00



 e,  0x89



 respectivamente. O registro que, de acordo com minha versão, trabalha com estados (registro  98



) é escrito como  0x10



, e então os bits 0 e 1 nele são apagados. Em seguida, o  CLKEN



 bit 5 é definido e o IEN0



 bit 0 é inserido. Isso, em princípio, é tudo de que precisamos! 



Assim, nomeamos o registro  e ele se  torna  . Nós sabemos isso  99



  UARTBUF



98



UARTSTA



UARTSTA



 precisa ser definido como 0x10 para que este bloco funcione, e sabemos que o bit 0 significa que o UART tem um byte livre na fila TX FIFO e o bit 1 significa que o UART tem um byte na fila RX FIFO para nós. Sabemos que o CLKEN



 bit 5 habilitou o relógio para o UART e que o número de interrupção 0 corresponde ao manipulador de interrupção UART. É apenas um tesouro de informações. Sabendo disso, consegui fazer um driver UART funcional em meu código e enviar uma mensagem de saída para o pino "UTX" desejado, que, como sabemos agora, está localizado na porta 0 pino 6 ( P0.6



). Também aprendemos que o ponto-chave "URX" está conectado  P0.7



e que esta é a linha RX no UART. O UART estava enviando dados a 115.200 bps, 8n1, e não foi afetado de forma alguma pelo registro CLKSPEED



... Então, quais são esses dois outros registros misteriosos que dão a esses significados mágicos? 



Tentei mexer nos dois registros restantes  9A



 e  9B



. Rapidamente ficou claro para que serviam. Estes são divisores de frequência. Eu conectei alguns valores para ver como eles afetam a taxa de transmissão. Acabou sendo simples.  9A



 (doravante referido como  UARTBRGL



) era o byte baixo e 9B



 (doravante referido  UARTBRGH



) era o byte alto (os 4 bits superiores são aparentemente ignorados). A taxa de transmissão é calculada simplesmente como  16M / (UARTBRGH:UARTBRGL + 1)



. Isso explica perfeitamente os valores que pareciam mágicos - eles correspondem a 115.200 baud.



Aparentemente, um pequeno bug está relacionado ao fato de que os bits de status podem ser apagados programaticamente sem afetar o FIFO, então se você acidentalmente apagar o bit que significa "há espaço livre no TX FIFO" ( UARTSTA



.1), então a interrupção nunca ocorrerá, e o bit permanecerá baixo.



Curiosamente, esses locais correspondem aos endereços 8051 corretos para  SCON



 e  SBUF



, que são os registros da porta serial do 8051. Os bits 0, 1 e 2  UARTSTA



 realmente se encaixam nas descrições  SCON



de 8051, mas é aí que a semelhança acabou. UART de 8051 requer que os bits 7 e 6 sejam definidos  SCON



em 0 e 1, só assim se tornará um UART normal. Este chip, neste caso, requer 0 e 0. Além disso, o 8051 UART geralmente não tem um divisor de baud, em vez do qual o temporizador 1 é usado.



Cronômetro de vigilância e "olhe!"



Nesse ponto, o limite de execução de 1 segundo garantido pela configuração padrão do watchdog estava começando a me incomodar. Decidi descobrir onde e como o watchdog está configurado. Normalmente, o cronômetro de watchdog é configurado como parte de sua própria função e é pequeno. Claro, não vou dizer que isso sempre acontece, mas na maioria das vezes é assim. Tive vários candidatos e tentei copiar de cada um deles as gravações de registros para meu programa de teste, mas o cão de guarda não cedeu. Eu precisava redefinir o chip corretamente a cada segundo.



Enquanto fazia exatamente isso, percebi uma função muito estranha. Aparentemente, ela leu o registro sob o número FF



, escreveu algo lá, em seguida, redefiniu P1DIR



, escreveu em algum outro registro e, em seguida, restaurou o valor original no registro  FF



. A esquisitice era que ele definia TODOS os  pinos da porta 1 como pino. Isso não faz sentido. Em outros modelos, a porta 1 possui vários pinos configurados como entrada. Além disso, esses registros são normalmente operados bit a bit, usando instruções  anl



(AND lógico) e  orl



(OR lógico). Uma escrita tão áspera para todo o registro parecia repulsiva de uma vez. O que há no registro FF



que precisa ser copiado e restaurado? Parecia muito estranho! 



Decidi investigar. Ao despejar o valor de registro para o console FF



, acabou sendo zero, o que, é claro, não combinava comigo. Procurei em todo o firmware e percebi que em quase todo lugar há uma gravação, depois um backup e, em seguida, o valor original é restaurado. Também percebi que escrever quase sempre acontece com um valor  0x04



e raramente com  0x00



... Este registro foi lido apenas durante o backup para posterior restauração, nenhuma outra ação foi executada neste valor. Que funcionalidade isso indica? Basicamente, é assim que os controles de banco de memória geralmente funcionam! Quando você tem mais informações do que pode caber em seu espaço de endereço, você tem que mudar. Esse padrão de acesso (backup antes da alteração e, em seguida, restauração) é típico para tais situações práticas. Mas o que eles podem armazenar? Este poderia ser? Esses loucos estão sobrecarregando o próprio espaço de memória SFR



?!



Eu escrevi um programa que poderia exibir os valores de todos  SFR



, todos os 128. Então eu transformei o bit  0x04



 em  FF



  SFR



e novamente tirou todo o espaço SFR



. Em seguida, o programa empacotou esse bit de volta e exibiu novamente todos os valores. Deus todo poderoso! E aqui está! O bit 2 no registro  FF



 realmente economiza espaço SFR



. Não tenho dúvidas de que, quando esse bit é definido, os valores que aparecem mudam. Aparentemente, isso não afetou TODOS os endereços  SFR



, mas muitos. Eu nomeei este registro CFGPAGE



.



Agora que  CFGPAGE



pensei que estava resolvido, voltei à minha função misteriosa, que zerou P1DIR



. Já sabendo que NÃO  é zerado neste caso P1DIR



, mas seu primo estranho em outra página SFR



, Tentei copiar este código em meu programa. Acredite ou não, eu acidentalmente me deparei com um código que desativa o WDT !!!



Investigou o código em torno desta função, uma vez que funções geralmente relacionadas em binários estão localizadas próximas umas das outras. De fato, havia várias funções próximas que também acessavam CFGPAGE



 e acessavam o endereço adjacente P1DIR



. Depois de algumas horas de tentativa e erro, entendi perfeitamente os detalhes de como funciona o watchdog. Na 4ª página de configurações, BF



aparece o endereço para controlar a habilitação e reinicialização do watchdog timer; o bit mais significativo deste registro ativa ou desativa a função de reinicialização do chip no cronômetro de watchdog. Eu nomeei isso WDTCONF



. Endereço  BA



 (que está  P1DIR



 na página de configuração 0) é o registro de ativação do watchdog. O bit 0 aqui habilita ou desabilita o próprio cronômetro de watchdog. Eu nomeei isso WDTENA



.



Até este ponto, eu ainda estava descobrindo como domar o cronômetro de vigilância. Demorou um pouco, mas no final eu descobri. Um registro  BB



 (agora denominado  WDTPET



) pode ser escrito em zero para domar o cronômetro de watchdog. Levei mais alguns minutos para descobrir como configurar o atraso no cronômetro de watchdog, uma vez que havia claramente uma lacuna no espaço de endereço entre BB



 e  BF



... O contador tem 24 bits e fica sobrecarregado quando domesticado. Não pode ser lido. Valor de recarga salvos em WDTRSTVALH



: WDTRSTVALM



: WDTRSTVALL



, localizado na BE



BD



BC



 respectivamente, na página de configuração 4. O contador conta  UP  com uma frequência de cerca de 62 kHz, e um estouro é acionado. Assim, a fim de fornecer um atraso maior, um valor menor deve ser escrito para esses registradores de reset.



Possibilidades mais sutis



Programação de memória flash



//    irqs 
voif flashDo(void) {
    TRIGGER |= 8;
    while (!(TCON2 & 0x08));
    
    TCON2 &=~ 0x48;
    SETTINGS &=~ 0x10;
}
 
void flashWrite(u8 pgNo, u16 ofst,
              void *src, u16 len) {
    u8 cfgPg, speed;
    
    speed = CLKSPEED;
    CLKSPEED = 0x21;
    cfgPg = CFGPAGE;
    CFGPAGE = 4;
    
    SETTINGS = 0x18;
    FWRTHREE = 3;
    FPGNO = pgNo;
    FWRDSTL = ofst;
    FWRDSTH = ofst >> 8;
    FWRLENL = len - 1;
    FWRLENH = (len - 1) >> 8;
    FWRSRCL = (u8)src;
    FWRSRCH = ((u16)src) >> 8;
    flashDo();
    
    CFGPAGE = cfgPg;
    CLKSPEED = speed;
}
void flashRead(u8 pgNo, u16 ofst,
    void __xdata *dst, u16 len) {
    u8 pgNo, cfgPg, speed;
    
    speed = CLKSPEED;
    CLKSPEED = 0x21;
    cfgPg = CFGPAGE;
    CFGPAGE = 4;
    
    SETTINGS = 0x8;
    FWRTHREE = 3;
    FPGNO = pgNo;
    FWRDSTL = (u8)dst;
    FWRDSTH = ((u16)dst) >> 8;
    FWRSRCL = ofst;
    FWRSRCH = ofst >> 8;
    FWRLENL = len - 1;
    FWRLENH = (len - 1) >> 8;
    flashDo();
    
    CFGPAGE = cfgPg;
    CLKSPEED = speed;
}
void flashErase(u8 pgNo) {
    u8 __xdata dummy = 0xff;
    u8 cfgPg, speed;
    
    speed = CLKSPEED;
    CLKSPEED = 0x21;
    cfgPg = CFGPAGE;
    CFGPAGE = 4;
    
    SETTINGS |= 0x38;
    FWRTHREE = 3;
    FPGNO = pgNo;
    FWRDSTL = 0;
    FWRDSTH = 0;
    FWRLENL = 0;
    FWRLENH = 0;
    FWRSRCL = (u8)&dummy;
    FWRSRCH = ((u16)&dummy) >> 8;
    flashDo();
    
    CFGPAGE = cfgPg;
    CLKSPEED = speed;
}

      
      





Concentrei-me na imagem OTA, pois é menor do que o firmware principal. Um detalhe que é definitivamente necessário na imagem OTA é a capacidade de gravar na memória flash. Com o que se parece? Supõe-se que precisamos de algum tipo de função que apague o flash, uma vez que o flash é apagado em blocos. Você também precisa de uma função de gravação que possa gravar uma página de dados ou menos. Precisamos de algum tipo de verificação dos dados registrados. O único detalhe que difere nas implementações é como alimentaremos os dados destinados à gravação no controlador de flash. Eu não sabia o que ele deve ser parecido, mas o resto foi fácil o suficiente para encontrar. A verificação provavelmente se resumiria a apenas ligar memcmp



ou ciclo. As operações de apagamento do flash desgastam a memória flash, por isso a página deve ser verificada antes de apagar e, em seguida, executar a operação. 



Procurando por uma verificação de pré-apagamento, rapidamente encontrei uma função que cria uma  área de bytes de 0x400



 byte para  XRAM



cheio 0xFF



. Em seguida, a área de memória é  CODE



comparada com esse buffer e, se não forem iguais, as interrupções são desabilitadas e algumas são tocadas SFR



na página de configuração 4. O tamanho da página na memória flash é claramente de 1024 bytes. Verificar quais outros lugares são afetados pelo mesmo SFR



, encontramos o código flash restante. É claro a partir do contexto o que esses registros fazem e como. Nesse caso, é interessante como os dados são enviados para a unidade de controle da memória flash. Este bloco de controle contém claramente um bloco DMA. Um endereço é fornecido para a unidade de controle de memória flash XDATA



e os dados são absorvidos diretamente de lá. Que legal!



Naquela época, eu ainda não tinha certeza de como ler o INFOBLOCK. Aparentemente, o código OTA não o preocupava, mas de algum lugar DEVE  ser lido - afinal, há dados nele. Eu verifiquei a imagem principal e notei um snippet de código afetando o mesmo  SFR



da memória flash, mas de uma forma diferente. Com mais algumas análises, consegui reproduzir a leitura correta do INFOBLOCK. É curioso que o mesmo método possa ser usado para ler qualquer outro bloco de memória flash, mas não há necessidade de fazer isso, pois tudo o que você precisa fazer para ler a memória flash é ler a área da memória  CODE



. INFOBLOCK só é acessível através da unidade de controle de memória flash. Para gravação e leitura da memória flash, o bloco de controle usa acesso direto à memória (DMA) e grava  XDATA



.



Um registrador  DF



 ( FWRTHREE



) desafiou qualquer tentativa de explicá-lo. Sempre teve um recorde com o valor 0x03



, Eu não sei porque. Meu código de acesso flash faz o mesmo. O registro  D8



 ( FPGNO



) é escrito com o número da página do flash. As páginas principais da memória flash são numeradas de 0 a 63, INFOBLOCK tem o número 128  DA



.: D9



 ( FWRSRCH



:) FWRSRCL



é a fonte do bloco DMA no bloco de controle da memória flash. Para gravar no flash, ele contém o endereço XDATA



onde encontramos os dados a serem gravados. Para ler o flash, um deslocamento de byte na página original é procurado e a leitura começa nesse deslocamento.  DC



: DB



 ( FWRDSTH



: FWRDSTL



) É a atribuição para DMA no bloco de gerenciamento de memória flash. Para gravar em flash, ele conterá o deslocamento de byte na página de destino e a gravação começará a partir desse ponto. Para ler o flash, é usado o endereço  XDATA



no qual os dados recebidos durante a leitura são gravados.  DE



: DD



 ( FWRLENH



:) FWRLENL



É o comprimento dos dados que o bloco DMA deve transferir, menos um.



A gravação na memória flash é acionada pela configuração de mais um bit SFR



. Vários bits nele também são definidos para controlar outro código, aparentemente não relacionado à memória flash, então concluí que esse registro provavelmente iniciaria várias ações. Eu nomeei este registro D7



 na página de configuração 4  TRIGGER



. O status de conclusão também é verificado em um registro que parece ser compartilhado por outro código também. CF



 Nomeei  este registro na página 4 de configuração  TCON2



, por que não? Havia também um registro ativado C7



, também usado em conjunto com outro código, que aparentemente configurava qual operação realizar. Eu nomeei isso SETTINGS



0x30



 foi escrito nele com um OU lógico para apagar + escrever,  0x18



 para escrever um flash,  0x08



 para ler um flash. Eu imaginei que o bit 0x08



 significa "transferência de dados pendente" 0x10



 significa "em flash", e  0x20



 "Apagar". Isso faz sentido considerando quais valores vemos e quais operações são realizadas aqui.



Ler e escrever no flash funcionou maravilhosamente bem, mas apagar aparentemente não funcionou. Em vez de apagar a página com o código fornecido, por algum motivo, a página em que o código que solicitava o apagamento estava sempre apagada. Obviamente, esse problema não estava no código contido neste dispositivo, eu estava fazendo algo errado. Verificado, verificado e verificado novamente para ter certeza de que meu código corresponde ao código de fábrica. Coincide. O que há de errado? Trabalhei vários dias até perceber que o código de fábrica funciona a 4MHz e o meu a 16MHz. Poderia ser esse o ponto? Aconteceu exatamente assim! Mudei meu código de apagamento do flash para manter o divisor de frequência atual e diminuí a velocidade do relógio para 4 MHz durante o apagamento do flash. Correu tudo bem porque este código já está em execução com interrupções desabilitadas.



Outra sutileza dessa unidade de controle de memória flash é que ela aparentemente não oferece uma operação simples de "apagar". Pensei em atribuir os bits if apropriados no registro  SETTINGS



, e então me pareceu lógico que, quando definido como 0x20



 ou  0x30



 , um simples apagamento deveria ocorrer. A única maneira de apagar isso é realizar uma operação de apagamento + gravação, que grava pelo menos um byte (já que não há como representar um comprimento zero em FWRLENH



:. FWRLENL



Para realizar um apagamento simples, simplesmente peço para escrever um único byte 0xFF



. Funciona



SPI



Basicamente, todos os drivers SPI são iguais. Um byte é recebido na entrada, um byte é retornado na saída. Claro, alguns têm DMA e alguns são acionados por interrupção, mas 99% deles em sistemas pequenos são controlados por software e em algum lugar há uma função simples u8 spiByte(u8 byte);



.



Era lógico examinar mais a fundo o SPI. Como sabemos que ele se SSD1623L2



 comunica com o SPI, e também sabemos os detalhes de como organizar essa comunicação, só precisamos olhar o código e descobrir qual parte dele deve fazer essa operação. Assim como o Sudoku, pelo quanto já sabemos, essa busca não será difícil. Olhando para a folha de dados SSD1623L2



 vemos que o número de registro do primeiro byte enviado é escrito nos bits 1..6, e o bit de "gravação" está na posição # 7. Todos os registros têm 24 bits de comprimento. É lógico que o programador escreverá um código que tomará o número do registro como parâmetro, deslocando-o para a esquerda em um, possivelmente lógico ou interno 0x80



, se uma escrita for solicitada, e então transferirá três bytes. Nem todos os programadores agem logicamente, mas essa suposição ajuda imensamente na engenharia reversa. Olhando para o código, é fácil ver as funções que parecem fazer exatamente isso. Alguns acrescentam 0x80



, outros não. Todos eles chamam a mesma função misteriosa para cada byte. Portanto, presumimos que algum texto seja exibido na tela, alguns leiam. Vamos abordar a função misteriosa em si.



Na verdade, tudo é tão fácil quanto descascar peras aqui. Ele muda  CFGPAGE



 para 4, depois grava o ED



 valor  no registrador  0x81



, escreve o byte a ser enviado EE



, escreve  0xA0



 para  EC



, faz um atraso de 12 microssegundos, define o bit 3 para  EB



, lê o byte recebido de  EF



, armazena  0x80



 em  ED



. Isso é tudo. Como compreender tudo isso? Como antes, contando com o que já se sabe.



0x80



 e  0x81



 diferem em apenas um bit, e nós o configuramos antes de iniciar a operação SPI, e após o final do trabalho nós o reinicializamos, então este é, aparentemente, um bit de "ativação" de algum tipo. Por outro lado, o significado  0xA0



 literalmente  soa como  algum tipo de configuração. O registro  EB



 ainda é um mistério. Mas, se eu reproduzir este código sem escrever nele, tudo funcionará, então concluo que não depende muito desse registro. Definitivamente EE



 isso  SPITX



EF



 isso  SPIRX



. Liguei  ED



 -  SPIENA



 e  EC



 -  SPICFG



.



Resta caracterizar o que as batidas fazem em SPICFG



... Eu fiz um pouco de tentativa e erro, armado com um analisador lógico. O bit 7 deve ser definido, o bit 6 deve ser apagado. O bit 5 inicia a transmissão do byte SPI e limpa a si mesmo quando termina com ele. Os bits 3 e 4 definem a frequência do clock, você pode escolher entre os valores: 500KHz, 1MHz, 2MHz, 4MHz. 2 é o bit de configuração padrão CPHA



 para SPI, o bit 1 é  CPOL



. O bit 0 parece violar RX. Estou assumindo que ele pode configurar o bloco para half duplex (em linha  MOSI



). Em geral, não é tão difícil.



Pin por pino, encontre rapidamente a configuração GPIO e veja o que P0.0



 é   isso SCLK



P0.1



isso  MOSI



 e  P0.2



 isso  MISO



... Ao procurar onde esses GPIOs estão configurados, também vemos como o bit CLKEN



 SPI é necessário  : esse é o bit 3. Ótimo - agora temos um SPI funcionando!



Determine a temperatura 



volatile u8 __xdata mTempRet[2];
 
void TEMP_ISR(void) __interrupt (10)
{
  uint8_t i;
  
  i = CFGPAGE;
  CFGPAGE = 4;
  mTempRet[0] = TEMPRETH;
  mTempRet[1] = TEMPRETL;
  CFGPAGE = i;
  IEN1 &=~ 0x10;
}
 
int16_t tempGet(void)
{
  u16 temp, sum = 0;
  u8 i;
  
  CLKEN |= 0x80;
  
  i = CFGPAGE;
  CFGPAGE = 4;
  TEMPCFG = 0x81;
  TEMPCAL2 = 0x22;
  TEMPCAL1 = 0x55;
  TEMPCAL4 = 0;
  TEMPCAL3 = 0;
  TEMPCAL6 = 3;
  TEMPCAL5 = 0xff;
  TEMPCFG &=~ 0x08;
  CFGPAGE = i;
  IEN1 &=~ 0x10;
  
  for (i = 0; i < 9; i++) {
    
    // 
    IEN1 |= 0x10;
  
    // 
    while (IEN1 & 0x10);
    
    if (i) {  //  
      
      sum += u8Bitswap(mTempRet[0]) << 2;
      if (mTempRet[1] & 1)
        sum += 2;
      if (mTempRet[1] & 2)
        sum += 1;
    }
    
    timerDelay(TICKS_PER_S / 1000);
  }
  // 
  CLKEN &=~ 0x80;
  
  return sum / 8;
}

      
      





Os visores do E-Ink são atualizados de maneira diferente com base na temperatura atual, portanto, saber a temperatura ambiente é fundamental para atualizá-los corretamente. As formas de onda corretas são selecionadas dependendo da temperatura. Aqui, o conhecimento de fora será útil. Portanto, se pudermos encontrar onde as formas de onda são carregadas no controlador de exibição, podemos encontrar onde as escolhas são feitas. Deste local você pode caminhar diretamente até o ponto onde a temperatura é medida, certo? Feito isso, vamos para exatamente uma função, cuja saída determina qual forma de onda será usada. Deve ser isso! A propósito: normalmente os sensores de temperatura são acoplados ao ADC - quase ninguém os fabrica em uma versão separada. Mas isso não importa [ainda].



Tudo começa com a definição do bit 7 para  CLKEN



e termina com seu reset, para que pelo menos saibamos que é assim que ligamos e desligamos o sensor de temperatura (ou ADC). A função muda  CFGPAGE



 para 4 e, em seguida, grava uma série de valores em uma série de registradores. Todos os valores são constantes. 0x81



 -> reg.  F7



0x22



 -> reg.  E7



0x55



 -> reg.  E6



0x00



 -> reg.  FC



0x00



 -> reg.  FB



0x03



 -> reg.  FE



0xFF



 -> reg.  FD



, então os bits são  0x81



 descarregados  F7



. Depois disso  CFGPAGE



 recupera e então limpa o bit 4 no registro A1



. Esta parece ser a configuração inicial. Depois que um determinado procedimento ocorre cinco vezes, os resultados de todas as operações, exceto a primeira, são calculados. Depois disso, muita matemática é feita sobre a média obtida dessa forma, em particular, usando os valores do INFOBLOCK - provavelmente são valores de calibração. O resultado é então retornado. Vamos dar uma olhada nos detalhes.



No processo, o bit 4 no registro foi simplesmente definido A1



, o bit global foi definido e, em seguida, no modo de espera ativa, demoramos até que o bit seja apagado. Os valores médios específicos, aparentemente, são tirados de algum global. Isso é estranho ... Eu procurei onde está escrito e encontrei no manipulador de interrupção # 10. Aparentemente, foi assim que o bit 4 no registro foi apagado A1



, então ocorreu a mudança para a página de configuração 4, os valores foram lidos dos registros F8



 e  F9



, algumas coisas estranhas foram feitas com eles, e então este valor global foi escrito . Mas o que é feito com esses valores? 



Eu estava apenas nos olhos picadas constantes  0x55



0xAA



0xCC



0x33



... Isso é possível? Alguém poderia ser tão direto que ... bem, sim. Essas são constantes para uma maneira inteligente de inverter a ordem dos bits em um byte. Complicado, mas apenas em processadores mais avançados. No 8051, essa abordagem é muito ineficaz. Mas por que? Parece que qualquer IP (ponteiro de comando) que eles licenciam para medir a temperatura, ele produz um resultado no qual os bits estão na ordem inversa. Por que esse problema deve ser resolvido no nível do software de um chip proprietário é uma grande questão. Afinal, inverter a ordem dos bits no hardware não é mais difícil do que reordenar alguns fios ... O que isso faz? Não sei. Na verdade, nunca entendi. 



Quase ninguém projeta um contador de comando dedicado para um sensor de temperatura, isso é simplesmente conectado ao ADC. Depois de reimplementar esse código e ter certeza de que funcionava muito bem, tentei alterar todos esses registros. A maioria deles influenciou o ganho do sensor de temperatura, alguns não surtiram efeito. Se este fosse um ADC normal, esperaríamos que alguns bits o mudassem para um tipo diferente de entrada e fornecessem um valor completamente diferente. Infelizmente, isso não aconteceu. Realmente parecia um sensor de temperatura normal. Isso também é confirmado porque esses registros não são tocados em nenhum outro lugar. Estranho como o inferno, mas tudo bem ... 



Como quase todos esses registros são escritos apenas uma vez, e são esses valores, e alterá-los afeta o valor medido, decidi simplesmente chamá-los de todos os valores de calibração de temperatura. Portanto, nos familiarizamos com TEMPCAL1



 (reg.  E6



),  TEMPCAL2



 (Reg.  E7



),  TEMPCAL3



 (Reg.  FB



),  TEMPCAL4



 (Reg.  FC



),  TEMPCAL5



 (Reg.  FD



) E  TEMPCAL6



 (reg.  FE



). Eu o chamei  porque ele é usado várias vezes e parece realmente gerenciar o carregamento do valor de calibração. Os resultados são publicados em   (reg.  F7



  TEMPCFG



TEMPRETH



F8



) e  TEMPRETL



 (reg.  F9



). Os resultados têm 10 bits de comprimento, alinhados à extremidade superior de um registrador de resultado de 16 bits, com ordem de bits invertida.  



Também notei que o bit 3 é  TEMPCFG



 definido quando a amostra termina de ser criada. Curiosamente, o código de fábrica não verifica, confiando na interrupção. Mas, na verdade, foi útil  para decodificar o propósito do registro A1



. Como você pode ver, o 8051 clássico é limitado a 7 fontes de interrupção, já que temos 8 bits no registrador  IEN



e o bit 7 é reservado para ativar uma interrupção global. Então, como você gerencia as interrupções numeradas de 7 em diante? Na verdade, é como o oeste selvagem, o que você quer é o que você faz. Mas aqui temos um pedaço de hardware que dispara a interrupção número 10 e, usando um pouco, podemos dizer quando foi feito. Isso é ótimo para experimentar. no qual queremos saber como as interrupções acima de 7 são ativadas e desativadas. Foi apenas necessário mexer neste código até se livrar da interrupção, mas o exemplo está criado . A busca não demorou muito. Deve ser isso A1



! Eu chamei ele  IEN1



... Não tenho certeza de qual é a função do bit 0 aqui, mas os bits 1 e acima controlam a ativação das interrupções de número 7 e acima. Pude confirmar isso mais tarde. Ok, pronto - documentamos mais um periférico, descobrindo assim ainda mais esquisitices ...



I2C



Nesse estágio, abri uma etiqueta de preço maior da e-Ink equipada com o mesmo chip. Era um modelo de 2,9 polegadas com display gráfico e-ink e NFC !!! Novamente, o conhecimento de terceiros é útil aqui. A maioria dos dispositivos NFC dirá exatamente o que são, se você perguntar educadamente. Isso é bom, pois o chip NFC na placa era muito pequeno para ser rotulado corretamente. Depois de escanear usando NFC e verificar o ID do dispositivo, descobrimos que é NXP NT3H1101 (cópia arquivada  aqui  para posteridade). Nesta página muito conveniente, você pode baixar a folha de dados - e fica imediatamente claro como a comunicação com este chip deve ocorrer. Informação util! (Todas as informações são úteis aqui). A única coisa irritante é que o endereço I2C deste dispositivo não é fixo, mas pode ser definido com qualquer valor; no entanto, um valor padrão é fornecido. O alfabeto da engenharia reversa: em 99,9% dos casos, os valores padrão não mudam. Aposto que o endereço I2C padrão também não mudou!



Encontrar um análogo binário para é  0x55



 bastante fácil - este valor não é tão comum. Aparentemente, todos eles são feitos antes das chamadas para uma das duas funções. Faz sentido que eles sejam conectados ao I2C. Além disso, em todos os casos, antes dessas chamadas, o bit 4 é definido em CLKEN



que é então descartado. Agora sabemos que I2C é ativado por meio deste bit. Vamos dar uma olhada no que essas funções fazem. Alguns copiam os dados do parâmetro fornecido no início, outros o fazem no final. No meio, todos eles escrevem algumas coisas globais, definem o bit global, limpam o bit 4 e definem o bit 5 no registrador 95



e esperam que ele seja limpo. Hmm, funciona como um sensor de temperatura. Aparentemente, o bit 2 na IEN1



 interrupção é ativado.



Vamos ver onde está localizado o manipulador de interrupção que afeta esses valores globais. Na verdade, seu número de interrupção é 8, como esperado. Ele define CFGPAGE



 como 0 e então lê o registro 91



... Os 3 bits menos significativos são ignorados e os bits restantes são usados ​​na caixa de comutação para decidir o que fazer. Este código acabou sendo um pouco confuso, então decidi experimentar. Anexe o analisador lógico às linhas que vão para o chip NFC e rapidamente descobre onde SDA



e onde  SCL



. Foi fácil porque existe uma ficha técnica para este chip.



Parece que limpar o bit 4 no registro  95



 não afetará nada, mas configurar o bit 5 faz com que a condição START no barramento seja verdadeira. Uma interrupção é acionada. Se você fizer o mesmo usando o manipulador embutido e ler os 5 bits mais significativos no registro 91



, vemos que eles têm um valor 0x08



... O byte de endereço é então armazenado com o 94



bit R / W (leitura / gravação) no registro  , e o bit 3 no registro é apagado  95



. Também deve ser observado que TODOS os caminhos por meio desse manipulador de interrupção resultam no bit 3 sendo limpo no registro 95



. Acho que esse é o "bit que precisa ser interrompido". Ainda não descobri, mas já podemos citar alguns registros. Parece que todos os registros I2C estão na página de configuração 0.



Vou chamar  porque é I2C que ele contém e nunca é lido por qualquer outro motivo. Eu nunca vi a mudança de três bits menos significativa ou usada de qualquer forma.  - então eu vou ligar  91



  I2CSTATE



I2CBUF



94



, já que os dados são bombeados por ele ao longo da esteira e,  95



 no futuro, serão nomeados  I2CCTL



, pois para que as coisas sejam feitas, algo precisa ser escrito nele.



Investigamos mais e descobrimos que quando o byte de endereço é enviado, um dos quatro valores de status pode ser obtido. Se o byte de endereço que enviamos exigir acesso de gravação, o estado será  0x18



se foi confirmado (ACK) e,  0x20



caso não seja. Se o byte de endereço que enviamos exigir acesso de leitura, o estado será  0x40



se foi confirmado (ACK) e,  0x48



caso não seja. O tratamento de NAK (byte não reconhecido) é bastante simples. Quando o bit 5 está definido para  I2CCTL



 a condição STOP no barramento é verdadeira.



O envio de dados no modo de gravação é fácil. O byte é simplesmente gravado  I2CBUF



. Se o byte enviado for confirmado (ACK), o estado se tornará, 0x28



e se não, então  0x30



. Para provocar um reinício, defina o bit 4 para  I2CCTL



 - funciona. Quando a execução do comando RESTART no barramento é concluída, o estado se torna  0x10



.



Se quisermos ler a informação, então, depois de enviar o bit de reinicialização e o byte de endereço em modo de leitura, assim que virmos o status 0x40



, podemos decidir como responder ao próximo byte que recebermos - ACK ou NAK. Para reconhecê-lo (ACK), defina o bit 2 para  I2CCTL



, e para não confirmar (NAK) - limpamos este bit. Com o retorno do manipulador, o byte será recebido. Quando isso for feito, veremos o status 0x50



se o byte foi confirmado e 0x58



se não foi confirmado. De uma forma ou de outra, o I2CBUF



 byte recebido estará contido em. 



Depois de revisar o código de inicialização e mexer em nossa cópia, descobrimos que o bit 7  I2CCTL



 controla se o dispositivo periférico acionará interrupções. Caso contrário, este registro é inicializado para  0x43



... Presumo que seja assim que o bloco está configurado para operar no modo mestre. Como não tenho um código de amostra para o modo escravo, não investiguei mais essa questão, mas tenho certeza de que o modo escravo é compatível. Isso pode ser feito, mas eu sou preguiçoso :).



O registro  96



 também registrou informações na hora da inicialização e depois não mudou mais. Isso se correlaciona bem com um bit de informação que ainda falta - indicando como a velocidade do clock está definida. Depois de experimentar este registro (que agora é chamado  I2CSPEED



), vemos que ele tem uma interdependência complexa com a frequência do relógio, mas após várias dezenas de tentativas cheguei ao seguinte:  rate = 16MHz / ((dividerB ? 10 * (1 + dividerB) : 12) << dividerA)



onde dividerA são os três bits menos significativos I2CSPEED



e dividerB é o próximo 4. O bit mais significativo aparentemente não é usado.



O fato de que a configuração inicial do GPIO ocorre perto do ponto de inicialização do periférico parece implicar que os pinos P1.4



 e  são importantes neste caso P1.5



.



Tudo funcionou, mas havia um segredo. Quando a interrupção para este bloco foi ativada (c  IEN1



), o bit 2 também foi definido no registro A2



. Como  IEN1



 está localizado no endereço  A1



, suspeito que tenha a ver com uma interrupção. Eu ainda não descobri exatamente o que ele faz e nenhum código diferente do código de configuração I2C inicial o usa. Eu já dei um nome I2CUNKNOWN



embora seja mais provável estar relacionado a interrupções do que a I2C. De qualquer forma, meu código agora pode realizar transações I2C como um mestre!



Detecção de mudança de pino



O firmware da etiqueta de preço foi ativado quando foi verificado por um dispositivo habilitado para NFC. O chip NFC integrado tem um pino de "detecção de campo" conectado ao microcontrolador principal. Coincidência? Nãopensar! Deve haver uma maneira de detectar alterações no pino. Ele até desperta o chip do modo de espera (economia de energia). Além disso, leva algum tempo para desenhar com tinta eletrônica e, durante essa espera, o chip provavelmente continuará inativo. O display sinalizará o final do desenho, alterando o sinal "OCUPADO". Então ... temos dois casos em que a CPU deve detectar uma mudança em um pino e, muito provavelmente, não estamos falando de um ciclo de espera ativo. Seria difícil encontrar o primeiro caso descrito - eu ainda não sabia exatamente onde está esse código de hibernação. O segundo caso, ao contrário, foi muito fácil de encontrar - quer dizer, foi fácil encontrar o código para desenhar na tela. Novamente, construir sobre o conhecimento existente é útil aqui. Eu sabia,cuja equipe é responsável por "atualizar a tela" em praticamente todos os chips de exibição de tinta eletrônica existentes. Acabei de entrar e vi o que iria acontecer. Havia muito código, muitos foram tocados  SFR



... Comecei a fazer experiências com os poucos que vi. Fez algumas suposições: todos os pinos devem ser capazes de acionar a detecção de alterações. Nem sempre é o caso, mas geralmente é feita uma suposição fundamentada. Presumi que, independentemente dos registros de configuração de que estávamos falando, eles seriam sequenciais e funcionariam com três portas. Também presumi que mudar o pino deveria fornecer uma interrupção, e não apenas ativar o dispositivo. Faz sentido que o número de registros de configuração seja bastante previsível. Para cada pino, precisamos ENABLE, STATUS e, provavelmente, DIRECTION. Além disso, os registros relacionados à detecção de alteração GPIO provavelmente estão próximos a outros registros de configuração GPIO.



Com base nisso, fiz alguns experimentos, já que poderia facilmente trocar pelo menos alguns dos pinos (por exemplo, TEST). Também levei algum tempo para ver como meu mapa atual está se desenvolvendo SFR



. Eu não me esqueci de olhar para os registros BC



BD



BE



 na página de configuração 0. Várias experiências têm mostrado que eles controlam o pullup de cada pino. É verdade, nunca vi nenhuma configuração que permitisse "puxar o pino para baixo". Eu os nomeei PxPULL



.



Depois de vários experimentos, ficou claro que existem três registros por porta e eles controlam as interrupções quando o pino muda.  PxLVLSEL



( A3



A4



A4



) seleciona o nível desejado (0 = alto, 1 = baixo).  PxINTEN



( A6



A7



A9



) Fornece mudança pin rastreamento no nível do hardware.  PxCHSTA



( AA



AB



AC



) Armazena o status de detecção (conjunto de bits = algo mudou). Outros experimentos mostraram que o número de interrupção ao trocar o pino é 11. Funciona bem, e eu até consegui tirar o chip do modo de economia de energia (mais sobre isso abaixo).



Segundo DPTR



Registra  84



 e  85



 misteriosamente economiza em meio a todas as transações de swap CFGPAGE



 e mantém todos os 8 bits neles armazenados. Em muitas variantes do 8051, é aqui que o segundo registro deve estar DPTR



. Mas, em caso afirmativo, como você muda  para ele? Todo mundo faz isso de maneira diferente. Eu decidi tentar. Escreveu um programa em assembler para reverter cada bit em cada registro por vez e verificar se a escrita de um inteiro DPTR



 (instrução especial) corresponde à leitura subsequente DPL



 e  DPH



 (acesso normal a  SFR



) É previsível que muitas dessas coisas não possam ser trocadas tão facilmente sem travar o programa. Mas, tendo praticado pular cuidadosamente um ou outro, isolei o bit 0 em  92



. Bem, sim ... Isso é o que ele faz. Como acontece com muitos 8051s, chamei esse registro  DPS



, que significa "seleção de ponteiro de dados". Registros 84



 e  85



 eu nomeei, naturalmente,  DPL1



 e  DPH1



.



Outros experimentos.



Alguns experimentos mostraram que os dois bits menos significativos em PCON



(espera e hibernação) funcionam como esperado no modo hibernar para o 8051 (embora o hibernar no modo de economia de energia também possa ser configurado). Também observei que a configuração do bit 4 está desativada  XRAM



. Isso economiza um pouco mais de energia no modo de suspensão! 



Registros na faixa B2



.. são interessantes  B6



. Eles parecem variar dependendo das instruções seguidas em sua localização. Tendo considerado tudo cuidadosamente, percebi que B4



: B5



 está sempre atualizado  PC



!!! Por que alguém pode precisar - eu não sei. Nomeou-os  PCH



 e  PCL



... Eles são somente leitura. Mas e os outros registros nesta faixa? B2



 e  B3



parece estar associado a saltos condicionais. Em um salto em distância (por exemplo, ao correr ljmp



lcall



ou  ret



), eles parecem armazenar o destino do salto. Com transições curtas (como  sjmp



),  B2



 parece descobrir o deslocamento. Coisas estranhas, mas inúteis, então não entrei mais nelas. Nomeei o resto dos registros  PERFMONx



.



Durma no modo de economia de energia



Pessoas são pessoas, e nada humano é estranho para elas. As pessoas adoram números redondos. Gosto de precisão, mesmo que não precise. Isso ajuda muito com a engenharia reversa. Por exemplo, como você responde a uma constante  0x380000



? Nenhum? Talvez. Que tal 0x36EE80



? Os olhos já estão agarrados a ela. que diabos isso significa? Traduza para o sistema decimal e você verá: 3.600.000.  Bem , esta é uma hora, expressa em milissegundos. Este valor pode ser útil, talvez, apenas no caso de um sono longo no modo de economia de energia. Estou cansado de contar quantas coisas eu "fiz engenharia reversa" contando com constantes desse tipo que lançam luz sobre onde o sonho é realizado! 



Aqui estão as constantes neste dispositivo que foram passadas para a função de interesse para mim: 1 5000 2 000 5 000 10 000 3 600 000 1 800 000 0xffffffff. É perfeitamente compreensível que seja uma indicação da duração em milissegundos. O último é provavelmente um esboço de "para sempre ou quase para sempre". 



Quase não havia chance de entender o que a maioria dos registros estão fazendo aqui, uma vez que eles são usados ​​pelo código quase exclusivamente no modo de suspensão. Alguns estavam dentro SFR



e alguns estavam no espaço  MMIO



... Consegui copiar o código e reproduzi-lo. Em particular, eu estava interessado em que o temporizador de sono pode funcionar em duas velocidades: com uma frequência de 32KHz e 1Hz. Este é um cronômetro de 24 bits, com o qual o sono mais curto possível dura cerca de 30 ms, e o mais longo pode durar cerca de 194 dias! Leia mais no SDK.



Rádio



O rádio geralmente requer uma configuração extensa, por SFR



 isso fica muito lotado em um espaço denso . A maioria dos 8051 equipados com rádios são usados ​​para resolver este problema MMIO



. A E / S mapeada por memória no 8051 geralmente é mapeada apenas para o espaço de endereço  XRAM



. Olhando diagonalmente pelo código, percebi que o rádio neste chip está ativado  MMIO:df00 — MMIO:dfff



.



Caminho RX



Novamente, decidi começar com a imagem OTA. É pequeno o suficiente para simplificar a análise. Logo ficou claro que a imagem OTA não envia pacotes de rádio, mas apenas os recebe (as confirmações são enviadas automaticamente no nível do hardware, o que é típico para a maioria dos chips ZigBee). Mas é bom! Graças a isso, basta analisarmos apenas metade do driver, o que significa que a tarefa é duas vezes mais simples possível! 



Quando comecei a procurar onde o código OTA obtém os dados, parecia que havia uma fila de buffer. O que é: é uma fila contendo bytes individuais, cada um dos quais é um ponteiro para uma lista de buffers. O código que parecia receber pacotes e processar os pacotes recebidos tirou o buffer da fila, processou-o e, em seguida, colocou-o em outra fila. Um esquema muito simples. Uma fila armazena buffers cheios de dados recebidos, outra fila armazena buffers vazios prontos para receber novos dados recebidos. Claro o suficiente.



Olhando em volta, descobrimos rapidamente onde as filas são acessadas de uma maneira diferente: removendo o buffer da fila "vazia" e enfileirando os cheios. Este é o manipulador da interrupção # 5! O manipulador de interrupção em si foi bastante simples, desde que o bit foi definido TCON2.2



, ele salvou  0xC6



 em  MMIO:df48



, dequeued o buffer, bytes copiados para ele e colocá-lo em outra fila. Mas de onde ele copiou os bytes? De onde você tirou o tamanho da cópia? Ambos foram retirados do buffer XRAM



no qual ele não escreveu! Nunca fui capaz de desvendar esse mistério.



A busca não parou por aí. A interrupção 4 desempenhou um papel fundamental e seu manipulador acabou sendo ainda mais simples. Ele testou o bit 5 em  MMIO:dfad



 (vou chamá-lo  RADIO_IRQ4_pending



e, se definido, ele chama um procedimento não chamado em nenhum outro lugar. Este procedimento lido , verificado se o valor nele era menor ou igual a 128, lido  , verificado que com um aumento de um, ele se tornaria igual ao valor anterior. Se algum dos itens acima não foi cumprido, ele foi salvo   em  ; caso contrário, a página de configuração 4 foi selecionada, o primeiro valor lido foi armazenado em uma variável global, que denotou ainda mais o comprimento. Este um valor negativo persistiu  , e o apontador para a memória intermédia a partir dos quais os dados copiados subsequentemente armazenado em : . Em seguida, o bit 2 foi definido  . SFR



  FA



MMIO:df98



0xC6



MMIO:df48



D5



D4



D3



TRIGGER







Aqui, novamente, conhecer o contexto ajuda. 127 é o valor máximo que um pacote 802.15.4 válido pode ter e esse comprimento inclui uma verificação de redundância cíclica de 2 bytes (CRC), mas não inclui o comprimento do próprio byte. Portanto, meu palpite é que FA



 esse é o comprimento resultante (levando em consideração o comprimento do byte e o CRC). Eu nomeei isso  RADIO_GOTLEN



. Nesse caso, faz sentido que  MMIO:df48



 (agora nomeado  RADIO_rxFirstByte



) seja o primeiro byte recebido (byte de comprimento). Com todos os registros restantes fica claro:  D5



 é o comprimento do DMA para RX DMA (agora chamado  RADIO_RXLEN



D4



: D3



 ele é desmontado em partes e aponta para o destino RX DMA ( RADIO_RXPTRH



 e  RADIO_RXPTRL



 respectivamente).



Então tudo deu certo. A interrupção número 4 é disparada assim que o rádio recebe um pacote no buffer interno. O bit 5 definido como  RADIO_IRQ4_pending



 (agora é chamado  RADIO_IRQ4_pending



) nos informa que isso aconteceu. Prosseguimos com a inspeção inicial do pacote (certificando-se de que seu comprimento está dentro dos limites razoáveis) e, em seguida, executamos o DMA do buffer interno para XRAM



, se tudo estiver bem. Se não, então nós escrevemos 0xC6



 no  MMIO:df48



. Logicamente, isso pode ser comparado a "esvaziar o RX FIFO", portanto, esse registro agora é chamado  RADIO_command



. Se tudo estava bem com o pacote e a operação DMA concluída, o bit 2 é definido em  TCON2



e é disparada a interrupção 5. Aqui, novamente, escrevemos "emptying RX FIFO" para  RADIO_command



. Isso é útil porque já extraímos os dados usando o método DMA. Em seguida, os dados são copiados e o trabalho está feito! 



Na maioria dos rádios, o código de redundância cíclico recebido não é fornecido nas camadas superiores - ele é simplesmente verificado e retornado com um único bit de status com um valor sim ou não. Como de costume, é aconselhável presumir que tudo está funcionando "normalmente". Você verifica - é realmente regular. A maioria dos rádios ZigBee reporta nesses dois bytes um LQI (Indicador de Qualidade do Link de Rádio) e RSSI (Indicador de Força do Sinal Recebido) em vez de um CRC. Nesse modelo, o rádio funciona da mesma maneira. Quase. Aparentemente, o primeiro byte é sempre 0xD0



mas o segundo parece realmente conter o LQI (nos 7 bits menos significativos) e o status CRC (no bit 7). Na verdade, é funcionalmente muito semelhante ao funcionamento do rádio Chipcon. O comando 0xC6



 também significa "vazio RX FIFO" para rádios chipcon (agora TI)! Muitas outras coisas não são iguais, mas os comandos são OPOSTOS , e isso me ajudou a navegar pelos outros elementos desta pilha de rádio!



Mais sobre rádio



Se você considerar como o código OTA inicia o rádio, você pode ver que MUITOS registros são afetados apenas uma vez, alguns valores são escritos neles, que parecem ser completamente aleatórios. Muito provavelmente, muitos deles são medidores. Qualquer registro que é gravado uma vez (ou repetidamente, mas o mesmo valor é inserido) é um registro de calibração. Ignorarei os detalhes enfadonhos dos registros envolvidos, mas falarei sobre o código de iniciação de trabalho que está no SDK.



Aqui, novamente, observamos quantos valores são gravados no registrador  RADIO_command



... Os valores registrados correspondem aos que esperaríamos ver se trabalhássemos com os valores dos comandos chipcon, embora possamos ver alguns valores que não estão nos módulos de rádio chipcon. Então, ou este rádio é um bastardo chipcon raro, ou ambos descendem de um ancestral comum. Em qualquer caso, esta situação ajuda a entender mais alguns comandos emitidos por eles.



Reproduzir o código de iniciação e escrever manipuladores de interrupção, como aqueles embutidos no chip, nos dá um binário funcional que pode funcionar para recepção e conduz a experimentos. Percebendo mais alguns registros nos quais o firmware principal grava, eu rapidamente determinei que MMIO:df88 — MMIO:df8f



 este é "meu endereço MAC longo", que será usado no nível do hardware para filtrar os pacotes de entrada. De forma similar,  MMIO:df90 — MMIO:df91



 define o "próprio PAN ID" para o filtro RX. A  MMIO:df92 — MMIO:df93



 define "endereço curto próprio". Este equipamento aceitará e reconhecerá (ACK) qualquer pacote enviado aos nossos endereços de broadcast.  MMIO:dfc0



 define o canal de rádio na numeração padrão 802.15.4 (11..26).



Como o rádio reconhecerá os pacotes, também consegui descobrir que a MMIO:dfc9



 intensidade de transmissão está sendo ajustada durante o ajuste  . Acho que é sobre o registro configurando a potência do TX. Também notei que quando um canal é definido no firmware de fábrica principal, mais dois registros são escritos com valores por canal. Existe apenas um desses registros no firmware OTA. Aquele relacionado a RX é chamado MMIO:dfcb



, e o relacionado a TX é chamado MMIO:dffd



... Fácil de replicar e entender. Então é hora de descobrir o TX!



Vamos enviar alguns bytes!



Depois de descriptografar o caminho de dados, transferi a função e os nomes de registro para minha imagem mestre desmontada. Olhando para o que ainda não está marcado, podemos ver onde está o caminho do TX. Na verdade, há mais duas filas de buffer aqui: uma cheia de buffers TX vazios prontos para uso e a outra cheia de buffers TX “usados” prontos para serem enviados. Achei a função de transferência muito rapidamente.



Em 802.15.4, é comum ouvir o canal de rádio antes de transmitir. Essa operação é chamada de CCA (avaliação de canal ocioso). Antes de fazermos qualquer coisa com os dados que estamos prestes a enviar, considere um loop que lê  MMIO:df98



 e verificando o bit 0. Se estiver definido, a função falhará e o cronômetro será definido para tentar novamente. Acho que esse é o caminho CCA. Se virmos zero neste bit 128 vezes, consideramos que o canal está livre. 



A função de transferência em si acabou sendo deprimente simples: você seleciona a página de configuração 4, o comprimento desejado (não incluindo o byte de comprimento ou CRC) e tudo é escrito CD



. Um apontador para um tampão em  XRAM



 escrita em CA



: C9



. O buffer começa com um byte de comprimento.  RADIO_command



 carregado com valor  0xCB



. Não existe tal comando em rádios chipcon, mas acho que significa "carregar TX FIFO". Então o bit 1 é definido em  TRIGGER



... Suponho que seja assim que o acesso DMA à fila de rádio TX FIFO interna é iniciado. Em seguida,  MMIO:dfc8



 definido como  0xFF



, 255 tentativas são feitas para aguardar o término do TX, verificando se o bit 7 em   MMIO:df9b



 (agora chamado  RADIO_curRfState



) está definido. Então, após um pequeno atraso, é  MMIO:dfc8



 definido como 0x7F



. Curiosamente, não tenho ideia de por que está sendo gravado MMIO:dfc8



. No meu código, tentei fazer sem ele e tudo funcionou bem.



Caudas 



Depois de experimentar um pouco, descobri alguns truques que o firmware de fábrica não pode fazer. O bit 6 RADIO_IRQ4_pending



 é definido depois que "TX" o pacote e o atraso de confirmação (ACK) expira. Se realmente recebermos um ACK, então também será definido o bit 4. Portanto, é fácil determinar (1) quando realmente enviamos um pacote e (2) se recebemos um ACK. Frio!



Além disso, se o bit 4 da entrada  RADIO_IRQ4_pending



estiver definido e o bit 5 da entrada RADIO_curRfState



não estiver ocupado, isso significa que estamos no processo de receber um pacote. Precisamos selecionar o RSSI manualmente, para o qual lemos MMIO:df84



 (agora  RADIO_currentRSSI



). Ele tem um deslocamento de cerca de 56 dBm.



Também notei que o bit 1 em TCON2



definido após a conclusão do TX DMA (mas não necessariamente o próprio processo de TX). O bit 0 in é  TCON2



definido quando a inicialização do rádio termina.



Mistérios não resolvidos



ADC / medição de bateria e mecanismo de criptografia AES 



Faz sentido que deva haver alguma maneira de medir a tensão da bateria, mas não encontrei nenhum vestígio de código semelhante. Sem um código que usa o ADC dessa forma, as chances de encontrar esse método de desaparecimento são mínimas. O bloco AES é, em princípio, igual ao ADC. Eu sei que há um bloco de aceleração AES no chip (necessário para ZigBee). Mas como o código real não o usa, não vejo uma maneira de encontrá-lo.



miscelânea



Coisas que não conseguimos encontrar, mas com as quais realmente não nos importamos, já que não podemos comprar este chip: controlador IR LED, unidade PWM, DAC. Vou deixar essas coisas para o leitor exercer por conta própria.



Pinagem de ZBS242 / 3, recursos, SFR, downloads 



Baixe o  SDK do ZBS24x .







  • As células sombreadas indicam registros endereçáveis ​​bit a bit 
  • Os registros sombreados diagonalmente que não são armazenados no banco CFGPAGE



  • Registros sombreados verticais, que, aparentemente, não aparecem em nenhuma das páginas. 
  • Células vazias são registros desconhecidos
  • Os nomes dos registros RADIO começam com a letra "r" 






Lições para um iniciante em engenharia reversa



  • Leia os materiais por pelo menos algumas horas ou dias antes de começar a trabalhar.
  • - . -, . 
  • . SPI , I2C , . .
  • , – ( ). 
  • : , . , , .  
  • , , . 
  • . - , - . , .
  • - . , , . 
  • , , . , , .
  • - . . 
  • - , , - . 







Os servidores em nuvem da Macleod são ótimos para hospedar sites.



Cadastre-se pelo link acima ou clicando no banner e ganhe 10% de desconto no primeiro mês de aluguel de um servidor de qualquer configuração!






All Articles