#include <stdint.h>
#include <stdbool.h>
// Macro flexível para acesso a qualquer registrador de 32 bits
#define REG(base, offset) (*(volatile uint32_t *)((base) + (offset)))
// Endereços Base do STM32L031K6
#define BASE_RCC 0x40021000
#define BASE_GPIOA 0x50000000
#define BASE_EXTI 0x40010400
#define BASE_SYSCFG 0x40010000
#define BASE_TIM21 0x40010800
// Variáveis Globais de Estado
volatile bool sistema_pausado = false;
volatile uint32_t ticks_ms = 0;
volatile uint8_t contador_4bits = 0;
volatile uint32_t tick_ultimo_clique = 0;
// Tabela de vetores de interrupção na RAM (Necessário para sobrescrever no Wokwi/Arduino)
uint32_t tabela_vetores_ram[48] __attribute__((aligned(256)));
// ============================================================================
// ROTINAS DE TRATAMENTO DE INTERRUPÇÃO (ISR)
// ============================================================================
// ISR do Botão (EXTI0 / Pino PA0)
extern "C" void handler_botao(void) {
if (REG(BASE_EXTI, 0x14) & 1) { // Checa a flag Pending Register (PR)
REG(BASE_EXTI, 0x14) = 1; // Limpa a flag escrevendo 1
// Debounce via software (250ms)
if ((ticks_ms - tick_ultimo_clique) > 250) {
sistema_pausado = !sistema_pausado; // Alterna entre Play e Pause
tick_ultimo_clique = ticks_ms;
}
}
}
// ISR do Timer 21 (Estoura a cada 1 milissegundo)
extern "C" void handler_timer(void) {
if (REG(BASE_TIM21, 0x10) & 1) { // Checa a flag de Update Interrupt (UIF)
REG(BASE_TIM21, 0x10) &= ~1; // Limpa a flag
ticks_ms++;
// Se não estiver pausado, incrementa o contador a cada 1 segundo (1000 ms)
if (!sistema_pausado) {
if (ticks_ms % 1000 == 0) {
contador_4bits++;
if (contador_4bits > 15) {
contador_4bits = 0;
}
}
}
}
}
// ============================================================================
// INICIALIZAÇÃO
// ============================================================================
void setup() {
// 1. Realocação da Tabela de Vetores (Bypass do Arduino Core)
uint32_t *vetores_flash = (uint32_t*)0x08000000;
for (int i = 0; i < 48; i++) {
tabela_vetores_ram[i] = vetores_flash[i];
}
// Substitui os vetores padrão pelas nossas funções bare-metal
tabela_vetores_ram[16 + 5] = (uint32_t)handler_botao;
tabela_vetores_ram[16 + 20] = (uint32_t)handler_timer;
REG(0xE000ED00, 0x08) = (uint32_t)tabela_vetores_ram; // SCB_VTOR
// 2. Habilitação de Clocks (GPIOA, SYSCFG e TIM21)
REG(BASE_RCC, 0x2C) |= (1 << 0); // IOPENR (GPIOA)
REG(BASE_RCC, 0x34) |= (1 << 0) | (1 << 2);// APB2ENR (SYSCFG e TIM21)
// 3. Configuração do Botão (PA0) -> Entrada com Pull-Up
REG(BASE_GPIOA, 0x00) &= ~(3 << 0); // MODER = 00 (Input)
REG(BASE_GPIOA, 0x0C) &= ~(3 << 0); // Limpa PUPDR
REG(BASE_GPIOA, 0x0C) |= (1 << 0); // PUPDR = 01 (Pull-up)
// 4. Configuração dos LEDs (PA1, PA3, PA4, PA5) -> Saída Digital
uint32_t mask_moder = (3 << 2) | (3 << 6) | (3 << 8) | (3 << 10);
uint32_t cfg_moder = (1 << 2) | (1 << 6) | (1 << 8) | (1 << 10);
REG(BASE_GPIOA, 0x00) &= ~mask_moder;
REG(BASE_GPIOA, 0x00) |= cfg_moder;
// 5. Configuração da Interrupção Externa (EXTI0) no PA0
REG(BASE_SYSCFG, 0x08) &= ~(0xF << 0); // Linka EXTI0 à porta PA
REG(BASE_EXTI, 0x00) |= (1 << 0); // IMR: Desmascara interrupção
REG(BASE_EXTI, 0x0C) |= (1 << 0); // FTSR: Dispara na borda de descida
// 6. Configuração do Temporizador (TIM21) para 1kHz (1ms)
// Clock do Wokwi Nucleo L031K6 = 32 MHz. Prescaler = 32, Auto-Reload = 1000
REG(BASE_TIM21, 0x28) = 31; // PSC
REG(BASE_TIM21, 0x2C) = 999; // ARR
REG(BASE_TIM21, 0x0C) |= (1 << 0); // DIER: Habilita interrupção de update
REG(BASE_TIM21, 0x00) |= (1 << 0); // CR1: Habilita contagem do timer
// 7. Configuração do Controlador de Interrupções (NVIC)
REG(0xE000E100, 0x00) |= (1 << 5) | (1 << 20); // Ativa linhas 5 (EXTI) e 20 (TIM21)
}
// ============================================================================
// LOOP PRINCIPAL
// ============================================================================
void loop() {
uint32_t estado_porta = REG(BASE_GPIOA, 0x14); // Lê o ODR atual
// Zera apenas os pinos dos LEDs (PA1, PA3, PA4, PA5)
estado_porta &= ~((1 << 1) | (1 << 3) | (1 << 4) | (1 << 5));
// Mapeamento matemático dos bits sem usar IFs:
// Bit 0 do contador vai para o Bit 1 da porta (PA1)
// Bit 1 vai para PA3, Bit 2 para PA4, Bit 3 para PA5.
uint32_t out = 0;
out |= ((contador_4bits >> 0) & 1) << 1;
out |= ((contador_4bits >> 1) & 1) << 3;
out |= ((contador_4bits >> 2) & 1) << 4;
out |= ((contador_4bits >> 3) & 1) << 5;
// Atualiza o registrador de saída
REG(BASE_GPIOA, 0x14) = (estado_porta | out);
}