⏳
Loading cheatsheet...
Microcontrollers, interfaces, interrupt handling, RTOS basics and embedded design workflow.
| Feature | 8051 | 8052 |
|---|---|---|
| ROM | 4 KB | 8 KB |
| RAM | 128 Bytes | 256 Bytes |
| Timer | 2 × 16-bit | 3 × 16-bit |
| I/O Ports | 4 × 8-bit (P0–P3) | 4 × 8-bit (P0–P3) |
| Serial Port | 1 UART | 1 UART |
| Interrupt Sources | 5 (2 ext, 3 int) | 6 (2 ext, 4 int) |
| Clock | Up to 12 MHz | Up to 12 MHz |
| Machine Cycle | 12 clock cycles | 12 clock cycles |
| Instruction Set | 111 instructions | 111 instructions |
| Accumulator | A (8-bit) | A (8-bit) |
| DPTR | 16-bit data pointer | 16-bit data pointer |
| Feature | Cortex-M0/M0+ | Cortex-M3 | Cortex-M4 | Cortex-M7 |
|---|---|---|---|---|
| Pipeline | 3-stage | 3-stage | 3-stage | 6-stage (dual issue) |
| Instruction Set | Thumb/Thumb-2 (subset) | Thumb-2 | Thumb-2 + FP | Thumb-2 + DP FP |
| Clock | Up to ~50 MHz | Up to ~120 MHz | Up to ~180 MHz | Up to 600+ MHz |
| DMIPS/MHz | 0.84 | 1.25 | 1.25 | 2.14 (DSP) / 5.0 (FP) |
| FPU | No | Optional | Single-precision | Single + Double |
| DSP | No | No | Yes (MAC, SIMD) | Yes (enhanced) |
| MPU | No (M0) / Optional (M0+) | Yes (8 regions) | Yes (8 regions) | Yes (16 regions) |
| Typical Use | Sensors, IoT | General purpose | DSP, audio, control | High-perf, vision |
| Packaging | Tiny (12K–256K gates) | Medium | Medium-Large | Large |
┌─────────────────────────────────────────────────────────────────┐
│ 8051 MEMORY ARCHITECTURE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 8051 has TWO distinct memory spaces: │
│ │
│ INTERNAL RAM (256 bytes): │
│ ┌────────────┬───────────────────────────────────┐ │
│ │ 0x00–0x1F │ Register Banks R0–R7 (4 banks) │ │
│ │ 0x20–0x2F │ Bit-addressable area (16 bytes) │ │
│ │ │ → 128 individual bit addresses │ │
│ │ 0x30–0x7F │ General purpose RAM (80 bytes) │ │
│ │ 0x80–0xFF │ SFRs (Special Function Registers) │ │
│ │ │ Only in 8052: 0x80-FF also RAM │ │
│ └────────────┴───────────────────────────────────┘ │
│ │
│ KEY SFRs: │
│ ACC (0xE0): Accumulator │
│ B (0xF0): B register (MUL/DIV) │
│ PSW (0xD0): Program Status Word (CY, AC, OV, RS0, RS1) │
│ SP (0x81): Stack Pointer (default: 0x07) │
│ DPL (0x82): Data Pointer Low │
│ DPH (0x83): Data Pointer High │
│ P0 (0x80): Port 0 | P1 (0x90): Port 1 │
│ P2 (0xA0): Port 2 | P3 (0xB0): Port 3 │
│ TCON(0x88): Timer Control │
│ SCON(0x98): Serial Control │
│ IE (0xA8): Interrupt Enable │
│ IP (0xB8): Interrupt Priority │
│ TMOD(0x89): Timer Mode │
│ PCON(0x87): Power Control │
│ │
│ EXTERNAL CODE MEMORY: up to 64 KB (using MOVC) │
│ EXTERNAL DATA MEMORY: up to 64 KB (using MOVX) │
│ INTERNAL CODE MEMORY: 4 KB (using MOVC @ 0000–0FFF) │
└─────────────────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────────────────┐
│ ARM CORTEX-M REGISTER MODEL │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CORE REGISTERS (16 × 32-bit): │
│ R0–R3 : Argument / scratch registers (a.k.a. a1–a4) │
│ R4–R11 : Callee-saved (variable) registers (v1–v8) │
│ R12 (IP): Intra-procedure scratch register │
│ R13 (SP): Stack Pointer │
│ R14 (LR): Link Register (return address) │
│ R15 (PC): Program Counter │
│ │
│ SPECIAL REGISTERS: │
│ xPSR : Program Status Register (APSR, IPSR, EPSR) │
│ Contains: N, Z, C, V flags; exception number; │
│ Thumb bit; saturation flags │
│ PRIMASK : Mask all interrupts except NMI and HardFault │
│ BASEPRI : Mask interrupts below certain priority │
│ FAULTMASK: Mask all faults │
│ CONTROL : Stack alignment, thread/handler mode │
│ │
│ STACK TYPES: │
│ Main Stack (MSP): Used after reset, in handler mode │
│ Process Stack (PSP): Used in thread mode (application code) │
│ Configured via CONTROL register bit[1] │
│ RTOS uses PSP for tasks, MSP for interrupts/handlers │
│ │
│ OPERATION MODES: │
│ Thread Mode : Normal application execution │
│ Handler Mode : Exception / interrupt handling │
│ Privileged : Can access all resources │
│ Unprivileged : Limited access (via MPU) │
└─────────────────────────────────────────────────────────────────┘MOVC for code, MOVX for external data, MOV for internal RAM). ARM Cortex-M uses a unified Von Neumann bus model but Harvard internally. The stack pointer (SP) in ARM is always 8-byte aligned due to EABI requirements. Register banks in 8051 allow fast context switching for interrupts.| Source | Vector Address | Flag | Priority (default) | Description |
|---|---|---|---|---|
| External 0 (INT0) | 0x0003 | IE0 | Highest (1) | P3.2 pin, edge/level triggered |
| Timer 0 Overflow | 0x000B | TF0 | 2 | Timer 0 overflows FFFF→0000 |
| External 1 (INT1) | 0x0013 | IE1 | 3 | P3.3 pin, edge/level triggered |
| Timer 1 Overflow | 0x001B | TF1 | 4 | Timer 1 overflows FFFF→0000 |
| Serial (RX/TX) | 0x0023 | RI/TI | 5 (lowest) | UART receive/transmit complete |
| Exception | Number | Priority | Description |
|---|---|---|---|
| Reset | -3 | Highest (fixed) | Processor reset; starts execution |
| NMI | -2 | Fixed -2 | Non-maskable interrupt |
| HardFault | -1 | Fixed -1 | Bus/usage/memory fault escalation |
| MemManage | 4 | Programmable | MPU violation (configurable) |
| BusFault | 5 | Programmable | Precise/imprecise bus error |
| UsageFault | 6 | Programmable | Undefined instruction, alignment, etc. |
| SVCall | 11 | Programmable | System service call (from SVC instruction) |
| PendSV | 14 | Programmable | Pendable service request (context switch) |
| SysTick | 15 | Programmable | System tick timer (OS scheduler) |
| IRQ 0–240 | 16+ | Programmable | External device interrupts (NVIC) |
┌─────────────────────────────────────────────────────────────────┐
│ INTERRUPT HANDLING — CONCEPTS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ INTERRUPT vs POLLING: │
│ ┌──────────────────┬──────────────────┬──────────────────┐ │
│ │ Feature │ Interrupt │ Polling │ │
│ ├──────────────────┼──────────────────┼──────────────────┤ │
│ │ CPU Utilization │ Efficient │ Wasteful │ │
│ │ Response Time │ Fast │ Slow (depends on │ │
│ │ │ │ poll interval) │ │
│ │ Hardware Needed │ Interrupt ctrl │ None │ │
│ │ Complexity │ Higher │ Simple │ │
│ │ Best For │ Event-driven │ Simple, few I/O │ │
│ └──────────────────┴──────────────────┴──────────────────┘ │
│ │
│ INTERRUPT LATENCY: │
│ Latency = Hardware response + Context save + ISR entry │
│ ARM Cortex-M: 6–12 cycles (deterministic!) │
│ 8051: 3–8 machine cycles (~3–8 µs at 12 MHz) │
│ │
│ ISR RULES: │
│ 1. Keep ISR as SHORT as possible │
│ 2. Do minimal processing — defer to main loop │
│ 3. Save/restore context (compiler handles on ARM) │
│ 4. Clear interrupt flag (auto-cleared on ARM) │
│ 5. Re-enable interrupts if nested ISRs needed │
│ 6. Use volatile for shared variables │
│ 7. Never call blocking functions in ISR │
│ 8. Use tail-chaining (ARM) to reduce overhead │
│ │
│ NESTED INTERRUPTS: │
│ ARM NVIC supports automatic nesting based on priority │
│ Higher priority interrupt preempts lower priority ISR │
│ Only additional registers are stacked (not full context) │
│ │
│ DEBOUNCING (for switch interrupts): │
│ Hardware: RC filter + Schmitt trigger │
│ Software: Timer-based (ignore interrupts for 10–50 ms) │
│ State machine: Detect stable state changes │
└─────────────────────────────────────────────────────────────────┘// ═══════════════════════════════════════════════════════
// 8051 ISR Example (Keil C51)
// ═══════════════════════════════════════════════════════
#include <reg51.h>
volatile unsigned char pulse_count = 0;
void external0_isr(void) interrupt 0 {
pulse_count++; // Called when INT0 (P3.2) goes low
}
void timer0_isr(void) interrupt 1 {
TH0 = 0x3C; // Reload for 50ms at 12 MHz
TL0 = 0xB0;
// Process pulse_count in main loop
}
// ═══════════════════════════════════════════════════════
// ARM Cortex-M ISR (CMSIS)
// ═══════════════════════════════════════════════════════
#include "stm32f4xx.h"
volatile uint32_t system_ticks = 0;
void SysTick_Handler(void) {
system_ticks++;
}
void EXTI0_IRQHandler(void) {
if (EXTI->PR & (1 << 0)) { // Check pending bit
EXTI->PR = (1 << 0); // Clear pending bit
// Handle interrupt
}
}
// Setting up interrupt priority (ARM):
void setup_nvic(void) {
NVIC_SetPriority(EXTI0_IRQn, 2); // Priority 2
NVIC_EnableIRQ(EXTI0_IRQn); // Enable interrupt
}volatile! Without volatile, the compiler may optimize away reads/writes in ISRs. On ARM Cortex-M, interrupt latency is deterministic (6–12 cycles) thanks to hardware stacking. Use NVIC_SetPriority() to configure priorities — lower number = higher priority. For RTOS context switches, PendSVat the lowest priority ensures it doesn't preempt ISRs.| Mode | TMOD Setting | Description | Max Count |
|---|---|---|---|
| Mode 0 | M1=0, M0=0 | 13-bit timer (TL0: 5 bits, TH0: 8 bits) | 2¹³ = 8192 |
| Mode 1 | M1=0, M0=1 | 16-bit timer (TH0:TL0 = full 16-bit) | 2¹⁶ = 65536 |
| Mode 2 | M1=1, M0=0 | 8-bit auto-reload (TL0 counts, TH0 reloads) | 2⁸ = 256 |
| Mode 3 | M1=1, M0=1 | Split: TL0 as 8-bit timer, TH0 as counter | 2⁸ each |
| Feature | Description |
|---|---|
| Prescaler | Divides clock (e.g., /1, /2, /4... /65536) |
| Auto-Reload | 16/32-bit value; counter resets when reached |
| PWM Generation | Compare match → toggle output pin |
| Input Capture | External edge → captures counter value (measure period) |
| Output Compare | Counter matches compare value → action (set/clear/toggle) |
| Encoder Mode | Quadrature encoder interface (direction + speed) |
| One-Pulse Mode | Single pulse output with programmable delay |
| DMA Request | Timer events can trigger DMA transfers |
┌─────────────────────────────────────────────────────────────────┐
│ TIMER CALCULATIONS & EXAMPLES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 8051 TIMER (Mode 1, 16-bit, 12 MHz clock): │
│ Machine cycle = 12 / 12 MHz = 1 µs │
│ Timer increments every 1 µs │
│ Max delay = 65536 × 1 µs = 65.536 ms │
│ │
│ For 1 ms delay: │
│ Initial value = 65536 - 1000 = 64536 = 0xFC18 │
│ TH0 = 0xFC; TL0 = 0x18; │
│ │
│ For 50 ms delay: │
│ Initial value = 65536 - 50000 = 15536 = 0x3CB0 │
│ TH0 = 0x3C; TL0 = 0xB0; │
│ │
│ Longer delays: Use software counter in ISR │
│ 1 second: 50ms × 20 = count to 20 in ISR │
│ │
│ ARM TIMER CALCULATION (e.g., STM32F4 @ 84 MHz, 16-bit): │
│ Timer clock = 84 MHz │
│ Desired: 1 kHz PWM frequency │
│ ARR (auto-reload) = Timer_clock / (Prescaler × f_PWM) │
│ │
│ With Prescaler = 84: │
│ Timer clock after prescaler = 84 MHz / 84 = 1 MHz │
│ ARR = 1 MHz / 1 kHz = 1000 │
│ Resolution = 1/1000 = 0.1% (10-bit effective) │
│ │
│ PWM Duty Cycle: │
│ CCR (compare) = ARR × duty_cycle │
│ 50% duty: CCR = 500 │
│ 25% duty: CCR = 250 │
│ │
│ BAUD RATE GENERATION (8051 using Timer 1, Mode 2): │
│ Baud = (2^SMOD / 32) × (F_osc / 12) / (256 - TH1) │
│ │
│ For 9600 baud at 11.0592 MHz (SMOD=0): │
│ TH1 = 256 - (11059200 / (384 × 9600)) = 256 - 3 = 253 │
│ TH1 = 0xFD │
│ ⚠️ Use 11.0592 MHz crystal for exact baud rates! │
└─────────────────────────────────────────────────────────────────┘// ═══════════════════════════════════════════════════════
// ARM Timer: PWM Setup (STM32 HAL-style)
// ═══════════════════════════════════════════════════════
void setup_pwm(void) {
// Enable TIM2 clock
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// Prescaler: 84 MHz / 84 = 1 MHz timer clock
TIM2->PSC = 83;
// Auto-reload: 1 MHz / 1000 = 1 kHz PWM
TIM2->ARR = 999;
// Compare (duty cycle): 50%
TIM2->CCR1 = 500;
// PWM Mode 1: CCR1 < CNT → active, CCR1 ≥ CNT → inactive
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC1PE; // Preload enable
TIM2->CCER |= TIM_CCER_CC1E; // Output enable
TIM2->CR1 |= TIM_CR1_ARPE; // Auto-reload preload
TIM2->CR1 |= TIM_CR1_CEN; // Start timer
}
// Change duty cycle dynamically:
void set_pwm_duty(uint16_t duty_percent) {
if (duty_percent > 100) duty_percent = 100;
TIM2->CCR1 = (TIM2->ARR + 1) * duty_percent / 100;
}11.0592 MHz crystal for 8051 UART because it divides evenly into standard baud rates. For ARM timers, the ARR/PSC pair determines frequency and the CCR register controls duty cycle for PWM.| Parameter | Typical MCU ADC | Notes |
|---|---|---|
| Type | Successive Approximation (SAR) | Most common in MCUs |
| Resolution | 10–16 bits (12-bit most common) | Higher → slower conversion |
| Channels | 1–24 (multiplexed) | Single ADC core, multiple inputs |
| Sample Rate | 100 kSPS – 5 MSPS | Depends on clock and resolution |
| V_ref | Internal (1.2–3.3V) or external | Lower V_ref → higher resolution per volt |
| Input Range | 0 to V_ref (unipolar) | Differential mode on some MCUs |
| Trigger | Software, timer, external pin | Timer trigger = periodic sampling |
| DMA Support | Yes (on ARM Cortex-M) | ADC → DMA → memory (no CPU load) |
| Parameter | Typical MCU DAC | Notes |
|---|---|---|
| Type | R-2R Ladder or current-steering | Buffered output on some MCUs |
| Resolution | 8–12 bits | 12-bit most common |
| Channels | 1–4 | Independent or dual-channel |
| Output | Voltage output (0–V_ref) | May need external buffer amp |
| Update Rate | 1–10 MSPS | Limited by settling time |
| Waveform Gen | Triangle, noise via DMA | DMA + DAC = arbitrary waveform gen |
┌─────────────────────────────────────────────────────────────────┐
│ EMBEDDED ADC — DESIGN CONSIDERATIONS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ADC RESOLUTION & LSB SIZE: │
│ LSB = V_ref / 2^n │
│ 12-bit, V_ref = 3.3V: LSB = 3.3/4096 = 0.806 mV │
│ 10-bit, V_ref = 3.3V: LSB = 3.3/1024 = 3.22 mV │
│ 16-bit, V_ref = 3.3V: LSB = 0.0504 mV (50 µV) │
│ │
│ ADC ACCURACY vs RESOLUTION: │
│ Resolution: Number of bits (digital representation) │
│ Accuracy: How close reading is to true value (±LSB, INL/DNL)│
│ A 16-bit ADC with ±4 LSB error is less accurate than │
│ a 12-bit ADC with ±0.5 LSB error! │
│ │
│ SAMPLING CONSIDERATIONS: │
│ Nyquist: f_sample ≥ 2 × f_signal │
│ Practical: f_sample ≥ 5–10 × f_signal (alias margin) │
│ Anti-aliasing filter: LPF at f_sample/2 BEFORE ADC │
│ Acquisition time: Time for S/H capacitor to charge │
│ Source impedance: Should be < 10 kΩ for accurate sampling │
│ │
│ DMA WITH ADC (best practice): │
│ 1. Configure ADC for continuous or triggered conversion │
│ 2. Set up DMA channel: ADC_DR → memory buffer │
│ 3. Enable circular mode for continuous acquisition │
│ 4. CPU is free for other tasks! │
│ │
│ VOLTAGE REFERENCE SELECTION: │
│ Internal V_ref: Convenient, but less accurate (±2-5%) │
│ External V_ref: Use precision reference IC (e.g., LM4040) │
│ Ratio-metric: V_ref = V_excitation (for bridge sensors) │
│ │
│ INPUT RANGE EXTENSION: │
│ Voltage divider: Only for measuring BELOW V_ref │
│ Op-amp level shift: For bipolar signals │
│ Instrumentation amp: For bridge sensors (strain gauge) │
│ Differential ADC input: Rejects common-mode noise │
└─────────────────────────────────────────────────────────────────┘ADC + DMA for continuous sampling — the DMA transfers conversion results to memory without CPU intervention. Source impedance matters: a high-impedance sensor may need an op-amp buffer between the sensor and ADC input to allow the sample-and-hold capacitor to charge in time.| Parameter | UART |
|---|---|
| Wires | 2 (TX, RX) + GND |
| Topology | Point-to-point (1:1) |
| Duplex | Full duplex |
| Speed | 9600, 19200, 115200 bps (common) |
| Frame | Start (0) + 5–9 data + parity (opt) + stop (1+) |
| Clock | Async (each side uses own baud rate) |
| Flow Control | RTS/CTS (hardware) or XON/XOFF (software) |
| Error Detection | Parity bit, framing error |
| Level | TTL (0–3.3V/5V) or RS-232 (±12V) |
| Parameter | SPI |
|---|---|
| Wires | 4 (MOSI, MISO, SCK, SS/CS) + GND |
| Topology | Master + multiple slaves (CS selects) |
| Duplex | Full duplex (simultaneous TX/RX) |
| Speed | Up to 80 MHz (depends on MCU and traces) |
| Clock | Synchronous (master generates clock) |
| Clock Polarity (CPOL) | 0: idle low, 1: idle high |
| Clock Phase (CPHA) | 0: sample on 1st edge, 1: sample on 2nd |
| Modes | 0 (0,0), 1 (0,1), 2 (1,0), 3 (1,1) |
| Chip Select | Active LOW; each slave needs own CS line |
| Parameter | I²C |
|---|---|
| Wires | 2 (SDA, SCL) + GND (open-drain with pull-ups) |
| Topology | Multi-master, multi-slave bus |
| Addressing | 7-bit (128 addr) or 10-bit (1024 addr) |
| Speed | Standard: 100 kHz, Fast: 400 kHz, Fast+: 1 MHz, HS: 3.4 MHz |
| Duplex | Half duplex |
| Clock | Synchronous (master generates SCL) |
| ACK/NACK | Every byte ACKed by receiver; NACK = stop/error |
| arbitration | Multi-master: winner continues, loser backs off |
| Pull-up Value | Typically 4.7 kΩ (standard) or 2.2 kΩ (fast) |
| Parameter | CAN |
|---|---|
| Wires | 2 (CAN_H, CAN_L) — differential |
| Topology | Multi-master bus (up to ~110 nodes) |
| Addressing | Message-based (no node addresses) |
| Speed | CAN: 1 Mbps (25m), CAN FD: 5+ Mbps |
| Frame Types | Data frame, Remote frame, Error frame, Overload frame |
| Arbitration | Identifier-based (lower ID = higher priority) |
| Error Handling | CRC + ACK; error-active/error-passive/bus-off states |
| Physical | Differential pair (dominant/recessive) |
| Used In | Automotive, industrial, medical equipment |
┌─────────────────────────────────────────────────────────────────┐
│ COMMUNICATION PROTOCOL COMPARISON │
├─────────────────────────────────────────────────────────────────┤
│ │
│ SPEED COMPARISON: │
│ UART: ~0.1 Mbps (typical, 115200) │
│ I²C: ~0.1–3.4 Mbps (max 3.4 MHz clock) │
│ SPI: ~1–80 Mbps (depends on clock) │
│ CAN: 1 Mbps (classic), 5+ Mbps (CAN FD) │
│ USB: 12 Mbps (FS), 480 Mbps (HS), 5 Gbps (SS) │
│ Ethernet: 10/100/1000 Mbps │
│ │
│ WIRE COUNT: │
│ UART: 2 (simplest point-to-point) │
│ I²C: 2 (but needs pull-ups; shared bus = fewer total wires) │
│ SPI: 4 + n (n = number of slaves for CS) │
│ CAN: 2 (shared bus, no addressing overhead) │
│ │
│ WHEN TO USE WHAT: │
│ UART: Simple point-to-point (MCU to PC, GPS module, Bluetooth)│
│ SPI: High-speed peripherals (SD card, display, flash, ADC) │
│ I²C: Sensors, EEPROM, RTC, multi-device bus (fewer wires) │
│ CAN: Automotive/industrial (robust, multi-master, error tol)│
│ │
│ I²C ADDRESS FORMATS: │
│ 7-bit: [S] [7-bit addr] [R/W] [ACK] [data...] [P] │
│ 10-bit: [S] [11110 + 2 MSBs addr] [R/W] [ACK] │
│ [8 LSBs addr] [ACK] [data...] [P] │
│ │
│ SPI DATA EXCHANGE: │
│ MOSI: Master Out → Slave In │
│ MISO: Master In ← Slave Out │
│ SCK: Clock (from master) │
│ CS: Chip Select (active LOW) │
│ Every clock cycle: 1 bit sent + 1 bit received │
│ Full duplex: TX and RX happen simultaneously │
└─────────────────────────────────────────────────────────────────┘I²C uses only 2 wires total regardless of slave count (addressed), making it ideal for connecting many sensors. CAN is built for noisy environments — its differential signaling and robust error handling make it standard in automotive systems. Remember: I²C uses open-drain outputs and requires pull-up resistors.| Concept | Description |
|---|---|
| Task | Independent thread of execution with its own stack and priority |
| Preemptive Scheduling | Higher priority task preempts lower priority immediately |
| Cooperative Scheduling | Tasks yield CPU voluntarily (no preemption) |
| Tick / Time Slice | Periodic timer interrupt (1–10 ms) for task switching |
| Context Switch | Save/restore registers + stack pointer when switching tasks |
| Idle Task | Lowest priority task; runs when no other task is ready |
| Deadline | Hard (must meet) vs Soft (best effort) real-time |
| Feature | API / Concept |
|---|---|
| Task Create | xTaskCreate(function, name, stackSize, params, priority, &handle) |
| Task Delete | vTaskDelete(handle) |
| Delay | vTaskDelay(pdMS_TO_TICKS(ms)) — yields CPU |
| Queue Create | xQueueCreate(length, itemSize) |
| Queue Send | xQueueSend(queue, &item, ticksToWait) |
| Queue Receive | xQueueReceive(queue, &buffer, ticksToWait) |
| Semaphore | xSemaphoreCreateBinary() / CreateMutex() / CreateCounting() |
| ISR-Safe Send | xQueueSendFromISR(queue, &item, &higherPriorityTaskWoken) |
// ═══════════════════════════════════════════════════════
// FreeRTOS — Producer-Consumer with Queue
// ═══════════════════════════════════════════════════════
#include "FreeRTOS.h"
#include "queue.h"
QueueHandle_t xSensorQueue;
// Producer Task (reads sensor, sends to queue)
void vSensorTask(void *pvParameters) {
uint16_t sensor_value;
while (1) {
sensor_value = read_adc();
xQueueSend(xSensorQueue, &sensor_value, pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(50)); // Sample every 50ms
}
}
// Consumer Task (processes sensor data)
void vProcessingTask(void *pvParameters) {
uint16_t received_value;
while (1) {
if (xQueueReceive(xSensorQueue, &received_value,
portMAX_DELAY) == pdTRUE) {
process_data(received_value);
}
}
}
// Main — create tasks and queue
int main(void) {
xSensorQueue = xQueueCreate(10, sizeof(uint16_t));
xTaskCreate(vSensorTask, "Sensor", 256, NULL, 2, NULL);
xTaskCreate(vProcessingTask, "Process", 512, NULL, 1, NULL);
vTaskStartScheduler(); // Start RTOS
while (1); // Should never reach here
}┌─────────────────────────────────────────────────────────────────┐
│ RTOS — KEY CONCEPTS & PITFALLS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PRIORITY INVERSION: │
│ Low-priority task holds a mutex │
│ High-priority task needs the mutex → BLOCKED │
│ Medium-priority task preempts low → HIGH STARVED! │
│ │
│ Solution: Priority Inheritance Protocol │
│ When high-priority task waits on mutex held by low, │
│ low-priority task temporarily inherits high priority │
│ → finishes quickly → releases mutex → high runs │
│ FreeRTOS: Enable in FreeRTOSConfig.h: configUSE_MUTEXES │
│ configUSE_PRIORITY_INHERITANCE = 1 │
│ │
│ DEADLOCK: │
│ Task A holds mutex 1, waits for mutex 2 │
│ Task B holds mutex 2, waits for mutex 1 │
│ → Both blocked forever! │
│ Prevention: Always acquire mutexes in same order │
│ Or use timeout: xSemaphoreTake(mutex, timeout) │
│ │
│ SHARED RESOURCE PROTECTION: │
│ 1. Mutex: Best for protecting shared resources │
│ 2. Binary Semaphore: For signaling between tasks/ISRs │
│ 3. Counting Semaphore: For resource pools (e.g., N buffers) │
│ 4. Critical Section: taskENTER_CRITICAL() / taskEXIT_CRITICAL│
│ Very short sections only (disables interrupts!) │
│ │
│ MEMORY MANAGEMENT (FreeRTOS): │
│ heap_1: Simplest, no free (only malloc) │
│ heap_2: Allows free, possible fragmentation │
│ heap_3: Wraps standard malloc/free (not thread-safe by def) │
│ heap_4: Coalesces free blocks (best for most cases) │
│ heap_5: Multiple memory regions (non-contiguous RAM) │
│ │
│ TICK RATE: │
│ configTICK_RATE_HZ = 1000 → 1 ms tick │
│ Minimum delay: 1 tick (1 ms) │
│ For sub-ms timing: use hardware timers (SysTick, TIMx) │
│ vTaskDelayUntil() for periodic tasks (better than vTaskDelay)│
└─────────────────────────────────────────────────────────────────┘vTaskDelay() for precise periodic tasks. UsevTaskDelayUntil() which accounts for processing time and maintains exact period. For ISR-to-task communication, always use FromISR variants of FreeRTOS APIs. Enable priority inheritanceto prevent priority inversion — it's the #1 RTOS bug.| Type | Volatile? | Retention | Endurance | Use Case |
|---|---|---|---|---|
| Flash | No | 20+ years | 10K–100K erase cycles | Code storage, constant data |
| EEPROM | No | 20+ years | 100K–1M erase cycles | Config, calibration data |
| SRAM | Yes | While powered | Infinite | Stack, heap, variables |
| Battery-backed SRAM | No (with battery) | Battery life | Infinite | RTC, critical data |
| FRAM (FeRAM) | No | 10+ years | 10¹⁵ cycles | Fast non-volatile (TI MSP430) |
| MRAM | No | 20+ years | Unlimited | Non-volatile RAM alternative |
| Feature | Description |
|---|---|
| Page Size | Smallest erasable unit (typically 1–4 KB) |
| Sector/Block | Group of pages; erasable together |
| Mass Erase | Erase entire flash chip at once |
| Write Granularity | Typically byte or half-word (no erase needed) |
| Program Time | ~10–50 µs per word |
| Erase Time | ~20–40 ms per page |
| Read Speed | Zero wait-state at lower clocks; may need wait states at high MHz |
| Flash Controller | Handles programming/erasing; may require unlock sequence |
┌─────────────────────────────────────────────────────────────────┐
│ EMBEDDED MEMORY LAYOUT (ARM Cortex-M) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ LINKER MEMORY MAP (typical): │
│ ┌─────────────────────┐ HIGH ADDRESS │
│ │ Stack (grows down) │ ← MSP or PSP │
│ ├─────────────────────┤ │
│ │ Heap (grows up) │ ← malloc() │
│ ├─────────────────────┤ │
│ │ .bss (zero-init) │ ← Uninitialized globals/locals │
│ ├─────────────────────┤ │
│ │ .data (initialized) │ ← Initialized globals (copied from │
│ │ │ flash at startup) │
│ ├─────────────────────┤ │
│ │ │ │
│ │ SRAM │ ← INTERNAL RAM │
│ │ │ │
│ ├─────────────────────┤ │
│ │ .text (code) │ ← FLASH │
│ │ .rodata (constants) │ ← FLASH │
│ │ .isr_vector │ ← FLASH (vector table at 0x00000000) │
│ └─────────────────────┘ LOW ADDRESS (0x00000000) │
│ │
│ STACK SIZE GUIDELINES: │
│ Minimum: Check worst-case stack depth with watermarking │
│ Typical: 256B–2KB per task (RTOS) │
│ Debug: Fill stack with known pattern (0xCC), count untouched│
│ ARM: Stack must be 8-byte aligned (AAPCS/EABI) │
│ Stack overflow causes HardFault (enable stack overflow check)│
│ │
│ COMMON MEMORY ISSUES: │
│ Stack overflow → HardFault, corrupt data │
│ Heap fragmentation → malloc fails after many alloc/free │
│ Buffer overflow → security vulnerability │
│ Uninitialized pointer → random crashes │
│ Solution: static analysis, MPU, stack canaries │
└─────────────────────────────────────────────────────────────────┘.data section is initialized by the startup code which copies it from flash to RAM. The .bss section is zeroed by startup. In RTOS, each task gets its own stack — always verify stack size is sufficient.| Mode | CPU | Clocks | Flash | Peripherals | Wake-up | Current |
|---|---|---|---|---|---|---|
| Run | Active | All ON | ON | ON | — | ~10–100 mA |
| Sleep | OFF | ON | ON | ON | Interrupt | ~1–10 mA |
| Deep Sleep | OFF | OFF (HSI off) | OFF | OFF (selected ON) | Interrupt or EXTI | ~1–100 µA |
| Standby | OFF | ALL OFF | OFF | OFF | RTC, WKUP pin | ~0.5–5 µA |
| Shutdown | OFF | ALL OFF | OFF | OFF | WKUP pin, BOR | ~0.01–0.5 µA |
| Technique | Savings | Description |
|---|---|---|
| Clock Gating | 50–90% | Disable clock to unused peripherals (RCC register) |
| Dynamic Voltage Scaling | ∝ V² | Lower voltage when CPU doesn't need max speed |
| Frequency Scaling | ∝ f | Run CPU slower when not time-critical |
| Sleep Modes | 99%+ | Put MCU to sleep between events |
| Peripheral Off | Varies | Turn off ADC, UART, SPI when not needed |
| GPIO Config | Significant | Configure unused GPIOs as analog (lowest leakage) |
| DMA Usage | CPU idle | Use DMA for transfers → CPU can sleep |
| Event-Driven | Significant | Wake on interrupt instead of polling |
| Feature | Independent (IWDG) | Window (WWDG) |
|---|---|---|
| Clock Source | Internal LSI (~32–40 kHz) | APB bus clock |
| Precision | Low (~±15%) | High (bus clock accuracy) |
| Timeout Range | ms to seconds | µs to ms (shorter) |
| Window Feature | No (any kick works) | Yes (must kick within window) |
| Purpose | Recover from software hang/crash | Detect timing deviations |
| Reset | Generates system reset if not kicked | Generates reset if kicked too early/late |
| Typical Use | Safety-critical systems | Strict timing requirements |
// ═══════════════════════════════════════════════════════
// STM32 Low Power: Sleep + Wake on UART
// ═══════════════════════════════════════════════════════
void enter_low_power_mode(void) {
// 1. Disable unused peripherals
RCC->AHB1ENR &= ~RCC_AHB1ENR_GPIOBEN; // Disable GPIOB clock
// 2. Configure unused GPIOs as analog (lowest leakage)
GPIOA->MODER = 0xEBFFFFFF; // PA0, PA1 as analog
// 3. Configure wake-up source (UART RXNE interrupt)
USART1->CR1 |= USART_CR1_RXNEIE; // Enable RX interrupt
EXTI->IMR |= (1 << 10); // Enable EXTI line 10
// 4. Enable WIC (Wakeup Interrupt Controller) for deep sleep
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // Deep sleep mode
// 5. Enter sleep (WFI = Wait For Interrupt)
__WFI(); // CPU sleeps here until interrupt wakes it
// Execution continues here after wake-up
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; // Back to normal
}
// ═══════════════════════════════════════════════════════
// Independent Watchdog Setup
// ═══════════════════════════════════════════════════════
void setup_iwdg(uint32_t timeout_ms) {
// Enable LSI (LSI oscillator for IWDG)
RCC->CSR |= RCC_CSR_LSION;
while (!(RCC->CSR & RCC_CSR_LSIRDY)); // Wait for LSI ready
IWDG->KR = 0x5555; // Enable register access
IWDG->PR = 4; // Prescaler /64
IWDG->RLR = timeout_ms * 40 / 64; // Reload value
IWDG->KR = 0xCCCC; // Start watchdog
}
void kick_watchdog(void) {
IWDG->KR = 0xAAAA; // Reload counter (feed the dog!)
}┌─────────────────────────────────────────────────────────────────┐
│ BATTERY LIFE CALCULATIONS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ AVERAGE CURRENT (duty-cycled): │
│ I_avg = (I_active × t_active + I_sleep × t_sleep) / │
│ (t_active + t_sleep) │
│ │
│ EXAMPLE: │
│ Active: 10 mA for 100 ms (sensor read + transmit) │
│ Sleep: 5 µA for 9900 ms (between measurements) │
│ I_avg = (10mA × 0.1s + 5µA × 9.9s) / 10s │
│ = (1mA·s + 0.0495mA·s) / 10s │
│ = 1.0495 / 10 = 0.10495 mA ≈ 105 µA │
│ │
│ BATTERY LIFE: │
│ Life = Battery capacity (mAh) / I_avg (mA) │
│ CR2032: 220 mAh → 220 / 0.105 = 2095 hours ≈ 87 days │
│ AA: 2000 mAh → 2000 / 0.105 = 19048 hours ≈ 794 days │
│ LiPo 1000mAh → 1000 / 0.105 = 9524 hours ≈ 397 days │
│ │
│ POWER DISSIPATION: │
│ P_dynamic = C × V² × f │
│ P_static = V × I_leakage │
│ P_total = P_dynamic + P_static │
│ │
│ REGULATOR EFFICIENCY: │
│ LDO: η = V_out / V_in (low I_q, but wasteful for big ΔV) │
│ Buck: η ≈ 85–95% (much better for high V_in/V_out ratio) │
│ Boost: η ≈ 80–90% (steps up voltage) │
│ Buck-Boost: η ≈ 80–90% (handles V_in above or below V_out) │
│ │
│ Example: 5V input → 3.3V LDO │
│ η = 3.3/5.0 = 66% → wastes 34% as heat! │
│ Use buck converter instead: 3.3V buck from 5V → ~90% eff │
└─────────────────────────────────────────────────────────────────┘__WFI() instruction puts the CPU to sleep until an interrupt wakes it. For battery-powered IoT devices, duty-cycling (sleep most of the time, wake briefly to sample/transmit) can extend battery life from days to years. Always include a watchdog timer in production firmware to recover from unexpected hangs.