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.

Tuesday, 23 September 2025

๐Ÿ”ง Bitwise Essentials for Firmware: Macros, Flags, and Bit Spreading


Clean bit-twiddling is a reliability feature. Small helpers + predictable patterns beat ad-hoc shifts every time. Here are three tight patterns you’ll reuse everywhere.

๐Ÿงท Maintainable Bit Macros + Ordered Ops (Set 2&7, Clear 3, Toggle 5)

Goal: modify an 8-bit register with readable, testable macros.

#include <stdio.h> #include <stdint.h> #define BIT(b) (1u << (b)) #define SET_BIT8(r,b) ((r) = (uint8_t)((r) | (uint8_t)BIT(b))) #define CLEAR_BIT8(r,b) ((r) = (uint8_t)((r) & (uint8_t)~BIT(b))) #define TOGGLE_BIT8(r,b) ((r) = (uint8_t)((r) ^ (uint8_t)BIT(b))) static inline uint8_t modify_register(uint8_t reg) { SET_BIT8(reg, 2); // set 2 SET_BIT8(reg, 7); // set 7 CLEAR_BIT8(reg, 3); // clear 3 TOGGLE_BIT8(reg, 5); // toggle 5 return reg; } int main(void) { uint8_t reg; scanf("%hhu", &reg); printf("%u", modify_register(reg)); return 0; }

Notes: BIT(b) is unsigned; casts clamp to 8-bit. For dynamic positions, ensure b < 8.


๐Ÿท️ Decode Status Register → Human-Readable Flags (LSB→MSB)

Goal: map bits to names and print only enabled ones.

#include <stdio.h> #include <stdint.h> static const char * const flag_names[8] = { "Power On","Error","Tx Ready","Rx Ready", "Overheat","Undervoltage","Timeout","Reserved" }; static void decode_status(uint8_t status_reg) { for (int i = 0; i < 8; ++i) if ((status_reg >> i) & 1u) printf("%s\n", flag_names[i]); } int main(void) { uint8_t reg; scanf("%hhu", &reg); decode_status(reg); return 0; }

Why this style: LUT keeps meanings centralized; LSB→MSB aligns with datasheets.


๐Ÿงฉ Bit Spreading (Interleave Zeros): 8→16 with Even-Bit Placement

Goal: place each input bit at even positions (0,2,4,…) with zeros in odd positions.

Fast “dilate bits” version (branchless)

#include <stdio.h> #include <stdint.h> static inline uint16_t spread_bits(uint8_t x) { uint16_t v = x; v = (v | (v << 4)) & 0x0F0F; v = (v | (v << 2)) & 0x3333; v = (v | (v << 1)) & 0x5555; return v; } int main(void) { uint8_t val; scanf("%hhu", &val); printf("%u", spread_bits(val)); return 0; }

Why it’s useful: display pipelines, Morton/Z-order, IO packing, DSP simulators.


๐ŸงŠ Myth vs Truth

  • Myth: “Bit hacks are unreadable.”

  • Truth: Small, named helpers + LUTs are clearer and safer than ad-hoc shifts.


๐Ÿ”Œ Embedded Relevance

  • Stable macros reduce register bugs.

  • Flag decoding documents behavior (that won’t rot).

  • Bit spreading appears in protocols, graphics, and indexing.


✅ Conclusion

Treat bit ops like APIs: clear names, tight scopes, and deterministic patterns. You’ll get safer register code, self-documenting status handling, and reusable transforms that scale.


Written By: Musaab Taha


This article was improved with the assistance of AI.

Monday, 22 September 2025

The Physics of Debug-Later vs TDD (and Why Embedded Needs It)

Traditional “debug-later” (DLP) lets bugs age. As Td (time-to-discover) grows, Tfind (time-to-locate) explodes; Tfix often rises too as wrong assumptions accrete. 

TDD collapses Td → 0, keeping Tfind + Tfix minimal and predictable.

๐Ÿงช DLP vs TDD: Cause & Effect

  • DLP: Long Td ⇒ context fades, dependencies pile up ⇒ Tfind spikes; Tfix grows when bad code becomes a foundation.

  • TDD: Td ≈ 0 (immediate test failure) ⇒ revert last change or make a pinpoint fix; defects are prevented, not shipped.

