2021/05/26

El gusanito revive en un trabajo práctico: Parte 2: el modelo choca con la realidad.

Habiendo fracasado rotúndamente en usar inputattach en la "Parte 1: inputattach" que ni siquiera he podido publicar, he aceptado rebajarme a usar USBMouse.

 

Esta placa es de 3.3 v "tolerante a 5v" en los pines marcados como FT en la hoja de datos, o sea, prácticamente todos. Siendo que el encoder usa 5v, me facilita la vida, la parte analógica siempre me asusta.


Como en otras tantas ocasiones, lo que voy a contar no es la crónica exacta de lo que transité ni necesariamente respetaré el orden de lo que hice, pues te aseguro que te resultaría completamente incomprensible. En su lugar voy a hacer una especie de collage significativo.

El la introducción hay más contexto.

 

Conectar un botón y que se comporte como botón izquierdo


Quizás un poco contrario al Diseño Emergente, como ya tenía una idea intuitiva de hacia donde pensaba ir, no hice la implementación más sencilla que funcione, si no que medio que me anticipé.

Lo que hice fue usar una InterruptIn y consideré el rebote mediante un Timeout, tal que si llega una transición, espere un ratito y compruebe un momento despues en qué estado quedo y en caso de corresponder recién ahí actúe.

Como actuar es pedirle al Mouse press() o release(), eso no lo puedo invocar en el Callback de InterruptIn o Timeout pues esos métodos probablemente usan un mutex, cosa que no se puede hacer en un Interrupt Handler y además no es buena práctica ejecutar mucho código durante una interrupción, se lo doy a una EventQueue y en algún momento lo hace y aparentemente funcionó muy bien.



setup
setup


 


click
click

 

Estos diagramas están en el repo en la carpeta doc, necesitás astah para verlos.

Dicho de otra manera, tenemos una clase genérica Debouncer<> que recibe un objeto y dos de sus métodos para rise() y fall(). Cuando Debouncer recibe por ejempo un rise(),  le pide a Timeout que llame a un método propio checkRise() que verifica que esté risen y encola una llamada a otro método propio notifyRise() que va eventualmente a llamar al método asociado al objeto provisto.

 

Conectar el encoder rotativo

 

Un encoder rotativo tiene dos pines, que van diciendo 00->01->11->10 si lo girás para un lado y lo inverso si es para el otro, usando un Gray Coding. Si estuviste prestando atención, no podés dejar de notar que es como si estuvieses haciendo clicks en dos botones, asi que si asocio dos InterruptIn a un mismo objeto pero con cuatro métodos, en teoría debería funcionar.

Fijate, esta es la instanciación de un click izquierdo asociado al botón azul de la placa:


Debouncer<MouseClick> leftButtonDebouncer(
   BUTTON1,
   &queue,
   &leftButton,
   &MouseClick::press,
   &MouseClick::release
);


y esto para el Encoder. objservá que es el mismo objeto encoder:

Debouncer<Encoder> clkEncoderDebouncer(
   PC_9,
   &queue,
   &encoder,
   &Encoder::clkUp,
   &Encoder::clkDown
);
Debouncer<Encoder> dtEncoderDebouncer(
   PC_8,
   &queue,
   &encoder,
   &Encoder::dtUp,
   &Encoder::dtDown
);

De hecho, funciona bastante bien usando una leve adaptación de la lógica del ejemplo de uso de USBMouse (en ./mbed-os/drivers/USBMouse.h), pero...

 

Muy excéntrico e irreversible
Muy excéntrico e irreversible

En negro es el giro para un lado, ponele que es aceptable, pero cuando invertís, se destroza. Esto tiene que ver con pequeños errores que debería tratar con un conocimiento intermedio de trigonometría y análisis matemático que me falta, así que lo voy a resolver mediante ingenio. Además, no me quiero anticipar pues prometí que mi camino iba a ser instructivo a costa de la verácidad, hay un error con el tratamiento de los tiempos y los estados de las interrupciones, luego veremos.

Va mi primera POC, qué pasa si en lugar de calcular posiciones según los ángulos, dado que el encoder tiene 30 posiciones precalculo los desplazamientos. Comenzaré con sólo cuatro y girando siempre para el mismo lado.

 

cuatro puntos
cuatro puntos

Ahora para una lado y para el otro, para pescarle la lógica.

 

ambos sentidos de giro
ambos sentidos de giro

