Ingeniería inversa de un microcontrolador desconocido





Corbata complicada



Fondo ...



Como parte de mi trabajo en  ingeniería inversa de etiquetas de precio de tinta electrónica,  encontré un problema interesante. Una empresa específica (Samsung Electro Mechanics / SoluM) pasó de usar chips de terceros, cuyo origen pude identificar (Marvell 88MZ100) a un nuevo chip, que comenzó a usar con sus etiquetas de precios de próxima generación.



Parece que este es su propio chip, desarrollado por la empresa con este mismo propósito. Adoptar la ingeniería inversa de tal cosa es un tema muerto. Un amigo me dio algunas etiquetas de precio con esas fichas, para que jugara. Resultó que son de dos tipos: uno con una pantalla segmentada en e-ink y el otro con una pantalla gráfica convencional en e-ink. El chip principal en ambos modelos es el mismo, así que lo primero que hice fue con un dispositivo de visualización segmentado, ya que es más sencillo y es más fácil lidiar con un sistema desconocido usándolo. No estaba del todo claro por dónde empezar, pero, por supuesto, ¡estas son las tareas que siempre son las más interesantes! 



Estudio





Es una tontería intentar resolver un crucigrama sin leerle las preguntas. Es igualmente tonto aplicar ingeniería inversa a un dispositivo sin recopilar primero toda la información que ya está disponible sobre él. Entonces, ¿qué sabemos inicialmente? El protocolo de transferencia de datos inalámbrica es probablemente el mismo que de costumbre, ya que ninguna empresa quiere migrar a uno nuevo o admitir dos protocolos para sus clientes a la vez, realizando la migración lentamente. El protocolo anterior era similar a ZigBee de 2,4 GHz, por lo que el nuevo probablemente sea el mismo. Aquí hay una foto del tablero de ambos lados.





Entonces ¿Qué vemos? Primero, un buen ejemplo de optimización de costos. ¡Laminaron la pantalla de tinta electrónica directamente sobre la PCB! ¿Quién necesita un panel posterior de vidrio conductor cuando hay una PCB? El panel frontal está hecho de plástico conductor. Pero no es importante.



Dos antenas son visibles, ambas, a juzgar por su tamaño, a 2,4 GHz. Como era de esperar, dado que los dispositivos de la generación anterior también contaban con dos antenas de 2,4 GHz. Vemos dos fichas. Grande y pequeño. El grande (designado "SEM9010") aparentemente tiene muchos contactos que van a la pantalla y ninguno a las antenas. Obviamente, este es un controlador de pantalla.



El pequeño (designado "SEM9110") parece ser el cerebro responsable de todas las operaciones. Está conectado a las antenas, el cristal de sincronización y los puntos clave que son obvios aquí para la programación de fábrica.



Aquí hay 12 almohadillas: una está conectada al terminal positivo de la batería, una a tierra, el propósito de las otras 10 es un misterio. Buscando el nombre del chip en línea, no encuentro nada útil, definitivamente su propio desarrollo. Pero, ¿quién diseña su propio chip para una aplicación tan sencilla? ¿Quizás solo un cambio de marca? ¡Aprovechados, estamos trabajando!



Curiosamente, la Búsqueda de imágenes de Google ayudó aquí. Sucede que esta herramienta es útil para la ingeniería inversa. En este caso, nos lleva a esta pepita. (copia archivada  aquí  para la posteridad). Esta es una pregunta de StackExchange: se pregunta cómo funcionan estas etiquetas electrónicas para estantes. La pregunta es interesante porque en la foto publicada aquí, la placa de circuito impreso se ve casi  idéntica a la  nuestra. Los chips también son exactamente iguales, ¡pero las etiquetas en ellos son diferentes! El tablero probablemente se fabricó antes de que SoluM comenzara a cambiar el nombre de estos chips.



El chip que supuse que era el controlador de pantalla está etiquetado  SSD1623L2



. De hecho, es un controlador de pantalla segmentado de tinta electrónica que admite hasta 96 segmentos. Buscando en línea, encuentro la  hoja de datos de la versión preliminar 0.1 (copia archivada  aquí para la posteridad). ¡Es bueno! Si supieran cómo llegar a esto, podrían elegir un código que comprenda, y tan pronto como veamos este código, ¡eso es todo!



Resulta que el microcontrolador principal es ZBS242



. Bueno. No estoy familiarizado con este microcontrolador. Busquemos un poco más en Internet, y las búsquedas nos llevarán al enlace  (copia de archivo  aquí para la posteridad), que también menciona la misma respuesta de StackExchange. La página es coreana, pero muestra que este chip tiene un núcleo 8051, así como un equipo periférico bastante predecible: UART, SPI, I2C, ADC, DAC, comparador, sensor de temperatura, PWM de 5 canales, controlador triac de 3 canales. , Transmisor IR, función de escaneo de teclas, función RF-Wake, espaciado de antena, radio compatible con ZigBiee y MAC. La imagen muestra que también hay un oscilador RC interno de 32 kHz que, como se indicó, puede consumir tan solo 1 uA en modo de suspensión. Creo que fue esta empresa la que fabricó nuestro chip para Samsung. Interesante ...



Miremos las imágenes y descubramos que el cristal SEM9110 que nos desconcertó también fue  disparado a quemarropa  (copia de archivo  aquí  para la posteridad). Se dice que es ZBS243. Supongo que eso significa que hay toda una familia de chips aquí: el ZBS24x. Muy interesante.



¡Tenemos un hilo!





Habiendo abierto otra etiqueta de segmento, seguimos disfrutando de la noticia: ¡el cabezal de programación está firmado con letras doradas claras y legibles! El cabezal parece tener un SPI, UART, pin de reinicio, fuente de alimentación, tierra y un pin llamado "prueba", probablemente utilizado para ingresar al modo de prueba de fábrica. Todo es más curioso y curioso.





Es lógico que el representante más antiguo de la hipotética familia ZBS24x se denomine "ZBS240". ¿Quizás una búsqueda de tal consulta nos dé algo interesante? Buscando "ZBS240" y filtrando la escoria, encontramos otra página interesante en coreano  (copia archivada  aquí  para la posteridad). Parece que esta empresa fabrica programadores grupales personalizados bajo demanda. Después de buscar en su sitio web, encontramos un manual  (copia de archivo  aquí para la posteridad) en su dispositivo de programación, e incluso podemos descargar una utilidad para que una PC funcione con dicho dispositivo. Esta utilidad incluso tiene una herramienta para actualizar el firmware del dispositivo. Miré para ver si era posible adivinar a partir de esta información cómo programar el dispositivo, pero el firmware resultó estar encriptado. Aparentemente, la utilidad del lado de la PC solo envía datos a través del puerto serie USB, por lo que tampoco hay información útil aquí. Triste ... 



Después de buscar un poco más, encontramos una página aún más interesante  (copia archivada  aquí para la posteridad). ¿Qué es? ¿Está rebajado? Definitivamente ya no, ¿verdad? Le escribí a esta empresa pidiendo jabón, por si acaso. Silencio ... Como gesto de desesperación, le pregunté a un amigo de Hong Kong si conocía a alguien en Corea que pudiera contactar a estos chicos, ya que su sitio web muestra que solo aceptan una transferencia de un banco coreano como pago. Me sorprendió cuando me golpeó y dijo, de hecho, ¡podía conseguirme este dispositivo a través de un intermediario encontrado en Corea! ¡Unos días después, DHL entregó el dispositivo!



¡Puedes alcanzarlo!



Como contactarlo



¡Obras! Puedo leer el chip y escribir en él. Me tomó un tiempo investigar la herramienta de programación. Aparentemente, el chip tiene 64 KB de memoria flash y un "bloque de información" de 1 KB, que creo que se usa para almacenar valores de calibración, direcciones MAC y similares. Pude interceptar algunos de los rastros, armado con el maravilloso  analizador lógico Saleae Logic , viendo al programador hacer su trabajo. Puedes descargar mis hallazgos aquí . En este archivo encontrará rastros de lectura, borrado y escritura en los espacios INFOBLOCK y CODE. De hecho, ¡el protocolo es MUY  simple! La frecuencia del reloj puede variar entre 100 kHz y 8 MHz.



Protocolo ISP: corte hasta el hueso 



Todo comienza con la configuración de las líneas en el estado deseado: SCLK inferior, MOSI superior, RESET superior, SS superior. Esta condición se mantiene durante 20 ms. Entonces RESET se reduce en 32 ms. Luego, se envían al menos 4 relojes de procesador a la línea SCK a 500 kHz. Luego hay otro retardo de 10 ms hasta que se presiona RESET. Ahora puede establecer un retraso de 100 ms antes de iniciar la comunicación. Después de eso, se puede realizar cualquier cantidad de transacciones. Algunas reglas básicas: debe haber al menos 5us entre SS bajando y enviando un byte, al menos 2us entre el final del byte y SS subiendo, y el período más corto que SS puede gastar es 2.5us. Por lo tanto, cada byte se envía en el estado: SS está inactivo, se envía un byte en el modo SPI 0, SS está activo. Sí, por supuesto,SS voltea para cada byte.



Todas las transacciones tienen una longitud de tres a cuatro bytes. El primer byte indica el tipo de transacción, el bit más bajo especifica la dirección de la transacción: cero significa escribir en el dispositivo, uno significa leer desde el dispositivo. Los comandos 0x02



/ 0x03



 se utilizan para iniciar sesiones de comunicación. El programador envía una escritura de tres bytes:  02 BA A5



y luego lee, primero envía el comando de lectura y la "dirección": el  03 BA



maestro envía FF



mientras recibe A5



. Si esto funciona, entonces se establece la comunicación.



Comandos  0x12



