sync during ir_recv dev
All checks were successful
Deploy Docs / build-and-deploy (push) Successful in 12s

This commit is contained in:
2026-02-16 14:02:03 +01:00
parent 9c1d19af67
commit b1f5578be9
3 changed files with 141 additions and 281 deletions

View File

@@ -1,7 +1,9 @@
if(CONFIG_IR_SEND)
add_subdirectory(send)
zephyr_include_directories(include)
endif()
if(CONFIG_IR_RECV)
add_subdirectory(recv)
endif()
zephyr_include_directories(include)
endif()

View File

@@ -1,29 +1,7 @@
#ifndef IR_RECV_H
#define IR_RECV_H
#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;
#include <ir.h>
/**
* @brief Initialize IR receive pipeline (stub).
@@ -33,4 +11,9 @@ typedef struct {
*/
int ir_recv_init(void);
#ifdef CONFIG_IR_RECV_SIMULATOR
/* Helper to trigger a simulation from main.c or shell */
void ir_recv_sim_send_packet(ir_packet_t *packet);
#endif
#endif /* IR_RECV_H */

View File

@@ -2,285 +2,161 @@
#include <zephyr/logging/log.h>
#include <string.h>
#include <ir_recv.h>
#include <ir.h>
#include <lasertag_utils.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "ir_recv.h"
#include "lasertag_utils.h"
LOG_MODULE_REGISTER(ir_recv, CONFIG_IR_RECV_LOG_LEVEL);
#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)
#define SAMPLES_PER_BUFFER CONFIG_IR_RECV_SAMPLES_PER_BUFFER
#define BUFFER_COUNT CONFIG_IR_RECV_BUFFER_COUNT
/* 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 int16_t adc_buffers[BUFFER_COUNT][SAMPLES_PER_BUFFER * ADC_CHANNELS];
static uint8_t write_idx = 0, read_idx = 0;
static struct k_sem adc_sem;
/* --- Simulator Logic --- */
/* --- Enhanced Simulator --- */
#ifdef CONFIG_IR_RECV_SIMULATOR
typedef enum
{
SCENARIO_PERFECT,
SCENARIO_NOISY,
SCENARIO_COUNT
} sim_scenario_t;
static sim_scenario_t current_scenario = SCENARIO_PERFECT;
static ir_packet_t sim_packet;
static bool sim_trigger = false;
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.
*/
/**
* @brief Improved simulator that respects bit values for timing.
* 0 -> 8 samples space, 8 samples mark (Total 16)
* 1 -> 16 samples space, 8 samples mark (Total 24)
*/
static bool get_sim_level_dynamic(uint32_t *pos)
{
uint32_t p = *pos;
// 1. Header: 32 samples mark
if (p < 32)
return 1;
// 2. Initial Gap: 8 samples space
if (p < 40)
return 0;
uint32_t current_p = 40;
for (int i = 0; i < sizeof(test_msg_bits); i++)
{
uint32_t bit_len = test_msg_bits[i] ? 24 : 16; // 16s/8m vs 8s/8m
uint32_t space_len = test_msg_bits[i] ? 16 : 8;
if (p < current_p + bit_len)
{
uint32_t phase = p - current_p;
return (phase >= space_len); // Space first, then Mark
}
current_p += bit_len;
}
return 0; // End of message
void ir_recv_sim_send_packet(ir_packet_t *packet) {
memcpy(&sim_packet, packet, sizeof(ir_packet_t));
// Auto-calculate CRC for the simulator
sim_packet.data.fields.crc = lastertag_crc8(sim_packet.data.bytes, 2);
sim_sample_pos = 0;
sim_trigger = true;
LOG_INF("Simulator: Packet queued (Type: %u, CRC: 0x%02X)",
sim_packet.data.fields.type, sim_packet.data.fields.crc);
}
void ir_recv_sim_thread(void *arg1, void *arg2, void *arg3)
{
// Update total samples for the new dynamic timing
uint32_t total_samples = 40;
for (int i = 0; i < sizeof(test_msg_bits); i++)
total_samples += (test_msg_bits[i] ? 24 : 16);
while (1)
{
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_samples)
{
bool level = get_sim_level_dynamic(&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++;
}
else
{
buf[i * ADC_CHANNELS + 0] = 0;
if (sim_sample_pos == total_samples)
{
LOG_INF("Simulation sequence finished. Stopping feeder.");
sim_sample_pos++; // Only log once
}
}
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);
}
static bool get_sim_level(uint32_t pos) {
if (pos < 32) return 1; // Header Mark
if (pos < 40) return 0; // Header Gap
uint32_t p = pos - 40;
for (int i = 0; i < 24; i++) {
// Extract bit from struct (LSB first to match our new right-shift receiver)
bool bit = (sim_packet.data.bytes[i/8] >> (i%8)) & 1;
uint32_t bit_len = bit ? 24 : 16;
uint32_t space_len = bit ? 16 : 8;
if (p < bit_len) return (p >= space_len);
p -= bit_len;
}
return 0;
}
void ir_recv_sim_thread(void *p1, void *p2, void *p3) {
while (1) {
k_usleep(75 * SAMPLES_PER_BUFFER);
if (!sim_trigger) continue;
int16_t *buf = adc_buffers[write_idx];
for (int i = 0; i < SAMPLES_PER_BUFFER; i++) {
bool level = get_sim_level(sim_sample_pos++);
bool active = IS_ENABLED(CONFIG_IR_RECV_INVERT_SIGNAL) ? !level : level;
buf[i * ADC_CHANNELS] = active ? 0x0FFF : 0x0000;
// Clear other channels
buf[i * ADC_CHANNELS + 3] = 2400;
}
write_idx = (write_idx + 1) % BUFFER_COUNT;
k_sem_give(&adc_sem);
if (sim_sample_pos > 600) { // Safety stop
sim_trigger = false;
LOG_INF("Simulation finished.");
}
}
}
K_THREAD_DEFINE(sim_tid, 1024, ir_recv_sim_thread, NULL, NULL, NULL, 5, 0, 0);
#endif
/* --- State Machine --- */
/* --- Robust State Machine --- */
typedef enum {
IR_STATE_IDLE,
IR_STATE_HEADER_SYNC,
IR_STATE_WAIT_HEADER_GAP,
IR_STATE_WAIT_MARK,
IR_STATE_WAIT_SPACE,
IR_STATE_VALIDATE
} ir_state_t;
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_state_t state;
uint32_t sample_window;
uint32_t bit_acc;
uint16_t timer;
uint8_t bit_count;
} ir_ctx_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_ctx_t channels[3];
static ir_channel_ctx_t channels[3];
static void process_sample(ir_ctx_t *ctx, int16_t raw) {
bool active = (raw > 2048);
if (IS_ENABLED(CONFIG_IR_RECV_INVERT_SIGNAL)) active = !active;
/**
* @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;
}
ctx->sample_window = (ctx->sample_window << 1) | (active ? 1 : 0);
uint8_t energy = __builtin_popcount(ctx->sample_window);
/* 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;
/* Extraktion der Bytes aus dem Akkumulator */
/* Byte 0 (älteste Bits), Byte 1 (mittlere), Byte 2 (neueste/CRC) */
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;
/* Berechnung der CRC über die ersten zwei Datenbytes */
uint8_t calc = lastertag_crc8(packet.data.bytes, 2);
/* Vergleich mit dem empfangenen dritten Byte (CRC) */
if (calc == packet.data.bytes[2])
{
LOG_INF("SUCCESS! Valid Frame Received.");
if (packet.data.fields.type == 1)
{ // Heal
LOG_INF("Type: HEAL, ID: %u, Amount: %u",
packet.data.fields.heal.healer_id,
packet.data.fields.heal.amount);
}
}
else
{
/* Dies wird aktuell ausgelöst, weil 0xA557 -> 0xFB ergibt, nicht 0x71 */
LOG_WRN("CRC Mismatch! Calculated: 0x%02X, Received: 0x%02X (Acc: 0x%06X)",
calc, packet.data.bytes[2], ctx->bit_accumulator);
}
ctx->state = IR_STATE_IDLE;
break;
}
default:
break;
}
switch (ctx->state) {
case IR_STATE_IDLE:
if (energy >= 24) {
ctx->state = IR_STATE_HEADER_SYNC;
ctx->timer = 0;
}
break;
case IR_STATE_HEADER_SYNC:
if (++ctx->timer >= 10) ctx->state = IR_STATE_WAIT_HEADER_GAP;
break;
case IR_STATE_WAIT_HEADER_GAP:
if (!active) { // Sync exactly on falling edge
ctx->state = IR_STATE_WAIT_MARK;
ctx->bit_count = 0;
ctx->bit_acc = 0;
ctx->timer = 0;
}
break;
case IR_STATE_WAIT_MARK:
if (active) {
// Gap measured! Timer had the space length.
if (ctx->timer >= 4) {
bool bit = (ctx->timer >= 12);
// Right-shift: First bit ends up at bit 0
ctx->bit_acc = (ctx->bit_acc >> 1) | (bit ? (1 << 23) : 0);
if (++ctx->bit_count >= 24) ctx->state = IR_STATE_VALIDATE;
else ctx->state = IR_STATE_WAIT_SPACE;
} else { ctx->state = IR_STATE_IDLE; }
ctx->timer = 0;
} else { ctx->timer++; }
break;
case IR_STATE_WAIT_SPACE:
if (!active) { ctx->state = IR_STATE_WAIT_MARK; ctx->timer = 0; }
break;
case IR_STATE_VALIDATE:
{
ir_packet_t p;
p.data.bytes[0] = ctx->bit_acc & 0xFF;
p.data.bytes[1] = (ctx->bit_acc >> 8) & 0xFF;
p.data.bytes[2] = (ctx->bit_acc >> 16) & 0xFF;
if (lastertag_crc8(p.data.bytes, 2) == p.data.fields.crc) {
LOG_INF("VALID: Type %u, ID %u, Val %u",
p.data.fields.type, p.data.fields.heal.healer_id, p.data.fields.heal.amount);
} else {
LOG_WRN("CRC Error! Acc: 0x%06X", ctx->bit_acc);
}
ctx->state = IR_STATE_IDLE;
}
break;
}
}
/**
@@ -324,8 +200,7 @@ int ir_recv_init(void)
{
channels[i].state = IR_STATE_IDLE;
}
LOG_INF("IR Receiver initialized. Mode: %s",
LOG_DBG("IR Receiver initialized. Mode: %s",
IS_ENABLED(CONFIG_IR_RECV_SIMULATOR) ? "Simulator" : "Hardware");
return 0;