Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 63 additions & 15 deletions Documentation/firmware/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,70 @@
# Overview
# Firmware Documentation

This firmware runs on the Nordic Semiconductor nRF5340 dual-core SoC and provides multi-modal biosignal acquisition with Bluetooth Low Energy (BLE) streaming. The system supports synchronized acquisition from multiple sensors:
This firmware runs on the **Nordic Semiconductor nRF5340** dual-core SoC and provides multi-modal biosignal acquisition with Bluetooth Low Energy (BLE) streaming. It is built on the **Zephyr RTOS** using the **nRF Connect SDK** and the custom **SENSEI SDK** board support package.

- **EEG/EMG**: 16 channels via dual ADS1298 AFE (500 Hz)
- **IMU**: LIS2DUXS12 3-axis accelerometer (400 Hz)
- **PPG**: MAX86150 sensor
- **Microphone**: PDM digital microphone (16 kHz)
## Supported Sensors

> [!CAUTION]
> The PPG sensor has currently NOT being tested
| Sensor | Device | Channels | Sample Rate | Module |
|--------|--------|----------|-------------|--------|
| EEG | 2x ADS1298 | 16 | 500 Hz | `sensors/eeg` |
| EMG | 2x ADS1298 | 16 | 500 Hz | `sensors/emg` |
| IMU | LIS2DUXS12 | 3-axis accel | 400 Hz | `sensors/imu` |
| Microphone | PDM digital mic | 1 (mono) | 16 kHz | `sensors/mic` |
| PPG | MAX86150 | 2 (Red/IR) | ~100 Hz | `sensors/ppg` |

## Getting Started
> **Note**: The PPG sensor has not been tested yet. PPG data, when active, is multiplexed into the ExG (i.e. EEG/EMG) BLE packet by replacing the last two ADS1298 channels.

To get started with the firmware, please refer to the [Getting Started Guide](./getting_started.md).
## Key Features

## Authors
- Multi-threaded sensor acquisition with dedicated threads per sensor
- Barrier-synchronized simultaneous start across all active sensors
- BLE streaming via Nordic UART Service (NUS) at 2M PHY, 251-byte MTU
- Inter-board GPIO synchronization for multi-device setups (Not tested)
- PMIC-based power management with battery monitoring

- Philipp Schilk (schilkp@ethz.ch), ETH Zurich
- Philip Wiese (wiesep@iis.ee.ethz.ch), ETH Zurich
- Sebastian Frey (sefrey@iis.ee.ethz.ch), ETH Zurich
- Giovanni Pollo (giovanni.pollo@polito.it), Politecnico di Torino
## Documentation Index

### Getting Started
- [Getting Started Guide](./getting_started.md) - Build, flash, and run the firmware

### Architecture
- [Architecture Overview](./architecture.md) - System architecture, threading model, and data flow

### Modules
- [BLE Module](./ble_module.md) - BLE connectivity, NUS service, connection management, and streaming
- [BLE Protocol](./ble_protocol.md) - BLE command protocol specification (command codes and packet formats)
- [Sensor Modules](./sensor_modules.md) - EEG, EMG, IMU, Microphone, and PPG acquisition threads
- [BSP Module](./bsp_module.md) - Board support package (power management, battery, system status)
- [Core Module](./core_module.md) - Core utilities (synchronization, I2C helpers, board sync)

### Reference
- [Configuration](./configuration.md) - Build configuration, Kconfig options, device tree overlays
- [Data Formats](./data_formats.md) - BLE packet formats for all sensor types

## Source Code Layout

