Added IR Lib, samples and specification
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 12s
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 12s
This commit is contained in:
@@ -107,13 +107,23 @@ sequenceDiagram
|
||||
* **Status:** `GAME_STATE_RUNNING`.
|
||||
* **Aktion:** Waffen sind entsperrt. Sensoren sind scharf.
|
||||
* **Treffer-Logik (Dezentral):**
|
||||
1. Waffe A schießt (sendet IR-Code mit `ShooterID` + `Damage`).
|
||||
2. Weste B empfängt IR-Signal.
|
||||
1. Waffe A schießt (sendet IR-Frame mit `Type=Hit`, `ShooterID`, `Damage`, `CRC8`).
|
||||
2. Weste B empfängt IR-Signal über TSOP4838, validiert CRC.
|
||||
3. Weste B berechnet Schaden (unter Berücksichtigung von Trefferzone-Multiplikator).
|
||||
4. Weste B zieht Lebenspunkte ab.
|
||||
5. **Feedback:** Weste B leuchtet/vibriert/spielt Sound ("Ugh!").
|
||||
6. **Speicherung:** Weste B speichert den Treffer im internen Flash-Log (`Timestamp, ShooterID, Zone, Damage`).
|
||||
7. *(Optional)* Weste B sendet UDP-Paket an Leader für Live-Scoreboard (Best Effort).
|
||||
|
||||
!!! info "Warum kein MilesTag2?"
|
||||
MilesTag2 wurde als Basis erwogen, ist aber mit ~40 ms Frame-Zeit und starren 8-Bit-IDs zu langsam und unflexibel. Unser Custom-Protokoll bietet:
|
||||
|
||||
- **Kürzere Frames:** ~36 ms vs. ~40 ms (weniger anfällig für Zittern/Bewegung)
|
||||
- **Flexible Type-Codes:** Hit/Heal/PowerUp/Admin in einem Format
|
||||
- **CRC8-Prüfung:** >99.5% Fehlerrate-Erkennung bei Sonnenlicht
|
||||
- **Variable Daten:** 13-Bit-Payload anpassbar pro Type
|
||||
|
||||
Details siehe [IR-Protokoll-Spezifikation](../specifications/ir_protocol.md).
|
||||
* **Heilquellen:** Medic/Medipack-IR (breit gestreut, kurze Reichweite, negativer Damage) werden als Heilung interpretiert.
|
||||
* **Zonen-Effekte:** Bases/Joiner senden `game/zone` (Link-Local, Hop=1); Weste prüft RSSI-Schwelle und addiert HP-Deltas (friend/foe) nach optionalem Warn-Countdown.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Diese Roadmap führt vom nRF52840DK bis zum fertigen Produkt.
|
||||
- [ ] Zephyr Setup: Installation des nRF Connect SDK (NCS) und VS Code.
|
||||
- [ ] Custom Board Definition: Board-File anlegen, das die Pins des nRF52840DK auf die geplanten Funktionen mappt (PWM für IR, GPIO für Buttons).
|
||||
- [ ] Thread Mesh: Minimalen OpenThread-Stack aufsetzen. Ein DK als Leader (FTD), einer als Child. UDP/CoAP-Ping bei Knopfdruck.
|
||||
- [ ] IR-Engine: MilesTag-Encoder mit nrfx_pwm + PPI implementieren. Signal mit Oszilloskop/Logic Analyzer verifizieren. Sicherstellen, dass Funk die IR-Engine nicht stört.
|
||||
- [ ] IR-Engine: Custom IR-Protokoll (pulse-distance, 38 kHz) mit nrfx_pwm + PPI implementieren. Signal mit Oszilloskop/Logic Analyzer verifizieren. Sicherstellen, dass Funk die IR-Engine nicht stört. **Hinweis:** MilesTag2 wurde verworfen zugunsten eines eigenen, kürzeren Protokolls mit CRC8 (siehe [Spezifikationen](specifications/ir_protocol.md)).
|
||||
|
||||
#### Phase 2: Der "Prototyp" (Integration)
|
||||
**Ziel:** Einbindung von Audio und Solenoid.
|
||||
|
||||
201
doc/docs/specifications/ir_protocol.md
Normal file
201
doc/docs/specifications/ir_protocol.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# IR-Kommunikationsprotokoll
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Infrarot-Kommunikationsprotokoll basiert auf Pulse-Distance-Codierung mit 38 kHz Träger und ist ähnlich Sony SIRC, aber optimiert für die Anforderungen des Lasertag-Systems. Das Protokoll bietet robuste Übertragung mit CRC-Fehlerprüfung und kurzen Frame-Zeiten (~36 ms).
|
||||
|
||||
## Physikalische Schicht
|
||||
|
||||
| Parameter | Wert | Anmerkung |
|
||||
|-----------|------|----------|
|
||||
| Trägerfrequenz | 38 kHz | Standard für TSOP48xx Empfänger |
|
||||
| Tastgrad (Duty Cycle) | 50 % | Konfigurierbar (25–75 %) |
|
||||
| Modulation | PWM mit Pulse-Distance-Codierung | Hardware-basiert via nRF52 PWM-Peripheral |
|
||||
| Empfänger | TSOP4838 (kompatibel) | Active-Low Ausgang, 38 kHz Bandpass |
|
||||
|
||||
## Timing-Spezifikation
|
||||
|
||||
| Symbol | Dauer | Toleranz | Beschreibung |
|
||||
|--------|-------|----------|-------------|
|
||||
| **Start Burst** | 2400 µs | ±200 µs | Frame-Synchronisations-Impuls |
|
||||
| **Mark** | 600 µs | ±100 µs | Träger AN (konstant für alle Bits) |
|
||||
| **Space 0** | 600 µs | ±100 µs | Träger AUS für logisch 0 |
|
||||
| **Space 1** | 1200 µs | ±150 µs | Träger AUS für logisch 1 |
|
||||
|
||||
### Bit-Codierung
|
||||
|
||||
```
|
||||
Bit 0: [Mark 600µs] + [Space 600µs] = 1.2 ms
|
||||
Bit 1: [Mark 600µs] + [Space 1.2ms] = 1.8 ms
|
||||
```
|
||||
|
||||
### Beispiel-Wellenform (3 Bits: `101`)
|
||||
|
||||
**Träger-Timing für Bits 1-0-1:**
|
||||
|
||||
| Segment | Dauer | State | Bit-Wert |
|
||||
|---------|-------|-------|---------|
|
||||
| Mark | 600 µs | AN | |
|
||||
| Space 1 | 1200 µs | AUS | **1** (1.8 ms total) |
|
||||
| Mark | 600 µs | AN | |
|
||||
| Space 0| 600 µs | AUS | **0** (1.2 ms total) |
|
||||
| Mark | 600 µs | AN | |
|
||||
| Space 1| 1200 µs | AUS | **1** (1.8 ms total) |
|
||||
|
||||
```mermaid
|
||||
block-beta
|
||||
columns 8
|
||||
mark1["Mark<br>600 µs"] space1["Space 1<br>1200 µs"]:2 mark2["Mark<br>600 µs"] space2["Space 0<br>600 µs"] mark3["Mark<br>600 µs"] space3["Space 1<br>1200 µs"]:2
|
||||
bit1["<b>1</b>"]:3 bit2["<b>0</b>"]:2 bit3["<b>1</b>"]:3
|
||||
|
||||
style space1 fill:none
|
||||
style space2 fill:none
|
||||
style space3 fill:none
|
||||
```
|
||||
|
||||
## Frame-Format
|
||||
|
||||
Alle Frames bestehen aus 24 Bits, übertragen MSB-first:
|
||||
|
||||
| Feld | Start Burst | Type | Data | CRC8 |
|
||||
|------|-------------|------|------|------|
|
||||
| **Dauer** | 2400 µs | 3 Bits | 13 Bits | 8 Bits |
|
||||
| **Funktion** | Synchronisation | Frame-Typ | Payload | Fehlerprüfung |
|
||||
| **Summe** | – | – | – | **24 Bits** |
|
||||
|
||||
```mermaid
|
||||
packet-beta
|
||||
title Frame
|
||||
+3: "Typ"
|
||||
+13: "Payload"
|
||||
+8: "Fehlerprüfung"
|
||||
```
|
||||
|
||||
**Gesamte Frame-Zeit:** ~36 ms (Start + 24 × 1.5 ms durchschnittliche Bit-Zeit)
|
||||
|
||||
### Type-Feld (3 Bits)
|
||||
|
||||
| Wert | Typ | Beschreibung |
|
||||
|------|-----|-------------|
|
||||
| `000` | Hit | Standard-Schuss |
|
||||
| `001` | Heal | Medic-Heilung oder Health Pack |
|
||||
| `010` | PowerUp | Station Power-Up Grant |
|
||||
| `011` | Admin | System-Steuerbefehle |
|
||||
| `100`–`111` | Reserviert | Zukünftige Nutzung |
|
||||
|
||||
### Data-Feld (13 Bits) – Type-abhängig
|
||||
|
||||
#### Hit-Frame (`000`)
|
||||
|
||||
| Bit-Range | Feld | Wertbereich | Beschreibung |
|
||||
|-----------|------|-------------|-------------|
|
||||
| 0–7 | Shooter ID | 0–255 | ID des Schützen (256 mögliche Spieler) |
|
||||
| 8–12 | Damage | 0–31 | Schadenpunkte (0 = kein Schaden, 31 = Maximum) |
|
||||
|
||||
#### Heal-Frame (`001`)
|
||||
|
||||
| Bit-Range | Feld | Wertbereich | Beschreibung |
|
||||
|-----------|------|-------------|-------------|
|
||||
| 0–7 | Healer ID | 0–255 | ID des Heilers (Medic oder Station) |
|
||||
| 8–12 | Amount | 0–31 | Heilpunkte wiederhergestellt |
|
||||
|
||||
#### PowerUp-Frame (`010`)
|
||||
|
||||
| Bit-Range | Feld | Wertbereich | Beschreibung |
|
||||
|-----------|------|-------------|-------------|
|
||||
| 0–7 | Station ID | 0–255 | Stations-ID, die das Power-Up gewährt |
|
||||
| 8–12 | PowerUp | 0–31 | Power-Up-Typ-Identifier |
|
||||
|
||||
#### Admin-Frame (`011`)
|
||||
|
||||
| Bit-Range | Feld | Wertbereich | Beschreibung |
|
||||
|-----------|------|-------------|-------------|
|
||||
| 0–12 | Command Data | 0–8191 | Implementierungsdefinierte Steuerbefehle |
|
||||
|
||||
### CRC-Feld (8 Bits)
|
||||
|
||||
- **Algorithmus:** CRC-8-CCITT
|
||||
- **Polynom:** 0x07 (x⁸ + x² + x + 1)
|
||||
- **Initialwert:** 0x00
|
||||
- **Eingabe:** Type (3 Bits) + Data (13 Bits) = 16 Bits
|
||||
- **Zweck:** Fehlererkennung bei Bitfehlern durch Umgebungslicht oder Interferenzen
|
||||
|
||||
**Erwartete Fehlererkennungsrate:** >99.5 % für Einfach- oder Doppelbitfehler
|
||||
|
||||
## Beispiel-Frame
|
||||
|
||||
**Hit von Spieler 42 mit 10 Schaden:**
|
||||
|
||||
```
|
||||
Type: 000 (Hit)
|
||||
Data: 00101010 01010 (ShooterID=42, Damage=10)
|
||||
CRC8: [berechnet aus obigen Daten]
|
||||
|
||||
Kompletter Frame (24 Bits):
|
||||
000 00101010 01010 CCCCCCCC
|
||||
│ │ │ └─ CRC8
|
||||
│ │ └─ Damage (10)
|
||||
│ └─ Shooter ID (42)
|
||||
└─ Type (Hit)
|
||||
```
|
||||
|
||||
**Übertragungsabfolge:**
|
||||
|
||||
1. Start Burst: 2400 µs Träger AN
|
||||
2. Bit 0 (Type): 600 µs Mark + 600 µs Space
|
||||
3. Bit 1 (Type): 600 µs Mark + 600 µs Space
|
||||
4. Bit 2 (Type): 600 µs Mark + 600 µs Space
|
||||
5. ... (21 weitere Bits)
|
||||
6. Ende: Träger AUS
|
||||
|
||||
## Empfänger-Implementierung
|
||||
|
||||
### Hardware-Anforderungen
|
||||
|
||||
- TSOP4838 verbunden mit GPIO mit Interrupt-Fähigkeit
|
||||
- Steigende/fallende Flanken-Erkennung
|
||||
- Timer zur Messung der Space-Dauern
|
||||
|
||||
### Software-State-Machine
|
||||
|
||||
1. **IDLE:** Auf Start Burst warten (2000–2800 µs)
|
||||
2. **SYNC:** Start Burst erkannt, Vorbereitung zur Bit-Empfang
|
||||
3. **DATA:** Space nach jedem Mark messen, 24 Bits dekodieren
|
||||
4. **VALIDATE:** CRC prüfen, Frame bei Gültigkeit verarbeiten
|
||||
|
||||
### Timing-Toleranzen
|
||||
|
||||
- Breite Toleranzbereiche (±17–33 %) kompensieren Interrupt-Jitter und Träger-Drift
|
||||
- Fehlgeschlagener CRC zeigt beschädigten Frame an → stille Verwerfung
|
||||
- Empfänger resynchronisiert automatisch beim nächsten Start Burst
|
||||
|
||||
## Konfigurierbare Parameter
|
||||
|
||||
Die Protokoll-Timing kann via Kconfig für verschiedene Umgebungen angepasst werden:
|
||||
|
||||
- `CONFIG_IR_SEND_CARRIER_HZ`: Trägerfrequenz (30–45 kHz)
|
||||
- `CONFIG_IR_SEND_DUTY_CYCLE_PERCENT`: PWM Tastgrad (25–75 %)
|
||||
- `CONFIG_IR_SEND_MARK_US`: Mark-Dauer (300–1000 µs)
|
||||
- `CONFIG_IR_SEND_SPACE0_US`: Space für Bit 0 (300–1000 µs)
|
||||
- `CONFIG_IR_SEND_SPACE1_US`: Space für Bit 1 (800–2000 µs)
|
||||
- `CONFIG_IR_SEND_START_BURST_US`: Start Burst (1500–4000 µs)
|
||||
|
||||
Die Standardwerte folgen Sony SIRC Timing-Konventionen für bewährte Zuverlässigkeit.
|
||||
|
||||
## Leistungscharakteristiken
|
||||
|
||||
| Metrik | Wert |
|
||||
|--------|------|
|
||||
| Frame-Zeit | ~36 ms |
|
||||
| Datenrate | ~670 bit/s |
|
||||
| Max. Spieler-IDs | 256 |
|
||||
| Reichweite (Außen) | ~5–10 m (abhängig von Sender-Leistung und Umgebungslicht) |
|
||||
| Fehler-Erkennung | >99.5 % via CRC-8 |
|
||||
| Störfestigkeit | Hoch (Hardware-Bandpass 38 kHz) |
|
||||
|
||||
---
|
||||
|
||||
## Bluetooth LE Protokoll
|
||||
|
||||
*Zu dokumentieren: BLE-Charakteristiken für Spielstatus-Synchronisierung, Team-Zuordnung, etc.*
|
||||
|
||||
@@ -8,6 +8,8 @@ nav:
|
||||
- Hardware: konzept/hardware.md
|
||||
- Software: konzept/software.md
|
||||
- Gameplay & Modi: konzept/gameplay.md
|
||||
- Spezifikationen:
|
||||
- IR-Protokoll: specifications/ir_protocol.md
|
||||
- Planung: planung.md
|
||||
- Lizenz: license.md
|
||||
|
||||
|
||||
10
firmware/apps/_samples/ir_send/CMakeLists.txt
Normal file
10
firmware/apps/_samples/ir_send/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
# Tell Zephyr to look into our libs folder for extra modules
|
||||
list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(ir_send)
|
||||
|
||||
# Define application source files
|
||||
target_sources(app PRIVATE src/main.c)
|
||||
13
firmware/apps/_samples/ir_send/Kconfig
Normal file
13
firmware/apps/_samples/ir_send/Kconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
config IR_SEND_SAMPLE_BURST_US
|
||||
int "IR test burst length (microseconds)"
|
||||
default 1000
|
||||
range 50 100000
|
||||
help
|
||||
Duration of the carrier burst for each test pulse in the sample app.
|
||||
|
||||
config IR_SEND_SAMPLE_PERIOD_MS
|
||||
int "IR test burst period (milliseconds)"
|
||||
default 1000
|
||||
range 10 60000
|
||||
help
|
||||
Interval between consecutive test bursts in the sample app.
|
||||
@@ -0,0 +1 @@
|
||||
../../../../boards/nrf52840dk/nrf52840dk_nrf52840.overlay
|
||||
13
firmware/apps/_samples/ir_send/prj.conf
Normal file
13
firmware/apps/_samples/ir_send/prj.conf
Normal file
@@ -0,0 +1,13 @@
|
||||
# Logging
|
||||
CONFIG_LOG=y
|
||||
|
||||
# IR Send Library
|
||||
CONFIG_IR_SEND=y
|
||||
|
||||
# PWM driver for IR carrier
|
||||
CONFIG_PWM=y
|
||||
CONFIG_PWM_NRFX=y
|
||||
|
||||
# Sample test configuration
|
||||
CONFIG_IR_SEND_SAMPLE_BURST_US=1000
|
||||
CONFIG_IR_SEND_SAMPLE_PERIOD_MS=1000
|
||||
37
firmware/apps/_samples/ir_send/src/main.c
Normal file
37
firmware/apps/_samples/ir_send/src/main.c
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* ir_send sample app - IR transmission test
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include "ir_send.h"
|
||||
|
||||
LOG_MODULE_REGISTER(ir_send);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
LOG_INF("=== IR Send Sample ===");
|
||||
LOG_INF("Board: %s", CONFIG_BOARD);
|
||||
|
||||
int ret = ir_send_init();
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to initialize IR send: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ir_send_set_frequency(CONFIG_IR_SEND_CARRIER_HZ);
|
||||
LOG_INF("Ready to test IR transmission (burst %u us every %u ms @ %u Hz)",
|
||||
CONFIG_IR_SEND_SAMPLE_BURST_US,
|
||||
CONFIG_IR_SEND_SAMPLE_PERIOD_MS,
|
||||
CONFIG_IR_SEND_CARRIER_HZ);
|
||||
|
||||
while (true) {
|
||||
ret = ir_send_pulse(CONFIG_IR_SEND_SAMPLE_BURST_US);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("ir_send_pulse failed: %d", ret);
|
||||
}
|
||||
k_msleep(CONFIG_IR_SEND_SAMPLE_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.20)
|
||||
# Tell Zephyr to look into our libs folder for extra modules
|
||||
list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../../libs)
|
||||
|
||||
# Set board root to find custom board overlays in firmware/boards
|
||||
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(lasertag_leader)
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@ cmake_minimum_required(VERSION 3.20)
|
||||
# Zephyr mitteilen, dass unsere Libs Teil des Projekts sind
|
||||
list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../../libs)
|
||||
|
||||
# Set board root to find custom board overlays in firmware/boards
|
||||
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(lasertag_weapon)
|
||||
|
||||
|
||||
56
firmware/boards/nrf52840dk/nrf52840dk_nrf52840.overlay
Normal file
56
firmware/boards/nrf52840dk/nrf52840dk_nrf52840.overlay
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Device Tree Overlay für nRF52840 DK
|
||||
* Definiert GPIO-Pins für Trigger, LEDs und IR-Transmission (PWM3 @ P0.16)
|
||||
*/
|
||||
|
||||
/ {
|
||||
aliases {
|
||||
trigger-btn = &button0;
|
||||
ir-output = &ir_tx0;
|
||||
led-status = &led0;
|
||||
led-power = &led1;
|
||||
};
|
||||
|
||||
buttons {
|
||||
compatible = "gpio-keys";
|
||||
button0: button_0 {
|
||||
gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
|
||||
label = "Trigger Button";
|
||||
};
|
||||
};
|
||||
|
||||
leds {
|
||||
compatible = "gpio-leds";
|
||||
led0: led_0 {
|
||||
gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
|
||||
label = "Status LED";
|
||||
};
|
||||
led1: led_1 {
|
||||
gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
|
||||
label = "Power LED";
|
||||
};
|
||||
};
|
||||
|
||||
ir_pwm: ir_pwm {
|
||||
compatible = "pwm-leds";
|
||||
ir_tx0: ir_tx_0 {
|
||||
pwms = <&pwm3 0 PWM_NSEC(26316) PWM_POLARITY_NORMAL>;
|
||||
label = "IR TX PWM";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&pwm3 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&pwm3_default>;
|
||||
pinctrl-names = "default";
|
||||
};
|
||||
|
||||
&pinctrl {
|
||||
pwm3_default: pwm3_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(PWM_OUT0, 0, 16)>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
# Build ble_mgmt and thread_mgmt first since lasertag_utils depends on them
|
||||
add_subdirectory(ble_mgmt)
|
||||
add_subdirectory(thread_mgmt)
|
||||
add_subdirectory(lasertag_utils)
|
||||
add_subdirectory(lasertag_utils)
|
||||
add_subdirectory(ir_send)
|
||||
@@ -1,4 +1,5 @@
|
||||
# Main entry point for custom project Kconfigs
|
||||
rsource "lasertag_utils/Kconfig"
|
||||
rsource "thread_mgmt/Kconfig"
|
||||
rsource "ble_mgmt/Kconfig"
|
||||
rsource "ble_mgmt/Kconfig"
|
||||
rsource "ir_send/Kconfig"
|
||||
5
firmware/libs/ir_send/CMakeLists.txt
Normal file
5
firmware/libs/ir_send/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
if(CONFIG_IR_SEND)
|
||||
zephyr_library()
|
||||
zephyr_sources(src/ir_send.c)
|
||||
zephyr_include_directories(include)
|
||||
endif()
|
||||
61
firmware/libs/ir_send/Kconfig
Normal file
61
firmware/libs/ir_send/Kconfig
Normal file
@@ -0,0 +1,61 @@
|
||||
config IR_SEND
|
||||
bool "IR Send Library"
|
||||
help
|
||||
Enable IR transmission library for laser tag system.
|
||||
Provides PWM-based IR carrier generation with pulse-distance coding.
|
||||
|
||||
if IR_SEND
|
||||
|
||||
menu "IR Protocol Configuration"
|
||||
|
||||
config IR_SEND_CARRIER_HZ
|
||||
int "IR carrier frequency (Hz)"
|
||||
default 38000
|
||||
range 30000 45000
|
||||
help
|
||||
Carrier frequency for PWM generation. Standard value is 38 kHz
|
||||
for TSOP48xx receivers. Adjust only if using different receivers.
|
||||
|
||||
config IR_SEND_DUTY_CYCLE_PERCENT
|
||||
int "Carrier duty cycle (%)"
|
||||
default 50
|
||||
range 25 75
|
||||
help
|
||||
PWM duty cycle percentage. Some receivers prefer 33%, others 50%.
|
||||
Default 50% works with most TSOP-series receivers.
|
||||
|
||||
config IR_SEND_MARK_US
|
||||
int "Mark duration (microseconds)"
|
||||
default 600
|
||||
range 300 1000
|
||||
help
|
||||
Duration of carrier burst (mark) for each bit. Standard is 600 µs
|
||||
following Sony SIRC timing.
|
||||
|
||||
config IR_SEND_SPACE0_US
|
||||
int "Space duration for bit 0 (microseconds)"
|
||||
default 600
|
||||
range 300 1000
|
||||
help
|
||||
Carrier-off duration (space) after mark for logical 0.
|
||||
Default 600 µs creates 1.2 ms total bit time.
|
||||
|
||||
config IR_SEND_SPACE1_US
|
||||
int "Space duration for bit 1 (microseconds)"
|
||||
default 1200
|
||||
range 800 2000
|
||||
help
|
||||
Carrier-off duration (space) after mark for logical 1.
|
||||
Default 1200 µs creates 1.8 ms total bit time.
|
||||
|
||||
config IR_SEND_START_BURST_US
|
||||
int "Start burst duration (microseconds)"
|
||||
default 2400
|
||||
range 1500 4000
|
||||
help
|
||||
Initial synchronization burst at frame start. Receivers detect
|
||||
this to sync on incoming frames. Default 2400 µs = 4× mark time.
|
||||
|
||||
endmenu
|
||||
|
||||
endif
|
||||
45
firmware/libs/ir_send/include/ir_send.h
Normal file
45
firmware/libs/ir_send/include/ir_send.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef IR_SEND_H
|
||||
#define IR_SEND_H
|
||||
|
||||
/**
|
||||
* @file ir_send.h
|
||||
* @brief Infrared transmission library for laser tag system.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Initialize IR output hardware (PWM backend).
|
||||
* @return 0 on success, negative error code otherwise.
|
||||
*/
|
||||
int ir_send_init(void);
|
||||
|
||||
/**
|
||||
* @brief Send a single IR pulse (carrier burst).
|
||||
* @param duration_us Pulse duration in microseconds.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int ir_send_pulse(uint32_t duration_us);
|
||||
|
||||
/**
|
||||
* @brief Send a carrier burst of given duration (us) at configured frequency.
|
||||
* @param burst_us Duration in microseconds.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int ir_send_burst_us(uint32_t burst_us);
|
||||
|
||||
/**
|
||||
* @brief Send IR message (sequence of pulses and gaps).
|
||||
* @param data IR payload (e.g., shooter ID, power-up type).
|
||||
* @param len Length of data in bytes.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int ir_send_message(const uint8_t *data, uint8_t len);
|
||||
|
||||
/**
|
||||
* @brief Set IR carrier frequency.
|
||||
* @param freq_hz Carrier frequency in Hz (typically 38000 Hz).
|
||||
*/
|
||||
void ir_send_set_frequency(uint32_t freq_hz);
|
||||
|
||||
#endif /* IR_SEND_H */
|
||||
78
firmware/libs/ir_send/src/ir_send.c
Normal file
78
firmware/libs/ir_send/src/ir_send.c
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* IR Send Library - PWM-based carrier generation
|
||||
*/
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/drivers/pwm.h>
|
||||
|
||||
#include "ir_send.h"
|
||||
|
||||
LOG_MODULE_REGISTER(ir_send_lib);
|
||||
|
||||
static const struct pwm_dt_spec ir_pwm = PWM_DT_SPEC_GET(DT_ALIAS(ir_output));
|
||||
static uint32_t carrier_freq = 38000; /* Standard IR carrier frequency in Hz */
|
||||
|
||||
int ir_send_init(void)
|
||||
{
|
||||
if (!device_is_ready(ir_pwm.dev)) {
|
||||
LOG_ERR("IR PWM device not ready!");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
LOG_INF("IR output PWM ready: dev=%p, channel=%u", ir_pwm.dev, ir_pwm.channel);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ir_send_pulse(uint32_t duration_us)
|
||||
{
|
||||
return ir_send_burst_us(duration_us);
|
||||
}
|
||||
|
||||
int ir_send_message(const uint8_t *data, uint8_t len)
|
||||
{
|
||||
if (!device_is_ready(ir_pwm.dev)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
LOG_DBG("Sending IR message (%u bytes)", len);
|
||||
|
||||
/* TODO: Implement IR protocol encoding (e.g., NEC, custom protocol)
|
||||
* For now, just a placeholder that sends a test pulse.
|
||||
*/
|
||||
ir_send_pulse(1000); /* 1ms test pulse */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ir_send_set_frequency(uint32_t freq_hz)
|
||||
{
|
||||
carrier_freq = freq_hz;
|
||||
LOG_DBG("IR carrier frequency set to %u Hz", carrier_freq);
|
||||
}
|
||||
|
||||
int ir_send_burst_us(uint32_t burst_us)
|
||||
{
|
||||
if (!device_is_ready(ir_pwm.dev)) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
uint64_t period_ns = PWM_HZ(carrier_freq);
|
||||
uint64_t duty_ns = period_ns / 2U; /* 50% duty cycle */
|
||||
|
||||
int ret = pwm_set_dt(&ir_pwm, period_ns, duty_ns);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to enable PWM burst: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
k_usleep(burst_us);
|
||||
|
||||
/* Stop carrier after burst */
|
||||
ret = pwm_set_dt(&ir_pwm, period_ns, 0);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to stop PWM after burst: %d", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
],
|
||||
"settings": {
|
||||
"nrf-connect.applications": [
|
||||
"${workspaceFolder}/firmware/apps/leader"
|
||||
"${workspaceFolder}/firmware/apps/_samples/ir_send"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user