/ 0x13



 se utilizan para leer / escribir registros de propósito especial (SFR) en la CPU (esto me resultó más difícil, pero en este caso el orden no es tan importante). Para seleccionar INFOBLOCK, SFR  0xD8



 debe establecerse en 0x80



, para seleccionar el área de flash principal, debe establecerse en 0x00



. Para escribir el valor de vv para registrar rr, se necesitan datos SPI  12 rr vv



. Para asegurarse de que se ha leído el valor, se puede volver a leer enviando primero un comando de lectura y una "dirección":  13 rr



después de lo cual el maestro envía  FF



mientras recibe  vv



.



Es fácil leer la memoria flash. Para hacer esto, aplique 0x09



, un comando de cuatro bytes. Después del byte de comando, se envía la dirección, primero el byte alto y luego el bajo. A continuación FF



, el maestro envía  , mientras tanto, recibe el byte leído. Bueno, sí. Se requiere un comando separado para leer cada byte. Escribir también es fácil. Para ello, se utiliza el comando 0x08



. Este es un comando de cuatro bytes. Después del byte de comando, se envía la dirección, primero el byte alto, luego el bajo y luego el byte a escribir. También se requiere un comando separado para escribir cada byte. Asegúrese de borrar antes de grabar. Para INFOBLOCK borrado, se requiere sólo una secuencia de 4 bytes:  48 00 00 00



. El borrado en la memoria flash principal se realiza mediante el comando 88 00 00 00



.



¡Ahora ya sabe lo suficiente como para programar trivialmente su ZBS24x!



¡Ponte a trabajar!







Imprimación para 8051



Si ya está familiarizado con el 8051, puede omitir  esta sección con seguridad  .



El 8051  es un  antiguo microcontrolador diseñado por Intel en la antigüedad . Es una molestia terrible trabajar con él, pero todavía se usa con bastante frecuencia porque su licencia es barata (de hecho, gratis). ¿Cuál es el problema? El 8051 tiene varios espacios de memoria separados. CODE



 Es el área de memoria reservada para el código. Su tamaño máximo es de 64 KB (dirección de 16 bits). En los diseños más modernos, se trata de una memoria flash. El código puede leer bytes desde aquí usando una instrucción especial movc



 ("MOVE from Code").  XRAM



 Es una memoria "externa". Es decir, externo al núcleo. Puede almacenar varias cosas en él, pero es casi inútil para cualquier otra cosa. Así: las únicas operaciones que se pueden realizar en esta memoria son la escritura y la lectura. Su tamaño máximo es de 64 KB (dirección de 16 bits). ¿Cómo funciona la memoria de direcciones de una dirección de 8 bits con una dirección de 16 bits de ancho? Resulta ser muy lento. El comando movx



 ("MOVE to / from eXternal") accede a este tipo de memoria, pero ¿cómo se especifica una dirección de 16 bits? Para ello, DPTR



 se utiliza un registro especial llamado  ("Data PoinTeR"), así como para trabajar con una instrucción movc



DPTR



 consta de un registro superior  DPH



 y un registro inferior  DPL



... En consecuencia, escribiendo la mitad de la dirección en cada uno de ellos, puede direccionar la memoria externa y la memoria de códigos. Como puede adivinar, este proceso comienza a deslizarse rápidamente, ya que, por ejemplo, para copiar una sección de la memoria externa a la memoria externa, necesitará mezclar repetidamente los valores entre DPL



 y  DPH



. Por esta razón, algunas de las versiones más avanzadas del 8051 tienen muchos registros  DPTR



, pero no todos, y no todos se implementan de la misma manera.



Intel ha agregado una forma más rápida de acceder a un subconjunto de memoria externa. En este caso, la idea es utilizar registros R0



 y  R1



 como registros de puntero. Pero tienen un tamaño de 8 bits, ¿de dónde vienen los otros 8 bits de la dirección? Son de un registro P2



 (que también controla el puerto 2 para los pines GPIO). Obviamente, esta práctica se interpone en el uso del puerto 2 para ... ya sabes ... GPIO. Hay formas de suavizar esta situación, pero no estoy hablando de eso ahora. Por lo tanto, la cantidad de memoria disponible para nosotros está limitada a 256 bytes (a menos que cambie dinámicamente el puerto 2, lo que probablemente no desee hacer). Por lo general, esta memoria se llama PDATA



. Los accesos a memoria similares también se realizan mediante una instrucción  movx



. Siguiente en la línea tenemos SFR



- varios registros de configuración con los que se configuran los periféricos. Solo se puede acceder a esta área de memoria directamente. Esta es la situación: la dirección debe estar codificada directamente en la instrucción, no habrá acceso a través de ningún registro de puntero. Hay 128 bytes SFR



. La siguiente tabla muestra las listas SFR



disponibles de acuerdo con el estándar 8051. Los recuadros grises contienen los SFR



bits a los que se puede acceder individualmente mediante comandos bit a bit. Esto es útil cuando se asignan pines de puerto atómicos, o cuando se activan / desactivan fuentes de interrupción, o cuando se comprueban algunos estados.



La memoria interna del 8051 es un poco complicada. En todos los 8051 modernos, es de 256 bytes. Los últimos 128 bytes  0x80-0xff



 están disponibles  solo  indirectamente a través de los registros  R0



 y  R1



, a diferencia de la situación con la memoria externa, ahora no solo estamos disponibles para lectura y escritura. Podemos realizar un aumento en uno ( inc



rement), bajar en uno ( dec



rement), sumar ( add



) y la mayoría de las otras operaciones esperadas. De hecho,  se accede a TODA la RAM interna indirectamente a través de estos registros de puntero. 128 bytes más bajos 0x00-0x7f



 También está disponible directamente (la dirección se codifica directamente en la propia instrucción, al igual que cuando se trabaja con SFR



. 16 bytes de memoria en el rango 0x20-0x2f



 también son direccionables mediante bits mediante instrucciones de procesamiento de bits. Es conveniente almacenar variables para valores booleanos en Esta parte. Los 32 bytes más bajos  0x00-0x1f



 componen 4 registros de bancos  R0



... R7



En el registro de estado  PSW



 hay bits que permiten seleccionar qué banco se está utilizando actualmente, pero en realidad, como suele haber escasez en el área de memoria interna, el código usa principalmente un solo banco de memoria.



El 8051 es una máquina diseñada principalmente para trabajar con un solo operando. Es decir: en la mayoría de las operaciones, la batería se utiliza como una de las fuentes y, posiblemente, como destino. Los registros también se pueden usar para muchas (pero no todas) operaciones, y algunas operaciones permiten el acceso indirecto a la RAM interna, como se describió anteriormente. La pila es un upstream vacío, direccionable SFR



, se llama  sp



 y se ubica solo en la RAM interna, su tamaño máximo está limitado a 256 bytes, pero en realidad es mucho menor. 



Cualquier imagen de ROM 8051 comienza con una tabla de vectores que contiene saltos al código inicial que desea ejecutar, así como a los controladores de interrupciones. En 8051, históricamente, el vector de reinicio se encuentra en 0x0000



, y los manejadores de interrupciones comienzan en la dirección 0x0003



 y luego cada 8 bytes. Dado que la instrucción reti



 solo se usa para regresar de interrupciones, se puede usar para detectar fácilmente si una función en particular es un manejador de interrupciones.



¡Llena tu canal de compilador de C con todo esto y dale una calada! 



Existe un compilador de C adecuado para esta arquitectura: Keil's C51. Pero no es barato. También hay un compilador de código abierto: SDCC . Es regular, pero gratis. Mientras hacía este proyecto, encontré solo dos errores geniales en él, que solo podrían superarse omitiendo; no está nada mal para un proyecto de código abierto.



Empecemos el análisis



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"
  );  }

      
      





Es fácil comenzar con la configuración GPIO. Como regla, encontrará varios bits coincidentes, que se establecerán o borrarán en varios registros seguidos. Esto es lógico, ya que al activar o desactivar, normalmente tienes que usar el pin como función (del GPIO), configurarlo como entrada o salida, y configurar o leer su valor. Debería encontrar este tipo de código al comienzo del trabajo. Veamos qué hay allí ... nos encontramos con que los registros estándar P0



P1



y en  P2



 realidad se usa de esa manera, cómo tratar con los registros GPIO. Al observar qué registros se escriben a su alrededor y qué sucede con los bits en ellos (ya sea que se lean (entrada) o se escriban (salida)), podemos suponer que los registros  AD



AE



AF



 Están diseñados para "la función" - y parece que GPIO, que se establecen los bits correspondientes no se utiliza como GPIO, y todos GPIO, en realidad utiliza como GPIO, empezar a trabajar tan sólo después de un bit correspondiente en uno de estos registros se borrará. Los nombré PxFUNC



donde x es el número de puerto. Entonces podemos concluir que  B9



BA



BB



 controlar la dirección. Siempre que se establece un bit en uno de ellos, el GPIO correspondiente solo se lee, y cuando se borra el bit, el GPIO correspondiente es de solo escritura. Por lo tanto, entendemos que estos registros controlan la dirección del GPIO. Los nombré  PxDIR



donde x es el número de puerto. Entonces, en teoría, podría controlar el GPIO. Si tan solo supiera cuál de ellos hace qué ... 



Decidí probarlos todos seguidos hasta encontrar el que controla el "teclado TEST" en el cabezal de programación, o tal vez los pads URX y UTX. De todos modos, en realidad ... encontré que el pin 0 ( P1.0



) del puerto 1 es "TEST",  P0.6



 esto es "UTX", y  P0.7



 esto es "URX". Al tener un GPIO controlado, puede simplificar su vida, pero solo mientras pueda manejar la depuración cambiando diferentes GPIO, y hasta que se canse de él. ¡Tuve tiempo para practicar esto! 



