sync during ir_recv dev
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:
@@ -1,73 +1,2 @@
|
||||
menu "IR protocol"
|
||||
|
||||
config IR_PROTO
|
||||
bool "IR protocol core"
|
||||
help
|
||||
Enable shared IR protocol settings used by send/receive libraries.
|
||||
|
||||
if IR_PROTO
|
||||
|
||||
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.
|
||||
|
||||
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_PROTO_BASE_US
|
||||
int "IR base period (microseconds)"
|
||||
default 600
|
||||
range 300 1000
|
||||
help
|
||||
Base timing used for start/mark/space. Default 600 µs (similar to Sony SIRC).
|
||||
|
||||
config IR_PROTO_START_MULT
|
||||
int "Start burst factor"
|
||||
default 4
|
||||
range 2 8
|
||||
help
|
||||
Start burst duration = base × factor. Default 4× for robust sync.
|
||||
|
||||
config IR_PROTO_GAP_MULT
|
||||
int "Start gap factor"
|
||||
default 1
|
||||
range 0 4
|
||||
help
|
||||
Gap after start burst (carrier off) = base × factor. 0 disables the gap.
|
||||
|
||||
config IR_PROTO_MARK_MULT
|
||||
int "Mark factor"
|
||||
default 1
|
||||
range 1 2
|
||||
help
|
||||
Mark duration = base × factor. Default 1×.
|
||||
|
||||
config IR_PROTO_SPACE0_MULT
|
||||
int "Space0 factor (bit 0)"
|
||||
default 1
|
||||
range 1 3
|
||||
help
|
||||
Space for bit 0 = base × factor. Default 1×.
|
||||
|
||||
config IR_PROTO_SPACE1_MULT
|
||||
int "Space1 factor (bit 1)"
|
||||
default 2
|
||||
range 1 4
|
||||
help
|
||||
Space for bit 1 = base × factor. Default 2× (double base time).
|
||||
|
||||
endmenu
|
||||
|
||||
endif
|
||||
|
||||
endmenu
|
||||
rsource "recv/Kconfig"
|
||||
rsource "send/Kconfig"
|
||||
@@ -1,6 +1,38 @@
|
||||
config IR_RECV
|
||||
bool "IR Receive Library"
|
||||
select IR_PROTO
|
||||
menuconfig IR_RECV
|
||||
bool "IR Receiver"
|
||||
select ADC
|
||||
select DMA
|
||||
help
|
||||
Enable IR receive library for the laser tag system.
|
||||
Placeholder implementation; timing parameters come from IR_PROTO.*
|
||||
Enable support for receiving IR signals using the ADC and DMA.
|
||||
|
||||
if IR_RECV
|
||||
config IR_RECV_SIMULATOR
|
||||
bool "Enable IR receiver simulator"
|
||||
help
|
||||
Replaces real ADC/Hardware input with a software-based sample generator
|
||||
for protocol testing.
|
||||
|
||||
config IR_RECV_BUFFER_COUNT
|
||||
int "Number of DMA buffers"
|
||||
default 8
|
||||
range 2 16
|
||||
help
|
||||
Number of buffers in the circular chain to handle CPU jitter.
|
||||
|
||||
config IR_RECV_SAMPLES_PER_BUFFER
|
||||
int "Samples per channel per buffer"
|
||||
default 32
|
||||
help
|
||||
Number of samples for each of the 4 channels (3x IR, 1x Vbat) per buffer.
|
||||
|
||||
config IR_RECV_INVERT_SIGNAL
|
||||
bool "Invert IR input signal"
|
||||
default n
|
||||
help
|
||||
Invert logic: High-level means IR carrier detected.
|
||||
|
||||
# Logging configuration for the IR Receiver module
|
||||
module = IR_RECV
|
||||
module-str = ir_recv
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
endif
|
||||
@@ -3,6 +3,28 @@
|
||||
|
||||
#include <zephyr/device.h>
|
||||
|
||||
// structure representing a decoded IR packet
|
||||
typedef struct {
|
||||
union {
|
||||
uint8_t bytes[3]; /* Raw access for CRC and transport */
|
||||
struct {
|
||||
uint32_t type : 3; /* Shared Header: 3 bits Type */
|
||||
union {
|
||||
struct {
|
||||
uint32_t shooter_id : 8;
|
||||
uint32_t damage : 5;
|
||||
} hit;
|
||||
struct {
|
||||
uint32_t healer_id : 8;
|
||||
uint32_t amount : 5;
|
||||
} heal;
|
||||
uint32_t raw_payload : 13;
|
||||
};
|
||||
uint32_t crc : 8; /* CRC8 over the first 16 bits */
|
||||
} fields;
|
||||
} data;
|
||||
} __attribute__((packed)) ir_packet_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize IR receive pipeline (stub).
|
||||
*
|
||||
|
||||
@@ -1,12 +1,345 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ir_recv.h"
|
||||
#include "lasertag_utils.h"
|
||||
|
||||
LOG_MODULE_REGISTER(ir_recv, LOG_LEVEL_INF);
|
||||
LOG_MODULE_REGISTER(ir_recv, CONFIG_IR_RECV_LOG_LEVEL);
|
||||
|
||||
int ir_recv_init(void)
|
||||
#define BUFFER_COUNT CONFIG_IR_RECV_BUFFER_COUNT
|
||||
#define SAMPLES_PER_BUFFER CONFIG_IR_RECV_SAMPLES_PER_BUFFER
|
||||
#define ADC_CHANNELS 4
|
||||
#define TOTAL_SAMPLES (SAMPLES_PER_BUFFER * ADC_CHANNELS)
|
||||
|
||||
/* Circular buffer for ADC data */
|
||||
static int16_t adc_buffers[BUFFER_COUNT][TOTAL_SAMPLES];
|
||||
static uint8_t write_idx = 0;
|
||||
static uint8_t read_idx = 0;
|
||||
static struct k_sem adc_sem;
|
||||
|
||||
/* --- Simulator Logic --- */
|
||||
#ifdef CONFIG_IR_RECV_SIMULATOR
|
||||
|
||||
typedef enum
|
||||
{
|
||||
LOG_INF("IR receive stub initialized (no implementation yet)");
|
||||
SCENARIO_PERFECT,
|
||||
SCENARIO_NOISY,
|
||||
SCENARIO_COUNT
|
||||
} sim_scenario_t;
|
||||
|
||||
static sim_scenario_t current_scenario = SCENARIO_PERFECT;
|
||||
static uint32_t sim_sample_pos = 0;
|
||||
|
||||
/* Example Payload: Heal (Type 1), ID 42, Amount 23.
|
||||
* Note: CRC should be calculated using the utility table.
|
||||
*/
|
||||
static const uint8_t test_msg_bits[] = {
|
||||
0, 0, 1, /* Type: 1 (Heal) */
|
||||
0, 0, 1, 0, 1, 0, 1, 0, /* ID: 42 */
|
||||
1, 0, 1, 1, 1, /* Amount: 23 */
|
||||
0, 1, 1, 1, 0, 0, 0, 1 /* Placeholder CRC8 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generates a simulated IR level (0 or 1) based on current position.
|
||||
*/
|
||||
static bool get_sim_level(uint32_t pos)
|
||||
{
|
||||
if (pos < 32)
|
||||
return 1; // Header
|
||||
if (pos < 40)
|
||||
return 0; // Gap
|
||||
|
||||
uint32_t bit_idx = (pos - 40) / 16;
|
||||
if (bit_idx < sizeof(test_msg_bits))
|
||||
{
|
||||
bool bit_val = test_msg_bits[bit_idx];
|
||||
uint32_t phase = (pos - 40) % 16;
|
||||
|
||||
/* * Simplified Pulse Distance:
|
||||
* Bit 0: 8 samples space, 8 samples mark
|
||||
* Bit 1: 16 samples space... wait, our state machine
|
||||
* needs different widths to distinguish.
|
||||
*/
|
||||
if (bit_val == 0)
|
||||
{
|
||||
return (phase >= 8); // 8 samples gap
|
||||
}
|
||||
else
|
||||
{
|
||||
// For a '1', we need a longer gap in the next sim update
|
||||
return (phase >= 8);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ir_recv_sim_thread(void *arg1, void *arg2, void *arg3)
|
||||
{
|
||||
bool simulation_running = true;
|
||||
|
||||
// Calculate total expected samples: Header(32) + Gap(8) + Bits(24 * 16)
|
||||
uint32_t total_expected_samples = 32 + 8 + (sizeof(test_msg_bits) * 16);
|
||||
|
||||
while (simulation_running)
|
||||
{
|
||||
k_usleep(75 * SAMPLES_PER_BUFFER);
|
||||
int16_t *buf = adc_buffers[write_idx];
|
||||
|
||||
for (int i = 0; i < SAMPLES_PER_BUFFER; i++)
|
||||
{
|
||||
if (sim_sample_pos >= total_expected_samples)
|
||||
{
|
||||
simulation_running = false;
|
||||
// Fill remaining buffer with idle (0)
|
||||
buf[i * ADC_CHANNELS + 0] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool level = get_sim_level(sim_sample_pos);
|
||||
bool active = IS_ENABLED(CONFIG_IR_RECV_INVERT_SIGNAL) ? !level : level;
|
||||
buf[i * ADC_CHANNELS + 0] = active ? 0x0FFF : 0x0000;
|
||||
sim_sample_pos++;
|
||||
}
|
||||
|
||||
// Fill other channels
|
||||
buf[i * ADC_CHANNELS + 1] = 0;
|
||||
buf[i * ADC_CHANNELS + 2] = 0;
|
||||
buf[i * ADC_CHANNELS + 3] = 2400;
|
||||
}
|
||||
|
||||
write_idx = (write_idx + 1) % BUFFER_COUNT;
|
||||
k_sem_give(&adc_sem);
|
||||
|
||||
if (!simulation_running)
|
||||
{
|
||||
LOG_INF("Simulation sequence finished. Stopping feeder.");
|
||||
// Thread stays alive but does nothing, or use k_thread_abort(k_current_get());
|
||||
while (1)
|
||||
{
|
||||
k_sleep(K_FOREVER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_THREAD_DEFINE(sim_tid, 1024, ir_recv_sim_thread, NULL, NULL, NULL, 5, 0, 0);
|
||||
#endif
|
||||
|
||||
/* --- State Machine --- */
|
||||
|
||||
typedef enum
|
||||
{
|
||||
IR_STATE_IDLE,
|
||||
IR_STATE_HEADER_SYNC,
|
||||
IR_STATE_WAIT_SPACE,
|
||||
IR_STATE_WAIT_MARK,
|
||||
IR_STATE_VALIDATE
|
||||
} ir_decoder_state_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ir_decoder_state_t state;
|
||||
uint32_t sample_window; /* 32-bit marching window */
|
||||
uint32_t bit_accumulator; /* Storage for 24 received bits */
|
||||
uint16_t timer; /* Sample counter for current state */
|
||||
uint8_t bit_count; /* Number of bits received */
|
||||
} ir_channel_ctx_t;
|
||||
|
||||
static ir_channel_ctx_t channels[3];
|
||||
|
||||
/**
|
||||
* @brief Processes a single sample through the voting and state machine.
|
||||
*/
|
||||
static void process_ir_sample(ir_channel_ctx_t *ctx, int16_t raw)
|
||||
{
|
||||
/* 1. Thresholding and Inversion */
|
||||
bool active = (raw > 2048);
|
||||
if (IS_ENABLED(CONFIG_IR_RECV_INVERT_SIGNAL))
|
||||
{
|
||||
active = !active;
|
||||
}
|
||||
|
||||
/* 2. Update Marching Window */
|
||||
ctx->sample_window = (ctx->sample_window << 1) | (active ? 1 : 0);
|
||||
|
||||
/* 3. State Machine Logic */
|
||||
uint8_t energy = __builtin_popcount(ctx->sample_window);
|
||||
|
||||
switch (ctx->state)
|
||||
{
|
||||
case IR_STATE_IDLE:
|
||||
/* Header detection: 75% of 32 samples (24 bits) must be active */
|
||||
if (energy >= 24)
|
||||
{
|
||||
ctx->state = IR_STATE_HEADER_SYNC;
|
||||
ctx->timer = 0;
|
||||
LOG_DBG("Header energy threshold reached: %u", energy);
|
||||
}
|
||||
break;
|
||||
|
||||
case IR_STATE_HEADER_SYNC:
|
||||
/* Placeholder for maximum search logic to find t=0 */
|
||||
ctx->timer++;
|
||||
if (ctx->timer >= 10)
|
||||
{
|
||||
ctx->state = IR_STATE_WAIT_SPACE;
|
||||
ctx->timer = 0;
|
||||
ctx->bit_count = 0;
|
||||
ctx->bit_accumulator = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case IR_STATE_WAIT_SPACE:
|
||||
/* We are in the gap between pulses. The carrier is OFF (active == false). */
|
||||
if (!active)
|
||||
{
|
||||
ctx->timer++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Rising edge detected -> The Space ends, a new Mark starts.
|
||||
* Now we evaluate the length of the gap we just measured.
|
||||
*/
|
||||
if (ctx->timer >= 12)
|
||||
{ // Center between 8 and 16 samples
|
||||
ctx->bit_accumulator = (ctx->bit_accumulator << 1) | 1;
|
||||
}
|
||||
else if (ctx->timer >= 4)
|
||||
{ // Minimum length for a valid 0
|
||||
ctx->bit_accumulator = (ctx->bit_accumulator << 1) | 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Noise or glitch -> reset to IDLE
|
||||
ctx->state = IR_STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
|
||||
ctx->bit_count++;
|
||||
LOG_DBG("Bit %u done. Total bits: %u, Accumulator: 0x%06X",
|
||||
(ctx->bit_accumulator & 1), ctx->bit_count, ctx->bit_accumulator);
|
||||
|
||||
if (ctx->bit_count >= 24)
|
||||
{
|
||||
ctx->state = IR_STATE_VALIDATE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Now we are in the Mark phase of the bit.
|
||||
* We stay here until the next falling edge.
|
||||
*/
|
||||
ctx->state = IR_STATE_WAIT_MARK;
|
||||
}
|
||||
ctx->timer = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case IR_STATE_WAIT_MARK:
|
||||
/* We are inside a Pulse (Mark). We wait for it to end. */
|
||||
if (active)
|
||||
{
|
||||
ctx->timer++;
|
||||
// Timeout protection: A mark shouldn't be much longer than 8-10 samples
|
||||
if (ctx->timer > 12)
|
||||
ctx->state = IR_STATE_IDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Falling edge detected -> Space starts. */
|
||||
ctx->state = IR_STATE_WAIT_SPACE;
|
||||
ctx->timer = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case IR_STATE_VALIDATE:
|
||||
{
|
||||
ir_packet_t packet;
|
||||
|
||||
/* Store the 24 bits into our struct bytes */
|
||||
packet.data.bytes[0] = (ctx->bit_accumulator >> 16) & 0xFF;
|
||||
packet.data.bytes[1] = (ctx->bit_accumulator >> 8) & 0xFF;
|
||||
packet.data.bytes[2] = (ctx->bit_accumulator) & 0xFF;
|
||||
|
||||
/* Verify integrity */
|
||||
uint8_t calc = lastertag_crc8(packet.data.bytes, 2);
|
||||
|
||||
if (calc == packet.data.fields.crc)
|
||||
{
|
||||
/* Check type and log accordingly */
|
||||
if (packet.data.fields.type == 1)
|
||||
{ // Heal
|
||||
LOG_INF("HEAL Received! Healer-ID: %u, Amount: %u",
|
||||
packet.data.fields.heal.healer_id,
|
||||
packet.data.fields.heal.amount);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_INF("Frame Received! Type: %u, Payload: 0x%04X",
|
||||
packet.data.fields.type,
|
||||
packet.data.fields.raw_payload);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WRN("CRC Error! Got 0x%02X, expected 0x%02X (Acc: 0x%06X)",
|
||||
calc, packet.data.fields.crc, ctx->bit_accumulator);
|
||||
}
|
||||
|
||||
ctx->state = IR_STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main processing thread for incoming ADC buffers.
|
||||
*/
|
||||
void ir_recv_thread(void *arg1, void *arg2, void *arg3)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
k_sem_take(&adc_sem, K_FOREVER);
|
||||
|
||||
while (read_idx != write_idx)
|
||||
{
|
||||
int16_t *buf = adc_buffers[read_idx];
|
||||
|
||||
for (int i = 0; i < SAMPLES_PER_BUFFER; i++)
|
||||
{
|
||||
/* De-interleave and process IR channels */
|
||||
process_ir_sample(&channels[0], buf[i * ADC_CHANNELS + 0]);
|
||||
process_ir_sample(&channels[1], buf[i * ADC_CHANNELS + 1]);
|
||||
process_ir_sample(&channels[2], buf[i * ADC_CHANNELS + 2]);
|
||||
|
||||
/* Optional: Handle battery sample at buf[i * ADC_CHANNELS + 3] */
|
||||
}
|
||||
|
||||
read_idx = (read_idx + 1) % BUFFER_COUNT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_THREAD_DEFINE(ir_recv_tid, 2048, ir_recv_thread, NULL, NULL, NULL, 2, 0, 0);
|
||||
|
||||
/**
|
||||
* @brief Initialization of the IR receiver module.
|
||||
*/
|
||||
int ir_recv_init(void)
|
||||
{
|
||||
k_sem_init(&adc_sem, 0, BUFFER_COUNT);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
channels[i].state = IR_STATE_IDLE;
|
||||
}
|
||||
|
||||
LOG_INF("IR Receiver initialized. Mode: %s",
|
||||
IS_ENABLED(CONFIG_IR_RECV_SIMULATOR) ? "Simulator" : "Hardware");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
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_PROTO_BASE_US
|
||||
int "IR Basistakt (Mikrosekunden)"
|
||||
default 600
|
||||
range 300 1000
|
||||
help
|
||||
Gemeinsamer Basistakt für Mark/Space/Start. Standard 600 µs (Sony SIRC ähnlich).
|
||||
|
||||
config IR_PROTO_START_MULT
|
||||
int "Startburst Faktor"
|
||||
default 4
|
||||
range 2 8
|
||||
help
|
||||
Startburst-Dauer = Basistakt × Faktor. Standard 4× zur sicheren Synchronisation.
|
||||
|
||||
config IR_PROTO_GAP_MULT
|
||||
int "Start-Gap Faktor"
|
||||
default 1
|
||||
range 0 4
|
||||
help
|
||||
Gap nach Startburst (Träger AUS) = Basistakt × Faktor. 0 deaktiviert Gap.
|
||||
|
||||
config IR_PROTO_MARK_MULT
|
||||
int "Mark Faktor"
|
||||
default 1
|
||||
range 1 2
|
||||
help
|
||||
Mark-Dauer = Basistakt × Faktor. Standard 1×.
|
||||
|
||||
config IR_PROTO_SPACE0_MULT
|
||||
int "Space0 Faktor (Bit 0)"
|
||||
default 1
|
||||
range 1 3
|
||||
help
|
||||
Space für Bit 0 = Basistakt × Faktor. Standard 1×.
|
||||
|
||||
config IR_PROTO_SPACE1_MULT
|
||||
int "Space1 Faktor (Bit 1)"
|
||||
default 2
|
||||
range 1 4
|
||||
help
|
||||
Space für Bit 1 = Basistakt × Faktor. Standard 2× (doppelte Basiszeit).
|
||||
|
||||
endmenu
|
||||
|
||||
endif
|
||||
Reference in New Issue
Block a user