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
y
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
y
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
y
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
o
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.