Tenemos printf!



Usé esta función para convertir el panel "TEST" en un puerto serial 8n1 regular usando el método bit-bang, y recogí la salida usando mi analizador lógico. Jugué con él hasta que dio la velocidad en baudios que podía manejar mi cable adaptador USB a serie. Ya tenía una implementación 8051 de printf en ensamblador. Durante una hora, practiqué la salida de líneas de depuración complejas desde este puerto serie improvisado. No es un mal comienzo, definitivamente, ¡esta es la única forma en que debe actuar para avanzar de manera efectiva! 



En este punto, he mostrado en la ventana los valores de todos SFR



, para al menos navegar cuáles son estos valores. Todavía había algunos problemas con la investigación adicional. Para empezar, el temporizador de vigilancia (WDT) parecía estar configurado de forma predeterminada y restablecer el chip después de un segundo de ejecución, por lo que todos mis experimentos tenían que caber en un segundo o menos. Todavía no sabía cómo operar WDT, así que aguanté esta limitación por un tiempo. Sea como fuere, ¡un segundo son muchos ciclos! 



Ampliando el acceso



Ahora que pude ejecutar el código de manera confiable y generar los resultados, decidí averiguar dónde están los controles de tick. Casi todos los registros tienen al menos un registro que controla diferentes velocidades (al menos la velocidad de la CPU) y otro registro que controla la frecuencia de reloj (o reinicio) de varios módulos. Por lo general, se encuentran así: el primero generalmente se registra MUY  temprano en la carga inicial, y luego apenas se toca (si es que se toca). El segundo suele tener un bit configurado (ciclos de reloj) o un poco borrado antes de que comencemos a configurar un periférico. No sabemos dónde están configurados los distintos periféricos, pero normalmente el conjunto  SFR



con números similares corresponde a un dispositivo periférico. Así que veamos. Definitivamente hay un caso, se ajustan a esta descripción: B7



. Vemos que se establece un bit a la vez, antes de que se escriban varios  SFR



con números similares, y los bits se borrarán después de que se SFR



detengan las llamadas a varios con números similares. También vemos que inicialmente se registra como  0x2F



, por lo que aquí estamos tratando con periféricos que vienen incluidos de antemano. Dado que los bits parecen estar configurados  antes de lo que consideramos periféricos de inicialización, llamaré a este registro CLKEN



... Jugué cambiando los bits en este registro, y parecía que no pasaba nada cuando se borraban. En principio, esto es lógico, ya que no utilizo ningún periférico.



Otro registro escrito cerca (el código alfabetizado generalmente inicializa todas las operaciones del reloj juntas), que luego no se reescribe, es este 8E



. Le escribe a  0x21



. Sugerí que podría estar relacionado con la velocidad. Experimenté. Aparentemente, los 4 bits menos significativos no se reflejan de ninguna manera en el trabajo, por lo que no tengo idea de por qué están configurados  0b0001



, pero los siguientes tres bits, probablemente, cambien la velocidad de la CPU de manera bastante significativa (hasta donde puedo juzgar por la velocidad de mi UART, sujeto a la deriva). El bit más significativo pareció cambiar un poco la frecuencia, asumí que es responsable de cambiar entre el circuito RC interno y el cristal externo. Tres bits, que asumí que funcionaban como divisor de frecuencia, configuran la velocidad del reloj para que parezca igual 16M / (1 + )



. Llamé a este registro CLKSPEED



. En consecuencia, la velocidad más alta se logra en el valor 0x01



y la más baja en  0xf1







Hacer que los temporizadores funcionen



Muchos fabricantes se basan en todo tipo de cosas en el 8051, por lo que aquí hay muy poca estandarización. Sin embargo, la mayoría no toca el equipo normal del 8051, como el temporizador 0 y el temporizador 1. Tenga en cuenta: esta no es una regla general. Por ejemplo, TI cambia significativamente los temporizadores de sus chips de la serie CC. Me di cuenta de que en este chip, los registros que normalmente se supone que configuran los temporizadores estándar 8051 parecen estar cerca, y el controlador de interrupciones n. ° 1 parece afectarlos también. ¿Es posible que? Temporizadores estándar? Lo intenté y funcionó. Completamente estándar, aparentemente exactamente igual que la especificación original. Revisé el registro  CLKEN



 y encontré ese bit 0 (máscara  0x01



) para que los temporizadores funcionen. Confirmado que el registro estándar IEN0



 también funciona como se esperaba, y que los números 1 y 3 en realidad controlan interrupciones para el temporizador 0 y el temporizador 1. Los temporizadores parecen funcionar exactamente a 1/12 de 16 MHz, exactamente como se esperaría en un 8051 estándar que funciona a 16 MHz. Hasta ahora, no he encontrado cómo cambiar esta frecuencia. Lo que sabemos ahora revela registros  TL0



TH0



TL1



TH1



TMOD



TCON



! ¡Ahora tenemos temporizadores de precisión en funcionamiento!



No fui demasiado vago para comprobar si el temporizador 2 está realmente implementado en el estándar 8052 (secuela del 8051). 



¿O quizás 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;
}

      
      





Había varias líneas en el módulo OTA. Tiene sentido que se relacionen con algo, ¿verdad? ¿Quizás un puerto serie de depuración? Esto iría bien con una placa que tenga los puntos clave "UTX" y "URX". Este código era un poco complicado, pero parecía que estaba almacenando bytes en algún tipo de búfer. El código definitivamente parecía un búfer de anillo estándar. Miré dónde se está leyendo este búfer. Resultó estar en el controlador de la interrupción # 0. Oooh, interesante. ¿Podría ser un controlador de interrupciones UART? El código parecía verificar el bit # 1 en un área que se asemejaba a un registro de estado (registro  98



), y si estaba configurado, leyó un byte de nuestro búfer de anillo y lo escribió en un registro. 99



