Wednesday, 15 October 2025

TDD for Embedded C: Project Layout + Unity & CMock (practical)

Keep tests separate from production, wire up a tiny runner, and mock hardware at the header boundary. Here’s the minimal, repeatable setup that works with CubeIDE/MDK/IAR or Make.


๐Ÿ“ Project anatomy (add, don’t tangle)

/Core/Src -> app code (e.g., main.c, modules) /Drivers -> HAL/CMSIS or your drivers /Build, /Debug -> IDE artifacts (auto-generated) /TDD -> ✅ tests live here (excluded from production build) /tests -> test_*.c (Unity tests) /unity -> Unity sources (vendor drop-in) /cmock -> CMock sources + generated mocks /mocks -> <auto-generated> mock_*.c/.h

๐Ÿ‘‰ In CubeIDE: right-click TDDResource Configurations → Exclude from Build… (all configs).


✅ Unity: tiny test + runner

// TDD/tests/test_my_module.c #include "unity.h" #include "my_module.h" void setUp(void) {} void tearDown(void) {} void test_get_value_returns_5(void) { TEST_ASSERT_EQUAL_UINT8(5, get_value()); } // TDD/tests/test_runner.c #include "unity.h" extern void test_get_value_returns_5(void); int main(void){ UNITY_BEGIN(); RUN_TEST(test_get_value_returns_5); return UNITY_END(); }

Build a test target that compiles: unity/*.c, tests/*.c, and module(s) under test.
(Optionally -DUNIT_TEST for test-only code paths.)


๐Ÿ”Œ CMock: mock the hardware boundary

// Drivers/driver.h (production header to be mocked) uint8_t driver_get_byte(void); // TDD/tests/test_sensor.c #include "unity.h" #include "mock_driver.h" // generated by CMock from driver.h #include "sensor.h" // module under test void test_sensor_reads_driver_value(void){ driver_get_byte_ExpectAndReturn(0x2A); TEST_ASSERT_EQUAL_UINT8(0x2A, sensor_read()); }
# TDD/cmock.yml (example CMock config) :mock_path: "TDD/mocks" :plugins: [expect, ignore, return_thru_ptr] :enforce_strict_ordering: true

Run CMock over headers → emits mock_driver.* into /TDD/mocks. Compile tests against mocks, not real drivers.


๐Ÿงช Build wiring (Make-ish sketch)

TEST_SRCS := TDD/tests/*.c TDD/unity/*.c TDD/mocks/*.c Core/Src/sensor.c PROD_SRCS := $(filter-out TDD/%,$(wildcard Core/Src/*.c Drivers/**/*.c)) # test target links TEST_SRCS; production target links PROD_SRCS only

In IDEs, make a Test build config that includes TDD/… and excludes hardware files you’re mocking.


⚡ Pro Tips

  • Keep tests fast & pure: no HAL clocks/ISR—mock them.

  • One behavior per test; readable names: test_<unit>_<behavior>_<expectation>.

  • Generate mocks from headers you own (stable API).

  • Gate test-only code with #ifdef UNIT_TEST.

  • Failures first: write the test, see it fail, make it pass, refactor.

๐ŸŽฏ Conclusion

Separate tests, add Unity runner, mock hardware with CMock—now your embedded code is testable without boards, fast, and refactor-friendly.


Written By: Musaab Taha


This article was improved with the assistance of AI.

SEGGER SystemView + FreeRTOS: Install, Integrate, Record (the clean way)

SystemView lets you see your RTOS: who ran, when, how long, and why. You’ll install the host app, drop target sources into your project, enable timestamping, and then record—either real-time (via RTT/J-Link) or single-shot (dump a RAM buffer).


✅ Minimal wiring (headers + start)

// main.c #include "SEGGER_SYSVIEW.h" #include "SEGGER_SYSVIEW_Conf.h" // from target sources int main(void) { // ... HAL/init ... SEGGER_SYSVIEW_Conf(); // configure app/device names, RTT, etc. SEGGER_SYSVIEW_Start(); // begin recording (single-shot or live) // create tasks... vTaskStartScheduler(); }

