Este sitio utiliza cookies propias y de terceros. Si continúa navegando consideramos que acepta el uso de cookies. OK Más Información.

[Tutorial] Microcontroladores ARM Cortex M en Ubuntu: Parte 1

  • 1 Respuestas
  • 4273 Vistas

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado skwlk

  • *
  • Underc0der
  • Mensajes: 12
  • Actividad:
    0%
  • Reputación 1
    • Ver Perfil
« en: Mayo 21, 2017, 04:41:07 pm »

En la actualidad, al escuchar de proyectos de electrónica lo primero que se nos viene a la mente es la existencia de un Arduino.

A pesar de que arduino es una buena plataforma estilo plug & play les puedo asegurar que llegará el momento en el que quieran evolucionar a microcontroladores o sistemas más potentes.

Ahí es donde aparecen los microcontroladores ARM Cortex M de 32 bits. En si ARM no fabrica los chips, si no que licencia el core y periféricos a terceros, para que ellos sean los encargados de fabricarlos.

De los ARM Cortex M, tenemos 3 principales familias: M0 y M0+, M3 y M4, M7. Además cuentan con una infinidad de tarjetas de desarrollo a precios más asequibles que los mismos arduinos.

Me podrán decir que en los arduinos también hay procesadores ARM Cortex con el Arduino DUE o incluso se podría meter aquí el Teensy, pero si comparamos los precios en canales oficiales, nos encontramos que los arduinos tienen precios más elevados que otras tarjetas de desarrollo con ARM Cortex M.

Si ponemos un ejemplo, por canales oficiales, el arduino pro mini 328 tiene un costo de alrededor de 13 usd más impuestos y envío, pero sólo te ofrece un procesador de 8 bits con 2K en RAM y 32K en FLASH, pero por el mismo precio, no podemos conseguir una serie de tarjetas Nucleo de STMicroelectronics con procesadores ARM Cortex M0 a M4.

Por ejemplo:

Nucleo-L432KC, ARM Cortex M4, 64K SRAM, 256K Flash, Unidad de Punto Flotante e instrucciones DSP.
Nucleo-F303RE, ARM Cortex M4, 64K SRAM, 512K Flash, Unidad de Punto Flotante e instrucciones DSP.

Ahora si nos vamos al rango de precios del Arduino Due (64 usd) ARM Cortex M3 con 96K en SRAM y 512K en FLASH y el teensy 3.6 (34 USD) ARM Cortex M4 256K SRAM y 1M en FLASH, de otro lado contamos con tarjetas como

Nucleo-F746ZG, ARM Cortex M7, 320K SRAM y 1M Flash (por 30 USD)

En definitiva, con estos MCUs podrían ser capaces de enfrentar proyectos más complejos usando sistemas operativos como FreeRTOS y Nuttx.

Instalando las herramientas de desarrollo en Ubuntu

Antes de querer usar un IDE, primero debemos entender como se manejan estos procesadores desde sus bases, por lo que requeriremos instalar 2 componentes esenciales:
  • Toolchain
  • Debuggger

El toolchain, son las herramientas que nos permiten traducir de un lenguaje de programación de alto nivel como lo es C/C++ al lenguaje máquina que es el que entiende el MCU.

El Debugger, son las herramientas que nos permiten analizar el comportamiento del sistema, nos permite verificar registros, variables, colocar breakpoints, de tal forma que podamos resolver problemas de una forma más adecuada que el uso de meras impresiones a consola.

Instalando el Toolchain

Esta es la parte más sencilla del proceso, ya que se mantiene un ppa con las últimas versiones del gcc para sistemas embebidos, por lo cual lo único que tenemos que hacer es abrir una terminal y escribir lo siguiente

Código: [Seleccionar]
sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa
sudo apt-get update
sudo apt-get install gcc-arm-embedded

Al momento, la última versión es la 6.3.1

Instalando el debugger

En los repositorios de ubuntu se encuentra el OpenOCD, pero si se quiere la última versión la tendrán que descargar y compilar. Para comenzar simplemente usaremos el OpenOCD de la distribución que en estos momentos es la versión 0.9

Código: [Seleccionar]
sudo apt-get install openocd
Como la mayorías de las tarjetas de desarrollo de STMicroelectronics cuentan con el programador ST-Link, tenemos otra opción que pueden descargar y compilar una herramienta llamada stlink. Más información de esta herramienta la pueden obtener de https://github.com/texane/stlink y como lo deben de compilar en https://github.com/texane/stlink/blob/master/doc/compiling.md

Para las primeras pruebas, esta última herramienta va a ser usada.

Hola Mundo

