La programación embebida se refiere al desarrollo de software para sistemas computacionales dedicados, generalmente con recursos limitados y funciones específicas.
Nota: El lenguaje C es el más utilizado en sistemas embebidos por su eficiencia, portabilidad y acceso a bajo nivel al hardware.
// En sistemas embebidos a menudo no hay sistema operativo
// Las funciones estándar como printf pueden no estar disponibles
// Se usan implementaciones reducidas o personalizadas
// Ejemplo: Acceso a registros de un microcontrolador
#define PORT_A (*(volatile uint8_t *)0x40000000)
void main() {
PORT_A = 0xFF; // Escribe todos los bits del puerto A a 1
}
// Uso de tipos de tamaño fijo para ahorrar memoria
#include <stdint.h>
int8_t smallVar; // Entero de 8 bits con signo
uint16_t mediumVar; // Entero de 16 bits sin signo
// Estructura típica de memoria en un sistema embebido
/*
0x00000000 - 0x0000FFFF Código (Flash)
0x20000000 - 0x2000FFFF RAM
0x40000000 - 0x400FFFFF Periféricos
*/
// Técnicas comunes:
// - Puntos de interrupción (breakpoints)
// - Depuración por printf (a través de UART)
// - LEDs de depuración
// - Analizadores lógicos
// En sistemas embebidos, el punto de entrada suele ser Reset_Handler
void Reset_Handler(void) {
// Inicializa la memoria (.data, .bss)
// Llama a main()
}
int main(void) {
// Código de aplicación
while(1) {
// Bucle infinito (common en sistemas embebidos)
}
}
// volatile: Indica que la variable puede cambiar fuera del flujo del programa
volatile uint32_t systemTick;
// static: Mantiene la variable en memoria (no stack) y limita su alcance
static uint8_t internalCounter;
// const: Almacenamiento en flash para ahorrar RAM
const char welcomeMsg[] = "Sistema iniciado";
// Ejemplo: Configurar un pin GPIO como salida
#define LED_PORT GPIOA
#define LED_PIN 5
void GPIO_Init(void) {
// Habilita el reloj para GPIOA
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// Configura el pin 5 como salida
LED_PORT->MODER &= ~(3U << (LED_PIN * 2)); // Limpia bits
LED_PORT->MODER |= (1U << (LED_PIN * 2)); // Modo salida
// Tipo push-pull
LED_PORT->OTYPER &= ~(1U << LED_PIN);
}
void toggleLED(void) {
LED_PORT->ODR ^= (1U << LED_PIN); // XOR para alternar el estado
}
// Ejemplo básico de transmisión UART
void UART_SendChar(uint8_t ch) {
while(!(USART1->ISR & USART_ISR_TXE)); // Espera buffer vacío
USART1->TDR = ch;
}
void UART_SendString(const char *str) {
while(*str) {
UART_SendChar(*str++);
}
}
// Configuración básica de un timer
void TIM_Init(void) {
// Habilita reloj para TIM2
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// Configura el prescaler para 1ms
TIM2->PSC = SystemCoreClock / 1000 - 1;
// Habilita interrupción por overflow
TIM2->DIER |= TIM_DIER_UIE;
// Habilita el timer
TIM2->CR1 |= TIM_CR1_CEN;
// Configura NVIC para TIM2
NVIC_EnableIRQ(TIM2_IRQn);
}
void TIM2_IRQHandler(void) {
if(TIM2->SR & TIM_SR_UIF) {
TIM2->SR &= ~TIM_SR_UIF; // Limpia flag de interrupción
// Código a ejecutar cada 1ms
}
}
void EXTI0_IRQHandler(void) {
// Verifica que la interrupción proviene del pin correcto
if(EXTI->PR & EXTI_PR_PR0) {
// Limpia el flag de interrupción
EXTI->PR = EXTI_PR_PR0;
// Código a ejecutar en la interrupción
toggleLED();
}
}
Advertencia: Las variables compartidas entre una ISR y el código principal deben ser declaradas como volatile
para evitar problemas de optimización.
/* Estructura típica del linker script:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
}
SECTIONS
{
.text : { *(.text) } > FLASH
.data : { *(.data) } > RAM AT> FLASH
.bss : { *(.bss) } > RAM
}*/
Nota: En muchos sistemas embebidos se evita el uso de malloc/free
debido a:
Se prefieren estructuras estáticas o pools de memoria.
void enterLowPowerMode(void) {
// Configura un pin como fuente de wake-up
EXTI->IMR |= EXTI_IMR_MR0; // Habilita interrupción en línea 0
EXTI->RTSR |= EXTI_RTSR_TR0; // Trigger en flanco de subida
// Configura el modo de bajo consumo
PWR->CR |= PWR_CR_LPDS; // Low-power deepsleep
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
// Entra en modo de bajo consumo
__WFI(); // Wait for interrupt
}
#include "FreeRTOS.h"
#include "task.h"
void vTask1(void *pvParameters) {
while(1) {
// Código de la tarea 1
vTaskDelay(pdMS_TO_TICKS(100)); // Espera 100ms
}
}
void vTask2(void *pvParameters) {
while(1) {
// Código de la tarea 2
vTaskDelay(pdMS_TO_TICKS(500)); // Espera 500ms
}
}
int main(void) {
// Crea las tareas
xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// Inicia el scheduler
vTaskStartScheduler();
while(1); // No debería llegar aquí
}
const
para datos que no cambian#include "stm32f1xx.h"
#define LED_PIN GPIO_PIN_13
#define LED_PORT GPIOC
#define BUTTON_PIN GPIO_PIN_0
#define BUTTON_PORT GPIOA
volatile uint8_t buttonPressed = 0;
void GPIO_Init(void) {
// Habilita relojes para GPIOA y GPIOC
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;
// Configura PC13 como salida push-pull (LED)
GPIOC->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_MODE13);
GPIOC->CRH |= GPIO_CRH_MODE13_1; // Salida 2MHz
// Configura PA0 como entrada con pull-up
GPIOA->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_MODE0);
GPIOA->CRL |= GPIO_CRL_CNF0_1; // Entrada con pull-up/pull-down
GPIOA->ODR |= GPIO_ODR_ODR0; // Pull-up
}
void EXTI_Init(void) {
// Habilita reloj para AFIO
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// Configura EXTI0 para PA0
AFIO->EXTICR[0] &= ~AFIO_EXTICR1_EXTI0;
AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI0_PA;
// Configura trigger en flanco de bajada
EXTI->FTSR |= EXTI_FTSR_TR0;
// Habilita interrupción EXTI0
EXTI->IMR |= EXTI_IMR_MR0;
// Configura prioridad en NVIC
NVIC_SetPriority(EXTI0_IRQn, 0);
NVIC_EnableIRQ(EXTI0_IRQn);
}
void SysTick_Init(void) {
SysTick->LOAD = SystemCoreClock/1000 - 1; // 1ms
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
}
void EXTI0_IRQHandler(void) {
if(EXTI->PR & EXTI_PR_PR0) {
EXTI->PR = EXTI_PR_PR0; // Limpia flag
buttonPressed = 1;
}
}
int main(void) {
GPIO_Init();
EXTI_Init();
SysTick_Init();
uint32_t lastTick = 0;
uint8_t ledState = 0;
while(1) {
if(buttonPressed) {
buttonPressed = 0;
ledState = !ledState;
if(ledState) {
LED_PORT->BSRR = LED_PIN; // Enciende LED
} else {
LED_PORT->BRR = LED_PIN; // Apaga LED
}
}
}
}
La programación en C para sistemas embebidos requiere un enfoque diferente al desarrollo de aplicaciones de propósito general. Es fundamental comprender el hardware subyacente, las limitaciones de recursos y los requisitos de tiempo real.
Este manual cubre los conceptos básicos, pero cada sistema embebido es único. Siempre consulta la documentación específica del microcontrolador que estés utilizando.