⏱ Enable precise timestamps (Cortex-M3/M4/M7)

// Enable DWT cycle counter (timestamps) #define DEMCR (*(volatile uint32_t*)0xE000EDFC) #define DWT_CTRL (*(volatile uint32_t*)0xE0001000) #define DWT_CYCCNT (*(volatile uint32_t*)0xE0001004) static inline void dwt_time_start(void){ DEMCR |= (1u<<24); // TRCENA DWT_CYCCNT = 0; // reset DWT_CTRL |= 1u; // CYCCNTENA }

Call dwt_time_start() early in main() before starting the scheduler.


⚙️ FreeRTOS config touch-ups

// FreeRTOSConfig.h #define configUSE_TRACE_FACILITY 1 #define configTICK_RATE_HZ 1000 // 1 ms ticks (typical)

(If you don’t use software timers, you can set configUSE_TIMERS to 0 to skip the timer task.)


๐Ÿ” Real-time vs. ๐Ÿ–ผ Single-shot

  • Real-time (continuous): J-Link + RTT streams events to the PC as they happen.

  • Single-shot: Fill the RTT buffer in RAM, pause, dump it to a .SVDat file, then File → Load Data in SystemView.

Tip (single-shot): In your IDE, locate the RTT control symbol (e.g., _SEGGER_RTT), open Memory Browser at its address, and Export RAW for the configured buffer size (e.g., 4096 bytes).


๐Ÿ—ฃ Sending messages to the timeline (cleanly)

#include <stdio.h> static char msg[100]; snprintf(msg, sizeof(msg), "Hello from %s", "Task-1"); SEGGER_SYSVIEW_PrintfTarget("%s", msg); // appears in Terminal & Timeline

๐Ÿงน Avoid garbled prints under pre-emption

Shared sinks (ITM/UART) need a mutex or switch to co-operative segments using taskYIELD() after complete messages.


⚡ Pro Tips

  • Increase SEGGER_SYSVIEW_RTT_BUFFER_SIZE (e.g., 4096) if you drop events.

  • Define the right core in SystemView config (e.g., Cortex-M3/M4/M7).

  • Keep include paths for SEGGER/Config, SEGGER/OS, and SEGGER target folders.

  • In Cube projects, put changes inside USER CODE BEGIN/END blocks.

๐ŸŽฏ Conclusion

Wire up SystemView once, and you’ll never guess again—timestamps, context switches, ISR time, CPU load are all on one timeline.


Written By: Musaab Taha


This article was improved with the assistance of AI.

32-bit Bitwork: Extract Even Bits, Set UART Baud, Big-Endian Bytes

Tiny, safe helpers you’ll use everywhere. 0-based indexing; LSB = 0. Keep them static inline in a header for zero-overhead reuse.


✅ Extract Even Bits (0,2,4,…,30) → packed

Compress even-positioned bits into consecutive bits (LSB-first). Result fits in 16 bits.

#include <stdint.h> static inline uint32_t extract_even_bits(uint32_t reg){ uint32_t out = 0; unsigned k = 0; for (unsigned i = 0; i < 32; i += 2) out |= ((reg >> i) & 1u) << k++; return out; } // Examples: 0b01010101 → 0b1111, 0b10101010 → 0b0000

๐Ÿ”ง Set UART Baud Field (bits 8..11)

Update just the 4-bit baud field; leave everything else unchanged.

#include <stdint.h> static inline uint32_t set_baud_rate(uint32_t reg, uint8_t baud){ uint32_t m = (uint32_t)0xFu << 8; return (reg & ~m) | (((uint32_t)baud & 0xFu) << 8); } // Example: reg=0x00000000, baud=0b1010 → 0x00000A00

๐Ÿ“ฆ Convert 32-bit to Big-Endian Bytes

Store MSB first for wire protocols.

