Files
modular-kbd/docs/firmware/firmware-architecture.md
2025-09-17 12:51:01 -07:00

126 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Firmware Architecture
The firmware operates under **three distinct modes** that define the devices behavior:
```c
#define MODE_INACTIVE 0
#define MODE_MASTER 1
#define MODE_MODULE 2
```
These modes dictate how the system processes input and manages data flow between modules. Together, they establish the rules for communication and arbitration within the modular keyboard system.
## Master Mode
In **Master Mode**, the device behaves like a standard keyboard connected to a host via USB. However, it also takes on additional responsibilities:
* **USB Handling**: Sends keypress reports directly to the host.
* **Neighbor Listening**: Receives keypress messages from connected Module devices and integrates them into the USB report.
* **Device Queries**: Responds to discovery or status requests from devices currently in **Inactive Mode**.
👉 The system can only enter Master Mode if it is physically connected to a USB host.
## Module Mode
In **Module Mode**, the device functions similarly to Master Mode but with one key difference:
* Instead of sending reports to the USB host, the device forwards its keypress data upstream to its **parent device**.
This parent is determined during the discovery process in Inactive Mode. The parent may be either the Master itself or another Module closer to the Master.
## Inactive Mode
**Inactive Mode** is the initial state of every device upon power-up (unless it is directly connected to USB).
Behavior in this mode includes:
* **Ignoring Local Inputs**: The device does not process its own switch presses.
* **Discovery Queries**: The device broadcasts a query to its neighbors to determine its parent.
* The Master has a depth of **0**.
* Each Module reports its depth as `(parent depth + 1)`.
The querying device selects a parent based on the lowest reported depth. This ensures the shortest and most efficient communication path to the Master.
**Example:**
If device A reports depth = 2, and device B reports depth = 4, the querying device sets:
* **Parent = A**
* **Depth = 3**
After selecting a parent, the device transitions from **Inactive Mode → Module Mode**.
Devices already in Master or Module mode should respond to discovery queries with their current depth value.
# UART Queue with DMA
Data transmission in this system is handled via **UART**. However, standard UART operations are **blocking**:
* When sending or receiving data without DMA, the CPU must wait until the operation completes.
* Running UART directly in the main loop would **stall other critical processes**, such as scanning keypresses or handling module communication.
An alternative is using **interrupts**, which allow the CPU to continue running while UART signals completion.
**Problem:** Interrupts can occur at any moment, potentially interrupting time-sensitive operations like key scanning or other UART transactions.
* Frequent or nested interrupts could lead to **race conditions**, data corruption, or missed key events.
## DMA Solution
The system uses **DMA (Direct Memory Access)** to offload UART data handling:
* Each RX port operates via DMA, continuously collecting incoming data **without CPU intervention**.
* Received data is placed into a **queue**.
* During the main loop, the firmware checks the queue:
* If the queue is not empty, it transmits the collected data to the parent/master device.
This system allows UART to behave like a **background subprocess**, leaving the CPU free for:
* Scanning the modules own keys.
* Processing received messages.
* Managing the module-to-master communication efficiently.
## Queue Usage
The **same queue system** also handles the modules own keypresses:
* Key events are pushed into the queue as they are detected.
* The main loop processes the queue, sending keypresses to the master device.
This unified queue system ensures **non-blocking, deterministic communication** between modules and the master, even under high data load.
---
# UART Message Structure
Each UART message is **32 bits (4 bytes)** and structured as follows:
| Byte 0 (MSB) | Byte 1 | Byte 2 | Byte 3 (LSB) |
|---------------------|-----------------|------------|--------------|
| Senders Depth | Message Type | Extra Bits | Key Code |
**Field Descriptions:**
- **Senders Depth:** Indicates the modules distance from the Master device. Useful for routing and arbitration.
- **Message Type:** Determines the purpose of the message (see below).
- **Extra Bits:** Reserved for additional flags or future extensions.
- **Key Code:** Represents the key being pressed (or released).
## Message Types
| Value | Name | Description |
|--------|------------------------|-----------------------------------------------------------------------------|
| 0x0F | Handshake Request | Sent by modules in **Inactive Mode** to discover parent/master devices. |
| 0xFF | Handshake Confirmation | Sent by active modules (or Master) in response to a Handshake Request. |
| 0x01 | Keycode Message | Generic keycode message sent from module to master. |
**Notes:**
- All communication between modules follows this 4-byte structure.
- Depth values help modules select the optimal parent device.
- Reserved **Extra Bits** can later store flags like key release, special functions, or priority indicators.