MilkV DUO comunicación entre núcleos
La milkV DUO tiene dos procesadores C906, uno principal (1 Ghz) y otro secundario o auxiliar (700 Mhz) ambos con arquitectura RISC-V en el principal corre un sistema operativo Linux y podemos ejecutar aplicaciones escritas en C o en Python y en el procesador secundario podemos correr un RTOS como freeRTOS o un sketch de arduino. En este artículo vamos a ver como comunicar ambos núcleos para enviar instrucciones desde el procesador principal al procesador secundario.
SISTEMAS EMBEBIDOS
Biblioman
9/20/20243 min leer
Introducción
La comunicación entre el núcleo principal y el núcleo secundario se realiza a través del módulo de buzón de correo. Los buzones de correo (mailboxes) es un mecanismo de comunicación muy utilizado en los procesadores multicore para comunicar los núcleos dentro del mismo SoC y básicamente se trata de una región de memoria compartida entre los procesadores, esa región de memoria generalmente consiste en un conjunto de registros o una pequeña área de memoria SRAM. Su funcionamiento es el siguiente:
El procesador emisor escribe un mensaje en el mailbox
El procesador receptor lee el mensaje del mailbox.
Existe un mecanismo de notificación generalmente una interrupción para avisar al receptor de que hay un nuevo mensaje.
La recepción de mensajes en el caso de la DUO es asíncrona, es decir el procesador receptor puede estar realizando otras tareas y cuando el emisor envía un mensaje se produce una interrupción para avisar al núcleo receptor que hay un mensaje del núcleo emisor.
¿En que consiste el ejemplo?
Vamos a escribir un programa en C que una vez compilado lo ejecutaremos en el procesador principal donde tenemos el sistema operativo Linux instalado, el programa permitirá introducir comandos a través del teclado esos comandos se enviarán al buzón a través de una estructura de datos determinada. En el procesador secundario tendremos ejecutando un sketch de arduino con las mismas estructuras de datos del buzón, cuando el emisor envía el mensaje se produce una interrupción que es atendida por el programa de arduino, la función de interrupción se encargará de leer el mensaje y mostrar el comando en un LCD de 16x2.
Código en C
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
enum SYSTEM_CMD_TYPE {
CMDQU_SEND = 1,
CMDQU_SEND_WAIT,
CMDQU_SEND_WAKEUP,
};
#define RTOS_CMDQU_DEV_NAME "/dev/cvi-rtos-cmdqu"
#define RTOS_CMDQU_SEND IOW('r', CMDQUSEND, unsigned long)
#define RTOS_CMDQU_SEND_WAIT IOW('r', CMDQUSEND_WAIT, unsigned long)
#define RTOS_CMDQU_SEND_WAKEUP IOW('r', CMDQUSEND_WAKEUP, unsigned long)
enum SYS_CMD_ID {
CMD_SEND_TEXT = 0x14, // Usamos este comando para enviar texto
};
struct valid_t {
unsigned char linux_valid;
unsigned char rtos_valid;
} attribute((packed));
typedef union resv_t {
struct valid_t valid;
unsigned short mstime; // 0 : no bloquear, -1 : bloquear indefinidamente
} resv_t;
struct cmdqu_t {
unsigned char ip_id;
unsigned char cmd_id : 7;
unsigned char block : 1;
union resv_t resv;
char param_ptr[100]; // Cambiamos a un buffer de 100 caracteres
} attribute((packed)) attribute((aligned(0x8)));
int main() {
int ret = 0;
int fd = open(RTOS_CMDQU_DEV_NAME, O_RDWR);
if (fd <= 0) {
printf("Error al abrir el dispositivo, fd = %dn", fd);
return 0;
}
struct cmdqu_t cmd = {0};
cmd.ip_id = 0;
cmd.cmd_id = CMD_SEND_TEXT;
cmd.resv.mstime = 100;
// Pedimos el texto al usuario
fgets(cmd.param_ptr, sizeof(cmd.param_ptr), stdin);
cmd.param_ptr[strcspn(cmd.param_ptr, "n")] = 0; // Remover el salto de línea
// Enviar el comando a través del ioctl
ret = ioctl(fd, RTOS_CMDQU_SEND_WAIT, &cmd);
if (ret < 0) {
printf("Error en ioctl!n");
close(fd);
return 0;
}
printf("Texto enviado correctamente: %sn", cmd.param_ptr);
close(fd);
return 0;
}
Código arduino
#include "mailbox.h"
#include <LiquidCrystal.h>
// Configuración de los pines del LCD
const int rs = 20, en = 19, d4 = 24, d5 = 25, d6 = 26, d7 = 27;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
#define CMD_SEND_TEXT 0x14
struct valid_t {
uint8_t linux_valid;
uint8_t rtos_valid;
} attribute((packed));
typedef union resv_t {
struct valid_t valid;
unsigned short mstime;
} resv_t;
typedef struct cmdqu_t cmdqu_t;
struct cmdqu_t {
uint8_t ip_id;
uint8_t cmd_id : 7;
uint8_t block : 1;
union resv_t resv;
char param_ptr[100]; // Buffer de 100 caracteres para recibir el texto
} attribute((packed)) attribute((aligned(0x8)));
// Función para mostrar el mensaje en el LCD
void showmsg(MailboxMsg msg) {
cmdqu_t cmdq = (cmdqu_t )msg.data;
if (cmdq->cmd_id == CMD_SEND_TEXT) {
// Mostramos el texto en el LCD
lcd.clear();
lcd.print(cmdq->param_ptr);
// Mostrar también por el serial
Serial.print("Comando recibido: ");
Serial.println(cmdq->param_ptr);
}
}
void setup() {
Serial.begin(115200);
lcd.begin(16, 2); // Inicia el LCD con 16 columnas y 2 filas
lcd.print("Esperando Coman.");
// Inicializar mailbox
mailbox_init(false);
mailbox_register(0, showmsg);
mailbox_enable_receive(0);
Serial.println("Mailbox iniciado");
}
void loop() {
// El loop se mantiene vacío ya que todo es controlado por interrupciones de mailbox
}