Embedded SystemUnit 511 min read
Embedded C: Syntax, Pointers, Interrupts & Hardware Control
Unit 5 of Embedded System covers Embedded C programming fundamentals—syntax, memory models, pointers, interrupts, and hardware interfacing—with real-world examples from Nepalese apps like eSewa and Ncell, and hands-on traces for microcontroller programming.
Core Concepts
What is Embedded C?
Embedded C is a subset of the C programming language optimized for microcontrollers (MCUs) and microprocessors (MPUs). It includes:
- Standard C features (variables, loops, functions).
- Microcontroller-specific extensions (register manipulation, bitwise operations).
- Hardware abstraction layers (HAL libraries for peripherals like UART, SPI).
Why Embedded C?
- Efficiency: Minimal runtime overhead (no OS bloat).
- Determinism: Predictable execution for real-time tasks.
- Hardware Control: Direct access to registers and peripherals.
Key Syntax and Memory Models
1. Memory Segments in Embedded Systems
Unlike standard C, embedded systems divide memory into distinct segments:
| Segment | Description | Example Use Case |
|---|---|---|
| Code | Stores executable instructions (FLASH). | Main program, ISRs. |
| Data | Global/static variables (initialized, .data section). |
Sensor calibration tables. |
| BSS | Uninitialized global/static variables (zeroed at startup, .bss section). |
Buffers for UART reception. |
| Stack | Runtime stack (grows downward, typically in RAM). | Function calls, local variables. |
| Heap | Dynamically allocated memory (RAM). | Large buffers for file I/O. |
Visualization:
Example (AVR GCC Linker Script):
MEMORY {
FLASH (rx) : ORIGIN = 0x0000, LENGTH = 32K
RAM (rw) : ORIGIN = 0x2000, LENGTH = 2K
}
SECTIONS {
.text : { *(.text*) } > FLASH
.data : { *(.data*) } > RAM AT > FLASH
.bss : { *(.bss*) } > RAM
}
Pointers and Register Manipulation
2. Pointers in Embedded C
Pointers are critical for:
- Register access (e.g.,
*((volatile uint32_t*)0x40000000)for ARM GPIO). - Memory-mapped I/O (peripherals appear as memory locations).
- Efficient data structures (linked lists for task scheduling).
Example: Controlling an LED via GPIO (STM32):
#define GPIOA_BASE 0x40010800
#define GPIO_ODR_OFFSET 0x14
// Direct register access
*(volatile uint32_t*)(GPIOA_BASE + GPIO_ODR_OFFSET) |= (1 << 5); // Turn on LED (PA5)
// Using HAL library (abstraction)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
Why volatile?
- Prevents compiler optimizations that could skip reads/writes to hardware registers.
- Ensures every read/write is executed (critical for real-time systems).
Interrupts and ISRs
3. Interrupt-Driven Programming
Interrupts allow the MCU to respond to events without polling. Key concepts:
- Interrupt Service Routine (ISR): Code executed when an interrupt occurs.
- Priority Levels: Higher-priority ISRs preempt lower-priority ones.
- Nested Interrupts: Enabling/disabling interrupts during critical sections.
Example: UART Reception ISR (AVR):
#include <avr/interrupt.h>
volatile uint8_t received_data;
ISR(USART_RX_vect) {
received_data = UDR0; // Read data from UART Data Register
// Process data (e.g., send to LCD)
}
Interrupt Flags and Control Registers (AVR):
| Register | Description |
|---|---|
SREG |
Status Register (global interrupt flag: I bit). |
GIMSK |
General Interrupt Mask Register (enables/disables specific interrupts). |
UCSR0B |
UART Control and Status Register (enables RX interrupt). |
Visualization:
sequenceDiagram
participant MCU
participant UART
participant Stack
participant ISR
UART->>MCU: RX Data Arrives (UART RX Pin)
MCU->>Stack: Pushes PC, SR (Context Save)
MCU->>MCU: Disables Global Interrupt (CLI)
MCU->>ISR: Jumps to USART_RX_vect
ISR->>ISR: Processes Data (e.g., Buffer Store)
ISR->>MCU: Enables Global Interrupt (SEI)
ISR->>Stack: Pops PC, SR (Context Restore)
MCU->>MCU: Returns to Main Program
note right of MCU: SREG (I-bit)
note right of ISR: GIMSK (Interrupt Mask)
note right of UART: UCSR0B (RX Interrupt Enable)Hardware Abstraction Layers (HAL)
4. HAL Libraries (STM32 Example)
HAL provides portable APIs for peripherals. Example: Configuring UART in Blocking Mode:
UART_HandleTypeDef huart2;
void SystemClock_Config(void);
static void MX_USART2_UART_Init(void) {
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
HAL_UART_Init(&huart2);
}
void send_char(char c) {
HAL_UART_Transmit(&huart2, &c, 1, HAL_MAX_DELAY);
}
Advantages of HAL:
- Portability: Same code works across STM32 families.
- Error Handling: Built-in status codes (e.g.,
HAL_OK,HAL_TIMEOUT). - Abstraction: Hides low-level register details.
Disadvantages:
- Overhead: Slightly slower than direct register access.
- Complexity: Large codebase for simple tasks.
Real-World Applications in Nepal
1. eSewa (Digital Payment System)
- Idea Used: Interrupt-driven UART communication between the eSewa app (mobile) and the merchant’s POS terminal (MCU-based).
- How:
- The POS terminal (e.g., Raspberry Pi Pico or STM32) uses UART interrupts to handle real-time payment data from the card reader or QR scanner.
- Example ISR: Processes each byte of the payment token without polling, reducing latency.
- Worked Example:
ISR(USART1_RX_vect) { static uint8_t token[32]; static uint8_t index = 0; token[index++] = UDR1; if (index == 32) { verify_payment(token); // Send to eSewa server index = 0; } }
2. Ncell’s IoT-Based Tower Monitoring
- Idea Used: Embedded C + HAL for sensor interfacing (temperature, humidity, signal strength).
- How:
- STM32 MCUs in cell towers read LTE signal strength via ADC interrupts and log data to an SD card.
- Example: ADC interrupt triggers when a new signal strength reading is available.
ISR(ADC_COMP_vect) { uint16_t signal_strength = ADC; log_to_sd_card(signal_strength); }
3. Daraz’s Warehouse Automation (Robotics)
- Idea Used: Pointers for memory-mapped I/O in robotic arms (e.g., Arduino Mega or ESP32).
- How:
- Robotic arms use direct register access (via pointers) to control stepper motors via PWM.
- Example: Setting PWM frequency for a motor driver.
#define TCCR1A (*(volatile uint8_t*)0x2F) // Timer Control Register A (ATmega2560) TCCR1A |= (1 << COM1A1) | (1 << WGM11); // Non-inverting PWM, Fast PWM mode
Worked Example: Traffic Light Controller (Nepal Context)
Scenario: Design an embedded system for a Kathmandu traffic light using an 8-bit AVR microcontroller (ATmega328P).
Requirements:
- 3 traffic lights (red, yellow, green) for each direction.
- Cycle: Green (30s) → Yellow (5s) → Red (30s).
- Use timers and interrupts for precise timing.
Solution:
#include <avr/io.h>
#include <avr/interrupt.h>
#define GREEN_PIN PD5
#define YELLOW_PIN PD6
#define RED_PIN PD7
volatile uint8_t state = 0; // 0: Green, 1: Yellow, 2: Red
volatile uint8_t timer_flag = 0;
ISR(TIMER1_COMPA_vect) {
timer_flag = 1;
}
void setup_timer() {
TCCR1B |= (1 << WGM12); // CTC mode
OCR1A = 15625; // 1s delay (16MHz/256/15625)
TIMSK1 |= (1 << OCIE1A);
TCCR1B |= (1 << CS12); // Prescaler 256
sei(); // Enable global interrupts
}
void set_lights(uint8_t state) {
switch (state) {
case 0: // Green
PORTD |= (1 << GREEN_PIN);
PORTD &= ~((1 << YELLOW_PIN) | (1 << RED_PIN));
break;
case 1: // Yellow
PORTD |= (1 << YELLOW_PIN);
PORTD &= ~((1 << GREEN_PIN) | (1 << RED_PIN));
break;
case 2: // Red
PORTD |= (1 << RED_PIN);
PORTD &= ~((1 << GREEN_PIN) | (1 << YELLOW_PIN));
break;
}
}
int main() {
DDRD |= (1 << GREEN_PIN) | (1 << YELLOW_PIN) | (1 << RED_PIN);
setup_timer();
while (1) {
if (timer_flag) {
timer_flag = 0;
state = (state + 1) % 3;
set_lights(state);
if (state == 1) {
// Yellow lasts only 5s
OCR1A = 78125; // 0.5s delay (for 5s total)
} else {
OCR1A = 15625; // 1s delay (for 30s total)
}
}
}
}
Visualization:
timeline
title Traffic Light Cycle
phase 1: Green (30s) :label: "Direction A"
phase 2: Yellow (5s) :label: "Direction A"
phase 3: Red (30s) :label: "Direction A"
phase 4: Green (30s) :label: "Direction B"
phase 5: Yellow (5s) :label: "Direction B"
phase 6: Red (30s) :label: "Direction B"Common Pitfalls and Best Practices
1. Volatile Keyword Misuse
- Wrong: Forgetting
volatilefor hardware registers → Compiler optimizations may break hardware control. - Right: Always declare hardware registers as
volatile.
2. Stack Overflow
- Cause: Deep recursion or large local variables.
- Fix: Use iterative algorithms or increase stack size in linker script.
3. Interrupt Latency
- Problem: Long ISRs delay other interrupts.
- Solution: Keep ISRs short; offload work to the main loop.
4. Memory Leaks
- Cause: Dynamic memory allocation without
free(). - Fix: Avoid
mallocin ISRs; use static buffers.
Exam Tip
What to Expect in PU Exams:
Syntax Questions:
- Write a snippet to configure a UART peripheral (e.g., STM32 HAL or AVR registers).
- Example: "Write code to set baud rate 9600 for USART0 on ATmega328P."
Pointers and Registers:
- Explain how pointers access memory-mapped I/O (e.g.,
*((volatile uint32_t*)0x40010800)). - Example: "What does
*(volatile uint8_t*)0x25do on an AVR MCU?"
- Explain how pointers access memory-mapped I/O (e.g.,
Interrupts:
- Draw a sequence diagram of an ISR execution flow.
- Example: "Explain the steps when a UART RX interrupt occurs on STM32."
HAL vs. Direct Register Access:
- Compare pros/cons in a table (as shown earlier).
- Example: "When would you use HAL_UART_Init instead of direct register manipulation?"
Real-World Scenarios:
- Design a simple embedded system (e.g., temperature logger, traffic light).
- Example: "Design a system to log humidity every 10s using an STM32 and DHT11 sensor."
Marking Scheme Insight:
- 50% Code Writing: Correct syntax, register names, and
volatileusage. - 30% Explanation: Why a keyword (e.g.,
volatile) is necessary. - 20% Applications: Relate to real-world systems (e.g., eSewa, Ncell).
Based on the PU BE Computer (PU) syllabus for Embedded System (ELX320), unit 5.
Discussion
Loading…