Categorías
Hardware

El ladrillo que se convierte en un LED que brilla (parte 3)

En el los dos posts anteriores hemos preparado las herramientas que vamos a necesitar para conseguir hacer parpadear un LED.

Si acabas de incorporarte sin haber leido los posts anteriores y estas pensando «¿Pero cuantos posts necesita este fulano para enceder un puñetero LED?» Te recomiendo que empieces desde el principio. AQUI

HORA DE PISAR TECLAS

Y ahora que ya tenemos todos los «papeles» e información necesaria vamos a empezar a ver código.

Pero antes, una cosa más.

SETUP

Te voy a contar el setup que voy a utilizar para escribir y compilar todo nuestro código.

SO: *Linux Mint 19.2 Tina

IDE: Visual Studio Code (He vivido toda mi vida odiando a Microsoft, pero joder, este IDE es la hostia)

Compilador: arm-none-eabi-gcc-10.3.1 (GNU Arm Embedded Toolchain 10.3-2021.10) 10.3.1 20210824

Debugger: Segger J-Link EDU**

SW Debugger: JLink 7.6.0 (paquete debian aquí)

Nordic SDK: nRF5_SDK_17.1.0

*Nota 1: Uso Linux porque soy así, me gusta. Estoy casi seguro (no al 100% porque no pienso probarlo) que podrías instalar todas estas herramientas en Windows y obtener el mismo resultado.
Puede que si usas el WLS(Windows Linux Subsystem) puedas replicar todos los pasos que voy a realizar como si estuvieras en una máquina Linux nativa (pero tampoco lo voy a hacer).

**Nota 2: El debugger SEGGER EDU es solo de uso educativo. Es decir, para cacharrear en casa o usarlo en centros educativos.
Si usas este debugger (que vale 60€ en lugar de los 400€ de la versión comercial) para hacer cosas profesionales (es decir, cobrando por ellas), aparte de hacer llorar al niño jesus, puedes meterte en problemas legales. Avisado quedas!!

UN POQUITO DE ATAJO

En algún momento de esta serie de post te he dicho que ibamos a hacer parpadear un LED desde cero.

Pues soy un sucio mentiroso. No va ha ser desde cero, cero.

Voy a utilizar el SDK que provee el fabricante de nuestro SoC.

¿Que es un SDK?

Pues es una serie de librerias y código fuente que nos entrega el fabricante para que a nosotros los desarrolladores nos resulte más sencillo usar su SoC.
Esto no lo hace porque sean como la madre Teresa de Calculta. Es un tema de pasta. Si usar su SoC es más sencillo que usar el de otra marca, pues nosotros, que lo que queremos es acabar nuestro curro rápido para irnos a beber cerveza compraremos sus microcontroladores.

¿Que incluye este SDK y en general cualquier SDK?

Pues ficheros de inicialización de muy bajo nivel (ensamblador), librerias y drivers de los diferentes periféricos que integra el SoC, ejemplos de uso de las diferentes funcionalidades que tiene el SoC, soporte para placas comerciales que la marca vende (una vez más poniendonos facil que nos dejemos la pasta en sus productos)

Entonces, partiendo del SW que hay en el SDK y apoyandome en el esquemático y el manual de referencia voy a adaptar el código de ejemplo de una placa similar a la nuestra (y con similar me refiero a que lleva el mismo SoC nRF52840).

Pero para purgar mi pecado de ser un mentiroso, voy a explicar lo que hacen cada uno de los ficheros que vamos a compilar y lo voy a enlazar con la información del manual y del esquemático. De tal forma que veas el proceso completo de como se hace el SW y como finalmente se ejecuta hasta que veamos el LED parpadear.

ENTENDIENDO EL SDK

Estructura carpetas SDK

Como puedes ver la estructura tiene bastantes carpetas. Pero para no perdernos por el camino, vamos a ir a la carpeta «examples» y desde allí vamos a empezar.

Dentro de la carpeta tenemos varias subcarpetas con exemplo de diferentes funcionalidades que tiene nuestro SoC (Bluetooth, cryptografía, NFC, etc)

Nos vamos a fijar en la carpeta peripherals que a su vez tiene más carpetas con ejemplos de como usar la memoria flash, el I2C, etc.

En ella tenemos una carpeta con un nombre bastante revelador. «Blinky».

Dentro veremos otras cuantas carpetas que corresponden a ejemplos para ser usados en varias de las tarjetas de desarrollo oficiales de Nordic Semiconductor.

En concreto nos interesa la carpeta pca10056. ¿Porque? pues porque esa tarjeta tiene el mismo SoC que nuestra Argon.