En amarillo en el código la modificación para invertir el sentido, en el dibujo un leve movimiento del cursor del mouse para evidenciar que es correcto.

Ahora resta precalcular para 30 puntos.


30 pasos ambos sentidos
30 pasos ambos sentidos

Ese leve desplazamiento a la derecha es para diferenciar un sentido del otro.


30 pasos ambos sentidos
30 pasos ambos sentidos

Ya sé, ya se que le falta un montón de optimizaciones como calcular un solo cuadrante y usarlo de modo espejado, pero la verdad es que a esta placa le sobran bytes y ciclos de reloj, va a quedar así.

Aparte, el STEPS en realidad es 60, pues se generan dos interrupciones por paso.

 

Links útiles


https://os.mbed.com/blog/entry/Simplify-your-code-with-mbed-events/

El código está versionado y corresponde al commit "math fixed, still excentric"

2021/05/17

mbed.os desde línea de comando

Pasos para compilar y debuggear un proyecto mbed desde línea de comando.

 

En el marco de la cursada y el trabajo práctico de la materia Introducción a los Sistemas Embebidos del CEIoT y apoyándome en un documento privado de Sergio Burgos que explica los caminitos para eclipse y STM32CubeIDE, estas son las instrucciones para compilar y debuggear la placa desde línea de comando.

 

¿Qué es mbed os? 

 

mbed os es un sistema operativo de tiempo real para procesadores Cortex-M, que tiene un simpático entorno web de programación muy completito. Lo estoy usando en esa materia para programar una placa NUCLEO-F429ZI de ST con un montón de periféricos, seguí el link si te interesa el detalle.

 

Compilar con gcc

 

Desde tu entorno de mbed exportá el proyecto con toolchain Make-GCC-ARM:


Exportar...
Exportar...

...como Eclipse-GCC-ARM
...como Eclipse-GCC-ARM


Mirá que un blinky pesa 37 MB... ok, esto no es un bare metal, es un sistema operativo entero.

Te deja un comprimido, descomprimilo...

Tomá el Makefile y comentá las siguientes líneas:

ASM_FLAGS += -include$
ASM_FLAGS += /filer/workspace_data/exports/...


o ejecutá el siguiente comando:

sed Makefile -i -e 's/^ASM_FLAGS += -include$//' \
-e 's/ASM_FLAGS += \/filer\/workspace_data\/exports\/.*//'


Instalá con el gestor de paquetes de tu distro o bajate el compilador y openocd.


Si no instalaste con el gestor de tu distribución, buscá donde está dejaste el compilador y agregá su carpeta bin al PATH, por ejemplo para el compilador:

export PATH=$PATH:/home/$USER/bin/gcc-arm-none-eabi-9-2020-q2-update/bin/


Ejecutá make, si te aparece un error como este, probablemente sea por tener un gnu-arm viejo, volvé dos casilleros para atrás:

../mbed-os/platform/cxxsupport/mstd_type_traits:1123:12: error: 'std::is_trivially_copyable' has not been declared  using std::is_trivially_copyable;

Me ocurrió con gcc-arm-none-eabi-4_9-2015q1.

Un ratito despues, varios minutos si es la primera vez, tenés una carpeta BUILD de 55 MB con el resultado,

link: Blinky.elf
arm-none-eabi-objcopy -O binary Blinky.elf Blinky.bin
===== bin file ready to flash: BUILD/Blinky.bin =====
arm-none-eabi-objcopy -O ihex Blinky.elf Blinky.hex



Nos interesa el .bin para grabar en la placa mediante arrastrar a la carpeta que se abre cuando conectás la placa y el .elf para debug.

cp BUILD/Blinky.bin "/media/$USER/NODE_F429ZI"

Si empezás a agregar archivos, vas a tener que tocar el Makefile. Por ejemplo si tuvieras una carpeta con módulos y en esta uno nuevo llamado "alarm":

OBJECTS += modules/alarm/alarm.o

INCLUDE_PATHS += -I../modules/alarm


y opcionalmente

ASM_FLAGS += -I../modules/alarm

 

debug con openocd y gdb: el camino difícil

 

Creá el archivo de configuración en la raiz del proyecto:

cat << EOF > stlink.cfg
source [find interface/stlink.cfg]
source [find target/stm32f4x.cfg]
reset_config srst_only srst_nogate
EOF