... Si se estableció otro bit (# 0) en el registro de estado antes mencionado, entonces leyó el registro  99



 e insertó el resultado en ... otro búfer circular. Bueno, ¡esto está bastante en línea con lo que esperaría de un controlador de interrupciones UART! ¿Qué hacemos a continuación? 



Cada búfer circular tiene dos punteros, uno para leer y otro para escribir. Tiene sentido que se deben inicializar antes de que el búfer se use para cualquier cosa. Entonces, si encontramos dónde se inicializan estos índices, probablemente encontraremos dónde está instalado el UART, ¿verdad? Definitivamente se ve así. En esa función, que inicializa la UART, vemos que GPIO  P0.6



 y se  P0.7



configura en modo función,  P0.7



 se pone en entrada y  P0.6



 - en salida. Dos registros más:  9A



 y  9B



 se escriben con  0x00



 y,  0x89



 respectivamente. El registro que, según mi versión, funciona con estados (registro  98



) se escribe como  0x10



, y luego se borran los bits 0 y 1. Luego,  CLKEN



 se establece el bit 5 y el IEN0



 bit 0. ¡Eso, en principio, es todo lo que necesitamos! 



Así que nombramos el registro  y el registro se  convierte en  . Lo sabemos  99



  UARTBUF



98



UARTSTA



UARTSTA



 debe establecerse en 0x10 para que este bloque funcione, y sabemos que el bit 0 significa que el UART tiene un byte libre en la cola TX FIFO, y el bit 1 significa que el UART tiene un byte en la cola RX FIFO para nosotros. Sabemos que el CLKEN



 bit 5 habilitó el reloj para la UART y que el número de interrupción 0 corresponde al manejador de interrupciones de la UART. Es solo un tesoro de información. Sabiendo esto, pude crear un controlador UART funcional en mi código y enviar un mensaje saliente al pin "UTX" deseado, que, como ahora sabemos, se encuentra en el puerto 0 pin 6 ( P0.6



). También aprendimos que el punto clave "URX" está conectado  P0.7



y que esta es la línea RX en la UART. El UART estaba enviando datos a 115.200 bps, 8n1, y el registro no lo afectó de ninguna manera. CLKSPEED



... Entonces, ¿cuáles son estos otros dos registros misteriosos que dan estos significados mágicos? 



Traté de jugar con los dos registros restantes  9A



 y  9B



. Rápidamente quedó claro para qué eran. Estos son divisores de frecuencia. He conectado algunos valores para ver cómo afectan la velocidad en baudios. Resultó sencillo.  9A



 (en lo sucesivo,  UARTBRGL



) era el byte bajo, y 9B



 (en lo sucesivo,  UARTBRGH



) era el byte alto (los 4 bits superiores aparentemente se ignoran). La velocidad en baudios se calcula simplemente como  16M / (UARTBRGH:UARTBRGL + 1)



. Esto explica perfectamente los valores que parecían mágicos: corresponden a 115.200 baudios.



Aparentemente, un pequeño error está relacionado con el hecho de que los bits de estado se pueden borrar programáticamente sin afectar el FIFO, por lo que si borra accidentalmente el bit que significa "hay espacio libre en TX FIFO" ( UARTSTA



.1), entonces la interrupción nunca ocurrirá, y el bit permanecerá bajo.



Curiosamente, estas ubicaciones coinciden con las direcciones 8051 correctas para  SCON



 y  SBUF



, que son los registros del puerto serie 8051. Los bits 0, 1 y 2  UARTSTA



 realmente se ajustan a las descripciones  SCON



de 8051, pero ahí es donde se acaba la similitud. UART de 8051 requiere que se establezcan los bits 7 y 6  SCON



en 0 y 1, solo así se convertirá en un UART normal. Este chip en este caso requiere 0 y 0. Además, el 8051 UART generalmente no tiene un divisor de baudios, en lugar del que se usa el temporizador 1.



Temporizador de perro guardián y "¡mira!"



En este punto, el límite de ejecución de 1 segundo garantizado por la configuración de vigilancia predeterminada comenzaba a molestarme. Decidí averiguar dónde y cómo está configurado el perro guardián. Normalmente, el temporizador de vigilancia se configura como parte de su propia función y es pequeño. Por supuesto, no diré que esto siempre sucede, pero la mayoría de las veces se ve así. Tenía varios candidatos y traté de copiar de cada uno a su vez las escrituras de registros en mi programa de prueba, pero el perro guardián no cedió. Necesitaba reiniciar correctamente el chip cada segundo.



Mientras hacía precisamente eso, noté una función muy extraña. Aparentemente, leyó el registro debajo del número FF



, escribió algo allí y luego reinició P1DIR



, escribió en algún otro registro y luego restauró el valor original en el registro  FF



. La rareza fue que puso TODOS los  pines del puerto 1 en pin. Esto no tiene sentido. En otros modelos, el puerto 1 tiene varios pines configurados como entrada. Además, estos registros suelen operarse bit a bit, utilizando instrucciones  anl



(AND lógico) y  orl



(OR lógico). Una escritura tan tosca en todo el registro parecía repulsiva. ¿Qué pasa con el registro FF



que debe respaldarse y restaurarse? ¡Se veía muy extraño! 



Decidí investigar. Al volcar el valor del registro a la consola FF



, resultó ser cero, lo que, por supuesto, no me convenía. Busqué en todo el firmware y noté que en casi todas partes hay una grabación, luego una copia de seguridad y luego se restaura el valor original. También noté que escribir casi siempre ocurre con un valor  0x04



y rara vez con  0x00



... Este registro fue de solo lectura durante la copia de seguridad para una restauración posterior; no se realizaron otras acciones en este valor. ¿Qué funcionalidad indica esto? Básicamente, ¡así es como suelen funcionar los controles del banco de memoria! Cuando tenga más información de la que pueda caber en su espacio de direcciones, debe cambiar. Este patrón de acceso (copia de seguridad antes del cambio y luego restauración) es típico para situaciones prácticas de este tipo. Pero, ¿qué pueden almacenar? ¿Puede ser esto? ¿Están estos locos sobrecargando el espacio de la memoria SFR



?



Escribí un programa que podía mostrar los valores de todos  SFR



, los 128. Luego convertí el bit  0x04



 en  FF



  SFR



y de nuevo sacó todo el espacio SFR



. Luego, el programa devolvió este bit y volvió a mostrar todos los valores. ¡Dios omnipotente! ¡Y ahí está! El bit 2 del registro  FF



 realmente ahorra espacio SFR



. Sin duda he visto que cuando se establece este bit, los valores que aparecen cambian. Aparentemente, esto no afectó a TODAS las direcciones  SFR



, pero sí a muchas. Llamé a este registro CFGPAGE



.



Ahora que  CFGPAGE



pensaba que estaba resuelto, volví a mi función misteriosa, que se puso a cero P1DIR



. Ya sabiendo que NO se  pone a cero en este caso P1DIR



, pero su extraño primo en otra página SFR



, Intenté copiar este código en mi programa. Lo crea o no, me topé accidentalmente con un código que desactiva WDT.



Investigé el código que rodea a esta función, ya que las funciones relacionadas en binarios generalmente se encuentran una al lado de la otra. De hecho, había varias funciones cercanas que también accedían CFGPAGE



 y accedían a la dirección adyacente P1DIR



. Después de unas horas de prueba y error, entendí completamente los detalles de cómo funciona el perro guardián. En la cuarta página de configuraciones, BF



aparece la dirección para controlar la habilitación y reinicio del temporizador de vigilancia; el bit más significativo de este registro habilita o deshabilita la función de reinicio del chip en el temporizador de vigilancia. Lo nombré WDTCONF



. Habla a  BA



 (que se encuentra  P1DIR



 en la página de configuración 0) es el registro de habilitación de vigilancia. Aquí, el bit 0 habilita o deshabilita el temporizador de vigilancia. Lo nombré WDTENA



.



Hasta este punto, todavía estaba averiguando cómo domar el temporizador del perro guardián. Me tomó un tiempo, pero al final lo descubrí. Un registro  BB



 (ahora llamado  WDTPET



) se puede escribir a cero para controlar el temporizador de vigilancia. Me tomó unos minutos más averiguar cómo configurar el retardo en el temporizador de vigilancia, ya que claramente había un agujero en el espacio de direcciones entre BB



 y  BF



... El contador tiene una longitud de 24 bits y está sobrecargado cuando se domestica. No se puede leer. Valor de recarga guardado en WDTRSTVALH



: WDTRSTVALM



: WDTRSTVALL



, situado en BE



BD



BC



 respectivamente, en la página de configuración 4. El contador cuenta  UP  a una frecuencia de aproximadamente 62 kHz, y un desbordamiento se activa. Por lo tanto, para generar un retraso mayor, se debe escribir un valor menor en estos registros de reinicio.



Posibilidades más sutiles



Programación de memoria 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;
}

      
      





Me concentré en la imagen OTA ya que es más pequeña que el firmware principal. Un detalle que definitivamente se necesita en la imagen OTA es la capacidad de escribir en la memoria flash. Cómo se ve? Se supone que necesitamos algún tipo de función que borre el flash, ya que el flash se borra en bloques. También necesita una función de escritura que pueda escribir una página de datos o menos. Necesitamos algún tipo de verificación de los datos registrados. El único detalle que difiere en las implementaciones es cómo alimentaremos los datos destinados a escribir en el controlador flash. No sabía lo que debe ser similar, pero el resto era bastante fácil de encontrar. La verificación probablemente se reduciría a solo llamar memcmp



o ciclo. Las operaciones de borrado flash agotan la memoria flash, por lo que se debe comprobar la página antes de borrar y luego realizar la operación. 



Buscando una verificación previa al borrado, encontré rápidamente una función que crea un  área de bytes de 0x400



 byte a  XRAM



completo 0xFF



. Luego, el área de memoria se  CODE



compara con este búfer y, si no son iguales, las interrupciones se desactivan y algunas se tocan SFR



en la página de configuración 4. El tamaño de la página en la memoria flash es claramente de 1024 bytes. Comprobando qué otros lugares se ven afectados por la misma SFR



, encontramos el código flash restante. Del contexto se desprende claramente qué hacen estos registros y cómo. En este caso, es interesante cómo se alimentan los datos a la unidad de control de la memoria flash. Este bloque de control contiene claramente un bloque DMA. Se proporciona una dirección a la unidad de control de la memoria flash XDATA



y los datos se absorben directamente desde allí. ¡Que guay!



En ese momento, todavía no estaba seguro de cómo leer INFOBLOCK. Aparentemente, el código OTA no le concierne, pero DEBE  leerse desde algún lugar ; después de todo, contiene datos. Revisé la imagen principal y noté un fragmento de código que afectaba a la misma  SFR



desde la memoria flash, pero de una manera diferente. Con un poco más de análisis, pude reproducir la lectura correcta de INFOBLOCK. Es curioso que se pueda usar el mismo método para leer cualquier otro bloque de memoria flash, pero no es necesario hacerlo, ya que todo lo que necesitas hacer para leer la memoria flash es leer el área de memoria  CODE



. INFOBLOCK solo es accesible a través de la unidad de control de la memoria flash. Tanto para escribir como para leer desde la memoria flash, el bloque de control usa acceso directo a memoria (DMA) y escribe en  XDATA



.



Un registro  DF



 ( FWRTHREE



) desafió cualquier intento de explicarlo. Siempre tuvo un récord con el valor 0x03



, Yo no sé por qué. Mi código de acceso flash hace lo mismo. El registro  D8



 ( FPGNO



) se escribe con el número de página flash. Las páginas principales de la memoria flash están numeradas del 0 al 63, INFOBLOCK tiene el número 128  DA



.: D9



 ( FWRSRCH



:) FWRSRCL



es la fuente del bloque DMA en el bloque de control de la memoria flash. Para escribir en flash, contiene la dirección XDATA



donde encontramos los datos para escribir. Para leer el flash, se busca un desplazamiento de bytes en la página original y la lectura comienza en ese desplazamiento.  DC



: DB



 ( FWRDSTH



: FWRDSTL



) Es la asignación para DMA en el bloque de administración de memoria flash. Para escribir en flash, contendrá el desplazamiento de bytes en la página de destino y la escritura comenzará desde ese punto. Para leer el flash, se utiliza la dirección  XDATA



en la que se escriben los datos recibidos durante la lectura.  DE



: DD



 ( FWRLENH



:) FWRLENL



Es la longitud de los datos que debe transferir el bloque DMA, menos uno.



La escritura en la memoria flash como tal se activa estableciendo un bit en uno más SFR



. Varios bits en él también están configurados para controlar otro código, aparentemente no relacionado con la memoria flash, por lo que concluí que este registro probablemente iniciaría varias acciones. Llamé a este registro D7



 en la página de configuración 4  TRIGGER



. El estado de finalización también se verifica en un registro que parece ser compartido también por otro código. CF



 Nombré  este registro desde la página de configuración 4  TCON2



, ¿por qué no? También había un registro encendido C7



, también utilizado junto con otro código, que aparentemente configuraba qué operación realizar. Lo nombré SETTINGS



0x30



 se le escribió con un O lógico para borrar + escribir,  0x18



 escribir un flash,  0x08



 leer un flash. Supuse que el bit 0x08



 significa "transferencia de datos pendiente" 0x10



 significa "en flash", y  0x20



 "Borrar". Esto tiene sentido considerando qué valores vemos y qué operaciones se realizan aquí.