Ya que tenemos nuestro toolchain procederemos a realizar el primer hola mundo, que consta en prender y apagar un LED de forma cíclica. Para esas pruebas voy a usar una tarjeta de desarrollo mínima que puede obtener por internet y que cuenta con el STM32F103C8T6.


También nos descargaremos las hojas de datos y manuales de referencia del microcontrolador. En este caso todos esos archivos los podemos conseguir en el siguiente enlace http://www.st.com/en/microcontrollers/stm32f103c8.html

Los documentos que nos interesan son las especificaciones del producto (DS5319), el manual de referencia (RM0008) y los manuales de programación (PM0075 y PM0056) y para finalizar, dependiendo de los periféricos que usemos en nuestros proyectos tendríamos que darle un vistazo a los erratas (ES096).

No es que nos tengamos que leer las 1137 páginas del manual de referencia, pero sirven como un apoyo para resolver dudas puntuales.

El objetivo de este primer ejemplo será entender como movernos por los archivos anteriores.

El primer paso es buscar el mapa de memoria. Una imagen se encuentra en el primer archivo sugerido en la página 34.



Según el mapa de memoría la RAM se ubica en la dirección 0x20000000, la flash en 0x08000000 y los registros de los periféricos se encuentran en 0x40000000 y a partir de esta dirección se encuentran los registros que controlan todos los periféricos de los que vamos a hacer uso.

Así que lo primero que es crear un directorio para nuestro primer ejemplo donde crearemos nuestro main.c

Código: [Seleccionar]
mkdir ex0
touch ex0/main.c

Y añadiremos estas primeras líneas

Código: C
  1. #define FLASH_BASE      0x08000000
  2. #define SRAM_BASE       0x20000000
  3. #define PERIPH_BASE     0x40000000
  4.  

A diferencia de los otros MCUs, en los arm cortex a cada periférico se le tiene que habilitar el reloj, por lo que si queremos usar el el puerto C y el pin 13 de este puerto, primero tenemos que saber a que reloj esta conectado para saber cual es el registro que tenemos que acceder. En la página 11 del mismo archivo tenemos un diagrama de bloques que nos indica cada periférico a cual bus y reloj esta conectados.



De la imagen anterior podemos ver que el puerto C esta conectado al bus APB2.

Hay una serie de registros que controlan en reloj y que inician en la dirección 0x40021000 y es llamado RCC (Reset and Clock Control), y el registro que nos interesa es el que nos permite activar el reloj para el Puerto C. La información de estos registros se encuentran en el manual de referencia (RM0008) en la sección 7





¿Que información obtuvimos? En primera instancia, sabemos que la dirección base de los registros RCC se encuentra en 0x40021000, por lo que el registro en el que estamos interesados para activar el reloj del puerto C se encuentra con un offset de 0x18 bytes de esa dirección base. Además obtenemos la información del bit y el valor que debemos colocar para activarlo.

Lo siguiente que vamos añadir a nuestro main.c es una referencia a la ubicación de este registros

Código: C
  1. #define RCC_BASE        (PERIPH_BASE + 0x21000)
  2. #define RCC_APB2ENR     (*(volatile unsigned long*)(RCC_BASE + 0x18))
  3.  

Técnicamente ya estaríamos preparados para activar el reloj para el puerto C con la siguiente instrucción

Código: C
  1. RCC_APB2ENR |= 1 << 4;
  2.  

Siguiendo el mismo procedimiento, añadimos los registros que se encargan de configurar el puerto C




Código: C
  1. #define GPIOC_BASE     (PERIPH_BASE + 0x11000)
  2. #define GPIOC_CRL       (*(volatile unsigned long*)(GPIOC_BASE + 0x00))
  3. #define GPIOC_CRH       (*(volatile unsigned long*)(GPIOC_BASE + 0x04))
  4. #define GPIOC_BSRR     (*(volatile unsigned long*)(GPIOC_BASE + 0x10))
  5. #define GPIOC_BRR       (*(volatile unsigned long*)(GPIOC_BASE + 0x14))
  6.  

En GPIOC_CRL y GPIOC_CRH se configuran la función de cada uno de los pines. En la tarjeta de desarrollo tenemos un LED en el pin 13 del puerto C, por lo que lo tenemos que configurar como salida pp (push pull) escribiendo 0b0011 en el registro correspondiente

Código: C
  1. GPIOC_CRH |= 0x3 << (LED_PIN - 8);  // Output at 50Mhz max
  2. GPIOC_CRH &= ~( (0x3 << (LED_PIN -8)) << 2 ); // Push Pull
  3.  