Y hasta aquí llegué con el documento. De openocd no sé nada, pero intuyo que si le doy a ese archivo de configuración y lo mismo que eclipse hace por defaut, debería funcionar.

El problema es que eclipse usa pyocd, que aparte de parecer el nombre de un antipiojos, debe hacer lo mismo que openocd.

Como configuración de referencia tengo la del firmware de la edu-ciaa-nxp https://github.com/ciaa/firmware_v3, arrancando por el Makefile:

TARGET=$(OUT)/$(PROGRAM_NAME).elf
OOCD_SCRIPT=scripts/openocd/q

# DEBUG with Embedded IDE (debug)
.debug:
        @echo DEBUG
        $(Q)$(OOCD) -f $(OOCD_SCRIPT) 2>&1

# DEBUG with Embedded IDE (run)
.run: $(TARGET)
        $(Q)$(OOCD) -f $(OOCD_SCRIPT) &
        $(Q)socketwaiter :3333 && arm-none-eabi-gdb -batch $(TARGET) -x scripts/openocd/gdbinit


Siendo el contenido de gdbinit:


target remote :3333
mon arm semihosting enable
mon reset halt
load
continue
quit


Me intriga mucho el socketwaiter

socketwaiter
socketwaiter



La otra es la de la carpeta eclipse-extras, en particular los archivos:

NUCLEO_F429ZI_Blinky_debug.launch
NUCLEO_F429ZI_Blinky_debug_pyocd_settings.launch

que son muy muy parecidos si los mirás con kdiff3.


Tendría que hacer el mapeo entre los valores de eclipse y la sintaxis de la ciaa. O mejor, aprender a usar openocd, una de las mil tareas pendientes que tengo.

¿Por qué, Charli, no te instalás el eclipse, usas su debugger y te dejás de joder? Es que pregunté en un chat si alguien tenía solucionado esto y mientra me llega la respuesta, o me pongo a investigar o me voy a ver la tele y a comer de modo indebido.

Podría instalarme el eclipse y terminar de ver como se manifiestan esos valores de los archivos o incluso ver si me tira pistas en la consola.


¿Por qué, Charli, no buscás la documentación de mbed a ver que dice respecto a openocd? Buena idea, dice, adaptando a mis ubicaciones:

openocd \
  -f /home/carlos/bin/openocd-0.10.0/tcl/board/stm32f429discovery.cfg \
  -f /home/carlos/bin/openocd-0.10.0/tcl/interface/stlink-v2-1.cfg \
  -c init -c "reset init"



se puso a titilar led de programación y además en la terminal apareció lo que debía, vamos bien. Le digo cual es la imagen y le pongo un par de breakpoints:

(gdb) file BUILD/slitherUSBMouse.elf
Reading symbols from BUILD/slitherUSBMouse.elf...done.

(gdb) break Debouncer::checkFall
Breakpoint 2 at 0x800c394: file ../classes/Debouncer.cpp, line 27.
(gdb) break Debouncer::fall
Breakpoint 3 at 0x800c438: file ../classes/Debouncer.cpp, line 44.


y ahora? run?

Don't know how to run.

Revisando como se hace con edu-ciaa-nxp:

(gdb) target remote localhost:3333
(gdb) file BUILD/slitherUSBMouse.elf
(gdb) continue

 

Breakpoint 1, Debouncer::rise (this=0x20006698 <_main_stack+2672>)
at ../classes/Debouncer.cpp:37
37       if (! rising) {

Funciona, recordá que había puesto unos breakpoints, ahora a pelear con el programa. 

Me falta explorar algunas opciones que ví por ahí y no presté atención, como:


  • -c "set mem inaccesible-by-default off"
  • La posibilidad de incorporar las opciones a algún archivo.
  • Meter todo en el Makefile poder hacer make debug, lo cual resolvería el nombre de programa automáticamente.

 

debug con openocd y gdb: el resultado fácil


Ok, necesitás lo resultados y no cómo transitar el proceso:

openocd \
  -f $OPENOCD_PATH/tcl/board/stm32f429discovery.cfg \
  -f $OPENOCD_PATH/tcl/interface/stlink-v2-1.cfg \
  -c init -c "reset init"

En otra ventana:

arm-none-eabi-gdb

(gdb) target remote localhost:3333

(gdb) file BUILD/prog.elf

(gdb) breakpoint class::method

(gdb) continue