Leer y escribir en flash funcionó maravillosamente bien, pero borrar aparentemente no funcionó. En lugar de borrar la página con el código dado, por alguna razón, la página en la que se encontraba el código que solicitaba el borrado se borraba todo el tiempo. Obviamente, este problema no estaba en el código contenido en este dispositivo, estaba haciendo algo mal. Verificado, verificado y verificado nuevamente para asegurarme de que mi código coincida con el código de fábrica. Emparejado. ¿Qué ocurre? Trabajé varios días hasta que me di cuenta de que el código de fábrica funciona a 4MHz y el mío a 16MHz. ¿Podría ser este el punto? ¡Resultó exactamente así! Cambié mi código de borrado de flash para mantener el divisor de frecuencia actual y reduje la velocidad del reloj a 4MHz durante la duración del borrado de flash. Todo salió bien porque este código ya se está ejecutando con las interrupciones deshabilitadas.



Otra sutileza de esta unidad de control de memoria flash es que aparentemente no proporciona una simple operación de "borrado". Pensé en asignar los bits if apropiados en el registro  SETTINGS



, y luego me pareció lógico que cuando se estableciera en 0x20



 o  0x30



 , se produjera un simple borrado. La única forma de borrar esto es realizar una operación borrar + escribir, que escribe al menos un byte (ya que no hay forma de representar una longitud cero en FWRLENH



:. FWRLENL



Para realizar un borrado simple, simplemente pido escribir un solo byte 0xFF



. Funciona



SPI



Básicamente, todos los controladores SPI son iguales. Se recibe un byte en la entrada, se devuelve un byte en la salida. Por supuesto, algunos tienen DMA y otros son controlados por interrupciones, pero el 99% de ellos en sistemas pequeños están controlados por software, y en algún lugar hay una función simple u8 spiByte(u8 byte);



.



Era lógico profundizar en SPI. Como sabemos que se SSD1623L2



 comunica con SPI, y también conocemos los detalles de la organización de dicha comunicación, solo necesitamos mirar el código y averiguar qué parte de él debe realizar esta operación. Al igual que el Sudoku, dado lo mucho que sabemos, esta búsqueda no será difícil. Mirando la hoja de datos SSD1623L2



 vemos que el número de registro del primer byte enviado está escrito en los bits 1..6, y el bit de "escritura" está en la posición # 7. Todos los registros tienen una longitud de 24 bits. Es lógico que el programador escriba un código que tomará el número de registro como parámetro, desplazándolo hacia la izquierda en uno, posiblemente lógico-o-in 0x80



, si se solicita una escritura, y luego transferirá tres bytes. No todos los programadores actúan de manera lógica, pero esta suposición ayuda enormemente en la ingeniería inversa. Al observar el código, es fácil ver las funciones que parecen hacer precisamente eso. Algunos agregan 0x80



, otros no. Todos llaman a la misma función misteriosa para cada byte. Entonces, asumimos que algunos muestran texto en la pantalla, otros leen. Abordemos la función misteriosa en sí.



De hecho, aquí todo es tan fácil como pelar peras. Se cambia  CFGPAGE



 a 4, a continuación, escribe el ED



 valor  en el registro  0x81



, escribe el byte para ser enviado a EE



, escribe  0xA0



 a  EC



, hace un retardo de 12 microsegundos, pone el bit 3 a  EB



, lee el byte recibido desde  EF



, tiendas  0x80



 a  ED



. Eso es todo. ¿Cómo comprender todo esto? Como antes, apoyándonos en lo ya conocido.



0x80



 y  0x81



 difieren en solo un bit, y lo configuramos antes de comenzar la operación SPI, y después del final del trabajo lo restablecemos, por lo que aparentemente es un bit de "activación" de algún tipo. Por otro lado, el significado  0xA0



 literalmente  suena como una  configuración de algún tipo. El registro  EB



 sigue siendo un misterio. Pero, si reproduzco este código sin escribir en él, todo funcionará, por lo que concluyo que no depende mucho de este registro. Definitivamente EE



 esto  SPITX



EF



 esto  SPIRX



. Llamé  ED



 -  SPIENA



 y  EC



 -  SPICFG



.



Queda por caracterizar lo que hacen los ritmos en SPICFG



... Hice un poco de prueba y error, armado con un analizador lógico. El bit 7 debe establecerse, el bit 6 debe borrarse. El bit 5 inicia la transmisión del byte SPI y se borra cuando termina con él. Los bits 3 y 4 establecen la frecuencia del reloj, puede elegir entre los valores: 500KHz, 1MHz, 2MHz, 4MHz. 2 es el bit de configuración estándar CPHA



 para SPI, el bit 1 es  CPOL



. El bit 0 parece violar RX. Supongo que puede configurar el bloque para semidúplex (en línea  MOSI



). En general, no es tan difícil.



Pin a pin, encuentre rápidamente la configuración GPIO y vea qué P0.0



 es   esto SCLK



P0.1



esto  MOSI



 y  P0.2



 esto  MISO



... Al buscar dónde están configurados estos GPIO, también vemos cómo se necesita el bit  CLKEN



 SPI: ese es el bit 3. Genial, ¡ahora tenemos un SPI que funciona!



Determina la 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;
}

      
      





Las pantallas de E-Ink se actualizan de manera diferente según la temperatura actual, por lo que conocer la temperatura ambiente es fundamental para actualizarlas correctamente. Las formas de onda correctas se seleccionan en función de la temperatura. Aquí el conocimiento del exterior será útil. Entonces, si podemos encontrar dónde se cargan las formas de onda en el controlador de pantalla, podemos encontrar dónde se hacen las elecciones. Desde este lugar puedes caminar directamente hasta el punto donde se mide la temperatura, ¿verdad? Una vez hecho esto, vamos a exactamente una función, cuya salida determina qué forma de onda se utilizará. ¡Debe ser esto! Por cierto: por lo general, los sensores de temperatura están conectados al ADC; casi nadie los fabrica en una versión separada. Pero no importa [todavía].



Todo comienza con el ajuste del bit 7 a  CLKEN



y termina con su reinicio, para que al menos sepamos que así es como encendemos y apagamos el sensor de temperatura (o ADC). La función cambia  CFGPAGE



 a 4, luego escribe una serie de valores en una serie de registros. Todos los valores son constantes. 0x81



 -> reg.  F7



0x22



 -> reg.  E7



0x55



 -> reg.  E6



0x00



 -> reg.  FC



0x00



 -> reg.  FB



0x03



 -> reg.  FE



0xFF



 -> reg.  FD



, luego los bits se  0x81



 vacían a  F7



. Después de eso  CFGPAGE



 se recupera y luego borra el bit 4 del registro A1



. Esta parece ser la configuración inicial. Después de que un determinado procedimiento ocurre cinco veces, se promedian los resultados de todas las operaciones excepto la primera. Después de eso, se hacen muchos cálculos matemáticos sobre el promedio obtenido de esta manera, en particular, utilizando los valores de INFOBLOCK; probablemente estos son valores de calibración. Luego se devuelve el resultado. Echemos un vistazo más de cerca a los detalles.



En el proceso, el bit 4 en el registro simplemente se estableció A1



, se estableció el bit global y luego, en el modo de espera activo, pasamos tiempo hasta que se borra el bit. Los valores promediados específicos, aparentemente, se toman de alguno global. Esto es extraño ... Busqué dónde está escrito y lo encontré en el controlador de interrupción # 10. Aparentemente, así fue como se borró el bit 4 en el registro A1



, luego se llevó a cabo el cambio a la página de configuración 4, se leyeron los valores de los registros F8



 y  F9



, y se hicieron algunas cosas extrañas con ellos, y luego se escribió este valor global . Pero, ¿qué se hace con estos valores? 



Acabo de estar en los ojos pinchado constantes  0x55



0xAA



0xCC



0x33



... es posible? ¿Podría alguien ser tan directo que ... bueno, sí? Estas son constantes para una forma inteligente de invertir el orden de los bits en un byte. Difícil, pero solo en procesadores más avanzados. En 8051, este enfoque es muy ineficaz. ¿Pero por qué? Parece que cualquier IP (puntero de comando) que licencian para medir la temperatura, produce un resultado en el que los bits están en orden inverso. Por qué este problema debe resolverse a nivel de software de un chip propietario es una gran pregunta. Después de todo, invertir el orden de los bits en el hardware no es más difícil que reordenar algunos cables ... ¿Qué hace? No lo sé. De hecho, nunca lo conseguí. 



Casi nadie diseña un contador de comandos dedicado para un sensor de temperatura, esto simplemente se conecta al ADC. Una vez que pude volver a implementar este código y asegurarme de que funcionaba muy bien, traté de cambiar todos estos registros. La mayoría de ellos influyó en la ganancia del sensor de temperatura, algunos no tuvieron ningún efecto. Si este fuera un ADC normal, esperaríamos que algunos bits lo cambiaran a un tipo diferente de entrada y dieran un valor completamente diferente. Por desgracia, esto no sucedió. Realmente parecía un sensor de temperatura normal. Esto también se confirma porque estos registros no se tocan en ningún otro lugar. Extraño como el infierno, pero está bien ... 



Dado que casi todos estos registros se escriben solo una vez, y son estos valores, y cambiarlos afecta el valor medido, decidí llamarlos simplemente todos los valores de calibración de temperatura. Por lo tanto, nos familiarizamos con TEMPCAL1



 (reg.  E6



),  TEMPCAL2



 (Reg.  E7



),  TEMPCAL3



 (Reg.  FB



),  TEMPCAL4



 (Reg.  FC



),  TEMPCAL5



 (Reg.  FD



) Y  TEMPCAL6



 (reg.  FE



). Lo nombré  porque se usa varias veces y parece que realmente administra la carga del valor de calibración. Los resultados se publican en   (reg.  F7



  TEMPCFG



TEMPRETH



F8



) y  TEMPRETL



 (reg.  F9



). Los resultados tienen una longitud de 10 bits, alineados con el extremo superior de un registro de resultados de 16 bits, con el orden de bits invertido.  



