Programacao em Nivel de Hardware
Video da aula estara disponivel em breve
Acesso Direto a Registradores
Na programacao em nivel de hardware (bare-metal), o programador interage diretamente com os registradores dos perifericos. Cada periferico tem um conjunto de registradores mapeados em enderecos fixos de memoria (Memory-Mapped I/O). O acesso e feito por ponteiros em C.
// Acesso direto ao registrador de GPIO no STM32F411
// Cada periferico tem um endereco base (definido no datasheet)
// Enderecos base dos perifericos GPIO
#define GPIOA_BASE 0x40020000
#define GPIOB_BASE 0x40020400
#define GPIOC_BASE 0x40020800
// Offsets dos registradores dentro de cada GPIO
#define GPIOx_MODER 0x00 // Mode Register (input/output/alt/analog)
#define GPIOx_OTYPER 0x04 // Output Type (push-pull / open-drain)
#define GPIOx_OSPEEDR 0x08 // Output Speed
#define GPIOx_PUPDR 0x0C // Pull-up / Pull-down
#define GPIOx_IDR 0x10 // Input Data Register (leitura)
#define GPIOx_ODR 0x14 // Output Data Register (escrita)
#define GPIOx_BSRR 0x18 // Bit Set/Reset Register (atômico)
// Macro para acessar registradores via ponteiro
#define REG(base, offset) (*(volatile uint32_t *)((base) + (offset)))
// Exemplo: configurar PA5 como saida (LED no Nucleo-F411RE)
void configurar_led(void) {
// 1. Habilitar clock do GPIOA (registrador RCC)
REG(0x40023800, 0x30) |= (1 << 0); // RCC_AHB1ENR bit 0 = GPIOAEN
// 2. Configurar PA5 como saida (MODER bits 11:10 = 01)
REG(GPIOA_BASE, GPIOx_MODER) &= ~(3 << 10); // limpar bits 11:10
REG(GPIOA_BASE, GPIOx_MODER) |= (1 << 10); // setar 01 = output
}
// Ligar LED (PA5 = 1)
void led_on(void) {
REG(GPIOA_BASE, GPIOx_BSRR) = (1 << 5); // set bit 5
}
// Desligar LED (PA5 = 0)
void led_off(void) {
REG(GPIOA_BASE, GPIOx_BSRR) = (1 << 21); // reset bit 5 (offset 16)
}
Manipulacao de Bits
A programacao de hardware depende fortemente de operacoes bitwise para manipular bits individuais de registradores sem afetar os demais. As operacoes fundamentais sao:
// 1. SETAR um bit (colocar em 1) — usa OR
registrador |= (1 << bit_posicao);
// Exemplo: setar bit 5
// Antes: 0b10100100
// Mascara: 0b00100000 (1 << 5)
// Depois: 0b10110100
// 2. LIMPAR um bit (colocar em 0) — usa AND com complemento
registrador &= ~(1 << bit_posicao);
// Exemplo: limpar bit 2
// Antes: 0b10110100
// ~Mascara: 0b11111011 ~(1 << 2)
// Depois: 0b10110000
// 3. ALTERNAR um bit (toggle) — usa XOR
registrador ^= (1 << bit_posicao);
// 4. TESTAR um bit (verificar se e 1) — usa AND
if (registrador & (1 << bit_posicao)) {
// bit esta setado
}
// 5. SETAR um campo de multiplos bits
// Exemplo: setar bits 5:4 para o valor 0b10
registrador &= ~(0x3 << 4); // limpar campo (2 bits)
registrador |= (0x2 << 4); // escrever valor
Acesso a Perifericos via MMIO
Cada periferico do microcontrolador tem registradores mapeados em enderecos fixos. O programador configura perifericos escrevendo nesses enderecos. Vejamos um exemplo completo: configurar a UART para comunicacao serial.
// Configurar USART2 no STM32F411 para 115200 baud
// PA2 = TX, PA3 = RX (funcao alternativa AF7)
#define RCC_BASE 0x40023800
#define USART2_BASE 0x40004400
// Registradores USART
#define USART_SR 0x00 // Status Register
#define USART_DR 0x04 // Data Register
#define USART_BRR 0x08 // Baud Rate Register
#define USART_CR1 0x0C // Control Register 1
void uart_init(void) {
// 1. Habilitar clocks
REG(RCC_BASE, 0x30) |= (1 << 0); // GPIOA clock
REG(RCC_BASE, 0x40) |= (1 << 17); // USART2 clock
// 2. Configurar PA2, PA3 como funcao alternativa
REG(GPIOA_BASE, 0x00) &= ~(0xF << 4);
REG(GPIOA_BASE, 0x00) |= (0xA << 4); // AF mode para PA2,PA3
REG(GPIOA_BASE, 0x20) |= (0x77 << 8); // AF7 (USART2)
// 3. Configurar baud rate (16 MHz / 115200 = 138.88)
REG(USART2_BASE, USART_BRR) = 0x008B; // mantissa=8, frac=11
// 4. Habilitar TX, RX e USART
REG(USART2_BASE, USART_CR1) = (1 << 13) // UE (USART Enable)
| (1 << 3) // TE (Transmitter Enable)
| (1 << 2); // RE (Receiver Enable)
}
void uart_send_char(char c) {
// Esperar ate o buffer de transmissao estar vazio
while (!(REG(USART2_BASE, USART_SR) & (1 << 7))); // TXE flag
REG(USART2_BASE, USART_DR) = c;
}
void uart_send_string(const char *s) {
while (*s) uart_send_char(*s++);
}
Interrupt Service Routines em Bare-Metal
Em programacao bare-metal, as ISRs sao funcoes cujos enderecos sao colocados na tabela de vetores de interrupcao. No Cortex-M, essa tabela fica no inicio da memoria Flash (endereco 0x00000000):
// startup.c — tabela de vetores de interrupcao
extern uint32_t _estack; // definido pelo linker script
void Reset_Handler(void);
void Default_Handler(void);
void EXTI0_IRQHandler(void); // nossa ISR customizada
// Vetor na secao .isr_vector (Flash offset 0x00)
__attribute__((section(".isr_vector")))
void (*const vectors[])(void) = {
(void(*)())&_estack, // Stack Pointer inicial
Reset_Handler, // Reset
Default_Handler, // NMI
Default_Handler, // HardFault
// ... outros vetores ...
[22] = EXTI0_IRQHandler, // EXTI Line 0
};
// ISR para botao no pino PA0
void EXTI0_IRQHandler(void) {
// Limpar flag de interrupcao pendente
REG(0x40013C00, 0x14) = (1 << 0); // EXTI_PR bit 0
// Toggle LED em PA5
REG(GPIOA_BASE, GPIOx_ODR) ^= (1 << 5);
}
Bare-Metal vs. RTOS
Existem duas abordagens fundamentais para programar microcontroladores:
Bare-Metal (Super Loop)
O programa roda em um loop infinito sem sistema operacional. Simples, deterministico, menor overhead.
int main(void) {
system_init();
gpio_init();
uart_init();
timer_init();
while (1) {
ler_sensores(); // polling
processar_dados();
atualizar_saidas();
verificar_comunicacao();
}
}
RTOS (Real-Time Operating System)
Um RTOS (como FreeRTOS, Zephyr, ThreadX) adiciona multitarefa com tasks, prioridades e escalonamento preemptivo. Mais complexo, mas essencial para sistemas com multiplas tarefas concorrentes e prazos criticos.
// Duas tarefas concorrentes com FreeRTOS
void tarefa_sensor(void *param) {
while (1) {
float temp = ler_temperatura();
xQueueSend(fila_dados, &temp, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms
}
}
void tarefa_display(void *param) {
float temp;
while (1) {
xQueueReceive(fila_dados, &temp, portMAX_DELAY);
display_mostrar(temp);
}
}
int main(void) {
system_init();
fila_dados = xQueueCreate(10, sizeof(float));
xTaskCreate(tarefa_sensor, "sensor", 128, NULL, 2, NULL);
xTaskCreate(tarefa_display, "display", 128, NULL, 1, NULL);
vTaskStartScheduler(); // nunca retorna
}
Caracteristica Bare-Metal RTOS
────────────────────────────────────────────────
Complexidade Baixa Media-Alta
Overhead Nenhum ~2-10 KB Flash + RAM
Multitarefa Manual (flags) Nativa (tasks)
Determinismo Total Preemptivo
Depuracao Simples Mais dificil
Escalabilidade Limitada Boa
Uso Projetos simples Produtos complexos
Linker Script e Mapa de Memoria
Em programacao bare-metal, o linker script define como o programa e organizado na memoria do microcontrolador. Ele especifica onde colocar codigo, dados e a pilha:
/* STM32F411 Memory Map */
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS {
.text : { /* Codigo (Flash) */
*(.isr_vector) /* Tabela de vetores primeiro */
*(.text*) /* Codigo do programa */
*(.rodata*) /* Constantes */
} > FLASH
.data : { /* Dados inicializados (copiados de Flash para RAM) */
*(.data*)
} > SRAM AT> FLASH
.bss : { /* Dados nao inicializados (zerados na RAM) */
*(.bss*)
} > SRAM
_estack = ORIGIN(SRAM) + LENGTH(SRAM); /* Topo da pilha */
}
No harness.os
A programacao em nivel de hardware ilustra conceitos que se aplicam diretamente ao harness.os:
- Registradores e configuracao = Project config: assim como cada periferico e configurado escrevendo em registradores especificos, cada projeto no harness.os e configurado com parametros especificos (slug, type, subtype, concerns). A "API" e diferente, mas o padrao e o mesmo: configurar antes de usar.
- Bit manipulation = Granular control: a manipulacao de bits individuais em registradores e analoga ao controle granular de flags e settings no harness.os. Setar um bit sem afetar os outros e como atualizar uma regra sem afetar o resto do sistema.
- Bare-metal vs. RTOS = Scale 1 vs. Scale 2: bare-metal (simples, direto, limitado) corresponde a Scale 1 (arquivos, CLAUDE.md). RTOS (complexo, multitarefa, escalavel) corresponde a Scale 2 (banco de dados, MCP server, agentes paralelos).
- ISR pattern = Event handling: o padrao "setar flag na ISR, processar no loop principal" e exatamente como o harness.os lida com eventos: registrar o evento rapidamente, processar na proxima oportunidade.
- Linker script = Architecture definition: o linker script define onde cada tipo de dado vive na memoria. O harness.os define onde cada tipo de conhecimento vive: regras em arquivos, decisoes no banco, learnings em ambos.
Exercicios
- Escreva em C o codigo bare-metal para configurar o Timer 2 do STM32F411 para gerar uma interrupcao a cada 1 segundo (clock de 16 MHz). Use prescaler e auto-reload register.
- Dado o registrador GPIOA_MODER com valor 0x28000000, identifique a configuracao de cada pino (PA0-PA15). Que operacoes bitwise sao necessarias para mudar PA7 de input para output sem afetar os outros pinos?
- Compare o padrao super-loop com FreeRTOS para um sistema que precisa: ler 3 sensores, atualizar display, e enviar dados via UART. Desenhe o fluxo de cada abordagem.
- Explique o risco de race condition no padrao Read-Modify-Write quando interrupcoes estao habilitadas. Como o registrador BSRR do STM32 resolve isso?
- Escreva um linker script para um microcontrolador com 64 KB de Flash (0x08000000) e 20 KB de SRAM (0x20000000). Inclua secoes para tabela de vetores, codigo, dados e pilha.
Resumo
- Programacao bare-metal acessa perifericos diretamente via registradores mapeados em memoria (MMIO)
- Operacoes bitwise (OR, AND NOT, XOR) sao essenciais para manipular bits individuais de registradores
- O padrao Read-Modify-Write pode causar race conditions; registradores atômicos (BSRR) resolvem isso
- ISRs em Cortex-M usam tabela de vetores na Flash; devem ser curtas e limpar flags de interrupcao
- Bare-metal (super loop) e simples; RTOS adiciona multitarefa com tasks, filas e escalonamento preemptivo
- O linker script define o mapa de memoria: onde codigo, dados e pilha residem no chip
Verifique seu entendimento
Qual operacao bitwise e usada para limpar (colocar em 0) um bit especifico de um registrador sem afetar os outros bits?