#include <stdint.h> static inline void to_big_endian(uint32_t v, uint8_t b[4]){ b[0]=(uint8_t)(v>>24); b[1]=(uint8_t)(v>>16); b[2]=(uint8_t)(v>>8); b[3]=(uint8_t)v; } // Example: 0x12345678 → {0x12,0x34,0x56,0x78}

⚡ Pro Tips

  • Use unsigned shifts (1u, uint32_t) to avoid UB.

  • Even-bit extract yields at most 16 bits → you can cast to uint16_t.

  • For HW registers, use volatile and guard shared sinks (UART/ITM) with a mutex.

๐ŸŽฏ Conclusion

Three drop-ins: slice even bits, tweak a 4-bit field, and serialize to network order—clean, predictable, portable.

Written By: Musaab Taha


This article was improved with the assistance of AI.

Tuesday, 14 October 2025

FreeRTOS Scheduling: Start, Preempt, Yield (and What’s Happening in RAM)

Your tasks are in Ready; the scheduler decides who runs. Here’s the practical path to start it, pick a policy (pre-emptive vs co-operative), and understand the heap/TCB story behind xTaskCreate().


✅ Start the Scheduler

vTaskStartScheduler() launches the kernel; it never returns unless heap is insufficient to create the Idle (and timer) task.

configASSERT(xTaskCreate(Task1,"T1",256,NULL,2,NULL)==pdPASS); configASSERT(xTaskCreate(Task2,"T2",256,NULL,2,NULL)==pdPASS); vTaskStartScheduler(); // returns only on failure

⚙️ Scheduling Policies

  • Pre-emptive (configUSE_PREEMPTION 1): the running task can be replaced on ticks or when a higher-priority task becomes ready.

  • Co-operative (configUSE_PREEMPTION 0): tasks voluntarily give up the CPU with taskYIELD() (no forced pre-emption).

// FreeRTOSConfig.h #define configUSE_PREEMPTION 1 #define configTICK_RATE_HZ 1000 // 1 ms tick

๐Ÿ” Round-Robin & ⏫ Priority-Based

  • Round-robin: equal-priority ready tasks share the CPU in time slices tied to the RTOS tick.

  • Priority-based: the highest priority ready task runs; when it blocks, the next highest takes over; if a higher priority task unblocks, it pre-empts immediately.


๐Ÿค Co-operative Example (clean prints)

static void Task1(void* pv){ for(;;){ puts("T1"); taskYIELD(); } } static void Task2(void* pv){ for(;;){ puts("T2"); taskYIELD(); } }

If your output looks garbled under pre-emption, protect the shared sink (UART/ITM) with a mutex.


๐Ÿง  Behind the Scenes: Heap, TCB, Stacks

  • xTaskCreate() dynamically allocates the TCB and the task’s private stack from the RTOS heap and then links the TCB into the Ready list.

  • Heap size is set by configTOTAL_HEAP_SIZE; many projects use heap4.c (malloc/free over a single heap array).

  • On Cortex-M, task stacks are tracked with PSP (process stack pointer); the TCB stores TopOfStack.

// FreeRTOSConfig.h #define configTOTAL_HEAP_SIZE (75*1024) // tune to your MCU RAM

⚡ Pro Tips

  • Keep for(;;){ … vTaskDelay(...) } or block on queues; avoid busy loops.

  • Few priority levels → fewer context switches (less RAM/time overhead).

  • Always check xTaskCreate() and enable overflow/malloc-fail hooks.

  • Put app code inside USER CODE BEGIN/END in Cube projects to survive regen.

๐ŸŽฏ Conclusion

Choose pre-emptive for responsiveness or co-operative for deterministic prints, start the scheduler, and size the heap/stack sanely, your tasks will play nicely and your logs will, too.


Written By: Musaab Taha


This article was improved with the assistance of AI.

Bit Fields in 32-bit Registers: Extract, Clear, Replace (Safely)

Working with control/status registers means precise bit slicing. Here are three tiny, safe helpers for 32-bit registers (0-based indexing; LSB=0) with a mask utility that avoids undefined shifts.