¿Y que vamos a hacer a continuación? Facil, lo que cualquier programador haría. Copiar la carpeta renombrandola como «particle_argon».

Yo la he llamado así en un alarde de originalidad, pero seguro que tu eres más imaginativo y le pones algún nombre loco como «ArgonKit», «ArgonBoard» o yo que se. !Que se note que somos gente que vivimos al límite!

CREANDO LOS FICHEROS DE NUESTRA PLACA

Bueno, pues ahora que tenemos nuestra carpeta para nuestra placa «inspirada» en otra similar, vamos a ir cambiando todas las cosas especificas de nuestra placa para llegara tener un impresionante y bonito LED parpadeante.

main.c

Empezamos por el main.

En ese archivo vamos a escribir la funcionalidad básica que queremos.

a) Configurar el pin al que está conectado el LED que queremos hacer parpadear com salida

b) Poner a 0 ese pin

b) Entrar en un bucle donde conmutamos el led cada 250ms

Inicialmente el ejemplo encendia y apagaba todos los leds de la placa en sucesión.

En nuestro caso, como solo queremos hacer parpadear uno, vamos a modificar el main para dejarlo así.

boards.h

En este fichero podemos ver dos partes importantes:

a) Las directivas de compilación condicional (lineas 46-93) que permiten incluir el header correspondiente a nuestra placa en función del define que esté habilitado. En nuestro caso al estar definido el define «BOARD_PARTICLE_ARGON» en el archivo Makefile el fichero que se utilizará será el «particle_argon.h» cuyo contenido vemos a continuación

b) Una serie de funciones genericas para utilizar nuestra placa. Dentro del fichero boards.c pueden verse la implementacion de cada una de estas funciones, aunque debido a lo minimalista de nuestro main no utilizamos ninguna de ellas.

particle_argon.h

En este archivo se definen las patillas asociadas a cada funcionalidad dentro de nuestra placa.

¿Os acordais del esquemático que conseguimos en el primer post?

Pues vamos a empezar a utilizarlo aquí.

Como puedes ver los pines donde están conectados los 3 LEDs RGB son los pines 13, 14 y 15 del puerto 0 y el LED de usuario al pin 12 del Puerto 1.

Pues bien, partiendo del fichero de configuración de la placa pca10056.h vemos que los LEDS se definen utilizando una macro que se llama NRF_GPIO_PIN_MAP la cual recibe dos parámetros. El número de puerto y el número de pin.

Esta claro que lo que tenemos que hacer el nuestro fichero particle_argon.h es definir los 4 leds que tiene nuestra placa de forma análoga. Y para ello utilizamos la información del esquemático.

Y antes de que te lo preguntes, vamos a ver que hace exactamente esa misteriosa macro NRF_GPIO_PIN_MAP(port, pin) y vamos a relacionarlo con el manual de referencia del SoC

Lo que hace es desplazar 5 posiciones a la izquierda el valor del campo «port» y luego hace una mascara del valor del campo pin y el valor hexadecimal «0x1F»

Basicamente está haciendo lo siguiente

port = 0b00000001 << 5

0b00100000

pin = 0b00001100 &

0b000FFFFF

value = 00100000 |

00001100

______________________________

00101100

Este valor es el que codifica el puerto y el número de pin

Ese valor es el que vamos a pasar a la función nrf_gpio_cfg_output que internamente llama a la función nrf_gpio_cfg

En esta función pasan dos cosas

a) Se obtiene la dirección de memoria donde están los registros para configurar esa patilla de ese puerto en concreto

b) Se setean los campos de ese registro para configurar la patilla como salida

Si vemos el apartado 6.9.2.10 podemos ver que sinifica cada uno de los 32 bits que conforman el registro.

*En el «code snippet» que ves encima están unificadas todas las funciones y defines relevantes para configurar el pin del LED correctamente. En el código real los valores están repartidos en 3 ficheros. nrf_gpio.h, nrfx.h y nrf52840_bitfields.h

Como vemos el puntero a la estructura NRF_GPIO_Type se le asigna la dirección 0x50000300 (linea 46 del code snippet)

Si miramos el manual vemos que en esa dirección tenemos los registros para manejar el puerto GPIO 1.

Y si comparamos la estructura NRF_GPIO_Type con los registros que aparecen en el manual de referencia vemos que coinciden.

Esta operación es muy común en sistemas embebidos y es definir una estructura que «mapea» una serie de registros en nuestro SoC.

Después definimos un puntero a ese tipo de estructura y le asignamos el valor de la dirección de memoria donde comienzan esos registros.

De esta forma cuando accedemos a cada uno de esos campos en nuestro puntero estamos escribiendo/leyendo en esos registros.

