College Online
0%

Programacao em Nivel de Hardware

Modulo 2 · Aula 3 ~25 min de leitura Nivel: Intermediario-Avancado

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.

C (STM32)
// 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:

C
// 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
*
Padrao Read-Modify-Write A sequência "ler registrador, modificar bits, escrever de volta" e chamada Read-Modify-Write (RMW). Em situacoes com interrupcoes ou DMA, esse padrao pode causar race conditions. Registradores como BSRR (Bit Set/Reset Register) no STM32 resolvem isso permitindo operacoes atômicas — um unico write seta ou reseta bits sem precisar ler o valor atual.

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.

C (STM32 bare-metal UART)
// 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):

C (Tabela de Vetores — STM32)
// 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.

C (Super Loop)
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.

C (FreeRTOS)
// 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
}
Comparacao
  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:

Linker Script (simplificado)
/* 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:

Exercicios

  1. 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.
  2. 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?
  3. 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.
  4. Explique o risco de race condition no padrao Read-Modify-Write quando interrupcoes estao habilitadas. Como o registrador BSRR do STM32 resolve isso?
  5. 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

Verifique seu entendimento

Qual operacao bitwise e usada para limpar (colocar em 0) um bit especifico de um registrador sem afetar os outros bits?

  • registrador |= (1 << bit) — OR com mascara
  • registrador &= ~(1 << bit) — AND com complemento da mascara
  • registrador ^= (1 << bit) — XOR com mascara
  • registrador = ~(1 << bit) — complemento da mascara