127 lines
5.5 KiB
Markdown
127 lines
5.5 KiB
Markdown
# Firmware Architecture
|
||
|
||
The firmware operates under **three distinct modes** that define the device’s 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.
|
||
* **Awaiting Responses**: Active devices (Master or Modules) respond with a **depth value**—a numerical measure of distance (in hops) to the Master.
|
||
|
||
* 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 module’s own keys.
|
||
* Processing received messages.
|
||
* Managing the module-to-master communication efficiently.
|
||
|
||
## Queue Usage
|
||
|
||
The **same queue system** also handles the module’s 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) |
|
||
|---------------------|-----------------|------------|--------------|
|
||
| Sender’s Depth | Message Type | Extra Bits | Key Code |
|
||
|
||
**Field Descriptions:**
|
||
|
||
- **Sender’s Depth:** Indicates the module’s 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.
|
||
|