diff --git a/doc/docs/konzept/software.md b/doc/docs/konzept/software.md index 603adcf..95ae0fd 100644 --- a/doc/docs/konzept/software.md +++ b/doc/docs/konzept/software.md @@ -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. diff --git a/doc/docs/planung.md b/doc/docs/planung.md index 184e3f8..674544f 100644 --- a/doc/docs/planung.md +++ b/doc/docs/planung.md @@ -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. diff --git a/doc/docs/specifications/ir_protocol.md b/doc/docs/specifications/ir_protocol.md new file mode 100644 index 0000000..e37d19f --- /dev/null +++ b/doc/docs/specifications/ir_protocol.md @@ -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
600 µs"] space1["Space 1
1200 µs"]:2 mark2["Mark
600 µs"] space2["Space 0
600 µs"] mark3["Mark
600 µs"] space3["Space 1
1200 µs"]:2 + bit1["1"]:3 bit2["0"]:2 bit3["1"]: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.* + diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml index 73c5978..41d703f 100644 --- a/doc/mkdocs.yml +++ b/doc/mkdocs.yml @@ -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 diff --git a/firmware/apps/_samples/ir_send/CMakeLists.txt b/firmware/apps/_samples/ir_send/CMakeLists.txt new file mode 100644 index 0000000..f81f13a --- /dev/null +++ b/firmware/apps/_samples/ir_send/CMakeLists.txt @@ -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) diff --git a/firmware/apps/_samples/ir_send/Kconfig b/firmware/apps/_samples/ir_send/Kconfig new file mode 100644 index 0000000..69023b7 --- /dev/null +++ b/firmware/apps/_samples/ir_send/Kconfig @@ -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. diff --git a/firmware/apps/_samples/ir_send/boards/nrf52840dk_nrf52840.overlay b/firmware/apps/_samples/ir_send/boards/nrf52840dk_nrf52840.overlay new file mode 120000 index 0000000..39e2587 --- /dev/null +++ b/firmware/apps/_samples/ir_send/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1 @@ +../../../../boards/nrf52840dk/nrf52840dk_nrf52840.overlay \ No newline at end of file diff --git a/firmware/apps/_samples/ir_send/prj.conf b/firmware/apps/_samples/ir_send/prj.conf new file mode 100644 index 0000000..eab55ba --- /dev/null +++ b/firmware/apps/_samples/ir_send/prj.conf @@ -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 diff --git a/firmware/apps/_samples/ir_send/src/main.c b/firmware/apps/_samples/ir_send/src/main.c new file mode 100644 index 0000000..aff4491 --- /dev/null +++ b/firmware/apps/_samples/ir_send/src/main.c @@ -0,0 +1,37 @@ +/* + * ir_send sample app - IR transmission test + */ + +#include +#include + +#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); + } +} + diff --git a/firmware/apps/leader/CMakeLists.txt b/firmware/apps/leader/CMakeLists.txt index a2daf85..87f1faf 100644 --- a/firmware/apps/leader/CMakeLists.txt +++ b/firmware/apps/leader/CMakeLists.txt @@ -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) diff --git a/firmware/apps/weapon/CMakeLists.txt b/firmware/apps/weapon/CMakeLists.txt index f25e480..9009ce9 100644 --- a/firmware/apps/weapon/CMakeLists.txt +++ b/firmware/apps/weapon/CMakeLists.txt @@ -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) diff --git a/firmware/boards/nrf52840dk/nrf52840dk_nrf52840.overlay b/firmware/boards/nrf52840dk/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000..b144684 --- /dev/null +++ b/firmware/boards/nrf52840dk/nrf52840dk_nrf52840.overlay @@ -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 = ; + }; + }; +}; + diff --git a/firmware/libs/CMakeLists.txt b/firmware/libs/CMakeLists.txt index 5174dc6..6fe1643 100644 --- a/firmware/libs/CMakeLists.txt +++ b/firmware/libs/CMakeLists.txt @@ -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) \ No newline at end of file +add_subdirectory(lasertag_utils) +add_subdirectory(ir_send) \ No newline at end of file diff --git a/firmware/libs/Kconfig b/firmware/libs/Kconfig index 82a4aee..8748f87 100644 --- a/firmware/libs/Kconfig +++ b/firmware/libs/Kconfig @@ -1,4 +1,5 @@ # Main entry point for custom project Kconfigs rsource "lasertag_utils/Kconfig" rsource "thread_mgmt/Kconfig" -rsource "ble_mgmt/Kconfig" \ No newline at end of file +rsource "ble_mgmt/Kconfig" +rsource "ir_send/Kconfig" \ No newline at end of file diff --git a/firmware/libs/ir_send/CMakeLists.txt b/firmware/libs/ir_send/CMakeLists.txt new file mode 100644 index 0000000..956ee18 --- /dev/null +++ b/firmware/libs/ir_send/CMakeLists.txt @@ -0,0 +1,5 @@ +if(CONFIG_IR_SEND) + zephyr_library() + zephyr_sources(src/ir_send.c) + zephyr_include_directories(include) +endif() diff --git a/firmware/libs/ir_send/Kconfig b/firmware/libs/ir_send/Kconfig new file mode 100644 index 0000000..4165bd4 --- /dev/null +++ b/firmware/libs/ir_send/Kconfig @@ -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 diff --git a/firmware/libs/ir_send/include/ir_send.h b/firmware/libs/ir_send/include/ir_send.h new file mode 100644 index 0000000..f1f1695 --- /dev/null +++ b/firmware/libs/ir_send/include/ir_send.h @@ -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 + +/** + * @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 */ diff --git a/firmware/libs/ir_send/src/ir_send.c b/firmware/libs/ir_send/src/ir_send.c new file mode 100644 index 0000000..d53652b --- /dev/null +++ b/firmware/libs/ir_send/src/ir_send.c @@ -0,0 +1,78 @@ +/* + * IR Send Library - PWM-based carrier generation + */ + +#include +#include +#include + +#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; +} diff --git a/lasertag.code-workspace b/lasertag.code-workspace index c7dc82f..0fb9e7c 100644 --- a/lasertag.code-workspace +++ b/lasertag.code-workspace @@ -6,7 +6,7 @@ ], "settings": { "nrf-connect.applications": [ - "${workspaceFolder}/firmware/apps/leader" + "${workspaceFolder}/firmware/apps/_samples/ir_send" ] } } \ No newline at end of file