# PCB box

# Sensor Board

All about the sensor board

# Overview

A 480MHz Swiss Army knife of the embedded team☮️

## Table of Contents

- **Page 1**<span style="white-space: pre-wrap;">: </span>[Table of Contents &amp; Overview](https://bookstack.roboteamtwente.nl/books/pcb-box/page/overview "Table of Contents & Overview")
- **Page 2**<span style="white-space: pre-wrap;">: </span>[GNSS (GPS) Sensor](https://bookstack.roboteamtwente.nl/books/pcb-box/page/gnss "GNSS (GPS) Sensor")
- **Page 3**<span style="white-space: pre-wrap;">: </span>[pH Sensor](https://bookstack.roboteamtwente.nl/books/pcb-box/page/ph-sensor "pH Sensor")
- **Page 4**<span style="white-space: pre-wrap;">: </span>[IMU (Inertial Measurement Unit)](https://bookstack.roboteamtwente.nl/books/pcb-box/page/imu "IMU (Inertial Measurement Unit)")
- **Page 5**<span style="white-space: pre-wrap;">: </span>[Load Cell Sensor](https://bookstack.roboteamtwente.nl/books/pcb-box/page/load-cell "Load Cell Sensor")
- **Page 6**<span style="white-space: pre-wrap;">: </span>[Pressure Sensor](https://bookstack.roboteamtwente.nl/books/pcb-box/page/pressure-sensor "Pressure Sensor")
- **Page 7:** [Sensor Basics Utility Library](https://bookstack.roboteamtwente.nl/books/pcb-box/page/sensor-basics-utility-library "Sensor Basics Utility Library")
- **Page 8**<span style="white-space: pre-wrap;">: </span>[System Architecture &amp; Integration](https://bookstack.roboteamtwente.nl/books/pcb-box/page/architecture "System Architecture & Integration")
- **Page 9**:[<span style="white-space: pre-wrap;"> Testin</span>](https://bookstack.roboteamtwente.nl/books/pcb-box/page/testing " Testing & Validation")g
- **Page 10**<span style="white-space: pre-wrap;">: </span>[Configuration](https://bookstack.roboteamtwente.nl/books/pcb-box/page/configuration "Configuration & Customization")
- **Page 11**<span style="white-space: pre-wrap;">: </span>[Reference](https://bookstack.roboteamtwente.nl/books/pcb-box/page/reference "Reference & Support")

### Purpose

The Sensor Board is a specialized embedded system designed to acquire environmental and motion data from multiple sensors. It integrates position (GNSS), water quality (pH), motion (IMU), force (load cells), and pressure measurements, transmitting all data over Ethernet using Protocol Buffer encoding.

### Hardware Platform

- **Microcontroller:**<span style="white-space: pre-wrap;"> STM32H753ZI (Nucleo board)</span>
- **Real-Time OS:**<span style="white-space: pre-wrap;"> FreeRTOS with CMSIS-RTOS V2</span>
- **Communication:**
    - Ethernet (LAN8742 PHY)
    - UART (Multiple sensor connections)
    - I2C/SPI (IMU, pressure sensors)
- **Memory:**<span style="white-space: pre-wrap;"> 64KB heap allocated for FreeRTOS</span>
- **Clock:**<span style="white-space: pre-wrap;"> 480 MHz ARM Cortex-M7</span>

### Key Board Features

- Multi-sensor fusion with independent sensor threads
- Network integration via UDP/Ethernet with Protobuf
- Real-time logging to UART (115200 baud)
- MAC address filtering for selective communication
- Packet dispatcher for incoming control signals
- LED status indicators (Green, Blue, Red)
- Heap monitoring with critical threshold alerts

### Core Sensors Integrated

1. **GPS (GNSS)**<span style="white-space: pre-wrap;"> - Global positioning and velocity</span>
2. **IMU**<span style="white-space: pre-wrap;"> - 3-axis acceleration, rotation, magnetic field</span>
3. **pH Sensor**<span style="white-space: pre-wrap;"> - Water quality measurement</span>
4. **Load Cells**<span style="white-space: pre-wrap;"> - Force measurement (×2)</span>
5. **Pressure Sensors**<span style="white-space: pre-wrap;"> - Pressure measurement (×2)</span>

# STM32CubeMX Sensor Configuration

This page documents the current STM32CubeMX configuration for the Sensor Board firmware and explains how to extend it for sensor interfaces (UART/I2C/SPI/ADC) in a way that is safe for code generation.

- **IOC File**<span style="white-space: pre-wrap;">: </span>`<span class="editor-theme-code">components/sensor_board/firmware/firmware.ioc</span>`
- **Generated HAL Init Files**<span style="white-space: pre-wrap;">: </span>`<span class="editor-theme-code">components/sensor_board/firmware/Core/Src/</span>`
- **Application Entry**<span style="white-space: pre-wrap;">: </span>`<span class="editor-theme-code">src/sensor_board/main.c</span>`

## Current CubeMX Snapshot

<table id="bkmrk-itemvaluemcustm32h75"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Item

</th><th>Value

</th></tr><tr><td>MCU

</td><td>STM32H753ZIT6 (NUCLEO-H753ZI)

</td></tr><tr><td>CubeMX Version

</td><td>6.15.0

</td></tr><tr><td>STM32Cube FW Package

</td><td>STM32Cube FW\_H7 v1.12.1

</td></tr><tr><td>Toolchain

</td><td>Makefile + GCC

</td></tr><tr><td>Post-generation Script

</td><td>`<span class="editor-theme-code">../../../scripts/post_code_generation.bash</span>`

</td></tr></tbody></table>

### Enabled CubeMX Components

- **CORTEX\_M7**<span style="white-space: pre-wrap;"> (I-Cache/D-Cache enabled, MPU configured)</span>
- **ETH**<span style="white-space: pre-wrap;"> (RMII mode)</span>
- **LWIP**<span style="white-space: pre-wrap;"> (Static IP, DHCP disabled)</span>
- **FREERTOS**<span style="white-space: pre-wrap;"> (CMSIS-RTOS v2, default task generated)</span>
- **TIM1**<span style="white-space: pre-wrap;"> (base timer)</span>
- **SYS/NVIC/RCC**<span style="white-space: pre-wrap;"> base platform configuration</span>

## Clock and Core Setup

### Clock Configuration (from IOC)

<table id="bkmrk-parametervalueclock-"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Parameter

</th><th>Value

</th></tr><tr><td>Clock Source

</td><td>HSE 8 MHz -&gt; PLL

</td></tr><tr><td>SYSCLK

</td><td>72 MHz

</td></tr><tr><td>APB1

</td><td>36 MHz (DIV2)

</td></tr><tr><td>APB2/APB3/APB4

</td><td>72 MHz

</td></tr><tr><td>TIM1 Clock

</td><td>72 MHz

</td></tr></tbody></table>

### Cortex-M7 / MPU

- Instruction cache: enabled
- Data cache: enabled
- <span style="white-space: pre-wrap;">MPU region at </span>`<span class="editor-theme-code">0x30000000</span>`<span style="white-space: pre-wrap;">, size </span>`<span class="editor-theme-code">32KB</span>`, shareable, non-cacheable

## Pinout and Peripheral Mapping

### Ethernet (RMII)

<table id="bkmrk-signalpineth_ref_clk"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Signal

</th><th>Pin

</th></tr><tr><td>ETH\_REF\_CLK

</td><td>PA1

</td></tr><tr><td>ETH\_MDIO

</td><td>PA2

</td></tr><tr><td>ETH\_CRS\_DV

</td><td>PA7

</td></tr><tr><td>ETH\_MDC

</td><td>PC1

</td></tr><tr><td>ETH\_RXD0

</td><td>PC4

</td></tr><tr><td>ETH\_RXD1

</td><td>PC5

</td></tr><tr><td>ETH\_TX\_EN

</td><td>PG11

</td></tr><tr><td>ETH\_TXD0

</td><td>PG13

</td></tr><tr><td>ETH\_TXD1

</td><td>PB13

</td></tr></tbody></table>

### Serial / COM

<table id="bkmrk-signalpinnoteusart1_"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>Signal

</th><th>Pin

</th><th>Note

</th></tr><tr><td>USART1\_TX

</td><td>PA9

</td><td><span style="white-space: pre-wrap;">Configured in generated </span>

`<span class="editor-theme-code">MX_GPIO_Init()</span>`

</td></tr><tr><td>USART1\_RX

</td><td>PA10

</td><td><span style="white-space: pre-wrap;">Configured in generated </span>

`<span class="editor-theme-code">MX_GPIO_Init()</span>`

</td></tr><tr><td>USART3\_TX

</td><td>PD8

</td><td>Used by NUCLEO COM path (

`<span class="editor-theme-code">MX_USART3_Init</span>`

<span style="white-space: pre-wrap;"> in app init)</span>

</td></tr><tr><td>USART3\_RX

</td><td>PD9

</td><td>Used by NUCLEO COM path (

`<span class="editor-theme-code">MX_USART3_Init</span>`

<span style="white-space: pre-wrap;"> in app init)</span>

</td></tr></tbody></table>

### Board IO / Misc

<table id="bkmrk-pinmodetypical-usepc"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>Pin

</th><th>Mode

</th><th>Typical Use

</th></tr><tr><td>PC13

</td><td>GPIO Input

</td><td>User button

</td></tr><tr><td>PB0

</td><td>GPIO Output

</td><td>Board output line

</td></tr><tr><td>PB7

</td><td>GPIO Output

</td><td>Board output line

</td></tr><tr><td>PB14

</td><td>GPIO Output

</td><td>Board output line

</td></tr><tr><td>PH0 / PH1

</td><td>HSE oscillator

</td><td>System clock source

</td></tr><tr><td>PC14 / PC15

</td><td>LSE oscillator

</td><td>Low-speed oscillator

</td></tr></tbody></table>

## Timer, RTOS, and Interrupts

### TIM1 Base Timer

```c
htim1.Init.Prescaler = 6400 - 1;
htim1.Init.Period = 10000;
```

With a 72 MHz timer clock, this gives an update period near 0.89 s.

### Interrupt Priorities (Key Entries)

<table id="bkmrk-irqprioritynoteseth_"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>IRQ

</th><th>Priority

</th><th>Notes

</th></tr><tr><td>ETH\_IRQn

</td><td>15

</td><td>Ethernet/LwIP path

</td></tr><tr><td>TIM1\_UP\_IRQn

</td><td>12

</td><td>TIM1 update interrupt

</td></tr><tr><td>TIM2\_IRQn

</td><td>7

</td><td>HAL tick time base

</td></tr><tr><td>EXTI15\_10\_IRQn

</td><td>6

</td><td>External interrupt group

</td></tr></tbody></table>

## Sensor Interface Status

### What Is Already Configured in CubeMX

- Networking stack and RMII pinout
- Base timer and RTOS scaffolding
- Basic UART-capable pins and NUCLEO COM integration path

### What Is Not Yet Fully Modeled in CubeMX (TO-DO ONCE sensors retrieved and assembled)

- Dedicated ADC channels for analog sensors (pH, load cell, pressure)
- Dedicated I2C/SPI buses for IMU and pressure variants
- Explicit sensor-specific pin labels and alternate-function assignments

**Important:**<span style="white-space: pre-wrap;"> Current sensor drivers include placeholders for hardware access in multiple modules. When bringing up physical sensors, add the corresponding CubeMX peripherals first, then update the sensor drivers to use generated handles.</span>

### Recommended Workflow

1. <span style="white-space: pre-wrap;">Open </span>`<span class="editor-theme-code">components/sensor_board/firmware/firmware.ioc</span>`<span style="white-space: pre-wrap;"> in STM32CubeMX.</span>
2. Add required peripherals for the target sensor like this:

```
GPS        -> USARTx (baud/parity/stop bits to match module)
IMU        -> I2Cx or SPIx (+ optional DRDY INT GPIO)
pH         -> ADCx channel (sampling time, resolution)
Load Cell  -> ADCx channel(s) or external ADC interface
Pressure   -> ADCx or I2Cx/SPIx (depends on sensor part)
```

1. Assign and lock pins in Pinout view; avoid overlap with RMII and COM pins.
2. Configure clocks for new peripherals in Clock Configuration.
3. Set NVIC priorities for new ISR sources so Ethernet/RTOS timing remains stable.
4. <span style="white-space: pre-wrap;">Generate code with </span>**Keep User Code**<span style="white-space: pre-wrap;"> enabled.</span>
5. Rebuild using PlatformIO and validate startup + sensor polling.

### Conflict To Look Out for Before Saving .ioc file

- No conflict with ETH RMII pins (PA1, PA2, PA7, PC1, PC4, PC5, PG11, PG13, PB13)
- No conflict with debug/COM path (PA9/PA10 and PD8/PD9)
- No conflict with oscillator pins (PH0, PH1, PC14, PC15)

### Related Pages

- [Configuration](https://bookstack.roboteamtwente.nl/books/pcb-box/page/configuration "Configuration")<span style="white-space: pre-wrap;"></span>
- [Reference](https://bookstack.roboteamtwente.nl/books/pcb-box/page/reference "Reference")
- [Sensor Basics Utility Library](https://bookstack.roboteamtwente.nl/books/pcb-box/page/sensor-basics-utility-library "Sensor Basics Utility Library")

# Sensor Basics Utility Library

---

These are basic features that could be used if required... But was something made in "spare time"

## Source Code Location

**Files:**

- `<span class="editor-theme-code">components/sensor_board/sensor_basics/sensor_basics.h</span>`<span style="white-space: pre-wrap;"> - Function declarations and documentation</span>
- `<span class="editor-theme-code">components/sensor_board/sensor_basics/sensor_basics.c</span>`<span style="white-space: pre-wrap;"> - Implementation</span>

**Dependencies:**

- `<span class="editor-theme-code">result.h</span>`<span style="white-space: pre-wrap;"> - Standard result/error code definitions</span>
- `<span class="editor-theme-code">stdint.h</span>`<span style="white-space: pre-wrap;"> - Integer type definitions</span>

---

## pH Sensor Functions

### validate\_ph\_value()

Validates if a pH value is within the acceptable range (0-14).

```c
/**
 * @brief Validates if a pH value is within the acceptable range (0-14).
 * @param ph_value The pH value to validate.
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_ph_value(float ph_value);
```

**Implementation:**

```c
result_t validate_ph_value(float ph_value) {
    if (ph_value >= 0.0f && ph_value <= 14.0f) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">ph_value</span>`<span style="white-space: pre-wrap;"> - Float value to validate (typical range: 0.0 to 14.0)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value is outside 0-14 range</span>

**Usage Example:**

```c
float ph_reading = ph_sensor.ph_value;

if (validate_ph_value(ph_reading) == RESULT_OK) {
    LOG_INFO("pH sensor valid: %.2f", ph_reading);
    diagnostics.ph_sensor.state = SENSOR_OPERATING;
} else {
    LOG_ERROR("pH out of range: %.2f", ph_reading);
    diagnostics.ph_sensor.state = SENSOR_ERROR;
    diagnostics.ph_sensor.error_code = PHErrorCode_PH_INVALID_DATA;
}
```

---

## Temperature Conversion Functions

### celsius\_to\_fahrenheit()

Converts temperature from Celsius to Fahrenheit.

```c
/**
 * @brief Converts temperature from Celsius to Fahrenheit.
 * @param celsius The temperature in Celsius.
 * @param fahrenheit Pointer to a float where the converted Fahrenheit temperature will be stored.
 * @return RESULT_OK on success, or RESULT_ERR_INVALID_ARG if fahrenheit is NULL.
 */
result_t celsius_to_fahrenheit(float celsius, float *fahrenheit);
```

**Conversion Formula:**<span style="white-space: pre-wrap;"> °F = (°C × 9/5) + 32</span>

**Parameters:**

- `<span class="editor-theme-code">celsius</span>`<span style="white-space: pre-wrap;"> - Input temperature in Celsius</span>
- `<span class="editor-theme-code">fahrenheit</span>`<span style="white-space: pre-wrap;"> - Pointer to output variable (must not be NULL)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Conversion successful</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> - fahrenheit pointer is NULL</span>

**Status: Currently commented out in implementation**

**Usage Example:**

```c
float temp_celsius = 25.0f;
float temp_fahrenheit;

if (celsius_to_fahrenheit(temp_celsius, &temp_fahrenheit) == RESULT_OK) {
    LOG_INFO("Temperature: %.1f°C = %.1f°F", temp_celsius, temp_fahrenheit);
}
```

### fahrenheit\_to\_celsius()

Converts temperature from Fahrenheit to Celsius.

```c
/**
 * @brief Converts temperature from Fahrenheit to Celsius.
 * @param fahrenheit The temperature in Fahrenheit.
 * @param celsius Pointer to a float where the converted Celsius temperature will be stored.
 * @return RESULT_OK on success, or RESULT_ERR_INVALID_ARG if celsius is NULL.
 */
result_t fahrenheit_to_celsius(float fahrenheit, float *celsius);
```

**Conversion Formula:**<span style="white-space: pre-wrap;"> °C = (°F - 32) × 5/9</span>

**Status: Currently commented out in implementation**

---

## IMU (Accelerometer) Functions

### validate\_accelerometer\_value()

Validates if a single accelerometer value is within the typical range.

```c
/**
 * @brief Validates if an accelerometer value is within the typical range.
 * @param accel_value The accelerometer value to validate.
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_accelerometer_value(float accel_value);
```

**Implementation:**

```c
result_t validate_accelerometer_value(float accel_value) {
    if (accel_value >= -160.0f && accel_value <= 160.0f) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">accel_value</span>`<span style="white-space: pre-wrap;"> - Single axis acceleration value in m/s²</span>

**Valid Range:**<span style="white-space: pre-wrap;"> -160.0 to +160.0 m/s² (typical ±16g sensor range)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value exceeds acceptable limits</span>

**Usage Example:**

```c
float accel_x = imu_data.accel[0];

if (validate_accelerometer_value(accel_x) == RESULT_OK) {
    LOG_DEBUG("Accel X valid: %.2f m/s²", accel_x);
} else {
    LOG_ERROR("Accel X out of range: %.2f m/s²", accel_x);
}
```

### validate\_imu\_data()

Validates all three axes of accelerometer data simultaneously.

```c
/**
 * @brief Validates all three axes of accelerometer data.
 * @param accel_x The acceleration value for the X-axis.
 * @param accel_y The acceleration value for the Y-axis.
 * @param accel_z The acceleration value for the Z-axis.
 * @return RESULT_OK if all values are valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_imu_data(float accel_x, float accel_y, float accel_z);
```

**Implementation:**

```c
result_t validate_imu_data(float accel_x, float accel_y, float accel_z) {
    TRY(validate_accelerometer_value(accel_x));
    TRY(validate_accelerometer_value(accel_y));
    TRY(validate_accelerometer_value(accel_z));
    return RESULT_OK;
}
```

**Parameters:**

- `<span class="editor-theme-code">accel_x</span>`<span style="white-space: pre-wrap;"> - X-axis acceleration (m/s²)</span>
- `<span class="editor-theme-code">accel_y</span>`<span style="white-space: pre-wrap;"> - Y-axis acceleration (m/s²)</span>
- `<span class="editor-theme-code">accel_z</span>`<span style="white-space: pre-wrap;"> - Z-axis acceleration (m/s²)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - All three axes within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Any axis exceeds acceptable limits</span>

**Usage Example:**

```c
if (validate_imu_data(imu_data.accel[0], imu_data.accel[1], imu_data.accel[2]) == RESULT_OK) {
    LOG_INFO("IMU acceleration valid");
    diagnostics.imu_sensor.state = SENSOR_OPERATING;
} else {
    LOG_ERROR("IMU acceleration out of range");
    diagnostics.imu_sensor.state = SENSOR_ERROR;
}
```

---

## Pressure Conversion Functions

### bar\_to\_psi()

Converts pressure from bar to psi (pounds per square inch).

```c
/**
 * @brief Converts pressure from bar to psi.
 * @param bar The pressure in bar.
 * @param psi Pointer to a float where the converted psi pressure will be stored.
 * @return RESULT_OK on success, or RESULT_ERR_INVALID_ARG if psi is NULL.
 */
result_t bar_to_psi(float bar, float *psi);
```

**Conversion Formula:**<span style="white-space: pre-wrap;"> psi = bar × 14.5038</span>

**Parameters:**

- `<span class="editor-theme-code">bar</span>`<span style="white-space: pre-wrap;"> - Pressure in bar</span>
- `<span class="editor-theme-code">psi</span>`<span style="white-space: pre-wrap;"> - Pointer to output variable (must not be NULL)</span>

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Conversion successful</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> - psi pointer is NULL</span>

**Status: Currently commented out in implementation**

**Usage Example:**

```c
float pressure_bar = 5.0f;
float pressure_psi;

if (bar_to_psi(pressure_bar, &pressure_psi) == RESULT_OK) {
    LOG_INFO("Pressure: %.2f bar = %.2f psi", pressure_bar, pressure_psi);
}
```

### psi\_to\_bar()

Converts pressure from psi to bar.

```c
/**
 * @brief Converts pressure from psi to bar.
 * @param psi The pressure in psi.
 * @param bar Pointer to a float where the converted bar pressure will be stored.
 * @return RESULT_OK on success, or RESULT_ERR_INVALID_ARG if bar is NULL.
 */
result_t psi_to_bar(float psi, float *bar);
```

**Conversion Formula:**<span style="white-space: pre-wrap;"> bar = psi ÷ 14.5038</span>

**Status: Currently commented out in implementation**

---

## GPS Functions

### validate\_gps\_latitude()

Validates GPS latitude value is within valid range.

```c
/**
 * @brief Validates GPS latitude value.
 * @param latitude The latitude value to validate (-90 to +90 degrees).
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_gps_latitude(double latitude);
```

**Implementation:**

```c
result_t validate_gps_latitude(double latitude) {
    if (latitude >= -90.0 && latitude <= 90.0) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">latitude</span>`<span style="white-space: pre-wrap;"> - Latitude in degrees (double precision)</span>

**Valid Range:**<span style="white-space: pre-wrap;"> -90.0 to +90.0 degrees</span>

- Negative = South
- Positive = North
- 0° = Equator

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value outside ±90 degrees</span>

### validate\_gps\_longitude()

Validates GPS longitude value is within valid range.

```c
/**
 * @brief Validates GPS longitude value.
 * @param longitude The longitude value to validate (-180 to +180 degrees).
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_gps_longitude(double longitude);
```

**Implementation:**

```c
result_t validate_gps_longitude(double longitude) {
    if (longitude >= -180.0 && longitude <= 180.0) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">longitude</span>`<span style="white-space: pre-wrap;"> - Longitude in degrees (double precision)</span>

**Valid Range:**<span style="white-space: pre-wrap;"> -180.0 to +180.0 degrees</span>

- Negative = West
- Positive = East
- 0° = Prime Meridian
- ±180° = International Date Line

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value outside ±180 degrees</span>

### validate\_gps\_hdop()

Validates GPS Horizontal Dilution of Precision (HDOP) value.

```c
/**
 * @brief Validates GPS HDOP value.
 * @param hdop The HDOP value to validate (0-50 typical range).
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_gps_hdop(float hdop);
```

**Implementation:**

```c
result_t validate_gps_hdop(float hdop) {
    if (hdop >= 0.0f && hdop <= 50.0f) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">hdop</span>`<span style="white-space: pre-wrap;"> - Horizontal dilution of precision value</span>

**Valid Range:**<span style="white-space: pre-wrap;"> 0.0 to 50.0</span>

**HDOP Quality Interpretation:**

- &lt;1 - Ideal
- 1-2 - Excellent
- 2-5 - Good
- 5-10 - Moderate
- 10-20 - Fair
- &gt;20 - Poor

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Value exceeds acceptable limits</span>

### validate\_gps\_satellite\_count()

Validates GPS satellite count is within valid range.

```c
/**
 * @brief Validates GPS satellite count.
 * @param satellites The number of satellites to validate.
 * @return RESULT_OK if the value is valid, RESULT_ERR_INVALID_DATA otherwise.
 */
result_t validate_gps_satellite_count(int32_t satellites);
```

**Implementation:**

```c
result_t validate_gps_satellite_count(int32_t satellites) {
    if (satellites >= 0 && satellites <= 30) {
        return RESULT_OK;
    }
    return RESULT_ERR_INVALID_DATA;
}
```

**Parameters:**

- `<span class="editor-theme-code">satellites</span>`<span style="white-space: pre-wrap;"> - Number of satellites in view</span>

**Valid Range:**<span style="white-space: pre-wrap;"> 0 to 30 satellites</span>

**Satellite Count Guidance:**

- 0-3 - No/poor fix possible
- 4-5 - 3D fix possible
- 6-9 - Good coverage
- 10+ - Excellent coverage

**Return Values:**

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> - Value is within valid range</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_DATA</span>`<span style="white-space: pre-wrap;"> - Negative value or exceeds 30</span>

**Usage Example (Full GPS Validation):**

```c
if (validate_gps_latitude(gps_data.latitude) == RESULT_OK &&
    validate_gps_longitude(gps_data.longitude) == RESULT_OK &&
    validate_gps_hdop(gps_data.hdop) == RESULT_OK &&
    validate_gps_satellite_count(gps_data.satellites) == RESULT_OK) {
    
    LOG_INFO("GPS fix valid: %.6f, %.6f (sats=%d)", 
             gps_data.latitude, gps_data.longitude, gps_data.satellites);
    diagnostics.gps_sensor_1.state = SENSOR_OPERATING;
} else {
    LOG_WARNING("GPS validation failed");
    diagnostics.gps_sensor_1.state = SENSOR_ERROR;
}
```

---

## Error Handling Pattern

All validation functions follow a consistent pattern:

```c
// Check sensor data
if (validate_<sensor>_<field>(value) == RESULT_OK) {
    // Data is valid - use it
    diagnostics.<sensor>.state = SENSOR_OPERATING;
} else {
    // Data is invalid - set error state
    diagnostics.<sensor>.state = SENSOR_ERROR;
    diagnostics.<sensor>.error_code = <ERROR_CODE>_INVALID_DATA;
}
```

**TRY Macro Usage:**

<span style="white-space: pre-wrap;">The implementation uses a </span>`<span class="editor-theme-code">TRY()</span>`<span style="white-space: pre-wrap;"> macro for error propagation (from result.h):</span>

```c
// In validate_imu_data()
result_t validate_imu_data(float accel_x, float accel_y, float accel_z) {
    TRY(validate_accelerometer_value(accel_x));  // Return on error
    TRY(validate_accelerometer_value(accel_y));  // Return on error
    TRY(validate_accelerometer_value(accel_z));  // Return on error
    return RESULT_OK;
}
```

---

## Implementation Status

**Currently Implemented (Active):**

- ✓ validate\_ph\_value()
- ✓ validate\_accelerometer\_value()
- ✓ validate\_imu\_data()
- ✓ validate\_gps\_latitude()
- ✓ validate\_gps\_longitude()
- ✓ validate\_gps\_hdop()
- ✓ validate\_gps\_satellite\_count()

**Currently Commented Out (Inactive):**

- ⊘ celsius\_to\_fahrenheit() - Declared but not implemented
- ⊘ fahrenheit\_to\_celsius() - Declared but not implemented
- ⊘ bar\_to\_psi() - Declared but not implemented
- ⊘ psi\_to\_bar() - Declared but not implemented

**Note:**<span style="white-space: pre-wrap;"> Temperature and pressure conversions are stubbed out in the current implementation. They can be enabled by uncommenting the implementation in sensor\_basics.c if needed for future features.</span>

---

## Testing

**Test Suite Location:**<span style="white-space: pre-wrap;"> </span>`<span class="editor-theme-code">test/sensor_board/test_sensor_basics/</span>`

**Building Tests:**

```bash
// Run all sensor_basics tests
pio test -e sensor_board -f test_sensor_basics

// Run with verbose output
pio test -e sensor_board -f test_sensor_basics -v
```

**Test Coverage:**

- Boundary value testing (min/max ranges)
- Edge cases (0 values, extreme values)
- Valid range acceptance
- Invalid range rejection
- NULL pointer handling for conversion functions

---

## Integration in Main Application

**Typical Usage in main.c:**

```c
// After polling a sensor
result_t poll_result = poll_gps_sensor(&gps_data);

if (poll_result == RESULT_OK) {
    // Validate all GPS fields before using
    if (validate_gps_latitude(gps_data.latitude) == RESULT_OK &&
        validate_gps_longitude(gps_data.longitude) == RESULT_OK) {
        
        diagnostics.gps_sensor_1.state = SENSOR_OPERATING;
        // Safe to use: gps_data.latitude, gps_data.longitude
        
    } else {
        diagnostics.gps_sensor_1.state = SENSOR_ERROR;
        diagnostics.gps_sensor_1.error_code = GPS_INVALID_DATA;
    }
} else if (poll_result == RESULT_ERR_COMMS) {
    diagnostics.gps_sensor_1.state = SENSOR_ERROR;
    diagnostics.gps_sensor_1.error_code = GPS_COMMUNICATION_FAILURE;
}
```

# Architecture

Complete system overview showing FreeRTOS, sensor polling loop, protobuf encoding, UDP transmission, and memory layout. Includes initialization sequence and error handling strategy.

## Main Loop Operation

```c
while (1) {
    // STEP 1: System Health Check
    // - Check heap (critical: <8KB)
    // - Toggle status LEDs
    // - Send raw Ethernet beacon
    
    // STEP 2: Initialize Diagnostics Message
    SensorBoardDiagnostics diagnostics_msg;
    
    // STEP 3: Poll All Sensors
    // - pH Sensor
    // - GPS Sensor
    // - IMU Sensor
    // - Load Cells (x2)
    // - Pressure Sensors (x2)
    
    // STEP 4: Encode & Transmit
    // - Encode diagnostics to Protobuf
    // - Send UDP broadcast (192.168.0.255:7)
    
    // STEP 5: Wait
    osDelay(5000); // 5 seconds
}
```

## Error Handling Strategy

### Polling Error States

```c
if (poll_result == RESULT_ERR_UNIMPLEMENTED) {
    sensor.state = SENSOR_IDLE;
    sensor.error_code = COMMUNICATION_FAILURE;

} else if (poll_result == RESULT_ERR_COMMS) {
    sensor.state = SENSOR_ERROR;
    sensor.error_code = COMMUNICATION_FAILURE;

} else if (poll_result == RESULT_OK) {
    if (validate_sensor_data(value) == RESULT_OK) {
        sensor.state = SENSOR_OPERATING;
        sensor.error_code = NO_ERROR;
    } else {
        sensor.state = SENSOR_ERROR;
        sensor.error_code = INVALID_DATA;
    }
}
```

### Sensor Status Codes

<table id="bkmrk-codemeaningsensor_id"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Code

</th><th>Meaning

</th></tr><tr><td>`<span class="editor-theme-code">SENSOR_IDLE</span>`

</td><td>Not connected or not implemented

</td></tr><tr><td>`<span class="editor-theme-code">SENSOR_OPERATING</span>`

</td><td>Normal operation, valid data

</td></tr><tr><td>`<span class="editor-theme-code">SENSOR_ERROR</span>`

</td><td>Communication failure or invalid data

</td></tr></tbody></table>

## Initialization Sequence

### Phase 1: Hardware Setup

```
1. MPU/Cache configuration
2. HAL initialization
3. System clock → 480 MHz
4. RTOS kernel init
5. GPIO initialization
6. Timer initialization (TIM1)
```

### Phase 2: Communication Setup

```
1. UART initialization (115200 baud)
2. Logging system init
3. Ethernet PHY init (LAN8742)
4. MAC address filtering
5. ARP table setup
```

### Phase 3: Application Setup

```
1. Packet dispatcher registration
2. Sensor initialization
3. UDP queue creation
4. UDP callback registration
```

### Phase 4: Main Loop

```
1. LED initialization
2. Sensor polling starts
3. Continuous 5-second cycles
```

```

```

# Configuration

<span style="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);">Compile-time parameters, runtime configuration, sensor calibration setup, network addressing, UDP ports, performance tuning options</span>

#### UDP Configuration

```c
// In src/sensor_board/main.c
#define SENSOR_UDP_DEST_PORT 7
uint8_t dest_ip[4] = {192, 168, 0, 255};  // Broadcast address
```

#### Main Loop Timing

```c
// In src/sensor_board/main.c
#define MAIN_TASK_DELAY_MS 5000             // 5 seconds between updates
```

#### Heap Management

```c
// Critical heap threshold
#define CRITICAL_HEAP_THRESHOLD 8192        // 8 KB minimum
```

#### Task Configuration

```c
const osThreadAttr_t mainTask_attributes = {
    .name = "mainTask",
    .stack_size = 1024 * 8,                 // 8 KB stack
    .priority = (osPriority_t)osPriorityNormal,
};
```

## Runtime Configuration

### Changing Sensor Poll Interval

```c
// Current: 5 seconds
#define MAIN_TASK_DELAY_MS 5000

// Change to 1 second
#define MAIN_TASK_DELAY_MS 1000

// Change to 10 seconds
#define MAIN_TASK_DELAY_MS 10000
```

### Enabling/Disabling Sensors

```c
// Current: skip all sensor polling during development
const bool skip_sensor_polling = true;

// To ENABLE actual polling:
const bool skip_sensor_polling = false;

// To SKIP (for testing without hardware):
const bool skip_sensor_polling = true;
```

### Adjusting Heap Threshold

```c
// Current: 8 KB critical
if (free_heap < 8192) {
    LOGE(TAG, "CRITICAL: Low heap detected! Free: %lu bytes", free_heap);
}
```

## Sensor Calibration Configuration

### pH Sensor Calibration

```c
// Set reference voltage (typically 3.3V or 5.0V)
ph_sensor_init(&ph_sensor, 3.3f);  // 3.3V reference

// Update calibration (after two-point calibration)
ph_sensor.calibration.offset = 0.0f;     // Adjust after pH 7.0 test
ph_sensor.calibration.slope = 3.5f;      // Adjust after second pH test
```

### Load Cell Calibration

```c
// After tare (no load):
load_cell_data[0].tare_offset_counts = adc_reading_no_load;

// After known weight measurement:
// scale = (force_newtons) / (adc_reading - tare_offset)
load_cell_data[0].scale_newtons_per_count = scale_factor;
load_cell_data[0].is_calibrated = true;
```

## Network Configuration

### Changing Broadcast Address

```c
//To be implemented
```

### Changing UDP Port

```c
//To be implemented
```

# GNSS

Global positioning module with NMEA 0183 protocol support. Contains data structures, initialization code, validation functions, and UART configuration for the GY-NEO6MV2 sensor.

### Hardware Specifications

<table id="bkmrk-parametervaluemodelg"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Parameter

</th><th>Value

</th></tr><tr><td>Model

</td><td>GY-NEO6MV2 (NEO-6M)

</td></tr><tr><td>Interface

</td><td>UART (TTL serial)

</td></tr><tr><td>Baud Rate

</td><td>9600 bps

</td></tr><tr><td>Protocol

</td><td>NMEA 0183

</td></tr><tr><td>Update Rate

</td><td>1 Hz (configurable)

</td></tr></tbody></table>

### GPS Fix Quality Types

```c
typedef enum {
    GPS_NO_FIX = 0,        // No GPS fix available
    GPS_GPS_FIX = 1,       // Standard GPS fix
    GPS_DGPS_FIX = 2,      // Differential GPS fix
    GPS_PPS_FIX = 3,       // PPS (Pulse Per Second) fix
    GPS_RTK_FIX = 4,       // Real-Time Kinematic fix
    GPS_RTK_FLOAT = 5      // RTK float solution
} gps_fix_quality_t;
```

## Data Structure

```c
typedef struct {
    // Position Data
    double latitude;           // ±90 degrees (North/South)
    double longitude;          // ±180 degrees (East/West)
    float altitude;            // Meters above sea level
    
    // Velocity & Direction
    float speed;               // Meters per second
    float heading;             // 0-360 degrees (course over ground)
    
    // Accuracy Indicators
    float hdop;                // Horizontal dilution of precision
    float vdop;                // Vertical dilution of precision
    int32_t satellites;        // Number of satellites in view
    gps_fix_quality_t fix_quality;
    
    // Timestamps
    int64_t utc_timestamp;     // Unix epoch (milliseconds)
    uint32_t timestamp;        // System timestamp (last reading)
    uint32_t last_timestamp;   // System timestamp (previous reading)
    
    // Status
    bool is_valid;             // Data validity flag
    
    // UART Parsing State
    char rx_buffer[256];       // UART receive buffer
    uint16_t rx_index;         // Current buffer position
    bool sentence_ready;       // Complete sentence available
} gps_data_t;
```

### NMEA Sentences Supported

<table id="bkmrk-sentencepurposeggafi"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Sentence

</th><th>Purpose

</th></tr><tr><td>**GGA**

</td><td>Fix data (time, position, fix quality, satellite count)

</td></tr><tr><td>**RMC**

</td><td>Recommended Min. Navigation Info (position, speed, heading, date)

</td></tr><tr><td>**GSA**

</td><td>DOP and active satellites (fix type, DOP values)

</td></tr></tbody></table>

## Initialization &amp; Usage

### Initialize GPS

```c
gps_data_t gps_data;
gps_sensor_init(&gps_data);
```

### Poll GPS Data

```c
result_t gps_poll_result = poll_gps_sensor(&gps_data);

if (gps_poll_result == RESULT_OK) {
    double latitude = gps_data.latitude;
    double longitude = gps_data.longitude;
    float altitude = gps_data.altitude;
    int32_t satellites = gps_data.satellites;
    float hdop = gps_data.hdop;
}
```

## Validation Functions

```c
// Validate latitude (-90 to +90)
result_t validate_gps_latitude(double latitude);

// Validate longitude (-180 to +180)
result_t validate_gps_longitude(double longitude);

// Validate HDOP (horizontal dilution of precision)
// Typical range: 0-50
result_t validate_gps_hdop(float hdop);

// Validate satellite count
// Typical: 0-30 satellites
result_t validate_gps_satellite_count(int32_t satellites);
```

## Protobuf Message Format

```protobuf
message SensorBoardGPSInfo {
    double latitude;
    double longitude;
    float altitude;
    float speed;
    float heading;
    float hdop;
    float vdop;
    int32 satellites;
    SensorState state;
    GPSErrorCode error_code;
}
```

## Error Handling

```c
if (gps_poll_result == RESULT_ERR_UNIMPLEMENTED) {
    // Hardware not connected
    diagnostics.gps_sensor_1.state = SensorState_SENSOR_IDLE;
    diagnostics.gps_sensor_1.error_code = GPSErrorCode_GPS_COMMUNICATION_FAILURE;
} else if (gps_poll_result == RESULT_ERR_COMMS) {
    // Communication error (timeout/CRC)
    diagnostics.gps_sensor_1.state = SensorState_SENSOR_ERROR;
} else if (gps_poll_result == RESULT_OK) {
    // Validate data before accepting
    if (validate_gps_latitude(gps_data.latitude) == RESULT_OK) {
        diagnostics.gps_sensor_1.state = SensorState_SENSOR_OPERATING;
    }
}
```

## Integration Notes

- Configured for dual GPS redundancy capability
- UART buffer size: 256 bytes
- NMEA sentence max length: 83 characters
- Data published to network at main loop interval (5 seconds default)
- All GPS data transmitted via UDP with Protobuf encoding
- Temperature range: -40°C to +85°C (standard)

# pH Sensor

The pH sensor provides water quality measurement critical for environmental monitoring and anomaly detection.

### Hardware Specifications

<table id="bkmrk-parametervaluemodeld"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Parameter

</th><th>Value

</th></tr><tr><td>Model

</td><td>DFRobot SEN0161 (Analog pH meter)

</td></tr><tr><td>Interface

</td><td>Analog ADC

</td></tr><tr><td>Reference Voltage

</td><td>3.3V or 5.0V (configurable)

</td></tr><tr><td>Output Range

</td><td>0-5V analog

</td></tr><tr><td>Measurement Range

</td><td>0-14 pH units

</td></tr><tr><td>Accuracy

</td><td>±0.1 pH @ 25°C

</td></tr><tr><td>Sample Rate

</td><td>Configurable (40 samples for averaging)

</td></tr></tbody></table>

## Calibration Model

The sensor uses linear voltage-to-pH conversion:

```
pH = (Voltage / Reference_Voltage) × Slope + Offset
```

**Default Parameters for SEN0161**<span style="white-space: pre-wrap;"> @ 25°C:</span>

- **Slope**: 3.5
- **Offset**: Variable (user calibration)

## Data Structure

```c
typedef struct {
    // Raw ADC Reading
    uint16_t raw_value;                // Raw ADC value
    
    // Calculated Values
    float voltage;                     // Converted voltage (0-5V)
    float ph_value;                    // Calculated pH (0-14)
    float reference_voltage;           // ADC reference (typically 3.3V or 5.0V)
    
    // Calibration Parameters
    ph_calibration_t calibration;      // { offset: float, slope: float }
    
    // Averaging Buffer (Noise Filtering)
    uint16_t sample_buffer[40];        // Last 40 samples
    uint8_t sample_index;              // Current position in buffer
    uint8_t samples_collected;         // Total samples collected (0-40)
} ph_sensor_t;
```

## Initialization &amp; Usage

### Initialize pH Sensor

```c
ph_sensor_t ph_sensor;
ph_sensor_init(&ph_sensor, 3.3f);  // 3.3V reference voltage
```

### Poll pH Sensor

```c
result_t ph_result = poll_ph_sensor(&ph_sensor);

if (ph_result == RESULT_OK) {
    float ph_value = ph_sensor.ph_value;
    float voltage = ph_sensor.voltage;
}
```

### Manual Sample Addition

```c
// For manual sampling at regular intervals
uint16_t adc_reading = 2048;  // Example ADC value
ph_sensor_add_sample(&ph_sensor, adc_reading);
```

## Validation

```c
result_t validate_ph_value(float ph_value);
// Returns RESULT_OK if 0 <= ph_value <= 14
// Returns RESULT_ERR_INVALID_DATA otherwise
```

## Sample Averaging Strategy

<table id="bkmrk-parametervaluesample"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Parameter

</th><th>Value

</th></tr><tr><td>Sample Buffer Size

</td><td>40 samples

</td></tr><tr><td>Method

</td><td>Circular buffer moving average

</td></tr><tr><td>Purpose

</td><td>Noise filtering and stable readings

</td></tr><tr><td>Typical Update Latency

</td><td>40ms-800ms

</td></tr></tbody></table>

### Averaging Algorithm

```
1. ADC sample added to circular buffer
2. All 40 samples averaged together
3. Averaged value converted to voltage
4. Voltage converted to pH via calibration
```

## Two-Point Calibration Procedure

### Step 1: Neutral Point (pH 7.0)

```
1. Immerse electrode in pH 7.0 buffer solution
2. Wait for stable reading (~2 minutes)
3. Record voltage: V_neutral
4. Calculate offset adjustment
```

### Step 2: Slope Calibration (pH 4.0 or 10.0)

```
1. Immerse electrode in second known pH solution
2. Wait for stable reading
3. Record voltage: V_reference
4. Calculate slope from two points:
   slope = (pH_reference - 7.0) / (V_reference - V_neutral)
```

## Protobuf Message Format

```protobuf
message SensorBoardPHInfo {
    float ph_value;
    float voltage;
    SensorState state;
    PHErrorCode error_code;
}

enum PHErrorCode {
    PH_NO_ERROR = 0;
    PH_COMMUNICATION_FAILURE = 1;
    PH_INVALID_DATA = 2;
}
```

## Error Handling

```c
if (ph_result == RESULT_ERR_UNIMPLEMENTED) {
    // Hardware not connected
    diagnostics.ph_sensor.state = SensorState_SENSOR_IDLE;
    diagnostics.ph_sensor.error_code = PHErrorCode_PH_COMMUNICATION_FAILURE;
} else if (ph_result == RESULT_OK) {
    if (validate_ph_value(ph_sensor.ph_value) == RESULT_OK) {
        diagnostics.ph_sensor.state = SensorState_SENSOR_OPERATING;
        diagnostics.ph_sensor.error_code = PHErrorCode_PH_NO_ERROR;
    } else {
        // Invalid data from sensor (out of 0-14 range)
        diagnostics.ph_sensor.state = SensorState_SENSOR_ERROR;
        diagnostics.ph_sensor.error_code = PHErrorCode_PH_INVALID_DATA;
    }
}
```

## Integration Notes

- Single sensor instance in main application
- Updates transmitted to network at main loop interval (5 seconds default)
- Temperature compensation not currently implemented (assumes ~25°C)
- Sample averaging reduces noise but introduces ~40ms latency per update
- Electrode response time: ~100-300ms depending on pH change magnitude

# IMU

The Inertial Measurement Unit (IMU) provides three-axis acceleration, angular velocity, and magnetic field measurements for attitude determination and motion analysis.

### Communication Interfaces

- **Accelerometer**: I2C or SPI
- **Gyroscope**: I2C or SPI
- **Magnetometer**: I2C or SPI
- **Update Rate**: Typically 1-100+ Hz (configurable)

## Data Structure

```c
typedef struct {
    // Acceleration (m/s² or g-units)
    float accel[3];            // [X, Y, Z] acceleration
    
    // Angular Velocity (rad/s or °/s)
    float gyro[3];             // [X, Y, Z] rotation rate
    
    // Magnetic Field (Gauss or µT)
    float mag[3];              // [X, Y, Z] magnetic field
    
    // Timestamps
    uint32_t timestamp;        // Current reading timestamp
    uint32_t last_timestamp;   // Previous reading timestamp
} imu_data_t;
```

## Initialization &amp; Usage

### Initialize IMU

```c
imu_data_t imu_data;
imu_sensor_init(&imu_data);
```

### Poll IMU Data

```c
result_t imu_result = poll_imu_sensor(&imu_data);

if (imu_result == RESULT_OK) {
    // Acceleration
    float accel_x = imu_data.accel[0];
    float accel_y = imu_data.accel[1];
    float accel_z = imu_data.accel[2];
    
    // Angular velocity
    float gyro_x = imu_data.gyro[0];
    float gyro_y = imu_data.gyro[1];
    float gyro_z = imu_data.gyro[2];
    
    // Magnetic field
    float mag_x = imu_data.mag[0];
    float mag_y = imu_data.mag[1];
    float mag_z = imu_data.mag[2];
}
```

## Advanced Functions

### Update with Raw Values

```c
result_t imu_sensor_update(
    imu_data_t *imu,
    float ax, float ay, float az,  // Accelerometer values
    float gx, float gy, float gz,  // Gyroscope values
    float mx, float my, float mz,  // Magnetometer values
    uint32_t timestamp
);
```

### Calculate Acceleration Magnitude

```c
float acceleration_magnitude = imu_get_acceleration_magnitude(&imu_data);
// Useful for impact detection and free-fall detection
```

### Thread-Safe Data Reading

```c
imu_data_t imu_copy;
imu_sensor_read(&imu_data, &imu_copy);
// Use imu_copy in another thread without locking
```

## Typical Sensor Ranges

<table id="bkmrk-measurementtypical-r"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>Measurement

</th><th>Typical Range

</th><th>Units

</th></tr><tr><td>Acceleration

</td><td>±16

</td><td>g

</td></tr><tr><td>Angular Velocity

</td><td>±2000

</td><td>°/s

</td></tr><tr><td>Magnetic Field

</td><td>±4800

</td><td>µT

</td></tr></tbody></table>

### Conversion Reference

<table id="bkmrk-fromtofactorgm%2Fs%C2%B2%C3%97-9"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>From

</th><th>To

</th><th>Factor

</th></tr><tr><td>g

</td><td>m/s²

</td><td>× 9.81

</td></tr><tr><td>°/s

</td><td>rad/s

</td><td>× π/180

</td></tr><tr><td>Gauss

</td><td>µT

</td><td>× 100

</td></tr></tbody></table>

## Validation Functions

```c
// Validate single accelerometer value
// Typical range: -50 to +50 m/s2  
result_t validate_accelerometer_value(float accel_value);

// Validate all three acceleration axes
result_t validate_imu_data(
    float accel_x,
    float accel_y,
    float accel_z
);
```

## Protobuf Message Format

```protobuf
message SensorBoardIMUInfo {
    float accel_x;
    float accel_y;
    float accel_z;
    float gyro_x;
    float gyro_y;
    float gyro_z;
    SensorState state;
}
```

## Common Applications

### Impact Detection

```c
float mag = imu_get_acceleration_magnitude(&imu_data);
if (mag > IMPACT_THRESHOLD) {
    // High acceleration detected
}
```

### Tilt Detection

```c
// Calculate tilt angle from acceleration
float tilt_angle = atan2(imu_data.accel[0], imu_data.accel[2]);
```

### Motion Classification

```c
// Static vs dynamic based on gyro magnitude
float gyro_mag = sqrt(gyro_x*gyro_x + gyro_y*gyro_y + gyro_z*gyro_z);
```

## Integration Notes

- Single IMU instance with 3-axis sensors (dual planned)
- All three axes transmitted as independent fields
- Acceleration magnitude available for impact detection
- Timestamp tracking enables dead reckoning applications
- Typical IMU update rate: 10-100 Hz
- Filter algorithms can be applied to raw data for smoothing

# Load Cell

Load cells measure force/weight to detect object presence, evaluate structural loading, or monitor mechanical stress. The system supports dual load cell configuration.

### Hardware Specifications

<table id="bkmrk-parametervaluesensor"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Parameter

</th><th>Value

</th></tr><tr><td>Sensor Count

</td><td>2 (independent)

</td></tr><tr><td>Interface

</td><td>Analog ADC

</td></tr><tr><td>Measurement

</td><td>Force (Newtons) / Mass (grams)

</td></tr><tr><td>Update Rate

</td><td>Configurable

</td></tr></tbody></table>

## Data Structure

```c
typedef struct {
    // Raw Reading
    int32_t raw_counts;                 // ADC value from sensor
    
    // Converted Measurements
    float force_newtons;                // Force in Newtons (N)
    float mass_grams;                   // Equivalent mass in grams (g)
    
    // Calibration Parameters
    float scale_newtons_per_count;      // Conversion factor (N/count)
    int32_t tare_offset_counts;         // Zero-load ADC offset
    
    // Status
    bool is_calibrated;                 // Calibration valid flag
} load_cell_data_t;
```

## Initialization

### Initialize Load Cells

```c
load_cell_data_t load_cell_data[2];  // Support 2 sensors

for (size_t i = 0; i < 2; i++) {
    load_cell_sensor_init(&load_cell_data[i]);
}
```

### Poll Load Cell Sensor

```c
result_t lc_result = poll_load_cell_sensor(&load_cell_data[0]);

if (lc_result == RESULT_OK) {
    float force = load_cell_data[0].force_newtons;
    float mass = load_cell_data[0].mass_grams;
    int32_t raw = load_cell_data[0].raw_counts;
}
```

## Data Access Functions

```c
// Get force in Newtons
result_t load_cell_get_force_newtons(
    const load_cell_data_t *data,
    float *force_newtons
);

// Get mass in grams (estimated)
result_t load_cell_get_mass_grams(
    const load_cell_data_t *data,
    float *mass_grams
);

// Get raw ADC counts
result_t load_cell_get_raw_counts(
    const load_cell_data_t *data,
    int32_t *raw_counts
);

// Get calibration parameters
result_t load_cell_get_calibration(
    const load_cell_data_t *data,
    float *scale_newtons_per_count,
    int32_t *tare_offset_counts
);

// Verify sensor validity
result_t load_cell_sensor_is_valid(
    const load_cell_data_t *data,
    bool *is_valid
);
```

## Calibration Procedure

### Two-Step Calibration

#### Step 1: Tare (Zero Load)

```
1. Remove all load from sensor
2. Measure ADC value: ADC_zero
3. Set tare_offset_counts = ADC_zero
```

#### Step 2: Span (Known Weight)

```
1. Place known weight on sensor
2. Measure ADC value: ADC_loaded
3. Know reference force: F_ref (Newtons)

4. Calculate scale:
   scale = (F_ref - 0.0) / (ADC_loaded - ADC_zero)
   
5. Set scale_newtons_per_count = scale
```

### Measurement Formulas

```c
// Raw force calculation
Force = (raw_counts - tare_offset_counts) × scale_newtons_per_count

// Mass conversion (approximate)
Mass_grams = (Force_newtons / 9.81) × 1000
           ≈ Force_newtons × 102.04
```

## Dual Sensor Management

### Configuration Example

```c
// Initialize both sensors
for (size_t i = 0; i < 2; i++) {
    load_cell_sensor_init(&load_cell_data[i]);
}

// Poll both in sequence
poll_load_cell_sensor(&load_cell_data[0]);
poll_load_cell_sensor(&load_cell_data[1]);

// Access by index
float load_0 = load_cell_data[0].force_newtons;
float load_1 = load_cell_data[1].force_newtons;
```

## Protobuf Message Format

```protobuf
message SensorBoardLoadCellInfo {
    uint32 sensor_index;         // 0 or 1
    float force_newtons;
    float mass_grams;
    SensorState state;
    LoadCellErrorCode error_code;
}
```

## Unit Conversions

<table id="bkmrk-fromtofactornewtonsk"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>From

</th><th>To

</th><th>Factor

</th></tr><tr><td>Newtons

</td><td>kilograms-force (kgf)

</td><td>÷ 9.81

</td></tr><tr><td>Newtons

</td><td>pounds-force (lbf)

</td><td>÷ 4.448

</td></tr><tr><td>grams

</td><td>kilograms

</td><td>÷ 1000

</td></tr></tbody></table>

### Manual Unit Conversion Example

```c
// Convert to pounds-force
float force_lbf = load_cell_data[0].force_newtons / 4.448f;

// Convert to kilograms
float mass_kg = load_cell_data[0].mass_grams / 1000.0f;
```

## Typical Specifications

<table id="bkmrk-load-cell-typemax-lo"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>Load Cell Type

</th><th>Max Load

</th><th>Accuracy

</th></tr><tr><td>Strain gauge (±50N)

</td><td>50N

</td><td>±0.1%

</td></tr><tr><td>Load cell (±100N)

</td><td>100N

</td><td>±0.05%

</td></tr><tr><td>Heavy duty (±1000N)

</td><td>1000N

</td><td>±0.1%

</td></tr></tbody></table>

## Integration Notes

- Supports up to 2 independent load cell sensors
- Hardware-specific ADC implementation
- Force conversion via linear scaling model
- Tare offset corrects for sensor mechanical zero
- Real-time monitoring of structural loads
- Each sensor maintains separate calibration
- Independent error reporting per sensor

# Pressure Sensor

Pressure sensors measure fluid/gas pressure for robotic gripper force feedback and control, depth sensing, altitude measurement, or system pressure monitoring. The system supports dual pressure sensor configuration for dual-pad gripper control with load distribution feedback.

### Hardware Specifications

<table id="bkmrk-parametervaluesensor"><colgroup><col></col><col></col></colgroup><tbody><tr><th>Parameter

</th><th>Value

</th></tr><tr><td>Sensor Count

</td><td>2 (independent)

</td></tr><tr><td>Interface

</td><td>Analog ADC or I2C

</td></tr><tr><td>Measurement Range

</td><td>Variable (typically 0-300 kPa)

</td></tr><tr><td>Update Rate

</td><td>Configurable

</td></tr></tbody></table>

## Data Structure

```c
typedef struct {
    float pressure_kpa;         // Pressure in kilopascals (kPa)
    float temperature_c;        // Temperature in Celsius (°C)
    float voltage;              // Sensor output voltage
    bool is_calibrated;         // Calibration status flag
} pressure_sensor_data_t;
```

## Initialization

### Initialize Pressure Sensors

```c
pressure_sensor_data_t pressure_data[2];  // Support 2 sensors

for (size_t i = 0; i < 2; i++) {
    pressure_sensor_init(&pressure_data[i]);
}
```

### Poll Pressure Sensor

```c
result_t ps_result = poll_pressure_sensor(&pressure_data[0]);

if (ps_result == RESULT_OK) {
    float pressure_kpa = pressure_data[0].pressure_kpa;
    float temperature_c = pressure_data[0].temperature_c;
}
```

## Data Access Functions

```c
// Get pressure in kilopascals
result_t pressure_sensor_get_pressure_kpa(
    const pressure_sensor_data_t *data,
    float *pressure_kpa
);

// Get temperature in Celsius
result_t pressure_sensor_get_temperature_c(
    const pressure_sensor_data_t *data,
    float *temperature_c
);

// Get raw sensor voltage
result_t pressure_sensor_get_voltage(
    const pressure_sensor_data_t *data,
    float *voltage
);

// Verify sensor validity
result_t pressure_sensor_is_valid(
    const pressure_sensor_data_t *data,
    bool *is_valid
);
```

## Pressure Unit Conversions

### Function-Based Conversions

```c
// Convert pressure from bar to psi
result_t bar_to_psi(float bar, float *psi);

// Convert pressure from psi to bar
result_t psi_to_bar(float psi, float *bar);
```

### Conversion Table

<table id="bkmrk-fromtomultiply-bybar"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>From

</th><th>To

</th><th>Multiply By

</th></tr><tr><td>bar

</td><td>kPa

</td><td>100

</td></tr><tr><td>psi

</td><td>kPa

</td><td>6.895

</td></tr><tr><td>atm

</td><td>kPa

</td><td>101.325

</td></tr><tr><td>kPa

</td><td>bar

</td><td>0.01

</td></tr><tr><td>kPa

</td><td>psi

</td><td>0.145

</td></tr><tr><td>kPa

</td><td>atm

</td><td>0.00987

</td></tr></tbody></table>

### Examples

```c
// 100 kPa = 1 bar
float kpa = 100.0f;
float bar = kpa * 0.01f;  // Result: 1.0 bar

// 50 psi to bar
float psi = 50.0f;
float bar = psi / 14.504f;  // Result: 3.45 bar

// Altitude from pressure (simplified)
// Altitude ≈ 44330 × (1 - (P/P0)^(1/5.255))
float altitude_m = 44330.0f * (1.0f - pow(pressure_kpa/101.325f, 1.0f/5.255f));
```

## Protobuf Message Format

```protobuf
message SensorBoardPressureInfo {
    uint32 sensor_index;         // 0 or 1
    float pressure_kpa;
    float temperature_c;
    SensorState state;
    PressureErrorCode error_code;
}
```

## Dual Sensor Management

### Configuration Example

```c
// Initialize both sensors
for (size_t i = 0; i < 2; i++) {
    pressure_sensor_init(&pressure_data[i]);
}

// Poll both in sequence
poll_pressure_sensor(&pressure_data[0]);
poll_pressure_sensor(&pressure_data[1]);

// Access by index
float pressure_0_kpa = pressure_data[0].pressure_kpa;
float pressure_1_kpa = pressure_data[1].pressure_kpa;
```

## Temperature Compensation

Pressure readings often need temperature compensation for accuracy:

```c
// Simplified temperature compensation
float compensated_pressure = pressure_data[0].pressure_kpa * 
    (reference_temperature + 273.15f) / 
    (pressure_data[0].temperature_c + 273.15f);
```

## Applications

### Robotic Gripper Control (Primary Use Case)

```c
// Gripper force feedback for adaptive grip strength
// Pressure reading controls servo/motor PWM to regulate grip force

#define GRIPPER_MIN_PRESSURE_KPA 20.0f   // Minimum safe grip
#define GRIPPER_MAX_PRESSURE_KPA 150.0f  // Maximum allowed grip
#define GRIPPER_TARGET_PRESSURE_KPA 80.0f // Desired grip force

// PID controller for gripper force regulation
typedef struct {
    float kp, ki, kd;            // PID coefficients
    float integral_error;
    float previous_error;
} gripper_pid_t;

// Adjust servo PWM based on pressure feedback
void adjust_gripper_force(float current_pressure_kpa, gripper_pid_t *pid) {
    float error = GRIPPER_TARGET_PRESSURE_KPA - current_pressure_kpa;
    
    pid->integral_error += error;
    float derivative_error = error - pid->previous_error;
    
    float pid_output = (pid->kp * error) + 
                       (pid->ki * pid->integral_error) + 
                       (pid->kd * derivative_error);
    
    // Clamp servo PWM to valid range
    uint16_t servo_pwm = (uint16_t)(GRIPPER_NEUTRAL_PWM + pid_output);
    servo_pwm = (servo_pwm < GRIPPER_MIN_PWM) ? GRIPPER_MIN_PWM : servo_pwm;
    servo_pwm = (servo_pwm > GRIPPER_MAX_PWM) ? GRIPPER_MAX_PWM : servo_pwm;
    
    set_gripper_pwm(servo_pwm);
    pid->previous_error = error;
}
```

**Gripper Control Features:**

- Grip force feedback for object handling
- Object presence detection (pressure spike threshold)
- Adaptive compliance for varying object sizes/materials
- Dual sensors support load sharing across gripper pads

### Implemented but Not Primary

#### Depth Sensing (Water)

```c
// Pressure to depth in water
// P = ρ × g × h
// where ρ = 1025 kg/m³ (seawater), g = 9.81 m/s²
float depth_meters = (pressure_kpa - atmospheric_pressure_kpa) / 10.0f;
```

#### Altitude Sensing (Air)

```c
// Barometric formula (simplified)
float altitude_m = 44330.0f * (1.0f - pow(pressure_kpa/101.325f, 1.0f/5.255f));
```

#### System Pressure Monitoring

```c
if (pressure_kpa > PRESSURE_WARNING_THRESHOLD) {
    // High pressure detected - safety alert
}
```

## Common Pressure Sensor Ranges

<table id="bkmrk-applicationrangetypi"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>Application

</th><th>Range

</th><th>Typical Sensor

</th></tr><tr><td>Altitude (aviation)

</td><td>10-110 kPa

</td><td>BMP280/BMP390

</td></tr><tr><td>Depth (diving)

</td><td>0-300+ kPa

</td><td>Custom depth sensor

</td></tr><tr><td>System pressure

</td><td>0-500+ kPa

</td><td>Industrial pressure transducer

</td></tr></tbody></table>

## Integration Notes

- **Primary Application:**<span style="white-space: pre-wrap;"> Robotic gripper force feedback and control</span>
- Supports up to 2 independent pressure sensors (dual gripper pads)
- Temperature measurement for compensation algorithms
- Hardware-specific ADC or I2C implementation
- Pressure-voltage conversion implemented internally
- Real-time depth/altitude sensing capability
- PID control loop integration for adaptive grip force
- Each sensor maintains independent calibration
- Temperature tracking for accuracy improvements
- Slip detection via pressure variance analysis

# Testing

<span style="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255);">Test organization, validation functions, Unity testing framework usage, manual hardware testing checklists and debugging/troubleshooting guide.</span>

## Test Organization

```
test/sensor_board/
├── test_gps_sensor/
│   ├── NMEA parsing verification
│   ├── Coordinate validation
│   └── Fix quality enumeration tests
│
├── test_imu_sensor/
│   ├── 3-axis data structure tests
│   ├── Acceleration magnitude calculation
│   └── Timestamp tracking validation
│
├── test_load_sensor/
│   ├── Force calculation (tare/scale)
│   ├── Mass conversion (g/kg/lbf)
│   ├── Dual sensor independence
│   └── Calibration parameter storage
│
├── test_ph_sensor/
│   ├── ADC to voltage conversion
│   ├── pH value calculation
│   ├── Sample averaging (40-sample buffer)
│   ├── Calibration offset/slope
│   └── Edge cases (pH 0, 14)
│
├── test_pressure_sensor/
│   ├── Pressure reading validation
│   ├── Temperature compensation
│   ├── Unit conversions (bar/psi/kPa)
│   └── Dual sensor independence
│
└── test_sensor_basics/
    ├── Conversion functions
    ├── Validation functions
    ├── Range checking
    └── Boundary conditions
```

## Validation Functions

### GPS Validation

```c
result_t validate_gps_latitude(double latitude);
// Valid range: -90.0 to +90.0 degrees

result_t validate_gps_longitude(double longitude);
// Valid range: -180.0 to +180.0 degrees

result_t validate_gps_hdop(float hdop);
// Typical range: 0.0 to 50.0

result_t validate_gps_satellite_count(int32_t satellites);
// Typical range: 0 to 30 satellites
```

### pH Validation

```c
result_t validate_ph_value(float ph_value);
// Valid range: 0.0 to 14.0
```

### IMU Validation

```c
result_t validate_accelerometer_value(float accel_value);
// Typical range: -50.0 to +50.0 m/s²

result_t validate_imu_data(float accel_x, float accel_y, float accel_z);
```

### Unit Conversion Tests(extra stuff)

```c
result_t celsius_to_fahrenheit(float celsius, float *fahrenheit);
result_t fahrenheit_to_celsius(float fahrenheit, float *celsius);
result_t bar_to_psi(float bar, float *psi);
result_t psi_to_bar(float psi, float *bar);
```

## Test Framework

### Testing Technology

- **Framework**: Unity (open-source testing framework)
- **Build System**: CMake / PlatformIO
- **Test Type**: Hosted unit tests (run on PC)
- **Mocking**: Mock ADC/UART/I2C interfaces

### Building Tests

```bash
// Build all tests
pio test -e sensor_board

// Run specific test suite
pio test -e sensor_board -f test_ph_sensor
```

## Debugging &amp; Troubleshooting

<table id="bkmrk-issuecausesolutionse"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>Issue

</th><th>Cause

</th><th>Solution

</th></tr><tr><td>Sensor IDLE

</td><td>Not connected

</td><td>Check UART/I2C/SPI wiring

</td></tr><tr><td>Sensor ERROR

</td><td>Communication failure

</td><td>Verify baud rates, addresses

</td></tr><tr><td>Invalid data

</td><td>Out of range

</td><td>Check calibration parameters

</td></tr><tr><td>Low heap warning

</td><td>Memory leak

</td><td>Review packet encoder

</td></tr></tbody></table>

# Reference

Checkout the embedded simplified from ERC

## Source Code References

<span style="white-space: pre-wrap;">If you made it till here, you a true G. Also, this was rather useful, check this out... </span>[ERC Embedded Simplified Official](https://creations.mtdv.me/Rick-ZarolERC-Embedded-Simplified-Official "ERC Embedded Simplified Official")

### Main Application

<table id="bkmrk-filepurposesrc%2Fsenso"><colgroup><col></col><col></col></colgroup><tbody><tr><th>File

</th><th>Purpose

</th></tr><tr><td>`<span class="editor-theme-code">src/sensor_board/main.c</span>`

</td><td>Main entry point and sensor loop

</td></tr></tbody></table>

### Sensor Drivers

<table id="bkmrk-sensorheadersourcegp"><colgroup><col></col><col></col><col></col></colgroup><tbody><tr><th>Sensor

</th><th>Header

</th><th>Source

</th></tr><tr><td>**GPS**

</td><td>`<span class="editor-theme-code">components/sensor_board/gps/gps_sensor.h</span>`

</td><td>`<span class="editor-theme-code">gps_sensor.c</span>`

</td></tr><tr><td>**IMU**

</td><td>`<span class="editor-theme-code">components/sensor_board/imu/imu_sensor.h</span>`

</td><td>`<span class="editor-theme-code">imu_sensor.c</span>`

</td></tr><tr><td>**pH**

</td><td>`<span class="editor-theme-code">components/sensor_board/ph/ph_sensor.h</span>`

</td><td>`<span class="editor-theme-code">ph_sensor.c</span>`

</td></tr><tr><td>**Load Cell**

</td><td>`<span class="editor-theme-code">components/sensor_board/load_cell/load_cell_sensor.h</span>`

</td><td>`<span class="editor-theme-code">load_cell_sensor.c</span>`

</td></tr><tr><td>**Pressure**

</td><td>`<span class="editor-theme-code">components/sensor_board/pressure/pressure_sensor.h</span>`

</td><td>`<span class="editor-theme-code">pressure_sensor.c</span>`

</td></tr><tr><td>**Utilities**

</td><td>`<span class="editor-theme-code">components/sensor_board/sensor_basics/sensor_basics.h</span>`

</td><td>`<span class="editor-theme-code">sensor_basics.c</span>`

</td></tr></tbody></table>

### Protobuf Definitions

**For detailed protobuf message format documentation, see:**<span style="white-space: pre-wrap;"> </span>[Sensor Board Protobuf](https://bookstack.roboteamtwente.nl/books/communication-system/page/sensor-board-protobuf "Sensor Board Protobuf")

```
ERC-Protobufs/components/sensor_board/
├── diagnostics.proto       - Board-level diagnostics and health status
├── sensor.proto            - Aggregated sensor state message
├── gps_sensor.proto        - GNSS positioning data
├── imu_sensor.proto        - Inertial measurement (acceleration, gyro, mag)
├── ph_sensor.proto         - Water quality pH measurement
├── load_cell.proto         - Force measurement (gripper control)
└── pressure_sensor.proto   - Pressure measurement (gripper control)
```

### Build Configuration

<table id="bkmrk-filepurposeplatformi"><colgroup><col></col><col></col></colgroup><tbody><tr><th>File

</th><th>Purpose

</th></tr><tr><td>`<span class="editor-theme-code">platformio.ini</span>`

</td><td>Build configuration for all boards

</td></tr><tr><td>`<span class="editor-theme-code">.mxproject</span>`

</td><td>STM32 CubeMX configuration

</td></tr><tr><td>`<span class="editor-theme-code">firmware.ioc</span>`

</td><td>CubeMX IOC file (device config)

</td></tr></tbody></table>

## Test References

### Test Suites

```
test/sensor_board/
├── test_gps_sensor/
├── test_imu_sensor/
├── test_load_sensor/
├── test_ph_sensor/
├── test_pressure_sensor/
└── test_sensor_basics/
```

### Running Tests

```bash
// Build all tests
pio test -e sensor_board

// Run with verbose output
pio test -e sensor_board -v

// Build only (no run)
pio test -e sensor_board --no-run
```

## Hardware References

### Microcontroller

- **STM32H753ZI**<span style="white-space: pre-wrap;"> - ARM Cortex-M7, 480 MHz, 1 MB Flash</span>
- **Datasheet**: ST Microelectronics STM32H7 reference

### Ethernet

- **LAN8742**<span style="white-space: pre-wrap;"> - Ethernet PHY</span>
- **Interface**: RMII (Reduced Media Independent Interface)
- **Link Speed**: 10/100 Mbps auto-negotiation

### Sensors

<table id="bkmrk-sensormodelprotocoln"><colgroup><col></col><col></col><col></col><col></col></colgroup><tbody><tr><th>Sensor

</th><th>Model

</th><th>Protocol

</th><th>Note

</th></tr><tr><td>GPS

</td><td>GY-NEO6MV2 (NEO-6M)

</td><td>UART 9600

</td><td>Default NMEA config

</td></tr><tr><td>IMU

</td><td>Reference design TBD

</td><td>I2C/SPI

</td><td>Dual IMU planned

</td></tr><tr><td>pH

</td><td>DFRobot SEN0161

</td><td>ADC Analog

</td><td>40-sample averaging

</td></tr><tr><td>Load Cell

</td><td>Application specific

</td><td>ADC

</td><td>Dual cells (×2)

</td></tr><tr><td>Pressure

</td><td>Application specific

</td><td>ADC/I2C

</td><td>Dual sensors (×2)

</td></tr></tbody></table>

## Development Workflow

### Building Firmware

```bash
// Build for sensor_board
pio run -e sensor_board

// Upload to board
pio run -e sensor_board --target upload

// Monitor serial output
pio device monitor -b 115200 -p COM4
```

### Development Cycle

```bash
// 1. Edit source code
// 2. Build
pio run -e sensor_board

// 3. Upload
pio run -e sensor_board --target upload

// 4. Monitor
pio device monitor

// 5. Run tests
pio test -e sensor_board
```

## Useful Commands

### PlatformIO CLI

```bash
// List available boards
pio boards

// Show board info
pio boards nucleo_h753zi

// Clean build
pio run -e sensor_board --target clean

// Full rebuild
pio run -e sensor_board --target clean --target upload
```

## Troubleshooting Guide

### Serial Monitor No Output

1. Check USB connection
2. Verify COM port in platformio.ini
3. Check baud rate (115200)
4. Verify UART initialization succeeded

### Sensor Shows IDLE

1. Check physical connection (UART/I2C/SPI)
2. Verify baud rate (for UART sensors)
3. Check pull-up resistors (for I2C)
4. Verify device address (for I2C/SPI)

### Network Packets Not Received

1. Check IP address configuration
2. Verify MAC address filtering
3. Check firewall settings
4. Verify UDP port 7 not blocked

### Low Heap Warning

1. Check for memory leaks in sensor drivers
2. Reduce buffer sizes
3. Verify UDP queue sizes
4. Check for recursive allocations

## Quick Reference Checklist

### Before Deployment

- [ ] All sensors connected and responding
- [ ] Network IP/MAC configured correctly
- [ ] Calibration parameters set for pH/load cells
- [ ] Serial monitor showing sensor data
- [ ] Heap usage monitored (&gt;8KB free)
- [ ] UDP packets reaching destination
- [ ] Protobuf encoding verified

### Monitoring in Production

- [ ] Check sensor status codes
- [ ] Monitor heap usage trend
- [ ] Verify data ranges match expectations
- [ ] Track error rates per sensor
- [ ] Review network packet statistics

---

**Hope you had fun☮️**

**END OF DOCUMENTATION FOR SESOR BOARD**☮️

**Last Updated: April 14, 2026**

# Debugging Board

Lil gameboy doodad

# Overview

<span style="white-space: pre-wrap;">The debugging board is a </span>**dedicated auxiliary system**<span style="white-space: pre-wrap;"> whose only job is to make the rest of the robot less painful to work with.</span>

<p class="callout info">It is not part of the rover’s core functionality.</p>

## Purpose

At a high level, the debugging board serves roles:

### Visibility

Provide real-time insight into system state:

- logs
- status indicators
- network activity
- subsystem health

Instead of digging through serial output on multiple MCUs or adding temporary debug code everywhere, this board aggregates and presents useful information.

### Control / Interaction

<p class="callout danger">Todo :D</p>

### Isolation of debugging concerns

<p class="callout danger">Todo :D</p>

## Physical components

The exact hardware may evolve, but the debugging board generally consists of:

### Ethernet interface

- Connects to the system network (switch / internal bus)
- Receives and sends packets (including protobuf-based messages)
- Acts as a bridge between the debugging interface and the rest of the robot

### Display

- Shows system state, logs, or selected information
- <span style="white-space: pre-wrap;">It is a </span>**ILI9341 SPI Display**

Used for quick, local feedback without needing a laptop.

### Input interface (buttons / panel)

- Physical buttons or switches
- Used to:
    - trigger actions
    - navigate menus
    - send commands

# Display - ILI9341 Hardware Configuratoin

The debugging board incorporates a graphical display based on the ILI9341 controller. This display serves as the primary local interface for presenting system state, diagnostics, and user feedback.

<span style="white-space: pre-wrap;">The ILI9341 is a widely used TFT LCD controller that integrates display driving logic, internal GRAM (Graphics RAM), and a command-based interface over serial or parallel buses. In this system, it is used in </span>**SPI mode**, which aligns with the board’s pin constraints and simplifies integration with the MCU.

## Functional Role in the System

Within the debugging board, the display is responsible for:

- Rendering system status (connectivity, subsystem health, etc.)
- Displaying structured debugging information
- Providing immediate visual feedback to user input (button interactions)
- Supporting simple UI constructs (menus, indicators, overlays)

<p class="callout warning"><span style="white-space: pre-wrap;">The display is not intended for high-throughput graphics or complex rendering. Its role is </span>**informational and interactive**, not graphical-intensive.</p>

## Features of the ILI9341

The ILI9341 controller provides a set of features well suited for embedded applications.

### Resolution and Color Depth

- <span style="white-space: pre-wrap;">Resolution: </span>**240 × 320 pixels**
- <span style="white-space: pre-wrap;">Color depth: </span>**16-bit RGB (RGB565)**

This provides sufficient resolution for:

- text rendering
- simple UI layouts
- basic graphical elements (icons, shapes)

### Internal GRAM (Frame Buffer)

<p class="callout info"><span style="white-space: pre-wrap;">The controller includes internal </span>**Graphics RAM (GRAM)**, which stores pixel data.</p>

- <span style="white-space: pre-wrap;">The MCU does </span>**not**<span style="white-space: pre-wrap;"> need to maintain a full framebuffer</span>
- Pixel data is written directly to the display over SPI
- The display retains the image until overwritten

This significantly reduces RAM requirements on the MCU, which is critical in embedded systems.

### Command-Based Interface

The display is controlled through a command/data protocol:

- Commands configure behavior (e.g., orientation, pixel format)
- Data writes update pixel values in GRAM

Typical operations include:

- setting an address window
- writing pixel data
- issuing initialization sequences

### Display Orientation and Addressing

The controller supports:

- configurable screen rotation (portrait / landscape)
- programmable address windows

This allows:

- flexible UI layout
- efficient partial updates (writing only specific regions)

### Hardware Reset and Initialization

The display requires:

- a hardware reset sequence
- a series of configuration commands during initialization

These typically configure:

- power control
- gamma curves
- pixel format
- memory access control

<p class="callout danger"><span style="white-space: pre-wrap;">ILI9341 is a relatively complex and if you want to do anything with the internal library of it you need more than what can be written here. </span>**Read the** [official documentation](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf)</p>

## MCU Configuration

The SPI peripheral must be configured with:

- <span style="white-space: pre-wrap;">Mode: </span>**Full-Duplex Master**
- <span style="white-space: pre-wrap;">Data size: </span>**8-bit**
- <span style="white-space: pre-wrap;">First bit: </span>**MSB-first**
- <span style="white-space: pre-wrap;">Clock polarity: </span>**Low**
- <span style="white-space: pre-wrap;">Clock phase: </span>**1st edge**
- <span style="white-space: pre-wrap;">NSS: </span>**Software**
- Baud rate prescaler: selected based on display stability

These settings must match the display’s timing requirements.

<p class="callout info">For the baud rate, you want it to be as high as possible without it being unstable. For debugging and testing, it's good practice to lower it first, get it working there (as it is a lot more stable) and then increase it again.</p>

# Display - ILI9341 Library

## Purpose

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">ili9341</span>`<span style="white-space: pre-wrap;"> library provides the low-level and mid-level drawing interface for the ILI9341-based display used on the debugging board.</span>

Its role is to hide the raw command sequence and SPI transaction details of the display controller behind a set of functions for:

- initialization
- display configuration
- pixel and region drawing
- primitive graphics
- text rendering
- monochrome bitmap rendering
- rounded rectangle rendering

<p class="callout info">In other words, this library is the software layer that turns the display from a peripheral into a usable rendering surface.</p>

## Scope of the Library

This library sits close to the hardware.

It is responsible for:

- driving the ILI9341 controller over SPI
- controlling the display GPIO lines (`<span class="editor-theme-code">CS</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">DC</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">RST</span>`)
- issuing the controller initialization sequence
- writing pixel data to the display GRAM
- exposing simple drawing primitives for higher-level UI code

<span style="white-space: pre-wrap;">It is </span>**not**<span style="white-space: pre-wrap;"> responsible for:</span>

- application UI logic
- layout management
- widget systems
- maintaining a full framebuffer
- asynchronous rendering scheduling

<p class="callout info">This is a direct-draw display driver and utility library, not a graphics framework.</p>

## High-Level Design

The library is structured around four layers of functionality.

### Transport layer

These functions send commands and bytes over SPI:

- `<span class="editor-theme-code">ILI9341_SPI_Send()</span>`
- `<span class="editor-theme-code">ILI9341_Write_Command()</span>`
- `<span class="editor-theme-code">ILI9341_Write_Data()</span>`

### Display control layer

These functions manage display state and configuration:

- `<span class="editor-theme-code">ILI9341_Reset()</span>`
- `<span class="editor-theme-code">ILI9341_Set_Address()</span>`
- `<span class="editor-theme-code">ILI9341_Set_Rotation()</span>`
- `<span class="editor-theme-code">ILI9341_Enable()</span>`
- `<span class="editor-theme-code">ILI9341_Init()</span>`

### Primitive drawing layer

These functions draw directly to the screen:

- single colours
- pixels
- colour bursts
- lines
- rectangles
- bitmaps
- colour arrays

### Utility rendering layer

These functions build on the primitives to provide:

- text rendering
- rounded-corner rendering
- custom monochrome corner bitmap generation

This layered structure is important. Most higher-level code should use the drawing primitives and utility functions, not manually emit ILI9341 commands unless there is a very specific reason.

## Hardware Interface Definitions

The header defines the display connection through compile-time macros.

### SPI instance

```c
#define HSPI_INSTANCE &hspi1
```

This selects the SPI peripheral used to communicate with the display.

### GPIO control lines

```c
#define LCD_CS_PORT TFT_CS_GPIO_Port
#define LCD_CS_PIN TFT_CS_Pin

#define LCD_DC_PORT TFT_DC_GPIO_Port
#define LCD_DC_PIN TFT_DC_Pin

#define LCD_RST_PORT TFT_RESET_GPIO_Port
#define LCD_RST_PIN TFT_RESET_Pin
```

These define:

- chip select
- data/command selection
- hardware reset

The library assumes these symbols are provided by the board support layer.

### Screen dimensions

```c
#define ILI9341_SCREEN_HEIGHT 240
#define ILI9341_SCREEN_WIDTH 320
```

These define the nominal physical display dimensions..

### Burst limit

```c
#define BURST_MAX_SIZE 500
```

This controls the maximum temporary buffer size used during burst-style SPI transfers.

It affects:

- solid colour fills
- colour array streaming
- bitmap rendering

This is a performance and stack/RAM tradeoff parameter.

## Color Definitions

The header provides a set of named RGB565 color constants, for example:

- `<span class="editor-theme-code">BLACK</span>`
- `<span class="editor-theme-code">WHITE</span>`
- `<span class="editor-theme-code">RED</span>`
- `<span class="editor-theme-code">GREEN</span>`
- `<span class="editor-theme-code">BLUE</span>`
- `<span class="editor-theme-code">YELLOW</span>`
- `<span class="editor-theme-code">CYAN</span>`
- `<span class="editor-theme-code">MAGENTA</span>`

These are convenience values for application code and drawing functions.

<span style="white-space: pre-wrap;">All colors are represented in </span>**16-bit RGB565 format**, which matches the configured pixel format of the display controller.

## Initialization Sequence

### `<span class="editor-theme-code">ILI9341_Init()</span>`

```c
void ILI9341_Init(void);
```

This is the main initialization routine.

### What it does

It performs:

1. display enable
2. SPI init hook
3. hardware reset
4. software reset
5. a full controller configuration sequence
6. exit from sleep mode
7. display on
8. initial screen rotation selection

### Initialization sequence contents

The function writes a fixed command sequence configuring:

- power control
- driver timing
- pump ratio
- VCOM control
- memory access control
- pixel format
- frame rate
- gamma correction
- sleep exit
- display enable

This is the board’s current known-good configuration for the display.

### Why this matters

This sequence is not arbitrary boilerplate. It defines the electrical and visual behavior of the panel.

If it is modified, the maintainer must understand whether the change is:

- controller-required
- panel-specific
- timing-related
- cosmetic
- or cargo-culted from another project

## Basic Drawing Primitives

### `<span class="editor-theme-code">ILI9341_Draw_Colour()</span>`

```c
void ILI9341_Draw_Colour(uint16_t Colour);
```

Writes one pixel’s worth of RGB565 data to the display.

This function assumes the correct address window is already set.

It is mainly an internal low-level helper.

### `<span class="editor-theme-code">ILI9341_Draw_Colour_Burst()</span>`

```c
void ILI9341_Draw_Colour_Burst(uint16_t Colour, uint32_t Size);
```

Draws a repeated color value over a number of pixels.

#### Use case

Efficiently fill:

- large solid regions
- lines
- screen clears

#### How it works

It creates a temporary burst buffer containing repeated color bytes and transmits it in chunks.

<p class="callout info">This is much more efficient than sending each pixel individually.</p>

#### Importance

This function is central to the performance of:

- full screen fills
- rectangle fills
- line drawing

### `<span class="editor-theme-code">ILI9341_Draw_Colour_Array()</span>`

```c
void ILI9341_Draw_Colour_Array(const uint16_t *Colour, uint32_t PixelCount);
```

Draws an array of RGB565 pixel values.

#### Use case

Use this when the caller already has pixel data prepared, for example:

- image rendering
- precomputed graphics
- generated color buffers

#### Important implementation detail

<p class="callout warning"><span style="white-space: pre-wrap;">The function converts each </span>`<span class="editor-theme-code">uint16_t</span>`<span style="white-space: pre-wrap;"> color into big-endian byte order before sending.</span></p>

This is correct for SPI transmission to the display controller.

### `<span class="editor-theme-code">ILI9341_Draw_Pixel()</span>`

```c
void ILI9341_Draw_Pixel(uint16_t X, uint16_t Y, uint16_t Colour);
```

Draws one pixel at a specific coordinate.

#### Behavior

It:

- bounds checks the coordinate
- manually sets X address
- manually sets Y address
- issues memory write
- writes one pixel color

#### Performance note

<p class="callout warning"><span style="white-space: pre-wrap;">This is a </span>**very slow** operation compared to region-based drawing because it reissues addressing commands for every pixel.</p>

It is suitable for:

- sparse pixel updates
- debugging
- very small shapes

It is not suitable for rendering larger regions.

### `<span class="editor-theme-code">ILI9341_Fill_Screen()</span>`

```c
void ILI9341_Fill_Screen(uint16_t Colour);
```

Fills the whole display with one color.

#### Behavior

It sets the address window to the whole screen and then sends a repeated-color burst.

## Text Rendering

### `<span class="editor-theme-code">ILI9341_WriteString()</span>`

```c
void ILI9341_WriteString(uint16_t x, uint16_t y, const char *str,
                         ILI9341_FontDef font, uint16_t color,
                         uint16_t bgcolor);
```

Renders a null-terminated string using the specified font and foreground/background colors.

### Behavior

- iterates through each character
- wraps to the next line if the current X position exceeds screen width
- stops if the next line would exceed screen height

### Internal helper

This uses the internal function:

```c
static void ILI9341_WriteChar(...)
```

which renders one character pixel-by-pixel using the font bitmap.

## Bitmap Rendering

### `<span class="editor-theme-code">ILI9341_Draw_Bitmap()</span>`

```c
void ILI9341_Draw_Bitmap(uint16_t x, uint16_t y,
                         uint16_t w, uint16_t h,
                         const uint8_t *bitmap,
                         uint16_t Color, uint16_t BgColor);
```

<span style="white-space: pre-wrap;">Draws a </span>**1-bit-per-pixel bitmap**<span style="white-space: pre-wrap;"> into a rectangular region.</span>

### Expected bitmap format

The input bitmap is interpreted as packed monochrome data:

- 1 bit per pixel
- row-major
- MSB-first within each byte

### Rendering behavior

For each bit:

- <span style="white-space: pre-wrap;">set bit -&gt; draw </span>`<span class="editor-theme-code">Color</span>`
- <span style="white-space: pre-wrap;">clear bit -&gt; draw </span>`<span class="editor-theme-code">BgColor</span>`

### Use case

This is useful for:

- icons
- glyph-like shapes
- masks
- rounded corner patterns

<p class="callout info">It is not for full-color image rendering.</p>

## R³: Rounded Rectangle Rendering

The library includes support for rounded rectangle outlines using generated monochrome corner bitmaps.

<p class="callout info">This is more advanced than the rest of the primitive API and deserves separate explanation.</p>

### Why?

<p class="callout success">The reasons why the R³ system is highly important - if not necessary - are plenty and extensive. That's why I compiled a pastebin[<span style="white-space: pre-wrap;"> that includes all reasons</span>](https://pastebin.com/h6b2e2W0). Feel free to read it even though I believe it is pretty self explanatory</p>

### Concept

A rounded rectangle is rendered by:

1. generating a 1bpp bitmap for one rounded corner
2. rotating that bitmap to obtain all four corners
3. drawing the four corner bitmaps
4. drawing straight rectangle segments between them

This is a practical method for an SPI-driven display because it avoids expensive per-pixel circle calculations at draw time for every corner.

### Internal helpers

The implementation includes internal static helpers:

- `<span class="editor-theme-code">ILI9341_Get_Rounded_Corner_Bitmap()</span>`
- `<span class="editor-theme-code">bitmap_rotate_90_cw_1bpp()</span>`
- `<span class="editor-theme-code">ILI9341_Build_All_Rounded_Corners()</span>`
- `<span class="editor-theme-code">ILI9341_Draw_Rectangle_Custom_Corner()</span>`

These are not part of the public API, but they are important for maintainers to understand.

### `<span class="editor-theme-code">ILI9341_Draw_Rectangle_Rounded_Corner()</span>`

```c
result_t ILI9341_Draw_Rectangle_Rounded_Corner(
    uint16_t X, uint16_t Y, uint16_t Width, uint16_t Height,
    uint8_t thickness, uint8_t radius,
    uint8_t *corner_buffer, size_t corner_buffer_size,
    uint16_t Colour, uint16_t Bg_Colour);
```

This is the main public rounded rectangle API currently implemented with explicit caller-provided corner buffer storage.

#### Why caller-provided memory is used

The function requires the caller to provide a temporary buffer for the generated corner bitmaps.

This avoids hidden dynamic allocation and gives the caller control over memory use.

#### Buffer sizing

<p class="callout warning"><span style="white-space: pre-wrap;">The function expects enough memory for </span>**four**<span style="white-space: pre-wrap;"> 1bpp bitmaps, one for each corner.</span></p>

It computes the required size as:

```c
4 * (((radius + 7) >> 3) * radius)
```

in bytes.

#### Return values

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_NO_MEM</span>`<span style="white-space: pre-wrap;"> if the provided buffer is too small</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> for invalid parameters via internal helpers</span>

#### Use case

This function is appropriate when the UI wants rounded bordered rectangles without a full framebuffer.

# Menu Driver - Overview

## Purpose

<p class="callout info"><span style="white-space: pre-wrap;">The menu driver is a </span>**page-based UI framework**<span style="white-space: pre-wrap;"> for an embedded display (ILI9341). </span></p>

It defines:

- how UI is structured into pages
- how state is stored per page
- how navigation works
- how rendering is organized

## Architecture Position

```
[ Application Logic ]        
↓[ Menu Driver ]        
↓[ ILI9341 Driver ]        
↓[ SPI / Hardware ]
```

<span style="white-space: pre-wrap;">It </span>**does not**:

- own the main loop
- schedule tasks
- interpret input fully

<span style="white-space: pre-wrap;">It </span>**does**:

- define UI structure
- manage page lifecycle
- coordinate rendering

---

## 1.3 Design Model

Everything revolves around:

> “A UI is a collection of pages with lifecycle and state.”

Each page has:

- state
- init/update/render/destruct
- parent relationship

```
         Input / System Events
                  │
                  ▼
        ┌─────────────────────┐
        │   menu_manager_t    │
        │─────────────────────│
        │ active_page_id      │
        │ pages[]             │
        │ get_input()         │
        └─────────┬───────────┘
                  │ selects active page
                  ▼
      ┌──────────────────────────────────┐
      │          Active Page             │
      │──────────────────────────────────│
      │ state pointer                    │
      │ init()       ─┐                  │
      │ update()      ├─ custom page     │
      │ render()      ┤  behavior        │
      │ destruct()    ┘                  │
      └────────────────┬─────────────────┘
                       │ reads/writes
                       ▼
              ┌──────────────────┐
              │   Page State     │
              │──────────────────│
              │ selection        │
              │ cached values    │
              │ render flags     │
              │ page-local data  │
              └──────────────────┘
```

# Menu Driver - Configuration Layer

## Visual Configuration

```c
#define MENU_DRIVER_BACKGROUND_COLOR 0x0000
#define MENU_DRIVER_FOREGROUND_COLOR 0xFFFF
```

<span style="white-space: pre-wrap;">Black background, white foreground. </span>

## Layout Constraints

```c
#define MENU_SIDEBAR_WIDTH 38
```

Sidebar (ribbon) width.

## Capacity Limits

### List Pages

```c
#define MAX_LIST_ENTRIES 10
#define MAX_LIST_TITLE_LEN 24
```

### Overview Pages

```c
#define MENU_OVERVIEW_MAX_ENTRIES 10
#define MENU_OVERVIEW_MAX_ENTRY_TITLE_LEN 12
```

### Global

```c
#define MAX_PAGE_NAME_LEN 20
```

These define:

- memory footprint
- UI density
- rendering assumptions

# Menu Driver - Core Data Structures

## Page State Types

A **page state**<span style="white-space: pre-wrap;"> is:</span>

> <span style="white-space: pre-wrap;">The </span>**persistent data container**<span style="white-space: pre-wrap;"> that represents everything a UI page needs to function between frames.</span>

Not just data. It’s:

- memory of what the user did
- memory of what was rendered
- memory of external data (diagnostics, etc.)

### List Page State

```c
typedef struct {
  uint8_t num_entries;
  uint8_t selected_index;
  uint8_t entry_ids[MAX_LIST_ENTRIES];
  const uint8_t (*entry_icons)[MENU_DRIVER_ICON_BYTE_SIZE];
  bool first_render;
} page_list_state;
```

#### Responsibilities:

- track selection
- map entries → page IDs
- hold icons
- manage first render optimization

### Overview Page State

<p class="callout danger">Todo :D</p>

### State Union

```c
typedef union {
  page_list_state list;
  page_overview_state overview;
} menu_page_state;
```

### Page Type

```c
typedef enum {
  MENU_PAGE_TYPE_LIST,
  MENU_PAGE_TYPE_OVERVIEW,
} menu_page_type_t;
```

Used to interpret the union correctly.

## Page Object

```c
typedef struct {
  menu_page_state *state;
  menu_page_type_t type;
  unsigned char id;
  unsigned char parent_id;
  bool needs_render;

  char name[MAX_PAGE_NAME_LEN];

  void (*init)(menu_page_state *);
  void (*update)(menu_manager_t *);
  void (*render)(menu_manager_t *);
  void (*destruct)(menu_page_state *);
} menu_page_t;
```

<p class="callout info"><span style="white-space: pre-wrap;">This is the </span>**core abstraction**.</p>

### Definition and Role

A page object represents one logical screen within the menu system. It encapsulates:

- the data required to represent the page (via its state)
- the functions required to manage its lifecycle
- metadata used for navigation and identification

This abstraction allows the menu system to treat all pages uniformly, regardless of their internal implementation or purpose.

### Important Fields

#### Render Control

```c
bool needs_render;
```

This flag indicates whether the page requires re-rendering.

It allows the system to avoid unnecessary redraw operations, which is critical in environments where display updates are expensive.

<span style="white-space: pre-wrap;">The responsibility for managing this flag lies with the </span>**page implementation**<span style="white-space: pre-wrap;">. </span>

### Lifecycle Function Pointers

Each page defines its own behavior through four function pointers:

#### Initialization

```c
void (*init)(menu_page_state *state);
```

Responsible for preparing the page state when the page becomes active.

Typical responsibilities include:

- resetting selection indices
- initializing flags
- preparing any required data structures

#### Update

```c
void (*update)(struct menu_manager_t *manager);
```

Handles input processing and state updates.

This function is expected to:

- read input through the manager
- modify internal state accordingly
- trigger page transitions if necessary

#### Render

```c
void (*render)(struct menu_manager_t *manager);
```

Responsible for drawing the page to the display.

This function should:

- read from the page state
- issue drawing commands via the display driver
- <span style="white-space: pre-wrap;">respect the </span>`<span class="editor-theme-code">needs_render</span>`<span style="white-space: pre-wrap;"> flag when applicable</span>

#### Destruction

```c
void (*destruct)(menu_page_state *state);
```

Handles cleanup when the page is no longer active.

In embedded systems, this typically involves:

- resetting state fields
- releasing logical ownership of resources

Dynamic memory cleanup is generally not required unless explicitly used.

## Menu Manager

```c
typedef struct {
  unsigned char active_page_id;
  const menu_page_t *pages;
  menu_input (*get_input)(void);
} menu_manager_t;
```

Responsibilities:

- track active page
- provide input access
- hold page table

Does NOT:

- validate anything
- own memory
- manage concurrency

# Menu Driver - Overview Page

## Introduction

<span style="white-space: pre-wrap;">The </span>**List Page**<span style="white-space: pre-wrap;"> is a navigation-oriented page type within the menu driver. It provides a structured interface for selecting between multiple entries, typically representing:</span>

- subpages
- actions
- system modules

<span style="white-space: pre-wrap;">It is the primary mechanism for </span>**user-driven navigation**<span style="white-space: pre-wrap;"> within the menu system.</span>

## Purpose

The list page exists to answer:

> **“Where do you want to go next?”**

It is not responsible for displaying system state in detail. Instead, it:

- presents a bounded set of selectable entries
- tracks the current selection
- provides visual feedback for navigation
- enables transitions to other pages

<span style="white-space: pre-wrap;">In practice, it functions as the </span>**entry point and routing layer**<span style="white-space: pre-wrap;"> of the UI.</span>

## Architectural Role

The list page sits at the intersection of:

- **input handling**<span style="white-space: pre-wrap;"> (user navigation)</span>
- **menu structure**<span style="white-space: pre-wrap;"> (page hierarchy)</span>
- **visual rendering**<span style="white-space: pre-wrap;"> (icons and labels)</span>

```
[ Input ] → [ List Page ] → [ Page Transition ]
```

<span style="white-space: pre-wrap;">It does not consume system data (like overview pages), but rather controls </span>**flow through the interface**.

## Data Model

The list page is backed by the following state structure:

```c
typedef struct {
  uint8_t num_entries;
  uint8_t selected_index;
  uint8_t entry_ids[MAX_LIST_ENTRIES];
  const uint8_t (*entry_icons)[MENU_DRIVER_ICON_BYTE_SIZE];
  bool first_render;
} page_list_state;
```

### Entry Management

`<span class="editor-theme-code">num_entries</span>`

Defines how many entries are currently active.

<span style="white-space: pre-wrap;">This value must not exceed </span>`<span class="editor-theme-code">MAX_LIST_ENTRIES</span>`.

`<span class="editor-theme-code">entry_ids</span>`

Maps each visible entry to a logical identifier.

These IDs are typically used to:

- determine which page to switch to
- associate actions with selections

`<span class="editor-theme-code">entry_icons</span>`

Pointer to icon data associated with each entry.

- Icons are rendered alongside entries
- Each icon is a fixed-size bitmap
- Icons are stored in flash as static data

### Selection State

#### `<span class="editor-theme-code">selected_index</span>`

Indicates which entry is currently selected.

This is the central piece of state for navigation.

All rendering and transitions depend on this value.

### Render Control

`<span class="editor-theme-code">first_render</span>`

Indicates whether the page is being rendered for the first time.

Used to:

- trigger full initial draw
- avoid redundant rendering of static UI elements

## Rendering Model

<span style="white-space: pre-wrap;">The list page uses a </span>**focused rendering strategy**, rather than displaying all entries simultaneously.

### Visible Entries

Only three entries are rendered at any time:

- previous entry
- current (selected) entry
- next entry

This creates a scrolling effect without requiring full list rendering.

### Rendering Optimization

The only expensive draw of the list page is the initial one which draws the selection border, all initial entries (both icons and names).

<span style="white-space: pre-wrap;">After that the only thing that gets redrawn are the icons and the texts. There is also heavier optimization done for minimal font redrawing by keeping track of previously rendered text widths. </span>

<p class="callout info">This is critical for SPI-driven displays, where bandwidth is limited.</p>

## Interaction Model

The list page assumes an abstract input interface:

```
menu_input (*get_input)(void);
```

The page does not interpret physical inputs directly. Instead, it operates on abstract input values, allowing it to remain independent of hardware specifics.

Expected interactions include:

- move selection up
- move selection down
- confirm selection

## Relationship to Menu System

The list page enables hierarchical navigation through:

- `<span class="editor-theme-code">entry_ids</span>`<span style="white-space: pre-wrap;"> → target page identifiers</span>
- `<span class="editor-theme-code">parent_id</span>`<span style="white-space: pre-wrap;"> (in </span>`<span class="editor-theme-code">menu_page_t</span>`) → upward navigation

<p class="callout info"><span style="white-space: pre-wrap;">This allows the menu system to behave as a </span>**tree of pages**, rather than a flat structure.</p>

## Performance Considerations

The list page is designed for constrained environments:

- partial rendering minimizes SPI usage
- static memory avoids allocation overhead
- limited visible entries reduce draw complexity