Architecture
<p id="bkmrk-arch-intro">Complete system overview: FreeRTOS task model, the sensor polling loop, protobuf encoding, UDP transmission, inbound packet dispatch, and the memory layout. All application logic lives in a single FreeRTOS task (MainTask) defined in src/sensor_board/main.c.</p>
<h2 id="bkmrk-initialization-sequence">
Initialization Sequence
<h3 id="bkmrk-phase-1-hardware">
Phase 1: Hardware Setup (init_board, before the kernel starts)
<pre id="bkmrk-phase1-code"><code class="language-c">
void init_board() {
MPU_Config_wrapper();
SCB_EnableICache();
SCB_EnableDCache();
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
/* NOTE: no threads here, kernel not initialized yet.
* osKernelInitialize() is called by cubemx_main.c afterwards. */
}</code></pre>
<h3 id="bkmrk-phase-2-drivers">
Phase 2: Driver Initialization (start of MainTask)
<ol id="bkmrk-phase2-list"><li value="1">
- BSP LEDs (Green, Blue, Red)
<h3 id="bkmrk-phase-3-network">
Phase 3: Communication Setup
<ol id="bkmrk-phase3-list"><li value="1">
- ETH_init with static IP 192.168.0.111, netmask 255.255.255.0, gateway 192.168.0.1 and a link status callback that re-adds the static ARP entry when the link comes up
<h3 id="bkmrk-phase-4-loop">
Phase 4: Main Loop
<p id="bkmrk-phase4-desc">The loop runs forever with a 5000 ms period (MAIN_TASK_DELAY_MS). Each iteration:</p>
<ol id="bkmrk-loop-steps"><li value="1">
- Read free heap; if below 4096 bytes log CRITICAL and sleep 10 s instead of polling
<h2 id="bkmrk-udp-transmission">
Protobuf Encoding and UDP Transmission
<p id="bkmrk-udp-desc">Every outbound message is one PBEnvelope with a oneof payload. Encoding uses nanopb via pb_message_encode, which allocates a heap buffer that is freed after sending.</p>
<pre id="bkmrk-udp-send-code"><code class="language-c">
static void udp_send_envelope(uint8_t dest_ip[4], PBEnvelope *env) {
if (!sendUDP) { return; } /* transmit gate, false by default */
uint8_t *encoded = NULL;
size_t size = 0;
result_t result = pb_message_encode(env, PBEnvelope_fields, &encoded, &size);
if (result == RESULT_OK) {
ETH_udp_send(dest_ip, PORT, encoded, (uint16_t)size, 1);
}
free(encoded);
}</code></pre>
<p id="bkmrk-sendudp-warning"><strong>Important:</strong> the static flag <code>sendUDP in main.c is currently </code><code>false, so envelope encoding and transmission are skipped entirely. Set it to true to actually transmit. There is a similar development flag </code><code>skip_sensor_polling (currently false) that disables all sensor polling when true.</code></p>
<h2 id="bkmrk-packet-dispatcher">
Inbound Packet Dispatcher
<p id="bkmrk-dispatcher-desc">Received UDP packets are decoded by the shared packet dispatcher (components/common/packet_dispatcher). Handlers are registered with PACKET_HANDLER_CONFIG_STATIC per envelope payload tag:</p>
| Handler | Behavior |
|---|---|---|
ph_info | handle_sensor_ph_info | Log only |
imu_info | handle_sensor_imu_info | Log only |
load_cell_info | handle_sensor_load_cell_info | Log only |
pressure_info | handle_sensor_pressure_info | Log only |
pump_info | handle_sensor_pump_command | Actuates the pump: applies enabled, direction, and speed_percent to the hardware |
<h2 id="bkmrk-status-model">
Sensor Status Model
<h3 id="bkmrk-sensor-state-codes">
SensorState (operating state)
Code |
|
|---|---|
SENSOR_IDLE | Not connected, not implemented, or intentionally off |
SENSOR_OPERATING | Normal operation, valid data |
SENSOR_ERROR | Communication failure or invalid data |
<h3 id="bkmrk-sensor-status-codes">
SensorStatus (connection status)
Code |
|
|---|---|
STATUS_OK | Healthy |
STATUS_DISCONNECTED | No hardware detected (poll returned UNIMPLEMENTED or COMMS) |
STATUS_ERROR | Unexpected failure |
STATUS_INITIALIZING | Warming up (flow sensor first sample window) |
<h3 id="bkmrk-poll-result-mapping">
Poll Result Mapping
<p id="bkmrk-poll-mapping-desc">handle_sensor_poll_result() maps driver results uniformly:</p>
<ul id="bkmrk-poll-mapping-list"><li value="1">
- RESULT_ERR_UNIMPLEMENTED or RESULT_ERR_COMMS: state IDLE, status DISCONNECTED (sensor not connected or driver not wired to hardware yet)
<p id="bkmrk-log-format-desc">Every sensor produces one uniform log line per loop: <code>name | STATUS | STATE | detail.</code></p>
<h2 id="bkmrk-pump-crosscheck">
Pump Health Cross-Check
<p id="bkmrk-pump-crosscheck-desc">The pump is an open loop actuator (no current sense or fault line), so firmware cannot directly detect a connected pump. The only on-board proof that fluid is moving is the inline flow sensor, so the main loop derives pump status from it:</p>
<ul id="bkmrk-pump-crosscheck-list"><li value="1">
- Not initialised: ERROR / ERROR
<h2 id="bkmrk-memory-layout">
Memory Layout
Region |
|
|---|---|
FreeRTOS heap, 64 KB | Task stacks, queues, protobuf encode buffers (malloc/free per message) |
0x30000000, 32 KB, MPU non-cacheable | Ethernet DMA descriptors and buffers |
0x30004900, 16 KB | LwIP RAM heap (MEM_SIZE) |
Static queues | Two UDP send queues, 80 entries each, allocated at compile time (xQueueCreateStatic) |
<h2 id="bkmrk-error-handling-strategy">
Error Handling Strategy
<ul id="bkmrk-error-strategy-list"><li value="1">
- Drivers never crash the loop: missing hardware degrades to IDLE/DISCONNECTED and the loop continues