Implementation of EPIRB receiver (#2754)

* Implementation of EPIRB receiver
* Baseband processing of EPIRB signal
* UI to ddecode and display EPIRB message with display on a map
* External application
* External proc element
* Delete CLAUDE.md
This commit is contained in:
Arne Luehrs
2025-08-13 14:24:18 +02:00
committed by GitHub
parent 6b05878532
commit 375d1ad54e
13 changed files with 1147 additions and 19 deletions

View File

@@ -279,7 +279,7 @@ macro(DeclareTargets chunk_tag name)
include_directories(. ${INCDIR} ${MODE_INCDIR})
link_directories(${LLIBDIR})
target_link_libraries(${PROJECT_NAME}.elf ${LIBS})
target_link_libraries(${PROJECT_NAME}.elf -Wl,-Map=${PROJECT_NAME}.map)
target_link_libraries(${PROJECT_NAME}.elf -Wl,--print-memory-usage)
@@ -578,6 +578,14 @@ set(MODE_CPPSRC
)
DeclareTargets(PAFR afskrx)
### EPIRB
set(MODE_CPPSRC
proc_epirb.cpp
)
DeclareTargets(PEPI epirb_rx)
### NRF RX
set(MODE_CPPSRC

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2024 EPIRB Receiver Implementation
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "proc_epirb.hpp"
#include "portapack_shared_memory.hpp"
#include "dsp_fir_taps.hpp"
#include "event_m4.hpp"
#include <ch.h>
EPIRBProcessor::EPIRBProcessor() {
// Configure the decimation filters for narrowband EPIRB signal
// Target: Reduce 2.457600 MHz to ~38.4 kHz for 400 bps processing
decim_0.configure(taps_11k0_decim_0.taps);
decim_1.configure(taps_11k0_decim_1.taps);
baseband_thread.start();
}
void EPIRBProcessor::execute(const buffer_c8_t& buffer) {
/* 2.4576MHz, 2048 samples */
// First decimation stage: 2.4576 MHz -> 307.2 kHz
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
// Second decimation stage: 307.2 kHz -> 38.4 kHz
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
const auto decimator_out = decim_1_out;
/* 38.4kHz, 32 samples (approximately) */
feed_channel_stats(decimator_out);
// Process each decimated sample through the matched filter
for (size_t i = 0; i < decimator_out.count; i++) {
// Apply matched filter for BPSK demodulation
if (mf.execute_once(decimator_out.p[i])) {
// Feed symbol to clock recovery when matched filter triggers
clock_recovery(mf.get_output());
}
}
}
void EPIRBProcessor::consume_symbol(const float raw_symbol) {
// BPSK demodulation: positive = 1, negative = 0
const uint_fast8_t sliced_symbol = (raw_symbol >= 0.0f) ? 1 : 0;
// Decode bi-phase L encoding manually
// In bi-phase L: 0 = no transition, 1 = transition
// This is a simple edge detector
const auto decoded_symbol = sliced_symbol ^ last_symbol;
last_symbol = sliced_symbol;
// Build packet from decoded symbols
packet_builder.execute(decoded_symbol);
}
void EPIRBProcessor::payload_handler(const baseband::Packet& packet) {
// EPIRB packet received - validate and process
if (packet.size() >= 112) { // Minimum EPIRB data payload size (112 bits)
packets_received++;
last_packet_timestamp = Timestamp::now();
// Create and send EPIRB packet message to application layer
const EPIRBPacketMessage message{packet};
shared_memory.application_queue.push(message);
}
}
void EPIRBProcessor::on_message(const Message* const message) {
}
int main() {
EventDispatcher event_dispatcher{std::make_unique<EPIRBProcessor>()};
event_dispatcher.run();
return 0;
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2024 EPIRB Receiver Implementation
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __PROC_EPIRB_H__
#define __PROC_EPIRB_H__
#include <cstdint>
#include <cstddef>
#include <array>
#include <complex>
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "rssi_thread.hpp"
#include "channel_decimator.hpp"
#include "matched_filter.hpp"
#include "clock_recovery.hpp"
#include "symbol_coding.hpp"
#include "packet_builder.hpp"
#include "baseband_packet.hpp"
#include "message.hpp"
#include "buffer.hpp"
// Forward declarations for types only used as pointers/references
class Message;
namespace baseband {
class Packet;
}
// EPIRB 406 MHz Emergency Position Indicating Radio Beacon
// Signal characteristics:
// - Frequency: 406.025 - 406.028 MHz (typically 406.028 MHz)
// - Modulation: BPSK (Binary Phase Shift Keying)
// - Data rate: 400 bps
// - Encoding: Bi-phase L (Manchester)
// - Transmission: Every 50 seconds ± 2.5 seconds
// - Power: 5W ± 2dB
// - Message length: 144 bits (including sync pattern)
// Matched filter for BPSK demodulation at 400 bps
// Using raised cosine filter taps optimized for 400 bps BPSK
static constexpr std::array<std::complex<float>, 64> bpsk_taps = {{// Raised cosine filter coefficients for BPSK 400 bps
-5, -8, -12, -15, -17, -17, -15, -11,
-5, 2, 11, 20, 29, 37, 43, 47,
48, 46, 42, 35, 26, 16, 4, -8,
-21, -33, -44, -53, -59, -62, -62, -58,
-51, -41, -28, -13, 3, 19, 36, 51,
64, 74, 80, 82, 80, 74, 64, 51,
36, 19, 3, -13, -28, -41, -51, -58,
-62, -62, -59, -53, -44, -33, -21, -8}};
class EPIRBProcessor : public BasebandProcessor {
public:
EPIRBProcessor();
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const message) override;
private:
// EPIRB operates at 406 MHz with narrow bandwidth
static constexpr size_t baseband_fs = 2457600;
static constexpr uint32_t epirb_center_freq = 406028000; // 406.028 MHz
static constexpr uint32_t symbol_rate = 400; // 400 bps
static constexpr size_t decimation_factor = 64; // Decimate to ~38.4kHz
std::array<complex16_t, 512> dst{};
const buffer_c16_t dst_buffer{
dst.data(),
dst.size()};
// Decimation chain for 406 MHz EPIRB signal processing
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
dsp::matched_filter::MatchedFilter mf{bpsk_taps, 2};
// Clock recovery for 400 bps symbol rate
// Sampling rate after decimation: ~38.4kHz
// Symbols per sample: 38400 / 400 = 96 samples per symbol
clock_recovery::ClockRecovery<clock_recovery::FixedErrorFilter> clock_recovery{
38400, // sampling_rate
400, // symbol_rate (400 bps)
{0.0555f}, // error_filter coefficient
[this](const float symbol) { this->consume_symbol(symbol); }};
// Simple bi-phase L decoder state
uint_fast8_t last_symbol = 0;
// EPIRB packet structure:
// - Sync pattern: 000101010101... (15 bits)
// - Frame sync: 0111110 (7 bits)
// - Data: 112 bits
// - BCH error correction: 10 bits
// Total: 144 bits
PacketBuilder<BitPattern, BitPattern, BitPattern> packet_builder{
{0b010101010101010, 15, 1}, // Preamble pattern
{0b0111110, 7}, // Frame sync pattern
{0b0111110, 7}, // End pattern (same as sync for simplicity)
[this](const baseband::Packet& packet) {
this->payload_handler(packet);
}};
void consume_symbol(const float symbol);
void payload_handler(const baseband::Packet& packet);
// Statistics
uint32_t packets_received = 0;
Timestamp last_packet_timestamp{};
/* NB: Threads should be the last members in the class definition. */
BasebandThread baseband_thread{
baseband_fs, this, baseband::Direction::Receive, /*auto_start*/ false};
RSSIThread rssi_thread{};
};
#endif /*__PROC_EPIRB_H__*/