El registro GPIOC_BSRR y GPIOC_BRR es usando para setear o reasetear cualquiera de los pines del puerto

Código: C
  1. GPIOC_BSRR |= 1 << LED_PIN; Bit Set
  2. GPIOC_BRR  |= 1 << LED_PIN; Bit Reset
  3.  

Colocando todo junto tenemos el siguiente código (no se preocupen por el código extra en la próxima entrega lo explicaré)

Código: C
  1. /* memory and peripheral start addresses */
  2. #define FLASH_BASE      0x08000000
  3. #define SRAM_BASE       0x20000000
  4. #define PERIPH_BASE     0x40000000
  5.  
  6. /* work out end of RAM address as initial stack pointer */
  7. #define SRAM_SIZE       20*1024     // STM32F103c8t6 has 20 Kbye of RAM
  8. #define SRAM_END        (SRAM_BASE + SRAM_SIZE)
  9.  
  10. /* LED connected to PIN 8 of GPIOC */
  11. #define LED_PIN         13
  12.  
  13. /* RCC peripheral addresses applicable to GPIOC */
  14. #define RCC_BASE        (PERIPH_BASE + 0x21000)
  15. #define RCC_APB2ENR     (*(volatile unsigned long*)(RCC_BASE + 0x18))
  16.  
  17. /* GPIOC peripheral addresses */
  18. #define GPIOC_BASE      (PERIPH_BASE + 0x11000)
  19. #define GPIOC_CRL       (*(volatile unsigned long*)(GPIOC_BASE + 0x00))
  20. #define GPIOC_CRH       (*(volatile unsigned long*)(GPIOC_BASE + 0x04))
  21. #define GPIOC_BSRR      (*(volatile unsigned long*)(GPIOC_BASE + 0x10))
  22. #define GPIOC_BRR       (*(volatile unsigned long*)(GPIOC_BASE + 0x14))
  23.  
  24. /* user functions */
  25. int main(void);
  26. void delay(unsigned long count);
  27.  
  28. /* vector table */
  29. unsigned long *vector_table[] __attribute__((section(".vector_table"))) =
  30. {
  31.     (unsigned long *)SRAM_END,   // initial stack pointer
  32.     (unsigned long *)main        // main as Reset_Handler
  33. };
  34.  
  35. int main()
  36. {
  37.     /* enable clock on GPIOC peripheral */
  38.     RCC_APB2ENR |= 1 << 4;
  39.      
  40.     /* set LED pin output mode */
  41.  
  42.     GPIOC_CRH |= 0x3 << (LED_PIN - 8);  // Output at 50Mhz max or High Speed
  43.     GPIOC_CRH &= ~( (0x3 << (LED_PIN -8)) << 2 ); // Push Pull
  44.  
  45.     while(1)
  46.     {
  47.         GPIOC_BSRR |= 1<<LED_PIN;  // set LED pin high
  48.         delay(360000);
  49.         GPIOC_BRR  |= 1<<LED_PIN;  // set LED pin low
  50.         delay(360000);
  51.     }
  52. }
  53.  
  54. void delay(unsigned long count)
  55. {
  56.     while(count--);
  57. }
  58.  
  59.  
  60.  

Lo que resta es crear el liker script y el Makefile para compilar nuestro proyecto

Linker Script (stm32_minimal.ld)

Código: C
  1. /* memory layout for an STM32F103C8T6 */
  2. MEMORY
  3. {
  4.     FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 64K
  5.     SRAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 20K
  6. }
  7.  
  8. /* output sections */
  9. SECTIONS
  10. {
  11.     /* program code into FLASH */
  12.     .text :
  13.     {
  14.         *(.vector_table)    /* Vector table */
  15.         *(.text)            /* Program code */
  16.     } >FLASH
  17.  
  18.     /* uninitialized global and static variables (which
  19.        we don't have any in this example) into SRAM */
  20.     .data :
  21.     {
  22.         *(.data)            
  23.     } >SRAM
  24. }  
  25.  

Makefile

Código: [Seleccionar]
CC = arm-none-eabi-gcc
LD = arm-none-eabi-ld
CP = arm-none-eabi-objcopy
 
LKR_SCRIPT = stm32_minimal.ld
 
CFLAGS  = -c -fno-common -O0 -g -mcpu=cortex-m3 -mthumb
LFLAGS  = -nostartfiles -T$(LKR_SCRIPT)
CPFLAGS = -Obinary
 
all: main.bin bootloader.hex
 
main.o: main.c
$(CC) $(CFLAGS) -o main.o main.c
 
main.elf: main.o
$(LD) $(LFLAGS) -o main.elf main.o
 