✅ Extract Field

Pull len bits starting at pos.

#include <stdint.h> static inline uint32_t u32_mask(uint8_t len){ return len>=32 ? 0xFFFFFFFFu : ((1u<<len)-1u); } static inline uint32_t extract_field(uint32_t reg, uint8_t pos, uint8_t len){ return (reg >> pos) & u32_mask(len); }

Examples:
reg=…1011xxxx, pos=28, len=4 → 0b1011 · reg=0x000000FF, pos=0, len=8 → 0xFF


❌ Clear Bits

Zero len bits starting at pos (others untouched).

static inline uint32_t clear_bits(uint32_t reg, uint8_t pos, uint8_t len){ return reg & ~(u32_mask(len) << pos); }

Examples:
0xFF, pos=4, len=4 → 0x0F · 0x0F, pos=0, len=2 → 0x0C


๐Ÿ”„ Replace Field

Overwrite len bits at pos with val (masked-in).

static inline uint32_t replace_field(uint32_t reg, uint32_t val, uint8_t pos, uint8_t len){ uint32_t m = u32_mask(len) << pos; return (reg & ~m) | ((val & u32_mask(len)) << pos); }

Examples:
reg=0xFF, val=0, pos=4, len=4 → 0x0F · reg=0x0F, val=0b10, pos=1, len=2 → 0x0D


⚡ Pro Tips

  • Guard inputs: pos < 32 and len > 0 && pos + len <= 32.

  • Always shift with 1u (unsigned) and use uint32_t.

  • For HW registers, use volatile and consider atomic sections if ISRs/DMA also touch the register.

  • Keep these in a header (static inline) for zero-overhead reuse.

๐ŸŽฏ Conclusion

These one-liners let you slice, zero, and patch register fields without collateral damage—portable, predictable, and ISR-friendly.


Written By: Musaab Taha


This article was improved with the assistance of AI.

Monday, 13 October 2025

FreeRTOS Tasks: Create, Run, and Delete (the Practical Way)

In RTOS land, you split your app into tasks, tiny, schedulable functions that each do one job (e.g., read sensor, refresh display, handle keys). Here’s the clean, code-first way to write handlers, create tasks, pass data, and mind stacks/priorities.


✅ Task Handler (a schedulable function)

A task is just a C function that usually runs forever. If you ever leave the loop, delete yourself first.

#include "FreeRTOS.h" #include "task.h" static void TaskFn(void *pv) { for (;;) { // do work vTaskDelay(pdMS_TO_TICKS(10)); } vTaskDelete(NULL); // if you ever break the loop }

๐Ÿ—️ Create a Task with xTaskCreate()

Pass handler, name, stack depth in words (not bytes), a parameter, priority, and an optional handle.

#include "FreeRTOS.h" #include "task.h" TaskHandle_t t1; BaseType_t ok = xTaskCreate(TaskFn, "Task-1", 200, // stack words (e.g., 200*4 B on Cortex-M) NULL, // pvParameters 2, // priority (0..configMAX_PRIORITIES-1) &t1); // out handle configASSERT(ok == pdPASS);

๐Ÿ“จ Passing Data to a Task

Give each task its own message via pvParameters. Cast back inside.

static void PrinterTask(void *pv) { const char *msg = (const char *)pv; for (;;) { puts(msg); vTaskDelay(pdMS_TO_TICKS(500)); } } xTaskCreate(PrinterTask, "P1", 200, (void*)"Hello from T1", 2, NULL); xTaskCreate(PrinterTask, "P2", 200, (void*)"Hello from T2", 2, NULL);

๐Ÿงต Local vs. static in Task Functions

  • Local (non-static) lives in the task’s private stack → each task instance gets its own copy.

  • static lives in global data → shared across all instances.

