Disciplina: Práticas de Sistemas Operacionais para Dispositivos Embarcados
Universidade Federal do Paraná
Prof. Jéfer – jefer@ufpr.br
Objetivo: Explorar práticas e exemplos reais de sistemas operacionais em dispositivos embarcados, com foco em ESP32, Raspberry Pi e Arduino.
Dispositivos Embarcados:
Papel do Sistema Operacional:
Objetivo:
Placas Similares à Raspberry Pi:
ESP32:
FreeRTOS:
Atribuição de Tarefas:
xTaskCreatePinnedToCore(taskFunction, "TaskName", 10000, NULL, 1, NULL, 0);
taskFunction
: Função que a tarefa executará."TaskName"
: Nome da tarefa para depuração.10000
: Tamanho da pilha da tarefa.1
: Prioridade da tarefa.0
: Núcleo onde a tarefa será executada (0 ou 1).Outras Funcionalidades do FreeRTOS:
xQueueSend(queueHandle, &data, portMAX_DELAY);
xSemaphoreGive(semaphoreHandle);
xTimerStart(timerHandle, portMAX_DELAY);
Exemplo Completo:
void taskFunction(void *pvParameters) {
while (1) {
// Código da tarefa
vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay de 1 segundo
}
}
void setup() {
xTaskCreatePinnedToCore(taskFunction, "Task1", 10000, NULL, 1, NULL, 0);
}
Referência: Atribuir tarefa a um núcleo do ESP32
Multiprocessamento no ESP32:
Criação de Tarefas:
xTaskCreate(taskSensor, "SensorTask", 10000, NULL, 1, NULL);
xTaskCreate(taskWiFi, "WiFiTask", 10000, NULL, 1, NULL);
taskSensor
e taskWiFi
: Funções das tarefas."SensorTask"
e "WiFiTask"
: Nomes das tarefas para depuração.10000
: Tamanho da pilha de cada tarefa.1
: Prioridade das tarefas.Comunicação entre Tarefas:
xQueueSend(queueHandle, &data, portMAX_DELAY);
xSemaphoreGive(semaphoreHandle);
Exemplo Completo:
QueueHandle_t queueHandle;
void taskSensor(void *pvParameters) {
int sensorData = 0;
while (1) {
sensorData = readSensor(); // Simula leitura de um sensor
xQueueSend(queueHandle, &sensorData, portMAX_DELAY);
vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay de 1 segundo
}
}
void taskWiFi(void *pvParameters) {
int receivedData;
while (1) {
if (xQueueReceive(queueHandle, &receivedData, portMAX_DELAY)) {
sendDataViaWiFi(receivedData); // Simula envio de dados via Wi-Fi
}
}
}
void setup() {
queueHandle = xQueueCreate(10, sizeof(int));
xTaskCreate(taskSensor, "SensorTask", 10000, NULL, 1, NULL);
xTaskCreate(taskWiFi, "WiFiTask", 10000, NULL, 1, NULL);
}
Referência: Multiprocessamento no ESP32
IRAM_ATTR
para baixa latência.
volatile int counter = 0;
void IRAM_ATTR buttonISR() {
counter++; // Incrementa contador na interrupção
}
void setup() {
pinMode(4, INPUT_PULLUP); // GPIO 4 com pull-up interno
attachInterrupt(digitalPinToInterrupt(4), buttonISR, FALLING);
}
void loop() {
Serial.printf("Botão pressionado %d vezes\n", counter);
delay(1000);
}
xTimerCreate
, mais flexíveis.
#include
#include
#include
TimerHandle_t ledTimer;
int ledPin = 2;
void timerCallback(TimerHandle_t xTimer) {
digitalWrite(ledPin, !digitalRead(ledPin)); // Alterna LED
}
void setup() {
pinMode(ledPin, OUTPUT);
ledTimer = xTimerCreate("LEDTimer", pdMS_TO_TICKS(500), pdTRUE, NULL, timerCallback);
xTimerStart(ledTimer, 0);
}
void loop() {
// Loop vazio; tudo gerenciado por FreeRTOS
}
portMAX_DELAY
).
QueueHandle_t sensorQueue;
void sensorTask(void *pvParameters) {
int sensorData = 0;
while (1) {
sensorData = analogRead(34); // Leitura do pino 34
xQueueSend(sensorQueue, &sensorData, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1000)); // 1s
}
}
void displayTask(void *pvParameters) {
int receivedData;
while (1) {
if (xQueueReceive(sensorQueue, &receivedData, portMAX_DELAY)) {
Serial.printf("Sensor: %d\n", receivedData);
}
}
}
void setup() {
sensorQueue = xQueueCreate(10, sizeof(int)); // Fila para 10 inteiros
xTaskCreate(sensorTask, "Sensor", 2048, NULL, 1, NULL);
xTaskCreate(displayTask, "Display", 2048, NULL, 1, NULL);
}
SemaphoreHandle_t ledMutex;
void task1(void *pvParameters) {
while (1) {
if (xSemaphoreTake(ledMutex, portMAX_DELAY)) {
digitalWrite(2, HIGH);
vTaskDelay(pdMS_TO_TICKS(500));
digitalWrite(2, LOW);
xSemaphoreGive(ledMutex);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setup() {
ledMutex = xSemaphoreCreateMutex();
pinMode(2, OUTPUT);
xTaskCreate(task1, "Task1", 2048, NULL, 1, NULL);
xTaskCreate(task1, "Task2", 2048, NULL, 1, NULL); // Mesmo código, outra instância
}
xTimerCreate
: Nome, período, auto-reload.xTimerStart
, xTimerStop
, xTimerReset
.
TimerHandle_t statusTimer;
void statusCallback(TimerHandle_t xTimer) {
Serial.println("Status: Sistema OK");
}
void setup() {
Serial.begin(115200);
statusTimer = xTimerCreate("StatusTimer", pdMS_TO_TICKS(2000), pdTRUE, NULL, statusCallback);
if (statusTimer != NULL) {
xTimerStart(statusTimer, portMAX_DELAY);
}
}
dd
(Linux/macOS) ou Raspberry Pi Imager.dd
:
sudo dd bs=4M if=raspberry-pi-os.img of=/dev/sdc status=progress
sync
lsblk
para confirmar o dispositivo (/dev/sdc
).rp2-pico-latest.uf2
.
from machine import Pin
import time
led = Pin(25, Pin.OUT) # LED onboard
while True:
led.toggle()
time.sleep(0.5)
.uf2
do site CircuitPython.
import board
import digitalio
import time
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
button = digitalio.DigitalInOut(board.GP15)
button.direction = digitalio.Direction.INPUT
while True:
led.value = button.value
time.sleep(0.1)
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
), instalar "Raspberry Pi Pico/RP2040".
#define LED_PIN 25
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
#include
#include
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QPushButton button("Votar");
button.show();
QObject::connect(&button, &QPushButton::clicked, []() {
qDebug() << "Voto registrado!";
});
return app.exec();
}
sudo apt install nodered
node-red-start
http://:1880
#include
void task1() { while(1) { digitalWrite(13, !digitalRead(13)); delay(500); } }
void task2() { while(1) { digitalWrite(12, !digitalRead(12)); delay(1000); } }
void setup() {
pinMode(13, OUTPUT);
pinMode(12, OUTPUT);
task_create(task1);
task_create(task2);
}
#include
OS_SEMAPHORE sem;
void task1(void *p) {
while (1) {
os_sema_take(&sem);
digitalWrite(13, HIGH);
os_delay(500);
digitalWrite(13, LOW);
os_sema_give(&sem);
}
}
void setup() {
pinMode(13, OUTPUT);
os_init();
os_sema_init(&sem, 1);
os_task_create(task1, NULL, 100);
os_start();
}
#include
void blinkTask(void *pvParameters) {
pinMode(13, OUTPUT);
while (1) {
digitalWrite(13, HIGH);
vTaskDelay(500 / portTICK_PERIOD_MS);
digitalWrite(13, LOW);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void setup() {
xTaskCreate(blinkTask, "Blink", 128, NULL, 1, NULL);
vTaskStartScheduler();
}
void loop() {}
#include
#include
QueueHandle_t sensorQueue;
void sensorTask(void *pvParameters) {
while (1) {
int value = analogRead(A0);
xQueueSend(sensorQueue, &value, portMAX_DELAY);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void displayTask(void *pvParameters) {
int receivedValue;
while (1) {
if (xQueueReceive(sensorQueue, &receivedValue, portMAX_DELAY)) {
Serial.print("Valor do sensor: ");
Serial.println(receivedValue);
}
}
}
void setup() {
Serial.begin(115200);
sensorQueue = xQueueCreate(5, sizeof(int));
xTaskCreate(sensorTask, "Sensor", 128, NULL, 1, NULL);
xTaskCreate(displayTask, "Display", 128, NULL, 1, NULL);
vTaskStartScheduler();
}
void loop() {}
SO | Recursos | Suporte | Facilidade | Compatibilidade |
---|---|---|---|---|
Duinos | Multitarefa básica | Arquivado (2013) | Média | Uno, Mega |
ArdOS | Multitarefa, semáforos | Em desenvolvimento | Média | Mega, Due |
FreeRTOS | Filas, semáforos, timers | Ativo, comunidade forte | Alta (com docs) | Uno, Mega, Due, Zero |
dd
ou Imager).FreeRTOSConfig.h
#define configMEM_MANGEMENT heap_4
xTaskCreate
aloca memória dinamicamente.configMAX_PRIORITIES - 1
).configMAX_PRIORITIES
(ex.: 5 ou 32).
void criticalTask(void *pvParameters) {
while (1) {
Serial.println("Tarefa Crítica");
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void secondaryTask(void *pvParameters) {
while (1) {
Serial.println("Tarefa Secundária");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(criticalTask, "Critical", 2048, NULL, 2, NULL); // Prioridade alta
xTaskCreate(secondaryTask, "Secondary", 2048, NULL, 1, NULL); // Prioridade baixa
vTaskStartScheduler();
}
xSemaphoreCreateMutex
).xSemaphoreCreateBinary
).
SemaphoreHandle_t serialMutex;
void task1(void *pvParameters) {
while (1) {
if (xSemaphoreTake(serialMutex, portMAX_DELAY)) {
Serial.println("Task 1 usa Serial");
xSemaphoreGive(serialMutex);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void task2(void *pvParameters) {
while (1) {
if (xSemaphoreTake(serialMutex, portMAX_DELAY)) {
Serial.println("Task 2 usa Serial");
xSemaphoreGive(serialMutex);
}
vTaskDelay(1500 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
serialMutex = xSemaphoreCreateMutex();
xTaskCreate(task1, "Task1", 2048, NULL, 1, NULL);
xTaskCreate(task2, "Task2", 2048, NULL, 1, NULL);
vTaskStartScheduler();
}
#include
#include
QueueHandle_t queueHandle;
SemaphoreHandle_t printMutex;
void producerTask(void *pvParameters) {
int data = 0;
while (1) {
xQueueSend(queueHandle, &data, portMAX_DELAY);
data++;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void consumerTask(void *pvParameters) {
int data;
while (1) {
if (xQueueReceive(queueHandle, &data, portMAX_DELAY)) {
if (xSemaphoreTake(printMutex, portMAX_DELAY)) {
Serial.print("Consumidor recebeu: ");
Serial.println(data);
xSemaphoreGive(printMutex);
}
}
}
}
void setup() {
Serial.begin(115200);
queueHandle = xQueueCreate(10, sizeof(int));
printMutex = xSemaphoreCreateMutex();
xTaskCreate(producerTask, "Producer", 2048, NULL, 1, NULL);
xTaskCreate(consumerTask, "Consumer", 2048, NULL, 2, NULL); // Prioridade maior
vTaskStartScheduler();
}
void loop() {}
Responda as seguintes questões:
Responda as seguintes questões:
A atribuição de tarefas a um núcleo no ESP32, feita com xTaskCreatePinnedToCore
, fixa uma tarefa a um núcleo específico (PRO_CPU ou APP_CPU), garantindo controle manual sobre a execução. Já o multiprocessamento aproveita os dois núcleos para executar tarefas simultaneamente, com o FreeRTOS balanceando automaticamente (sem afinidade) ou seguindo atribuições fixas, otimizando desempenho em aplicações complexas como IoT.
O FreeRTOS oferece multitarefa com escalonamento preemptivo, sincronização via filas, semáforos e mutexes, e suporte a prioridades, permitindo respostas em tempo real. Além disso, sua leveza (ocupa ~10 KB Flash) e portabilidade (ex.: ESP32, Arduino) o tornam ideal para gerenciar recursos limitados, como em sensores IoT ou controles industriais.
A instalação de um SO como Raspberry Pi OS ou Ubuntu Mate transforma a RPi em uma plataforma versátil, suportando interfaces gráficas (ex.: urna com Qt5), emulação (ex.: Recalbox para retrogaming) e servidores IoT (ex.: Node-RED). A gravação no SD com dd
ou Imager permite personalização, habilitando conectividade (Wi-Fi, SSH) e processamento local.
O Arduino enfrenta desafios como RAM limitada (2 KB no Uno) e ausência de multitarefa nativa, dificultando aplicações complexas. Duinos oferece multitarefa básica para modelos como Uno e Mega, enquanto ArdOS adiciona escalonamento preemptivo e semáforos, especialmente em Mega e Due. Contudo, ambos são limitados frente ao FreeRTOS, que é mais robusto.
Um SO completo na Raspberry Pi (ex.: RPi OS) suporta um sistema operacional inteiro com kernel, drivers e GUI, ideal para aplicações robustas como retrogaming ou servidores, instalado via SD. Já o MicroPython na Pico é um framework leve, instalado como firmware (.uf2), focado em microcontroladores com recursos limitados (264 KB SRAM), perfeito para tarefas simples como controlar LEDs ou sensores.
O FreeRTOS oferece esquemas como heap_1 (estático, sem liberação) e heap_4 (dinâmico com coalescência), afetando o uso de RAM. Heap_1 é leve mas inflexível, ideal para sistemas fixos, enquanto heap_4 reduz fragmentação, mas consome mais processamento. Escolher o esquema errado pode levar a falhas (ex.: estouro de RAM no ESP32) ou lentidão em tarefas dinâmicas.
O Node-RED, rodando em um SO como Raspberry Pi OS, aproveita o sistema de arquivos, conectividade (Wi-Fi, Ethernet) e bibliotecas (ex.: Node.js) para criar fluxos IoT, como monitoramento de sensores via GPIO. O SO gerencia recursos (CPU, RAM), permitindo dashboards web e integração com MQTT, algo inviável sem um sistema operacional completo.
A integração de ESP32 (com FreeRTOS para tempo real) e Raspberry Pi (com SO para processamento) em edge computing reduz latência ao processar dados localmente (ex.: análise de sensores ambientais). O ESP32 coleta dados rapidamente, enquanto a RPi executa IA (ex.: TensorFlow Lite), diminuindo dependência de nuvem e otimizando largura de banda.