Embedded Basics
Introduction
Embedded systems are specialized computing systems designed to perform dedicated functions within larger mechanical or electrical systems. Unlike general-purpose computers, embedded systems are typically resource-constrained and optimized for specific tasks. Understanding the fundamentals of embedded systems is essential for developing efficient and reliable embedded applications in C.
What are Embedded Systems?
An embedded system is a computer system with a dedicated function within a larger mechanical or electrical system, often with real-time computing constraints. These systems are embedded as part of a complete device, often including hardware and mechanical parts.
Characteristics of Embedded Systems
- Dedicated Function - Designed for specific tasks rather than general-purpose computing
- Resource Constraints - Limited memory, processing power, and energy consumption
- Real-time Operation - Often required to respond within strict time constraints
- Reliability - Must operate continuously and reliably for extended periods
- Cost Sensitivity - Optimized for low production costs
- Power Efficiency - Often battery-powered with strict power consumption requirements
Types of Embedded Systems
- Small-Scale Embedded Systems - Simple 8-bit or 16-bit microcontrollers
- Medium-Scale Embedded Systems - 16-bit or 32-bit processors with limited memory
- Sophisticated Embedded Systems - Complex 32-bit or 64-bit processors with extensive memory
Embedded System Architecture
Microcontroller vs Microprocessor
Microcontroller (MCU): - Integrated CPU, memory, and I/O peripherals on a single chip - Self-contained system with minimal external components - Common in small-scale embedded applications
Microprocessor (MPU): - CPU only, requires external memory and peripherals - More powerful but requires additional components - Common in medium to large-scale embedded applications
Typical Embedded System Components
- Processor - Central processing unit (CPU)
- Memory - RAM, ROM/Flash for program and data storage
- Input/Output Ports - GPIO, serial interfaces, ADC/DAC
- Timers/Counters - For timing and counting operations
- Interrupt Controller - Manages interrupt requests
- Clock Circuit - Provides timing reference
- Power Management - Regulates power consumption
Development Environments
Cross-Compilation
Embedded development typically involves cross-compilation, where code is compiled on a host computer for a different target platform.
# Example cross-compilation for ARM Cortex-M
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -o program.elf program.cDevelopment Tools
- Cross-Compiler - GCC, IAR, Keil
- Integrated Development Environment (IDE) - Eclipse, Keil µVision, IAR Embedded Workbench
- Debugger - JTAG/SWD debuggers, GDB
- Emulator/Simulator - QEMU, Proteus
- Programmer - Tools to flash code to target hardware
Memory Organization
Memory Types
- Flash/ROM - Non-volatile storage for program code
- RAM - Volatile storage for variables and stack
- EEPROM - Non-volatile storage for configuration data
- Registers - Memory-mapped hardware control registers
Memory Map Example
// Typical memory map for ARM Cortex-M3
// 0x00000000 - 0x0007FFFF: Code (Flash)
// 0x20000000 - 0x20007FFF: SRAM
// 0x40000000 - 0x400FFFFF: Peripheral registers
// 0xE0000000 - 0xE00FFFFF: Debug registersBasic Embedded C Programming
Startup Code
Embedded programs require special startup code to initialize the system:
// Minimal startup code example
extern unsigned int _etext;
extern unsigned int _data;
extern unsigned int _edata;
extern unsigned int _bss;
extern unsigned int _ebss;
void Reset_Handler(void) {
unsigned int *src, *dst;
// Copy initialized data from Flash to RAM
src = &_etext;
dst = &_data;
while (dst < &_edata) {
*dst++ = *src++;
}
// Clear BSS (zero-initialized data)
dst = &_bss;
while (dst < &_ebss) {
*dst++ = 0;
}
// Call main function
main();
// Should never reach here
while (1);
}Memory Sections
Understanding memory sections is crucial for embedded programming:
// Example of variable placement in different memory sections
// Placed in .data section (initialized data)
int initialized_var = 42;
// Placed in .bss section (zero-initialized data)
int uninitialized_var;
// Placed in .rodata section (read-only data)
const int constant_var = 100;
// Placed in .text section (code)
void example_function(void) {
static int static_var = 10; // .data section
static int static_uninit; // .bss section
}Hardware Abstraction
Register Definitions
Embedded programming often involves direct hardware register access:
// Example register definitions for a hypothetical microcontroller
#define GPIO_BASE_ADDR 0x40020000
#define GPIO_PORTA_DIR (*(volatile unsigned int*)(GPIO_BASE_ADDR + 0x00))
#define GPIO_PORTA_DATA (*(volatile unsigned int*)(GPIO_BASE_ADDR + 0x04))
#define GPIO_PORTA_SET (*(volatile unsigned int*)(GPIO_BASE_ADDR + 0x08))
#define GPIO_PORTA_CLEAR (*(volatile unsigned int*)(GPIO_BASE_ADDR + 0x0C))
// Example usage
void gpio_init(void) {
// Set PORTA pins 0-3 as outputs
GPIO_PORTA_DIR = 0x0F;
}
void gpio_set_pin(int pin) {
// Set specific pin
GPIO_PORTA_SET = (1 << pin);
}
void gpio_clear_pin(int pin) {
// Clear specific pin
GPIO_PORTA_CLEAR = (1 << pin);
}Bit Manipulation
Bit manipulation is fundamental in embedded programming:
#include <stdint.h>
// Bit manipulation macros
#define SET_BIT(reg, bit) ((reg) |= (1U << (bit)))
#define CLEAR_BIT(reg, bit) ((reg) &= ~(1U << (bit)))
#define TOGGLE_BIT(reg, bit) ((reg) ^= (1U << (bit)))
#define CHECK_BIT(reg, bit) (((reg) >> (bit)) & 1U)
// Example usage
volatile uint32_t gpio_port = 0;
void example_bit_operations(void) {
// Set bit 3
SET_BIT(gpio_port, 3);
// Clear bit 1
CLEAR_BIT(gpio_port, 1);
// Toggle bit 2
TOGGLE_BIT(gpio_port, 2);
// Check if bit 3 is set
if (CHECK_BIT(gpio_port, 3)) {
// Bit 3 is set
}
}Interrupt Handling
Interrupts are essential for real-time embedded systems:
// Example interrupt handler
void UART_IRQHandler(void) {
volatile unsigned int status;
// Read interrupt status
status = UART_STATUS_REG;
// Check for receive interrupt
if (status & UART_RX_INTERRUPT) {
// Read received data
char data = UART_DATA_REG;
// Process received data
process_received_data(data);
}
// Clear interrupt flag
UART_STATUS_REG = UART_RX_INTERRUPT;
}
// Enable interrupt
void enable_uart_interrupt(void) {
// Enable UART receive interrupt
UART_CTRL_REG |= UART_RX_INTERRUPT_ENABLE;
// Enable global interrupts
__enable_irq();
}Timing and Delays
Precise timing is often required in embedded systems:
#include <stdint.h>
// Simple software delay (blocking)
void delay_ms(uint32_t ms) {
// Assuming 1MHz clock, adjust for your system
for (uint32_t i = 0; i < ms * 1000; i++) {
// Small delay loop
for (int j = 0; j < 1000; j++) {
__asm__("nop"); // No operation instruction
}
}
}
// Timer-based delay (non-blocking)
volatile uint32_t system_ticks = 0;
void SysTick_Handler(void) {
system_ticks++;
}
void delay_ms_timer(uint32_t ms) {
uint32_t start_ticks = system_ticks;
while ((system_ticks - start_ticks) < ms) {
// Wait for specified time
}
}Practical Examples
LED Blinker Program
#include <stdint.h>
// Simplified register definitions
#define GPIO_PORT_ADDR 0x40020000
#define GPIO_DIR_REG (*(volatile uint32_t*)(GPIO_PORT_ADDR + 0x00))
#define GPIO_DATA_REG (*(volatile uint32_t*)(GPIO_PORT_ADDR + 0x04))
#define LED_PIN 0 // LED connected to pin 0
// Simple delay function
void delay(volatile uint32_t count) {
while (count--) {
__asm__("nop");
}
}
// Initialize GPIO for LED
void led_init(void) {
// Set pin as output
GPIO_DIR_REG |= (1 << LED_PIN);
}
// Turn LED on
void led_on(void) {
GPIO_DATA_REG |= (1 << LED_PIN);
}
// Turn LED off
void led_off(void) {
GPIO_DATA_REG &= ~(1 << LED_PIN);
}
// Toggle LED
void led_toggle(void) {
GPIO_DATA_REG ^= (1 << LED_PIN);
}
int main(void) {
// Initialize LED
led_init();
// Main loop
while (1) {
led_on();
delay(1000000); // Delay
led_off();
delay(1000000); // Delay
}
return 0;
}Summary
Embedded systems programming requires understanding of:
- System Architecture - Microcontrollers vs microprocessors, memory organization
- Development Environment - Cross-compilation, debugging tools
- Memory Management - Different memory sections and their usage
- Hardware Interaction - Direct register access, bit manipulation
- Interrupt Handling - Real-time event processing
- Timing Considerations - Delays, precise timing requirements
These fundamentals form the foundation for more advanced embedded programming topics. The next chapter will cover low-level programming techniques in greater detail.