También noté que el bit 3 en se  TEMPCFG



 establece cuando la muestra termina de crearse. Curiosamente, el código de fábrica no lo verifica, sino que se basa en la interrupción. Pero, de hecho, resultó útil  para decodificar el propósito del registro A1



. Como puede ver, el 8051 clásico está limitado a 7 fuentes de interrupción, ya que tenemos 8 bits en el registro.  IEN



y el bit 7 está reservado para activar una interrupción global. Entonces, ¿cómo gestiona las interrupciones del 7 en adelante? De hecho, es como el salvaje oeste, lo que quieres es lo que haces. Pero aquí tenemos una pieza de hardware que dispara la interrupción número 10, y usando un poco, podemos saber cuándo se hizo. Esto es genial para experimentar. en el que queremos saber cómo se activan y desactivan las interrupciones por encima de 7. Solo era necesario jugar con este código hasta deshacerse de la interrupción, pero se crea el ejemplo . La búsqueda no duró mucho. ¡Debe ser así A1



! Lo nombré  IEN1



... No estoy seguro de cuál es la función del bit 0 aquí, pero los bits 1 y superiores controlan la activación de las interrupciones número 7 y superiores. Pude confirmar esto más tarde. Bien, hecho, hemos documentado otro periférico, descubriendo así aún más rarezas ...



I2C



En esta etapa, abrí una etiqueta de precio de tinta electrónica más grande equipada con el mismo chip. ¡Era un modelo de 2,9 pulgadas con pantalla gráfica de tinta electrónica y NFC! Una vez más, el conocimiento de terceros es útil aquí. La mayoría de los dispositivos NFC te dirán exactamente qué son si los preguntas con amabilidad. Esto es bueno, ya que el chip NFC de la placa era demasiado pequeño para etiquetarlo correctamente. Después de escanearlo usando NFC y verificar el ID del dispositivo, descubrimos que es NXP NT3H1101 (copia archivada  aquí  para la posteridad). Desde esta página muy conveniente puede descargar la hoja de datos - e inmediatamente queda claro cómo debe proceder la comunicación con este chip. ¡Informacion util! (Toda la información es útil aquí). Lo único molesto es que la dirección I2C de este dispositivo no es fija, pero se puede establecer en cualquier valor; sin embargo, se proporciona un valor predeterminado. El alfabeto de la ingeniería inversa: en el 99,9% de los casos, los valores predeterminados no cambian. ¡Apuesto a que la dirección I2C predeterminada tampoco ha cambiado!



Encontrar un análogo binario para es  0x55



 bastante fácil; este valor no es tan común. Aparentemente, todos se realizan antes de las llamadas a una de las dos funciones. Tiene sentido que estén conectados a I2C. Además, en todos los casos, antes de estas llamadas, el bit 4 se establece en CLKEN



que luego se descarta. Ahora sabemos que I2C se activa a través de este bit. Echemos un vistazo a lo que hacen estas funciones. Algunos copian datos del parámetro proporcionado al principio, otros lo hacen al final. En el medio, todos escriben algunas cosas globales, establecen el bit global, 95



borran el bit 4 y establecen el bit 5 en el registro y esperan a que se borre. Hmm, funciona como un sensor de temperatura. Aparentemente, el bit 2 de la IEN1



 interrupción se activa.



Veamos dónde está ubicado el manejador de interrupciones que afecta estos valores globales. De hecho, su número de interrupción es 8, como se esperaba. Se pone CFGPAGE



 a 0 y luego lee el registro. 91



... Los 3 bits menos significativos se ignoran y los bits restantes se utilizan en el caso del interruptor para decidir qué hacer. Este código resultó ser un poco confuso, así que decidí experimentar. Conectó el analizador lógico a las líneas que van al chip NFC y rápidamente encontró dónde SDA



y dónde  SCL



. Fue fácil porque hay una hoja de datos para este chip.



Parece que borrar el bit 4 en el registro  95



 no afectará nada, pero establecer el bit 5 hace que la condición de ARRANQUE en el bus sea verdadera. Se dispara una interrupción. Si hace lo mismo usando el manejador incorporado y lee los 5 bits más significativos en el registro 91



, vemos que tienen un valor 0x08



... Luego, el byte de dirección se almacena con el 94



bit R / W (lectura / escritura) en el registro  , y el bit 3 en el registro se borra  95



. También debe tenerse en cuenta que TODAS las rutas a través de este manejador de interrupciones dan como resultado que el bit 3 se borre en el registro 95



. Supongo que esta es la "parte que debe interrumpirse". Aún no lo he descubierto, pero ya podemos nombrar algunos registros. Parece que todos los registros I2C están en la página de configuración 0.



Voy a llamar  porque es I2C lo que contiene y nunca se lee por ningún otro motivo. Nunca he visto los tres bits menos significativos cambiar o usar de alguna manera.  - así que llamaré  91



  I2CSTATE



I2CBUF



94



, ya que los datos se bombean a través de él a lo largo del transportador, y  95



 en el futuro se le dará un nombre  I2CCTL



, ya que para que se puedan hacer las cosas, es necesario escribir algo en él.



Investigamos más y encontramos que cuando se envía el byte de dirección, se puede obtener uno de los cuatro valores de estado. Si el byte de dirección que enviamos requería acceso de escritura, entonces el estado será  0x18



si fue reconocido (ACK), y  0x20



si no. Si el byte de dirección que enviamos requería acceso de lectura, entonces el estado será  0x40



si fue reconocido (ACK), y  0x48



si no. El manejo de NAK (byte no reconocido) es bastante sencillo. Cuando el bit 5 se establece en  I2CCTL



 la condición de PARADA en el bus es verdadera.



Enviar datos en modo de escritura es fácil. El byte simplemente se escribe en  I2CBUF



. Si se reconoce el byte enviado (ACK), el estado se convertirá en, 0x28



y si no, entonces  0x30



. Para provocar un reinicio, establezca el bit 4 en  I2CCTL



 - funciona. Cuando se completa la ejecución del comando RESTART en el bus, el estado pasa a ser  0x10



.



Si queremos leer la información, luego de enviar el bit de reinicio y el byte de dirección en modo lectura, tan pronto como veamos el estado 0x40



, podemos decidir cómo responder al siguiente byte que recibamos - ACK o NAK. Para reconocerlo (ACK), establezca el bit 2 en  I2CCTL



, y para no confirmar (NAK), borramos este bit. Con el regreso del manejador, se recibirá el byte. Una vez hecho esto, veremos el estado 0x50



si el byte fue confirmado y 0x58



si no fue confirmado. De una forma u otra, el I2CBUF



 byte recibido estará contenido en. 



Después de revisar el código de inicialización y jugar con nuestra copia, encontramos que el bit 7 en  I2CCTL



 controla si el dispositivo periférico activará interrupciones. Si no es así, este registro se inicializa a  0x43



... Supongo que así es como el bloque está configurado para operar en modo maestro. Como no tengo un código de muestra para el modo esclavo, no investigué más esta pregunta, pero estoy seguro de que el modo esclavo es compatible. Se puede hacer, pero soy un vago :).



El registro  96



 también registró información en el tiempo de inicialización y luego ya no cambia. Esto se correlaciona bien con un bit de información que todavía nos falta, lo que indica cómo se establece la velocidad del reloj. Después de experimentar con este registro (que ahora se llama  I2CSPEED



), vemos que tiene una interdependencia compleja con la frecuencia del reloj, pero después de varias docenas de intentos llegué a lo siguiente:  rate = 16MHz / ((dividerB ? 10 * (1 + dividerB) : 12) << dividerA)



donde dividerA son los tres bits menos significativos I2CSPEED



y dividerB es el siguiente 4. El bit más significativo aparentemente no se usa.



El hecho de que la configuración inicial de GPIO ocurra cerca del punto de inicialización del periférico parece implicar que los pines P1.4



 y  son importantes en este caso P1.5



.



Todo funcionó, pero había un secreto. Cuando se activó la interrupción para este bloque (c  IEN1



), el bit 2 también se estableció en el registro A2



. Dado que  IEN1



 está ubicado en la dirección  A1



, sospecho que tiene que ver con una interrupción. Todavía no he descubierto exactamente lo que hace, y ningún otro código que no sea el código de configuración inicial de I2C lo usa. Lo nombré previamente I2CUNKNOWN



aunque es más probable que esté relacionado con interrupciones que con I2C. De todos modos, ¡mi código ahora puede realizar transacciones I2C como maestro!



Detección de cambio de pin



El firmware de la etiqueta de precio se despertó cuando fue escaneado por un dispositivo habilitado para NFC. El chip NFC integrado tiene un pin de "detección de campo" conectado al microcontrolador principal. ¿Coincidencia? No¡pensar! Debe haber una forma de detectar cambios en el pin. Incluso despierta el chip desde el modo de suspensión (ahorro de energía). Además, se necesita algo de tiempo para dibujar con tinta electrónica y, durante esta espera, es probable que el chip continúe inactivo. La pantalla indicará el final del dibujo cambiando la señal "OCUPADO". Entonces ... tenemos dos casos en los que la CPU debe detectar un cambio en un pin y, lo más probable, no estamos hablando de un ciclo de espera activo. Sería difícil encontrar el primer caso descrito; todavía no sabía exactamente dónde está este código de hibernación. El segundo caso, por el contrario, fue muy fácil de encontrar; quiero decir, fue fácil encontrar el código para dibujar en la pantalla. Una vez más, es útil aprovechar los conocimientos existentes. Yo sabía,qué equipo es responsable de "actualizar la pantalla" en prácticamente todos los chips de pantalla de tinta electrónica que existen. Simplemente entré y vi lo que pasaría. Había mucho código, muchos fueron tocados  SFR