```
Firmware/
├── src_NRF/ # Main firmware source
│ ├── main.c # Entry point, initialization sequence
│ ├── CMakeLists.txt # Build configuration
│ ├── prj.conf # Zephyr project configuration
│ ├── Kconfig # Custom Kconfig options
│ ├── nrf5340_senseiv1_cpuapp.overlay # Device tree overlay
│ ├── pm_static.yml # Flash partition layout
│ ├── afe/ # ADS1298 AFE driver
│ ├── ble/ # BLE stack and application layer
│ ├── bsp/ # Board support (power, battery)
│ ├── core/ # Core utilities (sync, I2C)
│ ├── sensors/ # Sensor acquisition modules
│ │ ├── eeg/ # EEG streaming
│ │ ├── emg/ # EMG streaming
│ │ ├── imu/ # IMU (LIS2DUXS12) driver
│ │ ├── mic/ # PDM microphone
│ │ └── ppg/ # PPG (MAX86150) driver
│ └── child_image/ # Network core and bootloader configs
├── custom_dts/ # Custom device tree bindings for ADS1298
└── custom_shields/ # Shield definitions for ExG and PPG boards
├── SENSEI_ExGShield/
└── SENSEI_PPGShield/
```
54 changes: 54 additions & 0 deletions Documentation/firmware/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Architecture Overview

The BioGAP firmware is a multi-threaded sensor acquisition and BLE streaming system running on the **nRF5340** SoC, built on **Zephyr RTOS**.

## Dual-Core Architecture

The nRF5340 is a dual-core processor:

- **Application Core** — Runs the main firmware: sensor drivers, BLE application layer, and power management
- **Network Core** — Runs the BLE radio controller transparently; the application communicates with it via Zephyr's HCI IPC driver

## Initialization

On boot, the firmware initializes hardware in this order:

1. **Power** — Configure PMIC (MAX77654) power rails and enable battery charging
2. **Console** — Enable USB CDC ACM serial console
3. **AFE** — Configure ADS1298 SPI bus and Data Ready (DRDY) interrupt
4. **GAP9** — Power on the GAP9 co-processor
5. **BLE** — Start the BLE stack
6. **Sensors** — Initialize IMU (I2C), microphone (PDM), and AFE (EEG or EMG)
7. **Board Sync** — Configure inter-board GPIO sync (if enabled)

After initialization, the main thread sleeps and monitors the soft-reset button.

## Threading Model

Each sensor and subsystem runs in its own Zephyr thread. This allows sensors to operate independently and in parallel:

- **Sensor threads** — One per active sensor (EEG/EMG, IMU, Microphone). Each waits for a start signal, then continuously acquires data and enqueues BLE packets.
- **BLE send thread** — Dequeues packets from all sensors and transmits them over BLE.
- **BLE receive thread** — Processes incoming BLE commands (start/stop streaming, query status, etc.).
- **Battery thread** — Periodically reads battery status from the PMIC.

## Data Flow

### Sensor Streaming

Each sensor thread:
1. Waits for a start command (semaphore from BLE receive thread)
2. Powers up the sensor and configures it
3. Participates in barrier synchronization (if multi-sensor start)
4. Loops: reads samples, fills a BLE packet, enqueues it for transmission
5. On stop command: powers down and returns to idle

## Barrier Synchronization

When multiple sensors are started together, a barrier ensures they all begin at the same instant:

1. The barrier is set up for N subsystems
2. Each sensor thread registers and blocks at the barrier
3. When all N have arrived, they are all released simultaneously

This guarantees temporal alignment across different sensor modalities.
66 changes: 66 additions & 0 deletions Documentation/firmware/ble_module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# BLE Module

The BLE module (`src_NRF/ble/`) manages all Bluetooth Low Energy connectivity using the **Nordic UART Service (NUS)** for bidirectional data transfer. It is optimized for high-throughput sensor data streaming while supporting a command/response protocol for device control.

## File Overview

| File | Purpose |
|------|---------|
| `bluetooth.c/h` | BLE stack init, NUS service, connection callbacks, PHY/MTU negotiation, statistics |
| `ble_appl.c/h` | Application layer: send/receive threads, message queues, command dispatcher |
| `ble_commands.h` | Protocol command codes (see [BLE Protocol](./ble_protocol.md)) |

## Threading

### Send Thread (`ble_send_tid`)

- **Stack**: 2048 bytes
- Loops on `k_msgq_get(&send_msgq, ...)` with 100ms timeout
- Calls `send_data_ble()` → `bt_nus_send()` for each packet
- Tracks packet statistics by header byte

### Receive Thread (`ble_receive_tid`)

