2021-12-26
Formato .hex
Allá por los 70 Intel necesitaba una forma de poder llevar el código ensamblado de sus programas y de forma que pudieran estar legibles en papel así que desarrollo un formato de fichero que fuera fácilmente entendible y pudiera ser imprimible y leíble por todos.
Así nació el formato .hex, que actualmente se ha convertido en un estándar y se utiliza para guardar el contenido de la memoria de programa de microcontroladores, EEPROM o cualquier dispositivo que tenga una memoria no volátil.
Un ejemplo de esto lo podemos observar en Arduino. Nosotros creamos un "sketch" que no es nada más que un programa escrito en C++. Pero el Arduino no entiende ese lenguaje, necesita su lenguaje máquina. Por eso cuando apretamos el botón programar, el entorno compila, convierte en código máquina y guarda el contenido en un fichero .hex. Luego el programa avrdude el que se encarga de leer ese fichero .hex y grabar el contenido en la memoria de programa.
El formato.
Un fichero .hex no deja de ser un fichero de texto que podemos abrir con cualquier editor como el Notepad de Windows y editarlo a mano.
El contenido del fichero es un conjunto de líneas de texto, llamadas registros, que contiene información sobre el volcado de la memoria.
La memoria son bytes y esos bytes los podemos representar en binario, octal o hexadecimal. En el formato .hex optamos por este último y de hay el nombre: hex de hexadecimal.
La representación es fácil. Sabemos que un byte son 8 bits, y que con 4 bits podemos representar un valor hexadecimal de 0x0 a 0xf. Por lo tanto se necesitan dos caracteres para representar un byte, siendo el cero representado por la cadena "00" y el 255 como "FF". Cada registro se compone de una serie de bytes que representaremos de esta forma.
Cada registro empieza por un carácter de inicio, siendo este el "dos puntos" (:) y termina con un retorno de carro (CR) o salto de línea (FL).
El primer byte del veremos será la longitud de datos que contenga el registro. Aunque el valor máximo de datos en el registro es de 256 bytes, no se suele usar este valor y se utilizan valores más pequeños. Los valores mas comunes son 8, 16 o 32 bytes.
El siguiente campo que nos encontramos es una dirección de inicio donde los datos deben ser guardados. Se utilizan dos bytes para crear una dirección lo cual limita las direcciones a 64k de programa.
Luego tenemos el tipo de registro. En la siguiente tabla tenemos los registros mas utilizados:
| TIPO | DESCRIPCIóN |
| 00 | Registro normal de datos. |
| 01 | Fin de fichero. |
| 02 | Dirección de segmento extendida. |
| 03 | Dirección de inicio de segmento. |
| 04 | Dirección lineal extendida. |
| 05 | Inicio de dirección lineal. |
Los tipos de 2 al 5 suele usarse para memorias que se direccionan por segmentos como en las arquitecturas x86. Generalmente, en microcontroladores normales solo encontraremos registros del tipo 0 de datos y del tipo 1.
El tipo 1 indica el final del fichero y siempre lo encontraremos al final del .hex. Su campo de datos y su dirección de inicio será siempre cero:
:00000001FF
El siguiente campo es el de datos. Aquí encontraremos tantos bytes como indique el campo de longitud. Estos datos se grabaran en la posición de memoria indicada por el campo dirección.
El último campo es un campo de verificación o checksum. Este campo se calcula sumando todos los bytes de los campos anteriores (menos el incido obviamente) y calculando el complemento a dos de la parte baja del resultado.
Pongamos un ejemplo:
El registro empieza por ':'. El siguiente campo es la longitud, en este caso es 3. Después se nos indica la dirección que es 0x0030 y nos indica también que es un registro de datos. Luego tenemos los datos, que como indica el campo de longitud son 3: 0x02, 0x33, 0x7A. Por último, antes del retorno de carro tenemos el checksum.
Para calcular el checksum sumamos todos los bytes: 0x03+0x00+0x30+0x00+0x02+0x33+0x7A. Podemos usar la calculadora para realizar la operación siendo el resultado 0xE2. Como la longitud es solo un byte lo tomamos entero, si tuviera mas bits solo cogiéramos el byte mas bajo. Ahora calculamos el complemento a dos del resultado, siendo este 0x1E y lo añadimos al final.
Una forma de hallar el opuesto de un número binario positivo en complemento a dos es comenzar por la derecha (el dígito menos significativo), copiando el número original (de derecha a izquierda) hasta encontrar el primer 1, después de haber copiado el 1, se niegan (complementan) los dígitos restantes (es decir , copia un 0 si aparece un 1, o un 1 si aparece un 0).
Otra forma de hacerlo hacer el complemento a 1, invirtiendo todos los bits y sumando 1 al resultado.
En código podemos hacer una OR-exclusiva del número con 0xFF y sumándole 1.