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);

*(msg.data)=0;

}

}

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

}

Vídeo:
Enlace a tienda