... Empecé a experimentar con los pocos que vi. Hice algunas conjeturas fundamentadas: todos los pines deberían poder activar la detección de cambios. Este no es siempre el caso, pero por lo general se hace una suposición fundamentada. Supuse que cualquiera que sea la configuración de los registros de los que estábamos hablando, serían secuenciales y funcionarían con tres puertos. También asumí que cambiar el pin debería proporcionar una interrupción, y no solo despertar el dispositivo. Tiene sentido que el número de registros de configuración sea bastante predecible. Para cada pin, necesitamos ENABLE, STATUS y, muy probablemente, DIRECTION. Además, es probable que los registros relacionados con la detección de cambios de GPIO se encuentren cerca de otros registros de configuración de GPIO.



En base a esto, hice algunos experimentos, ya que podía cambiar fácilmente al menos algunos de los pines (por ejemplo, TEST). También me tomó un tiempo ver cómo se está desarrollando mi mapa actual SFR



. No me he olvidado de mirar los registros BC



BD



BE



 en la página de configuración 0. Varios experimentos han demostrado que controlan el pullup de cada pin. Es cierto que nunca he visto ninguna configuración que permita "tirar del pasador hacia abajo". Los nombré PxPULL



.



Después de varios experimentos, quedó claro que hay tres registros por puerto y controlan las interrupciones cuando cambia el pin.  PxLVLSEL



( A3



A4



A4



) selecciona el nivel deseado (0 = alto, 1 = bajo).  PxINTEN



( A6



A7



A9



) Proporciona cambio de pin de seguimiento a nivel de hardware.  PxCHSTA



( AA



AB



AC



) Almacena el estado de detección (bit = algo ha cambiado). Otros experimentos mostraron que el número de interrupción al cambiar el pin es 11. Funciona bien, e incluso logré reactivar el chip del modo de ahorro de energía (más sobre esto a continuación).



Segundo DPTR



Se registra  84



 y  85



 guarda misteriosamente en medio de todas las transacciones de intercambio CFGPAGE



 y mantiene los 8 bits almacenados en ellos. En muchas variantes del 8051, aquí es donde debería estar el segundo registro DPTR



. Pero, si es así, ¿cómo lo cambia  ? Todo el mundo lo hace de forma diferente. Me decidí a probarlo. Escribí un programa en ensamblador para invertir cada bit en cada registro por turno y verificar si la escritura de un entero DPTR



 (instrucción especial) coincide con la lectura posterior DPL



 y  DPH



 (acceso normal a  SFR



). Es predecible que muchas de estas cosas no se puedan cambiar tan fácilmente sin bloquear el programa. Pero, habiendo practicado cuidadosamente omitir uno u otro, aislé el bit 0 en  92



. Bueno, sí ... Eso es lo que hace. Al igual que con muchos 8051, llamé a este registro  DPS



, que significa "selección de puntero de datos". Registros 84



 y  85



 nombré, naturalmente,  DPL1



 y  DPH1



.



Otros experimentos.



Algunos experimentos han demostrado que los dos bits menos significativos en PCON



(espera y suspensión) funcionan como se esperaba en el modo de suspensión para el 8051 (aunque también se puede configurar la suspensión en modo de ahorro de energía). También noté que la configuración del bit 4 está desactivada  XRAM



. ¡Esto ahorra algo más de energía en el modo de suspensión! 



Los registros en el rango B2



.. son interesantes  B6



. Parecen variar según las instrucciones que se sigan en su ubicación. Habiendo considerado todo cuidadosamente, me di cuenta de que B4



: ¡ B5



 siempre está actualizado  PC



! Por qué alguien podría necesitarlo, no lo sé. Los nombró  PCH



 y  PCL



... Son de solo lectura. Pero, ¿qué pasa con los otros registros de este rango? B2



 y  B3



parece estar asociado con saltos condicionales. En un salto de longitud (por ejemplo, al correr ljmp



lcall



ret



), parecen almacenar el destino del salto. Con transiciones cortas (como  sjmp



),  B2



 parece descubrir el desplazamiento. Cosas extrañas, pero inútiles, así que no profundicé en ellas. Nombré el resto de los registros  PERFMONx



.



Dormir en modo de ahorro de energía



Las personas son personas y nada humano les es ajeno. A la gente le encantan los números redondos. Me gusta la precisión, incluso si no la necesito. Esto ayuda mucho con la ingeniería inversa. Por ejemplo, ¿cómo respondes a una constante  0x380000



? ¿Ninguno? Quizás. ¿Qué tal 0x36EE80



? Los ojos ya se aferran a ella. ¿Qué demonios significa eso? Traducirlo al sistema decimal y verá: 3.600.000  Bueno , esto es una hora, expresada en milisegundos. Este valor puede ser útil, quizás, solo en el caso de un sueño prolongado en modo de ahorro de energía. ¡Estoy cansado de contar cuántas cosas hice "ingeniería inversa" confiando en constantes de este tipo que arrojan luz sobre dónde se realizó el sueño! 



Aquí están las constantes en este dispositivo que se pasaron a la función que me interesa: 1 5000 2 000 5 000 10 000 3 600 000 1 800 000 0xffffffff. Es bastante comprensible que esta sea una indicación de la duración en milisegundos. Este último es probablemente un resguardo de "para siempre o casi para siempre". 



Casi no había posibilidad de comprender lo que la mayoría de los registros están haciendo aquí, ya que son utilizados por código casi exclusivamente en modo de suspensión. Algunos estaban dentro SFR



y otros en el espacio  MMIO



... Pude copiar el código y reproducirlo. En particular, me interesó que el temporizador de apagado automático pueda funcionar a dos velocidades: con una frecuencia de 32 KHz y 1Hz. Se trata de un temporizador de 24 bits, con el que el sueño más corto posible dura unos 30 ms, y el más largo puede durar unos 194 días. Leer más en el SDK.



Radio



La radio generalmente requiere una configuración extensa, por SFR



 lo que está demasiado llena en un espacio denso . La mayoría de los 8051 equipados con radios se utilizan para resolver este problema MMIO



. Las E / S asignadas en memoria en el 8051 generalmente se asignan al espacio de direcciones  XRAM



. Mirando diagonalmente a través del código, me di cuenta de que la radio de este chip está adentro  MMIO:df00 — MMIO:dfff



.



Ruta RX



Nuevamente, decidí comenzar con la imagen OTA. Es lo suficientemente pequeño para simplificar el análisis. Pronto quedó claro que la imagen OTA no envía ningún paquete de radio, sino que solo los recibe (los reconocimientos se envían automáticamente a nivel de hardware, que es típico de la mayoría de los chips ZigBee). ¡Pero es bueno! Gracias a esto, nos basta con analizar solo la mitad del controlador, lo que significa que la tarea es el doble de simple posible. 



Cuando comencé a buscar dónde el código OTA obtiene los datos, parecía que había una cola de búfer. Qué es: es una cola que contiene bytes individuales, cada uno de los cuales es un puntero a una lista de búferes. El código que parecía recibir paquetes y procesar los paquetes recibidos tomó el búfer de la cola, lo procesó y luego lo puso en otra cola. Un esquema muy simple. Una cola almacena búferes llenos de datos recibidos, otra cola almacena búferes vacíos listos para recibir nuevos datos recibidos. Bastante claro.



Mirando un poco a nuestro alrededor, descubrimos rápidamente dónde se accede a las colas de una manera diferente: quitando el búfer de la cola "vacía" y poniendo en cola las llenas. ¡Este es el controlador para la interrupción # 5! El propio manejador de interrupciones fue bastante simple, siempre que se activa el bit TCON2.2



, se salvó  0xC6



 en  MMIO:df48



, quita de la cola de la memoria intermedia, bytes copiados en ella y ponerla en otra cola. Pero, ¿de dónde copió los bytes? ¿De dónde sacaste la longitud de la copia? ¡Ambos fueron sacados del búfer XRAM



en el que no escribió! Nunca he podido desentrañar este misterio.