- **Stack**: 2048 bytes
- Waits on `ble_data_received` semaphore (signaled by NUS RX callback)
- Reads from `receive_msgq` and dispatches commands via `process_ble_rx_data()`

### Write Thread (`ble_write_thread_id`)

- **Stack**: 1024 bytes
- Waits for BLE initialization (`ble_init_ok` semaphore)
- Used for connection establishment monitoring

## Message Queues

### Send Queue (`send_msgq`)

```
K_MSGQ_DEFINE(send_msgq, sizeof(ble_packet_t), 64, 4)
```

- 64 entries of `ble_packet_t` (variable-size, max 244 bytes data)
- 4-byte alignment
- Filled by sensor streaming threads, drained by BLE send thread

### Receive Queue (`receive_msgq`)

```
K_MSGQ_DEFINE(receive_msgq, BLE_PCKT_RECEIVE_SIZE, 16, 1)
```

- 16 entries of 234 bytes each
- Filled by NUS RX callback, drained by BLE receive thread

## Public API

| Function | Description |
|----------|-------------|
| `send_data_ble(data, len)` | Send raw bytes over NUS (TX notify) |
| `add_data_to_send_buffer(pkt, len)` | Enqueue a packet for async BLE transmission |
| `get_ble_eeg_packets_sent()` | Get count of sent EEG packets |
| `get_ble_imu_packets_sent()` | Get count of sent IMU packets |
| `get_ble_mic_packets_sent()` | Get count of sent MIC packets |
| `get_ble_other_packets_sent()` | Get count of other sent packets |
| `get_ble_packets_failed()` | Get count of failed transmissions |
| `send_battery_status()` | Request battery status transmission |
82 changes: 82 additions & 0 deletions Documentation/firmware/ble_protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# BLE Protocol

The BioGAP firmware uses a custom command protocol over the Nordic UART Service (NUS) for bidirectional communication. Commands are sent from the BLE peer to the device, and responses are sent back as NUS notifications.

## Command Codes

| Code | Name | Description |
|------|------|-------------|
| 12 | `SET_DEVICE_SETTINGS` | Configure device settings |
| 13 | `GET_DEVICE_SETTINGS` | Read device settings |
| 14 | `REQUEST_HARDWARE_VERSION` | Returns hardware version (major='2', minor='b') |
| 15 | `GET_BOARD_STATE` | Returns current board state |
| 17 | `REQUEST_BATTERY_STATE` | Returns battery information (7 bytes) |
| 18 | `START_EEG_STREAMING` | Start EEG acquisition and BLE streaming |
| 19 | `STOP_EEG_STREAMING` | Stop EEG acquisition |
| 20 | `SET_BOARD_STATE` | Set operating mode (Nordic/GAP9) |
| 21 | `RESET_BOARD` | Reset the board |
| 24 | `GO_TO_SLEEP` | Enter low-power sleep mode |
| 26 | `START_MIC_STREAMING` | Start PDM microphone capture |
| 27 | `STOP_MIC_STREAMING` | Stop microphone |
| 31 | `START_STREAMING_ALL` | Start all sensors with barrier synchronization |
| 32 | `STOP_STREAMING_ALL` | Stop all active sensors |
| 33 | `START_IMU_STREAMING` | Start IMU (accelerometer) streaming |
| 34 | `STOP_IMU_STREAMING` | Stop IMU streaming |
| 35 | `START_EEG_MIC_STREAMING` | Combined EEG + microphone streaming |
| 37 | `START_EMG_STREAMING` | Start EMG acquisition and BLE streaming |
| 38 | `STOP_EMG_STREAMING` | Stop EMG acquisition |

## Command Flow Examples

### Start EEG Streaming

```
Peer → Device: [18] (START_EEG_STREAMING)
Device → Peer: [0x55][cnt][ts][data...][tr] (EEG data packets @ 500 Hz)
...
Peer → Device: [19] (STOP_EEG_STREAMING)
Device → Peer: (streaming stops)
```

### Start All Sensors (Synchronized)

