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).
VariablesLoopsFunctionsStandard C FeaturesRegister ManipulationBitwise OperationsInterrupt Service Routines (ISRs)MCU-Specific ExtensionsUARTSPIGPIOHardware Abstraction Layers (HAL)Embedded C
Hierarchy of Embedded C components

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

Secure Payment ProcessingUART Communication with POSeSewaSPI Sensor InterfaceInterrupt-Driven Data LoggingNcell IoT Tower MonitoringMotor Control via PWMMemory-Efficient BufferingDaraz Warehouse RoboticsNepal Embedded Applications
Embedded system applications in Nepal's tech sector

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 volatile for 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 malloc in ISRs; use static buffers.

Exam Tip

What to Expect in PU Exams:

  1. 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."
  2. Pointers and Registers:

    • Explain how pointers access memory-mapped I/O (e.g., *((volatile uint32_t*)0x40010800)).
    • Example: "What does *(volatile uint8_t*)0x25 do on an AVR MCU?"
  3. Interrupts:

    • Draw a sequence diagram of an ISR execution flow.
    • Example: "Explain the steps when a UART RX interrupt occurs on STM32."
  4. 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?"
  5. 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 volatile usage.
  • 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…