Um “primeiro projeto” completo e idiomático no Zephyr: threads + logging + LED (com overlay) + console
Nesta seção eu vou fechar um tutorial que você consegue reaproveitar como “base padrão” para quase qualquer firmware Zephyr: ele sobe, inicializa log, cria duas threads (produtor/consumidor simples), pisca LED descrito por DeviceTree e manda mensagens no console.
A ideia é você ter um projeto que já nasce com arquitetura mínima saudável, sem virar “superloop gigante” e sem depender de pinos hardcoded.
Estrutura final do que vamos ter
Dentro do seu repo (renomeado ou não), o essencial fica assim:
app/CMakeLists.txtapp/prj.confapp/src/main.capp/boards/<sua_board>.overlayapp/boards/<sua_board>.conf(opcional)
app/prj.conf: console + logging + threads
Use isso como base:
# Console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_PRINTK=y
# Logging (mais estruturado que printk)
CONFIG_LOG=y
CONFIG_LOG_MODE_DEFERRED=y
CONFIG_LOG_DEFAULT_LEVEL=3
# Kernel/Threads (normalmente já vem, mas deixo explícito)
CONFIG_MULTITHREADING=y
Por que LOG e não só printk? Porque LOG_* te dá níveis, tags por módulo, e controle fino (inclusive pra builds de release). O printk ainda é ótimo pra “pânico/early boot”, mas em firmware que cresce, log estruturado é mais sustentável.
app/CMakeLists.txt: simples e direto
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(primeiro_projeto_zephyr)
target_sources(app PRIVATE src/main.c)
Esse arquivo é o “ponto de entrada” do build da aplicação.
app/boards/<board>.overlay: LED via alias (relembrando)
Exemplo para nucleo_f411re (ajuste o GPIO para seu hardware real):
app/boards/nucleo_f411re.overlay
/ {
leds {
compatible = "gpio-leds";
user_led: led_0 {
gpios = <&gpioc 13 GPIO_ACTIVE_HIGH>;
label = "USER_LED";
};
};
aliases {
led0 = &user_led;
};
};
Se sua board já define led0 por padrão, você pode até não precisar disso. Mas eu gosto de manter no app quando estou “padronizando” comportamento entre placas.
app/src/main.c: 2 threads + fila + LED + logs
Aqui vai um exemplo completo e bem “Zephyr idiomático”, com comentários didáticos:
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/gpio.h>
/* Cria um "módulo de log" para este arquivo */
LOG_MODULE_REGISTER(app_main, LOG_LEVEL_INF);
/* LED vindo do DeviceTree alias led0 (definido no overlay) */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
/*
* Vamos criar um canal simples produtor/consumidor:
* - Produtor gera um contador (tick)
* - Consumidor recebe e decide piscar LED
*/
K_MSGQ_DEFINE(tick_msgq, sizeof(uint32_t), 8, 4);
/* Stack e controle das threads */
#define STACK_SZ 1024
#define PRODUCER_PRIO 5
#define CONSUMER_PRIO 5
K_THREAD_STACK_DEFINE(producer_stack, STACK_SZ);
K_THREAD_STACK_DEFINE(consumer_stack, STACK_SZ);
static struct k_thread producer_thread;
static struct k_thread consumer_thread;
/* Função da thread produtora */
static void producer_fn(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
uint32_t tick = 0;
while (1) {
tick++;
/* Envia tick para a fila (não bloqueia; se lotar, descarta o mais novo) */
int rc = k_msgq_put(&tick_msgq, &tick, K_NO_WAIT);
if (rc != 0) {
LOG_WRN("Fila cheia: tick %u descartado", tick);
} else {
LOG_DBG("Produziu tick=%u", tick);
}
k_sleep(K_MSEC(200));
}
}
/* Função da thread consumidora */
static void consumer_fn(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
uint32_t tick = 0;
while (1) {
/* Espera por um tick */
int rc = k_msgq_get(&tick_msgq, &tick, K_FOREVER);
if (rc != 0) {
LOG_ERR("Falha ao receber da fila (%d)", rc);
continue;
}
/* A cada 5 ticks, alterna o LED */
if ((tick % 5U) == 0U) {
gpio_pin_toggle_dt(&led);
LOG_INF("tick=%u -> toggle LED", tick);
}
}
}
/* Entry point */
int main(void)
{
/* Inicialização do LED */
if (!gpio_is_ready_dt(&led)) {
LOG_ERR("LED nao esta pronto. Verifique overlay/alias led0.");
return 0;
}
int rc = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
if (rc < 0) {
LOG_ERR("Falha ao configurar LED (%d)", rc);
return 0;
}
LOG_INF("Primeiro projeto Zephyr: threads + msgq + DT LED + logging");
/* Criação das threads */
k_thread_create(&producer_thread, producer_stack, STACK_SZ,
producer_fn, NULL, NULL, NULL,
PRODUCER_PRIO, 0, K_NO_WAIT);
k_thread_create(&consumer_thread, consumer_stack, STACK_SZ,
consumer_fn, NULL, NULL, NULL,
CONSUMER_PRIO, 0, K_NO_WAIT);
/* Em Zephyr, main pode terminar; mas aqui vamos “viver” no idle */
return 0;
}
O que este main.c te entrega (em termos de arquitetura):
- Separação clara: produção de dados (thread A) ≠ consumo/ação (thread B)
- Fila com backpressure controlável:
k_msgqé simples e previsível - Hardware descrito fora do código: LED vem do DeviceTree (overlay)
- Logs estruturados: fica fácil subir nível, baixar nível, filtrar por módulo
7.6 Build e flash (rotina diária)
cd my-workspace/meu-produto-fw
west build -p always -b nucleo_f411re app
west flash
Se você estiver usando uma board diferente, só troca o -b.