Added IR Lib, samples and specification
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 12s

This commit is contained in:
2026-01-04 20:53:39 +01:00
parent 2cb0a33b8e
commit 667600c14e
19 changed files with 546 additions and 6 deletions

View 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)

View 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.

View File

@@ -0,0 +1 @@
../../../../boards/nrf52840dk/nrf52840dk_nrf52840.overlay

View 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

View 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);
}
}

View File

@@ -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)

View File

@@ -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)

View 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)>;
};
};
};

View File

@@ -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)

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
if(CONFIG_IR_SEND)
zephyr_library()
zephyr_sources(src/ir_send.c)
zephyr_include_directories(include)
endif()

View 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

View 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 */

View 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;
}