```
Peer → Device: [31] (START_STREAMING_ALL)
↓ sync barrier: all sensors start simultaneously
Device → Peer: [0x55][...] (EEG data @ 500 Hz)
Device → Peer: [0x56][...] (IMU data @ 400 Hz)
Device → Peer: [0xAA][...] (MIC data @ 16 kHz)
...
Peer → Device: [32] (STOP_STREAMING_ALL)
```

### Query Battery

```
Peer → Device: [17] (REQUEST_BATTERY_STATE)
Device → Peer: [17][charging][rsv][pwr][soc][vbat][temp]
```

### Combined EEG + Microphone

```
Peer → Device: [35] (START_EEG_MIC_STREAMING)
↓ sync barrier: EEG + MIC start simultaneously
Device → Peer: [0x55][...] (EEG data)
Device → Peer: [0xAA][...] (MIC data)
...
Peer → Device: [19] (STOP_EEG_STREAMING)
Peer → Device: [27] (STOP_MIC_STREAMING)
```

## Data Packet Headers

Streaming data packets are identified by their first byte (header):

| Header | Sensor | Trailer | See |
|--------|--------|---------|-----|
| `0x55` | EEG/EMG (EXG) | `0xAA` | [Data Formats](./data_formats.md) |
| `0x56` | IMU | `0x57` | [Data Formats](./data_formats.md) |
| `0xAA` | Microphone | `0x55` | [Data Formats](./data_formats.md) |

See [Data Formats](./data_formats.md) for detailed packet structure documentation.
86 changes: 86 additions & 0 deletions Documentation/firmware/bsp_module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# BSP Module (Board Support Package)

The BSP module (`src_NRF/bsp/`) provides board-level hardware management including power supply control, battery monitoring, and system status aggregation.

## Submodules

```
bsp/
├── pwr_bsp.c/h # Board-level power initialization and control
├── battery/
│ ├── battery.c # Battery monitoring thread
│ └── battery.h # Battery status structure
├── power/
│ ├── power.c # ADS1298 power rail control
│ └── power.h # Power API
└── system_status/
├── system_status.c # System info aggregator
└── system_status.h # Status API
```

## Power Management (`pwr_bsp`)

### Key Functions

| Function | Description |
|----------|-------------|
| `pwr_init()` | Initialize PMIC I2C communication |
| `pwr_bsp_start()` | Configure all PMIC rails (SBB0-2, LDO0-1) |
| `pwr_charge_enable()` | Enable battery charging (285mA input, 90mA fast-charge, 4.2V CV) |
| `pwr_bsp_soft_rst_cb()` | Soft-reset button GPIO interrupt handler |
| `gap9_pwr(on)` | Power on/off the GAP9 co-processor via I2C |



## ADS1298 Power Control (`power`)

The ADS1298 analog supply voltage depends on the electrode configuration:

### Unipolar Mode (EEG)

Used for EEG with common reference electrodes.

```
power_ads_on_unipolar() → LDO1 set to 3.0V
power_ads_off_unipolar() → LDO1 disabled
```

### Bipolar Mode (EMG)

Used for EMG with differential electrode pairs.

```
power_ads_on_bipolar() → LDO1 set to 1.5V, SBB1 set to 2.7V
power_ads_off_bipolar() → LDO1 disabled, SBB1 disabled
```

## Battery Monitoring (`battery`)

### Battery Thread

- **Stack**: 1024 bytes, **Priority**: 6
- **Poll interval**: 5 seconds
- Runs continuously, reading PMIC battery gas gauge data

### Battery Status Structure

```c
typedef struct {
uint8_t soc_percent; // State of charge (0-100%)
uint16_t voltage_mv; // Battery voltage in millivolts
bool is_charging; // Whether battery is charging
uint16_t power_mw; // Power consumption in milliwatts
const char *power_source; // "USB/External" or "Battery"
} battery_status_t;
```

### Key Functions

| Function | Description |
|----------|-------------|
| `battery_update_thread()` | Main thread loop, polls PMIC every 5 seconds |
| `get_battery_status()` | Returns current `battery_status_t` |

### Behavior During Streaming

Battery reads are **skipped** during active sensor streaming to avoid I2C/SPI interference. The battery data is only read when sensors are idle.
Loading