๐Ÿ” The TDD Microcycle (Red–Green–Refactor)

  1. Add a small test (one behavior).

  2. Run all tests → red (fail/doesn’t compile).

  3. Implement the minimum to pass.

  4. Run all tests → green.

  5. Refactor (remove duplication, clarify intent), with tests guarding behavior.

Each loop: seconds to minutes. Feedback stays fresh; progress is measurable; breakages are obvious.

๐ŸงŠ Myth vs Truth

  • Myth: “Write lots of tests, then lots of code.”

  • Truth: Tiny test → tiny code → refactor. Small steps keep Td near zero.

๐Ÿ“ˆ TDD Benefits (Developer Reality)

  • Fewer bugs & regressions (fast feedback prevents drift).

  • Less debugging (defects die early).

  • Fewer side effects (tests codify assumptions/constraints).

  • Executable documentation (examples that don’t lie).

  • Design pressure (testable code ⇒ smaller, decoupled units).

  • Progress signal (green bar defines “done”).

  • It’s motivating (tight loops, visible wins).

๐Ÿ”Œ Why Embedded Especially Benefits

  • Hardware independence early: verify production code before boards exist or when access is scarce.

  • Shorter target cycles: remove bugs on host; flash less, learn more.

  • Faster HW debug: isolate HW/SW boundaries with test doubles.

  • Better architecture: decoupling for testability reduces tight coupling to peripherals.

✅ Conclusion

Treat defects like radioactive isotopes, halve their life. By keeping Td near zero, TDD minimizes Tfind and Tfix, turns specs into executable tests, and makes embedded code reliable, predictable, and change-friendly. DLP institutionalizes waste; TDD institutionalizes feedback.


Written By: Musaab Taha


This article was improved with the assistance of AI.


References:

Test-Driven Development for Embedded C,  James W. Grenning

FreeRTOS on STM32 - Manual Integration

“Build succeeded” isn’t the finish line. With RTOS work, the real traps are missing configs, handler clashes, and timer conflicts that pass compile but fail on hardware. Here’s a compact, reliable path.

๐Ÿ˜ฌ The Trap: “It Compiles!” but Doesn’t Run

  • Missing FreeRTOSConfig.h → silent defaults or hard errors

  • Duplicate exception handlers (SysTick/PendSV/SVC) → link conflicts

  • HAL vs FreeRTOS both using SysTick → time-base collision

  • Undefined shifts/hooks/heap setup → runtime weirdness

⚙️ Two Ways to Add FreeRTOS

  • Manual (portable skill): add kernel sources yourself; you control layout and learn what matters.

  • CubeIDE GUI (fast): Middleware → FreeRTOS (CMSIS-RTOS v1/v2) auto-adds kernel + CMSIS layer.