La búsqueda no terminó ahí. La interrupción 4 jugó un papel clave, su manejador resultó ser aún más simple. Probó el bit 5 en  MMIO:dfad



 (lo llamaré  RADIO_IRQ4_pending



y, si se establece, llama a un procedimiento que no se llama en ningún otro lugar. Este procedimiento leyó , verificó que el valor en él fuera menor o igual a 128, leyó  , verificó que con un aumento en uno, se volvería igual al valor anterior. Si alguno de los anteriores no se cumplía, se guardaba   en  , de lo contrario se seleccionaba la página de configuración 4, el primer valor leído se almacenaba en una variable global, que además denotaba la longitud. Este valor menos uno persistió  , y el puntero a la memoria intermedia de la que los datos posteriormente copiados almacenados en : . Luego se configuró el bit 2  . SFR



  FA



MMIO:df98



0xC6



MMIO:df48



D5



D4



D3



TRIGGER







Aquí, nuevamente, conocer el contexto ayuda. 127 es el valor máximo que puede tener un paquete 802.15.4 válido, y esta longitud incluye una verificación de redundancia cíclica (CRC) de 2 bytes, pero no incluye la longitud del byte en sí. Por lo tanto, supongo que FA



 esta es la longitud resultante (teniendo en cuenta la longitud del byte y el CRC). Lo nombré  RADIO_GOTLEN



. En tal caso, tiene sentido que el  MMIO:df48



 (ahora nombrado  RADIO_rxFirstByte



) podría ser el primer byte recibido (byte de longitud). Con todos los registros restantes está claro:  D5



 es la longitud del DMA para RX DMA (ahora llamado  RADIO_RXLEN



D4



: D3



 se desmonta en partes puntero al destino RX DMA ( RADIO_RXPTRH



 y  RADIO_RXPTRL



 respectivamente).



Entonces todo salió bien. La interrupción número 4 se activa tan pronto como la radio recibe un paquete en el búfer interno. El bit 5 establecido en  RADIO_IRQ4_pending



 (esto ahora se llama  RADIO_IRQ4_pending



) nos dice que esto sucedió. Procedemos con la inspección inicial del paquete (asegurándonos de que su longitud esté dentro de límites razonables), y luego ejecutamos el DMA desde el búfer interno a XRAM



, si todo está bien. Si no, entonces escribimos 0xC6



 en  MMIO:df48



. Lógicamente, esto se puede comparar con "vaciar el RX FIFO", por lo que este registro ahora se llama  RADIO_command



. Si todo estaba bien con el paquete y se completó la operación DMA, entonces el bit 2 se establece en  TCON2



, y se dispara la interrupción 5. Aquí, de nuevo, escribimos "vaciando RX FIFO" en  RADIO_command



. Esto es útil porque ya hemos extraído los datos mediante el método DMA. ¡Luego se copian los datos y el trabajo está hecho! 



En la mayoría de las radios, el código de redundancia cíclica recibido no se proporciona en las capas superiores; simplemente se verifica y se devuelve con un solo bit de estado con un valor de sí o no. Como es habitual, es aconsejable asumir que todo funciona "normalmente". Verifica, es realmente regular. La mayoría de las radios ZigBee informan en estos dos bytes un LQI (indicador de calidad de enlace de radio) y un RSSI (indicador de intensidad de señal recibida) en lugar de un CRC. En este modelo, la radio funciona de la misma manera. Casi. Aparentemente, el primer byte es siempre 0xD0



pero el segundo parece contener realmente el LQI (en los 7 bits menos significativos) y el estado CRC (en el bit 7). De hecho, es funcionalmente muy similar a cómo funciona la radio Chipcon. ¡El comando 0xC6



 también significa "RX FIFO vacío" para radios chipcon (ahora TI)! Muchas otras cosas no son iguales, pero los comandos son OPUESTOS , ¡y me ayudaron a navegar por los otros elementos de esta pila de radio!



Más sobre radio



Si considera cómo el código OTA inicia la radio, puede ver que MUCHOS registros se ven afectados solo una vez, algunos valores están escritos en ellos, que parecen ser completamente aleatorios. Lo más probable es que muchos de ellos sean de calibre. Cualquier registro en el que se escribe una vez (o repetidamente, pero se ingresa el mismo valor) es un registro de calibración. Omitiré los aburridos detalles de los registros involucrados, pero hablaré sobre el código de inicio de trabajo que está en el SDK.



Aquí, nuevamente, observamos cuántos valores se escriben en el registro  RADIO_command



... Los valores registrados coinciden con los que esperaríamos ver si trabajáramos con los valores de los comandos chipcon, aunque podemos ver algunos valores que no están en los módulos de radio chipcon. Entonces, o esta radio es un raro chipcon bastardo, o ambos descienden de un ancestro común. En cualquier caso, esta situación ayuda a comprender algunos comandos más emitidos por ellos.



Reproducir el código de inicio y escribir controladores de interrupciones, como los integrados en el chip, nos proporciona un binario funcional que puede funcionar para la recepción y es propicio para los experimentos. Al notar algunos registros más en los que escribe el firmware principal, rápidamente determiné que MMIO:df88 — MMIO:df8f



 esta es "mi dirección MAC larga", que se utilizará a nivel de hardware para filtrar los paquetes entrantes. Similar,  MMIO:df90 — MMIO:df91



 establece el "ID PAN propio" para el filtro RX. A  MMIO:df92 — MMIO:df93



 establece "propia dirección corta". Este equipo aceptará y reconocerá (ACK) cualquier paquete enviado a nuestras direcciones de transmisión.  MMIO:dfc0



 establece el canal de radio en la numeración estándar 802.15.4 (11..26).



Dado que la radio reconocerá los paquetes, también pude encontrar que la MMIO:dfc9



 fuerza de transmisión se está ajustando al ajustar  . Creo que se trata de que el registro establezca la potencia de TX. También noté que cuando se configura un canal en el firmware principal de fábrica, se escriben dos registros más con valores por canal. Solo hay un registro de este tipo en el firmware OTA. El relacionado con RX se llama MMIO:dfcb



, y el relacionado con TX se llama MMIO:dffd



... Bastante fácil de replicar y comprender. ¡Entonces es hora de descubrir TX!



¡Enviemos algunos bytes!



Después de descifrar la ruta de datos, transferí la función y los nombres de registro en mi imagen maestra desensamblada. Mirando lo que aún no está marcado, podemos ver dónde se encuentra el camino de TX. De hecho, hay dos colas de búfer más aquí: una llena de búferes TX vacíos listos para usar y la otra llena de búferes TX "agotados" listos para ser enviados. Encontré la función de transferencia muy rápidamente.



En 802.15.4, se acostumbra escuchar el canal de radio antes de transmitir. Esta operación se denomina CCA (Evaluación de canal inactivo). Antes de hacer algo con los datos que estamos a punto de enviar, considere un bucle que dice  MMIO:df98



 y comprobando el bit 0. Si está establecido, la función falla y el temporizador está configurado para reintentar. Creo que este es el camino de CCA. Si vemos cero en este bit 128 veces, entonces consideramos que el canal está libre. 



La función de transferencia en sí resultó ser deprimentemente simple: selecciona la página de configuración 4, la longitud deseada (sin incluir el byte de longitud o CRC) y todo está escrito CD



. Un puntero a un búfer en  XRAM



 escrito en CA



: C9



. El búfer comienza con un byte de longitud.  RADIO_command



 cargado de valor  0xCB



. No existe tal comando en las radios chipcon, pero supongo que significa "cargar TX FIFO". Entonces el bit 1 se establece en  TRIGGER



... Supongo que así es como se inicia el acceso DMA a la cola de radio interna TX FIFO. Luego se  MMIO:dfc8



 establece en  0xFF



, se realizan 255 intentos para esperar a que termine TX, verificando que el bit 7 en   MMIO:df9b



 (ahora llamado  RADIO_curRfState



) esté establecido. Luego, después de un breve retraso, se  MMIO:dfc8



 establece en 0x7F



. Curiosamente, no tengo ni idea de por qué se está grabando MMIO:dfc8



. En mi código, intenté prescindir de él y todo funcionó bien.



Cruz 



Después de experimentar un poco, descubrí algunos trucos que el firmware de fábrica no puede hacer. El bit 6 in RADIO_IRQ4_pending



 se establece después de que "TX" el paquete y expira el retardo de reconocimiento (ACK). Si realmente recibimos un ACK, también se establecerá el bit 4. Por lo tanto, es fácil determinar (1) cuándo enviamos un paquete y (2) si recibimos un ACK. ¡Fresco!



Además, si el bit 4 in  RADIO_IRQ4_pending



está establecido y el bit 5 in RADIO_curRfState



no está ocupado, esto significa que estamos en el proceso de recibir un paquete. Necesitamos seleccionar el RSSI manualmente, para lo cual leemos MMIO:df84



 (ahora  RADIO_currentRSSI



). Tiene un desplazamiento de aproximadamente 56 dBm.



También noté que el bit 1 en TCON2



establecido al finalizar TX DMA (pero no necesariamente el proceso de TX en sí). El bit 0 in se  TCON2



establece cuando finaliza la inicialización de radio.



Misterios sin resolver



Motor de encriptación AES y medición de batería / ADC 



Tiene sentido que haya alguna forma de medir el voltaje de la batería, pero no he encontrado ningún rastro de ningún código similar. Sin un código que use el ADC de esta manera, las posibilidades de encontrar este método de desaparición son escasas. El bloque AES es, en principio, el mismo que el ADC. Sé que hay un bloque de aceleración AES en el chip (requerido para ZigBee). Pero dado que el código real no lo usa, no veo la manera de encontrarlo.



miscelánea



Cosas que no podemos encontrar, pero que realmente no nos importan, ya que no podemos comprar este chip: controlador LED IR, unidad PWM, DAC. Dejaré estas cosas para que el lector las ejercite por su cuenta.



Pinouts ZBS242 / 3, características, SFR, descargas 



Descarga  ZBS24x SDK .







  • Las celdas sombreadas indican registros direccionables bit a bit 
  • Los registros sombreados en diagonal que no están almacenados en el banco CFGPAGE



  • Registros sombreados verticales, que aparentemente no aparecen en ninguna de las páginas. 
  • Las celdas vacías son registros desconocidos
  • Los nombres de los registros de RADIO comienzan con la letra "r" 






Lecciones para un principiante en ingeniería inversa



  • Lea los materiales durante al menos unas horas o días antes de comenzar a trabajar.
  • - . -, . 
  • . SPI , I2C , . .
  • , – ( ). 
  • : , . , , .  
  • , , . 
  • . - , - . , .
  • - . , , . 
  • , , . , , .
  • - . . 
  • - , , - . 







Los servidores en la nube de Macleod son excelentes para alojar sitios web.



Regístrese usando el enlace de arriba o haciendo clic en el banner y obtenga un 10% de descuento durante el primer mes de alquiler de un servidor de cualquier configuración.






All Articles