Project · C Programming (from zero to systems)
Project: a cold-chain temperature logger
The brief
Vaccines must stay between 2 and 8 °C all the way from the factory to a village health centre. You write firmware-style C for a fridge logger: a ring buffer with unit tests, a status register driven by bit masks, a moving average, then you use AddressSanitizer to find a real bug, gdb to inspect the alarm path, and size to see how much flash the code needs.
Why it matters
This is the daily work of an embedded firmware engineer at companies that make meters, medical devices, cold-chain monitors and automotive parts: fixed-size buffers instead of malloc, status registers read and written bit by bit, host-side unit tests, sanitizers and gdb before code reaches the board, and a close eye on code size. Embedded interviews in India ask for ring buffers and bit masks almost every time.
Skills you practise
- ring buffers
- bit masks and bit fields
- fixed-point values
- unit tests with assert
- AddressSanitizer and UBSan
- gdb backtraces
- make variables
- size and code footprint
You need
- Linux or WSL with gcc, make, gdb and binutils (sudo apt install build-essential gdb)
- AddressSanitizer support in gcc (included with gcc on Ubuntu and WSL)
What you will have at the end
A tested, sanitizer-clean C logger (ringbuf.c, logger.c, the fixed report.c) with a passing unit-test run, a gdb backtrace of the alarm path and a size report; plus ring.js with a tested ring buffer and bit helpers.
How it is marked (rubric, 100 points)
| Correct firmware logicEvery check passes: the unit tests, every status value from 0x01 to 0x1F, the maximum and the summary. | 35 |
|---|---|
| Bit handlingFlags are set, cleared and read with the macros, the error field saturates at 15, and no other bit is ever disturbed. | 20 |
| Tools used wellThe ASan bug found and fixed, a gdb backtrace read, and a release build measured with size. | 25 |
| ExplainedYou can explain fixed point, why the ring buffer needs no heap, and what text, data and bss mean. | 20 |
0/7 steps done
Step 1 of 7
A ring buffer, tested in JavaScript
ringPush and ringPop keep values in first-in, first-out order, refuse a push when full, and wrap round the end of the array.
Firmware usually cannot call malloc, so a queue is a fixed array used as a ring: head is where the next value goes, tail is where the next value comes out, and both go back to 0 after the last slot. count tells full from empty (when head == tail the ring could be either).
ringPush stores at head and moves head on; ringPop reads at tail and moves tail on. When the ring is full, refuse the new value and return false: in a real UART driver that is a lost byte you count, not a crash. Get the logic right here; the C version in step 2 is the same code.
Run shows what your code prints. Check runs the step's tests. Ctrl+Enter checks too.