You are tasked with implementing firmware for a sensor gateway node running on an STM32F411RE microcontroller. The system receives binary telemetry packets from up to 4 sensors over a UART interface, stores the data in memory, monitors for sensor failures, and provides a diagnostic command interface.
This challenge runs entirely on your PC using the Renode simulation platform — no physical hardware is required. The firmware must operate under strict resource constraints typical of production embedded systems.
New to this challenge? Run the automated setup:
./setup.sh # Downloads FreeRTOS + creates heap_none.c
cd firmware
mkdir build
cd build
cmake ..
make -j$(nproc)Then see SETUP.md for detailed build and simulation instructions.
Install the following tools on your development machine:
-
ARM GCC Toolchain:
gcc-arm-none-eabi- Ubuntu/Debian:
sudo apt install gcc-arm-none-eabi - macOS:
brew install --cask gcc-arm-embedded - Windows: Use WSL2 with Ubuntu
- Ubuntu/Debian:
-
Renode (v1.15.0+): Embedded systems simulator
- Download: https://github.com/renode/renode/releases
- Ubuntu: Download .deb and
sudo apt install ./renode_*.deb - macOS:
brew install --cask renode
-
CMake (v3.15+)
- Ubuntu/Debian:
sudo apt install cmake - macOS:
brew install cmake
- Ubuntu/Debian:
-
Python 3 and Robot Framework (for testing)
pip3 install robotframework robotframework-renode
- MCU: STM32F411RE (Cortex-M4, 512KB Flash, 128KB RAM)
- RTOS: FreeRTOS v10.x with static allocation only
- Toolchain:
arm-none-eabi-gcc - Simulator: Renode
First time setup:
./setup.sh # Downloads FreeRTOS v10.6.2 + creates heap_none.cBuild:
cd firmware
mkdir build # Create build directory
cd build
cmake .. # Configure build system
make -j$(nproc) # Compile firmwareVerify:
arm-none-eabi-size firmware.elf # Check RAM/Flash usage
ls -lh firmware.* # List build artifactsThe build produces:
firmware.elf- Executable for Renode simulationfirmware.bin- Raw binary imagefirmware.hex- Intel HEX formatfirmware.map- Memory map (linker output)
From the repository root:
renode platform/run.rescThis starts Renode and loads your firmware. The diagnostic shell is available on TCP port 5555.
The firmware provides an interactive command shell over UART1 (exposed as TCP port 5555):
Linux / macOS:
telnet localhost 5555Windows (without WSL):
- Download PuTTY: https://www.putty.org/
- Set Connection Type: Raw
- Host Name:
localhost - Port:
5555 - Click Open
Windows (with WSL):
wsl
telnet localhost 5555Type commands like STATUS, DUMP, ? and press Enter to interact with the firmware.
Your task is to implement the firmware logic in firmware/src/fw_main.c (and any additional source files you create). The file currently contains only stub functions — you must implement the complete system behavior.
The firmware receives binary sensor telemetry packets on UART2 (115200 baud). Each packet has the following format:
┌──────┬──────┬───────────┬─────────────┬─────────────┬──────┐
│ 0xA5 │ 0x5A │ sensor_id │ payload_len │ payload[N] │ CRC8 │
└──────┴──────┴───────────┴─────────────┴─────────────┴──────┘
(header) (1 byte) (1 byte) (1-16 bytes) (1 byte)
Requirements:
- Header: Must be exactly
0xA5 0x5A - sensor_id:
0x00to0x03(4 sensors maximum) - payload_len:
1to16bytes - payload: Variable-length sensor data
- CRC8: Dallas/Maxim polynomial (0x31), computed over all bytes including header, sensor_id, payload_len, and payload
- Error handling:
- Packets with invalid CRC must be silently discarded
- Arbitrary noise bytes before a valid header must be ignored
- Parser must re-sync on the next valid
0xA5 0xAheader
Implementation notes:
- UART2 delivers one byte at a time via an ISR callback
- Use a FreeRTOS queue to hand bytes from ISR to a task for processing
- The byte-by-byte parser must handle stream re-synchronization
For each valid packet received, store the payload in a per-sensor ring buffer:
- 4 sensors maximum (IDs
0x00to0x03) - 8 slots per sensor (ring buffer)
- All storage must be statically allocated (no
malloc)
Per sensor, track:
last_payload: Most recent payload receivedsample_count: Total number of valid packets ever received (not capped at 8)last_rx_tick: FreeRTOS tick count when last packet arrivedregistered: Boolean — has this sensor sent at least one packet?
A dedicated FreeRTOS task must toggle GPIO PA5 every 500 ms unconditionally.
Requirements:
- This task must NEVER block on any shared resource
- If PA5 stops toggling for >1000ms, it indicates scheduler starvation (test will fail)
- Task stack: ≤ 512 bytes
A dedicated fault monitor task checks periodically (every 200 ms) whether any registered sensor has been silent for more than 2000 ms:
- If fault detected: Set GPIO PA6 HIGH
- When all faults cleared: Set GPIO PA6 LOW
- Important: A sensor that has never sent any packet (
registered == false) must NEVER trigger a fault
UART1 operates as a bidirectional ASCII command shell. Commands are newline-terminated (\n).
| Command | Response Format | Description |
|---|---|---|
STATUS |
OK sensors=N fault=F\n |
N = count of registered sensors, F = 0 or 1 |
DUMP |
S<id> last=<hex4> count=<N>\n for each sensor, then END\n |
List all registered sensors |
RESET |
OK\n |
Clear all sensor state (counts, timestamps, registered flags) |
? |
List of commands, then END\n |
Help |
| (unknown) | ERR unknown\n |
Any unrecognized command |
DUMP format details:
<id>: Sensor ID (0-3)<hex4>: First two bytes of most recent payload, zero-padded (e.g.,ABCD)<N>: Total historical packet count (NOT capped at 8)- Unregistered sensors (never seen) must NOT appear in output
- Must acquire sensor store mutex before reading
When GPIO PB0 receives a falling-edge interrupt, the firmware must automatically emit a DUMP response on UART1 (identical to receiving the DUMP command).
- Separation of concerns: Keep protocol parsing separate from UART hardware access
- Thread safety: Use mutexes for shared resources (sensor store, UART1 TX)
- ISR discipline: Callbacks run in interrupt context - use queues to communicate with tasks
- Modularity: Consider creating separate modules for protocol parsing, command handling, and storage
These constraints are verified automatically by CI:
| Constraint | Limit | Enforcement |
|---|---|---|
Total RAM (.data + .bss) |
≤ 6 KB | arm-none-eabi-size check |
| Total static stack allocation | ≤ 2 KB | Declared static arrays |
Dynamic allocation after fw_init() |
ZERO | heap_none.c causes hard fault on pvPortMalloc |
| FreeRTOS allocation mode | Static only | configSUPPORT_STATIC_ALLOCATION = 1 |
Use of printf |
FORBIDDEN | grep check (use hal_uart1_send_str instead) |
All FreeRTOS objects must use static APIs:
xTaskCreateStatic()(NOTxTaskCreate())xQueueCreateStatic()(NOTxQueueCreate())xSemaphoreCreateMutexStatic()(NOTxSemaphoreCreateMutex())
You MAY modify:
firmware/src/fw_main.c— Your main implementation- Create additional
.cand.hfiles infirmware/src/as needed
You MUST NOT modify:
- Any file marked with
/* DO NOT MODIFY */at the top platform/directory (Renode configuration)firmware/include/hal_*.h(HAL interfaces)firmware/CMakeLists.txt(build system)firmware/STM32F411_FLASH.ld(linker script)firmware/startup_stm32f411xe.s(startup code)tests/public/(test suite)
A public smoke test suite verifies basic functionality:
cd tests/public
renode-test smoke.robotIMPORTANT: A public smoke test suite is provided in tests/public/. It verifies basic functionality and is intended to help you confirm your firmware is on the right track. Additional private tests covering system behavior under stress will be run during evaluation. Writing code that only passes the visible tests is not sufficient — design for correctness, not for the tests you can see.
Click the "Use this template" button at the top of this repository to create your own copy:
- Name your repository (e.g.,
fw-challenge-yourname) - Choose visibility:
- Public if you want to showcase your work
- Private if you prefer to keep it confidential
- Click "Create repository from template"
# Clone your repository
git clone https://github.com/yourusername/fw-challenge-yourname
cd fw-challenge-yourname
# Set up and build
./setup.sh
cd firmware/build
cmake ..
make
# Implement your solution in firmware/src/fw_main.c
# (You can create additional .c/.h files as needed)# Run public tests
cd tests/public
renode-test smoke.robot
# All 6 tests should pass ✓Ensure your repository includes:
- Working firmware that builds successfully
- All public tests passing
- Clean commit history with meaningful messages
Choose one of the following submission methods:
- Push your final solution to your repository
- Email us at [hiring@yourcompany.com]:
- Subject: Firmware Challenge Submission - [Your Name]
- Body:
Name: [Your Full Name] Repository: https://github.com/yourusername/fw-challenge-yourname Completion Date: [Date] Notes: [Optional - anything you'd like us to know]
If your repository is private, also complete Option B below so we can access it.
If your repository is private:
- Go to your repository on GitHub
- Navigate to: Settings → Collaborators → Add people
- Add our evaluator: [evaluator-github-username]
- We'll send you this username via email
- Grant Read access (default)
- Email us confirming access has been granted
Both options are acceptable - choose what you're most comfortable with!
Your submission will be evaluated on:
- Correctness — Does the firmware meet all functional requirements?
- Architecture — Is the code well-structured with clear module boundaries?
- Resource discipline — Does the code respect memory and stack constraints?
- Edge case handling — Does it handle protocol errors, timeouts, and race conditions?
- Code quality — Is the code readable, maintainable, and well-documented?
- Start simple: Get the watchdog LED toggling first (verifies scheduler is running)
- Test incrementally: Validate each requirement before moving to the next
- Use the diagnostic shell: Send commands via telnet to debug your implementation
- Check resource usage: Run
arm-none-eabi-size firmware/build/firmware.elffrequently - Remember static allocation: Declare all task stacks, queues, and mutexes as static arrays
- FreeRTOS documentation: https://www.freertos.org/Documentation/RTOS_book.html
- STM32F411RE reference manual: https://www.st.com/resource/en/reference_manual/dm00119316.pdf
- Renode documentation: https://renode.readthedocs.io/
- CRC8 calculator (for testing):
scripts/crc8.py
If you encounter build issues, simulation problems, or have questions about requirements, please open an issue in this repository.
Good luck!