diff --git a/firmware/libs/ir/CMakeLists.txt b/firmware/libs/ir/CMakeLists.txt index d3324b1..218ce1c 100644 --- a/firmware/libs/ir/CMakeLists.txt +++ b/firmware/libs/ir/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/firmware/libs/ir/recv/include/ir_recv.h b/firmware/libs/ir/recv/include/ir_recv.h index c43c9ea..ff58ac6 100644 --- a/firmware/libs/ir/recv/include/ir_recv.h +++ b/firmware/libs/ir/recv/include/ir_recv.h @@ -1,29 +1,7 @@ #ifndef IR_RECV_H #define IR_RECV_H -#include - -// 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 /** * @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 */ diff --git a/firmware/libs/ir/recv/src/ir_recv.c b/firmware/libs/ir/recv/src/ir_recv.c index ef5e89b..e7f77e7 100644 --- a/firmware/libs/ir/recv/src/ir_recv.c +++ b/firmware/libs/ir/recv/src/ir_recv.c @@ -2,285 +2,161 @@ #include #include +#include +#include +#include + +#include +#include #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;