Skip to content
SPONSORS
By Corsi
Picture of Corsi
Corsi

Multi-core ​

O RP2350 é um sistema dual-core simétrico. Os dois núcleos operam simultaneamente e de forma independente, oferecendo alto throughput de processamento e a capacidade de direcionar interrupções para núcleos distintos, melhorando tanto o throughput quanto a latência no tratamento de interrupções.

Os dois núcleos possuem uma visão simétrica do barramento do sistema: todos os recursos de memória no RP2350 são igualmente acessíveis por ambos os núcleos, com o mesmo desempenho.

Paradigmas de Arquitetura para Sistemas Multicore ​

Em sistemas embarcados multicore, a divisão de responsabilidades entre os núcleos pode seguir diferentes estratégias. Considerando um cenário inicial sem sistema operacional (bare-metal), destacam-se dois paradigmas principais:

1. Arquitetura Mestre–Acelerador (Master–Worker) ​

Um núcleo (core primário) executa a lógica principal da aplicação, incluindo controle de fluxo e gerenciamento de estado.

O segundo núcleo (core secundário) é dedicado à execução de tarefas computacionalmente intensivas (CPU-bound), como:

  • Processamento digital de sinais (DSP)
  • Cálculos matemáticos complexos (FTP)
  • Criptografia
  • Processamento de dados

2. Particionamento Funcional Simétrico ​

Cada núcleo é responsável por um subconjunto específico do sistema, com responsabilidades de complexidade semelhante. A divisão pode ser feita por domínio funcional (ex.: comunicação e controle) ou por etapas de processamento.

Esse modelo exige definição clara de sincronização, compartilhamento de memória e mecanismos de exclusão mútua.

Interrupções ​

Cada núcleo possui um controlador de interrupções independente, e ambos compartilham praticamente o mesmo conjunto de fontes de interrupção.

O SDK associa a interrupção ao núcleo que a inicializou, conforme o exemplo abaixo:

c
void gpio_set_irq_callback(gpio_irq_callback_t callback) {
    uint core = get_core_num();

gpio

Por esse motivo, cada núcleo pode ter um callback de interrupção independente do outro. Ou seja, podemos ter dois callbacks de GPIO distintos — um para cada núcleo.

Usando multiplos COREs ​

A ideia é que teremos duas funções main cada uma com um loop infinito while(1) executando independente em cada um dos COREs ARM que a pico possui.

Para isso iremos ter que criar uma segunda função main, usualmente chamada de core1_entry nos exemplos oficiais da rasp pico, e indicar para o processador que ela será executada no segundo core, isso é feito pelo uso da função pela chamada da função no main principal.

multicore_launch_core1(core1_entry);

Sincronismo ​

Ao trabalhar com sistemas multicore, é comum precisar:

  1. Sincronizar um núcleo com o outro (por exemplo, liberar a execução do outro núcleo após determinado evento).
  2. Transferir informações entre os núcleos.

Variável global ​

Podemos resolver isso utilizando uma variável global compartilhada entre os núcleos? Teoricamente, sim.

Uma variável global é visível e acessível por ambos os núcleos no RP2350. Como ambos compartilham o mesmo espaço de memória SRAM, declarar uma variável como global permite que os dois núcleos leiam e escrevam nela.

Porém — e este ponto é crítico — visibilidade ≠ sincronização.

Acesso concorrente sem mecanismos de proteção resulta em:

  • Data races
  • Comportamento indefinido
  • Bugs difíceis de reproduzir e depurar

Portanto, é necessário utilizar mecanismos apropriados de sincronização.

FIFO ​

Um dos mecanismos clássicos de sincronização e transferência de dados entre núcleos é a FIFO (First-In, First-Out).

Os microcontroladores da série RP possuem duas FIFOs para passagem de dados, mensagens ou eventos ordenados entre os dois núcleos e cada FIFO possui largura de 32 bits.

Funções principais da API de FIFO ​

c
// Verifica se há dados disponíveis na FIFO de leitura
// (dados enviados pelo outro núcleo)
static bool multicore_fifo_rvalid (void);

// Envia dados para a FIFO de escrita
// (dados destinados ao outro núcleo)
void multicore_fifo_push_blocking (uint32_t data);

// Remove (lê) um dado da FIFO de leitura
// (dados vindos do outro núcleo)
uint32_t multicore_fifo_pop_blocking (void);

// Remove (lê) um dado da FIFO de leitura com timeout
bool multicore_fifo_pop_timeout_us (uint64_t timeout_us, uint32_t *out);

SDK ​

Para usar o ADC você deve modificar o CMakeLists.txt adicionando pico_multicore no target_link_libraries:

diff
target_link_libraries(
                      ....
                      .....
+                     pico_multicore
)

E adicionar no headfile:

c
#include "pico/multicore.h"

Snippets ​

Códigos exemplo para uso do multicore da pico.

Hello world ​

O exemplo a seguir executa dois printf um em cada core (main e core1_entry):

c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"

void core1_entry() {
    while(1){
        printf("Hello, core 1!\n");
        sleep_ms(100);
    }
}

int main() {    
    stdio_init_all();

    // inicializa core 1 com a função core1_entry
    multicore_launch_core1(core1_entry);

    while(1){
        printf("Hello, core 0!\n");
        sleep_ms(250);
    }
}

Sincronismo ​

No código a seguir, cada CORE controla um dos LEDs que estão conectados na pico, o CORE_0 controla o LED_0 e o CORE_1 controla o LED_1. Os LEDs nunca piscam juntos, primeiro o LED_0 pisca por 5 vezes então o LED_1 pisca por outras 5 vezes. Uma FIFO de comunicação é utilziada como mecanismo de sincronizacão.

c
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"

const int LED_0 = 15;
const int LED_1 = 16;

void core1_entry() {
    printf("Hello, core 1!\n");

    gpio_init(LED_1);
    gpio_set_dir(LED_1, GPIO_OUT);

    int i = multicore_fifo_pop_blocking();
    while(1){

        if (i++ < 5) {
            gpio_put(LED_1, 1);
            sleep_ms(250);
            gpio_put(LED_1, 0);
            sleep_ms(250);
        } else {
            multicore_fifo_push_blocking (0);
            i = multicore_fifo_pop_blocking();
        }
    }
}

int main() {
    stdio_init_all();
    printf("Hello, core 0!\n");

    // inicializa core 1 com a função core1_entry
    multicore_launch_core1(core1_entry);

    gpio_init(LED_0);
    gpio_set_dir(LED_0, GPIO_OUT);

    int i = 0;
    while(1){
        if (i++ < 5) {
            gpio_put(LED_0, 1);
            sleep_ms(250);
            gpio_put(LED_0, 0);
            sleep_ms(250);
        } else {
            multicore_fifo_push_blocking (0);
            i = multicore_fifo_pop_blocking();
        }
    }
}