main.bin: main.elf
$(CP) $(CPFLAGS) main.elf main.bin

flash:
st-flash --reset write main.bin 0x8000000

clean:
rm -rf *.o *.elf *.bin


Después de crear estos archivos en una terminal, localizados en nuestro proyecto ejecutamos

Código: [Seleccionar]
make

Si todo ha ido bien, lo que veremos a la salida es

Código: [Seleccionar]
ex0$ make
arm-none-eabi-gcc -c -fno-common -O0 -g -mcpu=cortex-m3 -mthumb -o main.o main.c
arm-none-eabi-ld -nostartfiles -Tstm32_minimal.ld -o main.elf main.o
arm-none-eabi-objcopy -Obinary main.elf main.bin

Si no hubo error, conectamos nuestra tarjeta al programador st-link, lo conectamos a la PC y ejecutamos

Código: [Seleccionar]
make flash

Código: [Seleccionar]
ex0$ make flash
st-flash --reset write main.bin 0x8000000
2017-05-21T14:19:17 INFO src/common.c: Loading device parameters....
2017-05-21T14:19:17 INFO src/common.c: Device connected is: F1 Medium-density device, id 0x20036410
2017-05-21T14:19:17 INFO src/common.c: SRAM size: 0x5000 bytes (20 KiB), Flash: 0x10000 bytes (64 KiB) in pages of 1024 bytes
2017-05-21T14:19:17 INFO src/common.c: Attempting to write 140 (0x8c) bytes to stm32 address: 134217728 (0x8000000)
Flash page at addr: 0x08000000 erased
2017-05-21T14:19:17 INFO src/common.c: Finished erasing 1 pages of 1024 (0x400) bytes
2017-05-21T14:19:17 INFO src/common.c: Starting Flash write for VL/F0/F3 core id
2017-05-21T14:19:17 INFO src/flash_loader.c: Successfully loaded flash loader in sram
  0/0 pages written
2017-05-21T14:19:17 INFO src/common.c: Starting verification of write complete
2017-05-21T14:19:17 INFO src/common.c: Flash written and verified! jolly good!

Con lo que debemos ver nuestro LED parpadear.


En la siguiente entrega explicaré como usar el debugger (OpenOCD) con nuestro programa de ejemplo.
« Última modificación: Mayo 22, 2017, 05:50:09 am por Gabriela »

Desconectado skwlk

  • *
  • Underc0der
  • Mensajes: 12
  • Actividad:
    0%
  • Reputación 1
    • Ver Perfil
« Respuesta #1 en: Junio 02, 2017, 10:07:48 pm »

La principal dificultad que existe a la hora de debuggear sistemas embebidos, es el hecho de que el destino de nuestro programa no se encuentra en la misma máquina donde realizamos la compilación.

Existen varias alternativas para realizar el debug. Cuando programamos para el escritorio la primera forma y la más sencilla para realizar el debug es por medio de impresiones a pantalla con instrucciones printf. Es la más sencilla pero también la más intrusiva.

En los sistemas embebidos, esto es totalmente distinto, porque de alguna forma le tenemos que comunicar eso que queremos mostrar a nuestra PC de desarrollo.

Aunque parezca fácil, necesitamos crear una infraestructura para soportar el envío de esa información a la PC, y podría llegar a ser demasiado intrusiva y consumir recursos innecesarios. Para ello los ARM Cortex ofrecen varias formas de realizar el debug, siendo los más comunes el JTAG y el SWD, soportados, ambos, por OpenOCD, el cual fue instalado siguiendo las instrucciones en la parte primera de este tutorial.

En esta primera entrega nos enfocaremos en como iniciar el proceso usando OpenOCD.

OpenOCD

Para ejecutar openOCD, es necesario pasarle como parámetro un archivo de configuración, por lo general estos archivos de configuración los encontramos dentro de /usr/local/share/opencd/scripts/boards, aunque no siempre vamos a encontrar la configuración de nuestra tarjeta, por lo que tenemos que copiar y modificar una existente.

Por ejemplo, para la tarjeta que estoy usando de ejemplo más el st-link v2, ese archivo de configuración quedaría de la siguiente forma.

Código: [Seleccionar]
# This is for using with STLINK/V2

source [find interface/stlink-v2.cfg]

# increase working area to 8KB
set WORKAREASIZE 0x2000

# chip name
set CHIPNAME STM32F103C8T6

source [find target/stm32f1x_stlink.cfg]

# use hardware reset, connect under reset
reset_config srst_only srst_nogate

Colocamos este archivo dentro del directorio del proyecto