๐Ÿ›  Manual, Minimal, Deterministic Setup

  1. Create project in STM32CubeIDE (CubeMX-based).

  2. Add sources under ThirdParty/FreeRTOS/:

    • Copy: License, Source/ (incl. include/) and portable/

    • Keep only portable/GCC/ARM_CM4F for F4 + FPU; delete other compilers/arches

  3. Heap: keep heap_4.c; delete heap_1/2/3/5. Exclude sysmem.c (FreeRTOS provides its own heap mgmt).

  4. Include paths (Project → Properties → C Compiler → Includes):

    • .../FreeRTOS/Source/include

    • .../FreeRTOS/Source/portable/GCC/ARM_CM4F

  5. Add FreeRTOSConfig.h (start from an STM32F407 demo; then tailor). Ensure:

    • extern uint32_t SystemCoreClock; enabled for your compiler (e.g., #if defined(__ICCARM__) || defined(__GNUC__) || defined(__CC_ARM)).

  6. Resolve handler duplicates (in .ioc → System Core → NVIC → Code generation):

    • Uncheck SVC, PendSV, and SysTick handlers (FreeRTOS provides them via macros).

    • Regenerate.

  7. Separate time bases:

    • .iocSYS → HAL Time base source = TIM6 (reserve SysTick for FreeRTOS).

    • NVIC priority grouping 4 bits preemption, 0 bits subpriority.

  8. Start lean: in FreeRTOSConfig.h set initially
    configUSE_TICK_HOOK=0, configUSE_MALLOC_FAILED_HOOK=0, configCHECK_FOR_STACK_OVERFLOW=0 (enable later with proper handlers).

  9. Build → now you’re integrated cleanly.

๐Ÿ–ฑ CubeIDE One-Click Path (CMSIS-RTOS v2)

  • .iocMiddleware > FreeRTOS → choose CMSIS-RTOS v2; set heap size.

  • Honor the IDE warnings:

    • Change HAL time base to TIM6 (not SysTick).

    • Enable Newlib reentrancy (Advanced settings → USE_NEWLIB_REENTRANT) for thread-safe libc in multitasking.

  • Generate code. You can use CMSIS APIs (e.g., osThreadNew) or native FreeRTOS APIs.

๐ŸงŠ Myth vs Truth

  • Myth: “If it links, RTOS will run.”

  • Truth: RTOS needs correct handlers, time base, and config. Compile-time success doesn’t prove scheduler health.

๐Ÿ”Œ Embedded Relevance

  • Deterministic bring-up removes heisenbugs before they reach hardware.

  • Clear separation of SysTick (RTOS) and HAL time base stabilizes timing.

  • Manual path teaches portable skills you can reuse on any MCU/IDE.

✅ Conclusion

Treat RTOS bring-up as a reliability exercise: one owner for critical handlers, one timer per role, and a known-good FreeRTOSConfig.h. Do that, and your “Hello, World” task isn’t luck—it’s repeatable, predictable, and production-ready.


Written By: Musaab Taha


This article was improved with the assistance of AI.

Bitwise Micro-Patterns for Embedded C (Reliable, Register-Safe)

For register work, “just shift & pray” leads to flaky bugs: off-by-one positions, undefined shifts, and masks that bleed into neighboring fields. Below are three tiny, composable patterns—each safe, predictable, and easy to review.

๐Ÿงท Pattern 1 — Is the Bit Set?

Goal: return 1 if bit at pos is 1, else 0.
Why: clean flag checks without branching.

#include <stdint.h> static inline uint8_t is_bit_set_u8(uint8_t reg, uint8_t pos) { if (pos >= 8) return 0; // defensive bound return (uint8_t)((reg >> pos) & 1u); }

Notes: Bound-check avoids accidental UB from oversized shifts; expression stays constant-time and side-effect free.


๐Ÿ”ง Pattern 2 — Set a Run of Bits (pos..pos+len-1) in a 32-bit Register

Goal: set a contiguous field to 1s without touching other bits.
Why: safe field enabling and mode configuration.

#include <stdint.h> static inline uint32_t set_bits_u32(uint32_t reg, uint8_t pos, uint8_t len) { if (len == 0 || pos >= 32) return reg; if (len > 32 - pos) len = (uint8_t)(32 - pos); // clamp instead of UB // Build mask in 64-bit to avoid (1U << 32) UB, then cast down. uint32_t mask = (uint32_t)(((uint64_t)1 << len) - 1u) << pos; return reg | mask; }

Notes:

  • Using 64-bit during mask build sidesteps undefined behavior for len == 32.

  • Clamping guarantees pos + len ≤ 32.

  • Pure bit-ops, no branches after checks.


๐Ÿ Pattern 3 — Keep Only the Highest Set Bit (uint16_t)

Goal: clear all bits except the MSB of the input.
Why: priority encode, normalize amplitudes, fast binning.

#include <stdint.h> static inline uint16_t keep_highest_bit_u16(uint16_t x) { if (!x) return 0; x |= (uint16_t)(x >> 1); x |= (uint16_t)(x >> 2); x |= (uint16_t)(x >> 4); x |= (uint16_t)(x >> 8); return (uint16_t)(x - (x >> 1)); // leaves only the topmost 1 }

Notes: Branchless bit-trick; portable to fixed widths (extend with more shifts for wider types).


๐ŸงŠ Myth vs Truth

  • Myth: “Bit-twiddling is fast even if a little sloppy.”

  • Truth: One undefined shift or leaky mask can corrupt adjacent fields—precision is performance.


๐Ÿ”Œ Embedded Relevance

  • Deterministic, reviewable code for registers and flags.

  • UB-free masks → fewer Heisenbugs during HW bring-up.

  • Easy to wrap into driver libraries and unit-test.

✅ Conclusion

Small, defensive bitwise patterns pay off: no undefined shifts, no accidental field clobbering, and behavior that stays predictable under pressure. Treat masks like APIs—validate inputs, avoid UB, and your register code becomes boringly reliable (the best kind).


Written By: Musaab Taha


This article was improved with the assistance of AI.