Envío de mensajes del núcleo secundario (Arduino) al principal (Python) en la milkV DUO
Habíamos visto como enviar mensajes o comandos del núcleo principal al secundario utilizando Mailbox en la milkV DUO pero faltaba el proceso inverso es decir enviar datos desde el procesador secundario al principal, en este artículo te muestro los problemas que tuve y que método utilice para solucionarlos.
SISTEMAS EMBEBIDOS
Biblioman
10/1/20245 min leer
Introducción
En la página oficial de milkV DUO viene un ejemplo escrito en C para comunicar el núcleo principal de la DUO con el núcleo secundario donde corre una aplicación de arduino. Aquí tenéis los apuntes de esa primera toma de contacto que tuve con la comunicación entre núcleos a través del módulo de buzón de correo (Mailbox) y la DUO, la aplicación facilitada es suficiente para comprender y escribir tus propios programa para enviar datos o comandos desde el núcleo principal al núcleo secundario. Pero surgen las siguientes preguntas ¿como hago para enviar datos desde el procesador secundario al principal? ¿y si en vez de utilizar C en el procesador principal quiero utilizar Python?. No encontrareis respuestas a estas preguntas ni en la página oficial del fabricante ni preguntando directamente en el foro de la DUO, por lo menos a fecha de la publicación de estos apuntes.
El problema se reduce a que no está disponible de manera completa el código fuente de la librería ni de los ejemplos, si buscáis los archivos mailbox.h y mailbox.cpp del ejemplo de Arduino veréis que no están ni en las librerías de arduino ni en los repositorios de GitHub de milkV, sin esos archivos no hay manera de saber por ejemplo el nombre de la función para enviar comandos desde el arduino al procesador principal, ni que parámetros tiene la función, ni siquiera saber si realmente está implementada dicha función en la librería, mi teoría es que esa librería viene ya compilada y se añadió cuando se instalo la placa en el IDE de arduino y está incompleta, creo que mas adelante a medida que el proyecto avance la librería así como el código completo de los ejemplos estará disponible para todos los usuarios, es normal que en placas de desarrollo de reciente creación falten cosas por desarrollar.
Nota: Quiero decir que encontré la librería mailbox para arduino, se encuentra en una carpeta oculta, la ruta es la siguiente:
Para Linux en mi caso Ubuntu: ~/.arduino15/packages/sophgo/hardware/SG200X/0.2.4/cores/sg200x
En Windows: \Users\us\AppData\Local\Arduino15\packages\sophgo\hardware\SG200X\0.2.4\cores\sg200x
A la vista de lo que pone en el encabezado parece ser que la librería no es de código abierto:
/*
* Copyright (C) Cvitek Co., Ltd. 2019-2020. All rights reserved.
*/
Ahora estaríamos en disposición de realizar un ejemplo para enviar datos desde el procesador secundario al procesador principal utilizando Mailbox, pero puede que tengáis otro problema si habéis actualizado a la última versión de sistema operativo y no habéis comprobado que la actualización pertenece a la rama de arduino en estos momentos la v1.1.3 no es para Arduino y no están instalados los controladores para la interfaz USB-Serie del dispositivo RNDIS con lo cual no tendremos disponible el puerto serie para programar el procesador secundario a través del IDE de arduino. Tenéis que buscar la última actualización perteneciente a la rama de arduino en estos momentos la v1.1.2. en el siguiente vídeo comento este dato que puede pasar desapercibido: https://youtu.be/O2bxdHN7nog
Solución al problema
Mientras no se solucione el problema con las librerías de Mailbox hay una solución muy simple pero completamente funcional y que además es independiente del lenguaje de programación que utilicemos tanto en el procesador principal como en el secundario y es comunicar ambos procesadores a través de UART, la milkV DUO tiene hasta 5 UARTs por lo que no veo problema en utilizar este sistema para este menester.
¿Como se hace?
Primeramente nos conectamos por SSH a nuestra DUO y ejecutamos el siguiente comando:
dmesg|grep -i tty
Obtendremos lo siguiente:
[ 0.000000] Kernel command line: root=/dev/mmcblk0p2 rootwait rw console=ttyS0,115200 earlycon=sbi riscv.fwsz=0x80000 loglevel=9
[ 0.481564] printk: console [ttyS0] disabled
[ 0.486095] 4140000.serial: ttyS0 at MMIO 0x4140000 (irq = 15, base_baud = 1562500) is a 16550A
[ 0.495226] printk: console [ttyS0] enabled
[ 0.514108] 4150000.serial: ttyS1 at MMIO 0x4150000 (irq = 16, base_baud = 1562500) is a 16550A
[ 0.524062] 4160000.serial: ttyS2 at MMIO 0x4160000 (irq = 17, base_baud = 1562500) is a 16550A
[ 0.534049] 4170000.serial: ttyS3 at MMIO 0x4170000 (irq = 18, base_baud = 1562500) is a 16550A
[ 0.544061] 41c0000.serial: ttyS4 at MMIO 0x41c0000 (irq = 19, base_baud = 1562500) is a 16550A
De aquí podemos sacar la conclusión de que el puerto ttyS0 está asociado a la consola del sistema además este puerto esta mapeado a la UART0 de la placa ( Tx--> Pin16 y Rx--> Pin17) si conectas de forma cruzada un convertidor de USB a serial como el FTDI232 (3.3V) y un clente como CuteCom podremos ver desde nuestro PC el proceso de arranque de la DUO así como logearnos y acceder al sistema al igual que si fuera una conexión SSH.
Por otro lado sabemos según la documentación de la DUO que la UART3 es la que viene configurada por defecto para la aplicación de arduino que corre en el núcleo secundario. Por tanto para poder enviar datos desde el procesador secundario debemos unir con un puente Tx (Pin6) de la UART3 con Rx (Pin17) de la UART0 pero antes debemos "desligar" la UART0 de la consola del sistema y dejarla libre para las aplicaciones del usuario.
Para ello ejecutamos el siguiente comando para editar el archivo inittab que se encuentra en el directorio /etc:
vi /etc/inittab
Debemos buscar el siguiente párrafo:
# Put a getty on the serial port
#console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL
console::respawn:/sbin/getty -L console 115200 vt100 -n -l /usr/local/bin/autologin
y comentar la última línea, debe de quedarnos así:
# Put a getty on the serial port
#console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL
#console::respawn:/sbin/getty -L console 115200 vt100 -n -l /usr/local/bin/autologin
Ahora ya podremos enviar datos del procesador secundario (arduino) al procesador principal en este ejemplo a través de un script en Python pero sería igual de fácil hacer la aplicación en C, aquí tenéis el código del ejemplo en Python y arduino:
Código arduino
El ejemplo de arduino simplemente lee el valor del pin análógico 31 y convierte el valor en el dato que enviaremos por medio del método Serial.print() al procesador secundario a través de UART.
Nota (1): Resulta confuso la nomenclatura utilizada para nombrar los pines, en arduino es el pin físico el que tenemos que poner en las funciones, pero si utilizamos Python o la consola es diferente. Hay que ver la nomenclatura a utilizar en la documentación según el lenguaje que vayamos a utilizar en la programación del ejemplo.
Nota(2): Al principio no me funciono hasta que me di cuenta que había un problema de sincronización entre las UARTs, se soluciono bajando la velocidad en baudios a 9600.
void setup() {
Serial.begin(9600); // Iniciar la comunicación serie a 9600 baudios
}
void loop() {
int analogValue = analogRead(31); // Leer valor analógico del pin 31
float voltage = (analogValue / 1023.0) * 3.3; // Convertir lectura a voltaje (0 - 3.3V)
// Enviar el valor de la tensión por el puerto serie
Serial.print("Voltaje: ");
Serial.println(voltage, 2); // Enviar con dos decimales
delay(1000); // Esperar un segundo antes de la siguiente lectura
}
Código en Python
El script de Python lo único que hace es abrir el puerto serie ttyS0 que es el que está mapeado en el procesador principal y esperar los datos procedentes del procesador secundario a través de la UART, los datos recibidos son decodificados en formato ascii y mostrados en la terminal.
import serial
import time
try:
ser = serial.Serial(
port='/dev/ttyS0',
baudrate=9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
if ser.isOpen():
print("Puerto serie abierto. Esperando datos...")
while True:
data = ser.readline().decode('ascii', errors='ignore').strip()
print(f"Datos recibidos desde arduino: {data}")
time.sleep(0.1)
except serial.SerialException as e:
print(f"Error al abrir el puerto serie: {e}")
finally:
if 'ser' in locals() and ser.isOpen():
ser.close()
print("Puerto serie cerrado.")