Código: [Seleccionar]
ex0$ ls *.cfg
stm32f103c8t6.cfg

Nota: En caso de que la tarjeta que tengamos este soportada, simplemente apuntamos openocd al lugar indicado.

En este momento ya tenemos preparado todo lo necesario para realizar nuestro primera sesión de debug, para este paso tenemos que abrir 3 terminales en el directorio del proyecto.

En la primera terminal ejecutamos

Código: [Seleccionar]
openocd -f archiv.cfg

O en mi caso particular

Código: [Seleccionar]
openocd -f stm32f103c8t6.cfg

Si todo ha ido bien veremos algo como

Código: [Seleccionar]
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v17 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.205222
Info : STM32F103C8T6.cpu: hardware has 6 breakpoints, 4 watchpoints

Uno de los aspectos que nos informa del MCU es la cantidad de breakpoints disponibles en el hardware.

Cuando veamos esa información, abrimos otra consola y ejecutamos

Código: [Seleccionar]
telnet localhost 4444

Salida

Código: [Seleccionar]
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
>

En estos momentos ya estamos preparados para tomar el control del MCU por medio de un reset halt

Código: [Seleccionar]
> reset halt
timed out while waiting for target halted
TARGET: STM32F103C8T6.cpu - Not halted
in procedure 'reset'
in procedure 'ocd_bouncer'


>


En la tarjeta presionamos el boton de reset

Código: [Seleccionar]
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000008 msp: 0x20005000
>


Con esto ya tenemos el control total del MCU y podemos observar el contenido de los registros del sistema con reg

Código: [Seleccionar]
> reg
===== arm v7m registers
(0) r0 (/32): 0x00057E40
(1) r1 (/32): 0xFFFDFFFF
(2) r2 (/32): 0x000562B1
(3) r3 (/32): 0x000562B2
(4) r4 (/32): 0xFFFFFFFF
(5) r5 (/32): 0x7FFFFFFE
(6) r6 (/32): 0xB5EFB7AA
(7) r7 (/32): 0x20004FE8
(8) r8 (/32): 0xFFFE75DC
(9) r9 (/32): 0xFFFFFFDF
(10) r10 (/32): 0x2A7BF9DD
(11) r11 (/32): 0xA63F5073
(12) r12 (/32): 0xFFFFFFFF
(13) sp (/32): 0x20005000
(14) lr (/32): 0xFFFFFFFF
(15) pc (/32): 0x08000008
(16) xPSR (/32): 0x01000000
(17) msp (/32): 0x20005000
(18) psp (/32): 0x6AFF7268
(19) primask (/1): 0x00
(20) basepri (/8): 0x00
(21) faultmask (/1): 0x00
(22) control (/2): 0x00
===== Cortex-M DWT registers
(23) dwt_ctrl (/32)
(24) dwt_cyccnt (/32)
(25) dwt_0_comp (/32)
(26) dwt_0_mask (/4)
(27) dwt_0_function (/32)
(28) dwt_1_comp (/32)
(29) dwt_1_mask (/4)
(30) dwt_1_function (/32)
(31) dwt_2_comp (/32)
(32) dwt_2_mask (/4)
(33) dwt_2_function (/32)
(34) dwt_3_comp (/32)
(35) dwt_3_mask (/4)
(36) dwt_3_function (/32)

Con help, obtenemos una lista detallada de los comandos disponibles

Enseguida abrimos una tercera terminal para ejecutar arm-none-eabi-gdb

Código: [Seleccionar]
arm-none-eabi-gdb main.elf

Y nos enlazamos con el target remoto

Código: [Seleccionar]
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
main () at main.c:36
36 {

Aunque probablemente quieran usar una GUI, por lo que tenemos varias alternativas, una de ellas es ddd, que está incluida en los repositorios de ubuntu y en lugar de

Código: [Seleccionar]
arm-none-eabi-gdb main.elf

Hacemos un

Código: [Seleccionar]
ddd --eval-command="target remote localhost:3333" --debugger arm-none-eabi-gdb  main.elf


En la siguiente entrega mostraré como imprimir secciones de memoria del MCU al igual que variables.

 

¿Te gustó el post? COMPARTILO!



[Tutorial] Cómo reparar una memoria USB

Iniciado por Stiuvert

Respuestas: 1
Vistas: 4869
Último mensaje Marzo 19, 2019, 05:43:36 am
por alexcv
Recopilacion de manuales, revistas y programas [MICROCONTROLADORES PIC]

Iniciado por BlindOwl

Respuestas: 7
Vistas: 7770
Último mensaje Agosto 14, 2019, 10:13:05 pm
por ANTRAX