Makefile

El archivo Makefile contiene unas cuantas consas importantes que comentar.

En primer lugar los ficheros fuente que se van a compilar

Son solo 3. En el ejemplo de base habia unos cuantos más pero me he propuesto hacer algo «minimalista».

a) main.c

Nuestro querido main, que nunca puede faltar en un buen programa en C. Ya sabes lo que hace, porque lo hemos comentado previamente

b) system_nrf52840.c

Que en realidad es el fichero system_nrf52.c puesto que el fichero system_nrf52840.c solo tiene un include a system_nrf52.c.

Esta función basicamente implementa la función SystemInit y es la encargada de las inicializaciones del microcontrolador

– Reloj de sistema

– Configurar algunos perifericos de una forma específica para evitar ciertos fallos de fabricacion del microcontrolador*

c) gcc_startup_nrf52840.S

Este fichero es muy importante, porque es el encargado de realizar las operaciones de más bajo nivel y más especificas de la arquitectura.

Estas son
– Definir la tabla de vectores de interrupción

– Definir la dirección de memoria del stack

– Definir la función Reset_Handler que será la encargada de:

– Copiar las variables de la memoria flash a la memoria RAM

– Las variables inicializadas a cero al segmento .bss

– Las variables inicializadas (a un valor distinto de cero) al segmento .data

– Saltar a la función SystemInit de la que hemos hablado hace un momento.

– Saltar a la función _mainCRTStartup que es una función que provee la toolchain dentro del fichero crt0.o** y que realiza las inicializaciones necesarias para tener un entorno de ejecucción para programas en C.

– Saltar a nuestra función main y comenzar a hacer parpadear nuestro LED

*Esto es bastante común y es conocido como «Errata». A veces los microcontroladores no se comportan como dice su manual de referencia. Esto, tarde o temprano es descubierto por algún usuario. La marca lo analiza y pueden pasar dos cosas.

a) Que el error sea simplemente del documento, y se edita una nueva versión del mismo corregido.

b)Puede que por un error de diseño hw el microcontrolador tiene un funcionamiento erroneo que se puede «arreglar» configurandolo de una forma especifica.

**CRT es el acrónimo de C Run Time. El fichero crt0.o, suele lincarse automágicamente por las toolchains y dentro se implementan las operaciones básicas que necesita un «main» de C para funcionar correctamente. Que resumiendo mucho son: Setear las variables no inicializadas (e inicializadas con valor cero) a cero y setear las variables que han sido seteadas con cualquier otro valor con su valor asignado. Y tras esas inicializaciones saltar a la funcion main.

OTROS ARCHIVOS IMPORTANTES

Y para terminar el recorrido por los archivos «oscuros» y «tenebrosos» llegamos al fichero blinky_gcc_nrf52.ld que es el fichero que utilizará nuestro linkador para formar nuestro binario.

Internamente el fichero blinky_gcc_nrf52.ld llama al fichero nrf_common.ld

Sin entrar en demasiados detalles, porque la sintaxis de los ficheros de lincado daría para una serie completa de posts, estos ficheros le dicen al linkador en que direcciones de memoria y en que orden colocar todos los objetos generados durante la compilación (ficheros .o)

GRABANDO NUESTRO BINARIO

Dentro de nuestro directorio _build vemos que se generan varios archivos al compilar.

A destacar

a) particle_argon.out -> fichero de salida en formato elf -> podemos desemsamblarlo para ver las operaciones que realiza en enesamblador

b) particle_argon.hex -> Fichero binario en formato hex (Para más info sobre el formato hex AQUI)

c) particle_argon.bin -> Fichero binario en formato binario (valga la redundancia) Este fichero grabado tal cual en nuestra memoria flash contiene todas las instrucciones en código máquina a ejecutar por nuestro SoC.

Para grabar nuestra tarjeta vamos a utilizar el debugger JFlash-EDU junto con el software JFlashLite

J-Flash Lite

Los pasos son realmente sencillos

a) Elegir el fichero a grabar

b) Borrar la tarjeta para asegurarnos de que grabamos correctamente

c) Pulsar en program

Si todo ha salido bién, deberíamos estar viendo a nuestro LED parpadear

Para muestra un botón

CODIGO EJEMPLO

El código que he utilizado puedes encontrarlo en mi repositorio de github

AQUI

DESPEDIDA Y CIERRE

Ha sido un camino largo y tortuoso, que espero te haya gustado y en el que hayas aprendido algunas cosas sobre el oscuro pero apasionante mundo del desarrollo de firmware.

El próximo post no se de que será, pero intentaré que sea un poco mejor y sobre todo con menos faltas de otrografía.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.