static void Work(void *pv) { int counter = 0; // per-task copy (stack) static int shared = 0; // one shared copy (void)pv; for (;;) { counter++; shared++; } }

๐Ÿง  Priorities (who runs first?)

  • Range: 0 .. (configMAX_PRIORITIES - 1) (set in FreeRTOSConfig.h).

  • Higher number = higher urgency.

xTaskCreate(TaskFn, "Low", 200, NULL, 1, NULL); xTaskCreate(TaskFn, "High", 200, NULL, 3, NULL);

๐Ÿ“‹ Minimal Two-Task Demo

static void Sensor(void *pv){ for(;;){ /* read temp */ vTaskDelay(pdMS_TO_TICKS(100)); } } static void Display(void *pv){ for(;;){ /* draw */ vTaskDelay(pdMS_TO_TICKS(50)); } } xTaskCreate(Sensor, "Sensor", 256, NULL, 2, NULL); xTaskCreate(Display, "Display", 256, NULL, 2, NULL); vTaskStartScheduler(); // never returns

⚡ Pro Tips

  • Stack depth is in words, not bytes (configSTACK_DEPTH_TYPE). 200 on Cortex-M ≈ 800 B.

  • Check return: configASSERT(xTaskCreate(...) == pdPASS);

  • Keep handlers inside for(;;); call vTaskDelete(NULL) if you must exit.

  • Be stingy with large local arrays; they burn task stack.

  • Too many priority levels → more context switches → more RAM/time overhead.

  • In STM32Cube projects, put app code inside USER CODE BEGIN/END blocks.


๐ŸŽฏ Conclusion

Treat tasks as small, focused loops. Create them with clear priorities, right-sized stacks, and explicit parameters. Keep handlers lean, delete on exit, and your scheduler will stay happy.

Written By: Musaab Taha


This article was improved with the assistance of AI.

Wednesday, 1 October 2025

Clean Bitwork in Embedded C (Fields, Nibbles, Ranges)

Working with registers means packing fields, slicing nibbles, and flipping ranges—safely. Below are tight, reusable helpers you can drop into firmware.


✅ Macro-Based Register Config Helper (16-bit)

Layout

  • ENABLE → 1 bit @ 0

  • MODE → 2 bits @ 1–2

  • SPEED → 3 bits @ 3–5

  • RESERVED → 2 bits @ 6–7 (must be 0)

#define SET_ENABLE(x) (((x)&1u) << 0) #define SET_MODE(x) (((x)&3u) << 1) #define SET_SPEED(x) (((x)&7u) << 3) static inline uint16_t build_register(uint8_t e, uint8_t m, uint8_t s){ return (uint16_t)(SET_ENABLE(e)|SET_MODE(m)|SET_SPEED(s)); // 6–7 stay 0 }

Examples

  • enable=1, mode=2, speed=4 → 37 (bin 0000 0000 0010 0101)

  • enable=0, mode=1, speed=3 → 26 (bin 0000 0000 0001 1010)


๐Ÿ”„ Extract a Nibble (8-bit)

static inline uint8_t extract_nibble(uint8_t reg, uint8_t pos){ return ((pos ? (reg >> 4) : reg) & 0x0Fu); }

Examples

  • 0xAB, pos=0 → 11 (lower = 0xB)

  • 0xAB, pos=1 → 10 (upper = 0xA)

  • 0xFF, pos=0 → 15


๐Ÿ”น Set Multiple Bits in Range (8-bit)

static inline uint8_t set_range(uint8_t reg, uint8_t start, uint8_t end){ uint8_t w = (uint8_t)(end - start + 1); return reg | (uint8_t)(((1u << w) - 1u) << start); }

Examples

  • 00000000, 1..3 → 00001110

  • 00001000, 0..2 → 00001111

  • 00000001, 3..5 → 00111001


⚡ Pro Tips

  • Use unsigned shifts (1u) + fixed-width types.

  • Mask inputs and parenthesize macros.

  • volatile for HW regs; guard concurrency.

  • Prefer static inline for type safety where possible.

๐ŸŽฏ Conclusion
These tiny helpers make register code predictable, reusable, and portable—without scattering magic shifts across your drivers.


Written By: Musaab Taha


This article was improved with the assistance of AI.