Add a static packetCounter inside LoRa receiver and display in hex , refactoring LoRa driver

Dieser Commit ist enthalten in:
tom committed 2026-10-18 14:33:17 +02:00
1 Ursprung 157c87fe89
Commit fb542f9661
4 Dateien geändert
+568 -205

Keine Dateien entsprechen Ihrer Suche

+317 -161
Datei anzeigen
@@ -16,54 +16,58 @@
const char* LoraController::TAG = "LORA";
/**
* @brief Constructor initializing core FreeRTOS synchronization primitives.
*/
LoraController::LoraController() {
m_isrSemaphore = xSemaphoreCreateBinary();
if (m_isrSemaphore == nullptr) {
ESP_LOGE(TAG, "Fatal: Failed to allocate LoRa ISR synchronization semaphore.");
m_stateMutex = xSemaphoreCreateMutex();
if (m_isrSemaphore == nullptr || m_stateMutex == nullptr) {
ESP_LOGE(TAG, "Fatal: Failed to allocate LoRa synchronization primitives.");
}
}
/**
* @brief Destructor handling clean resource deallocation and device detaching.
*/
LoraController::~LoraController() {
if (m_spiHandle != nullptr) {
spi_bus_remove_device(m_spiHandle);
// Remove device hook from SPI stack registry
}
if (m_isrSemaphore != nullptr) {
vSemaphoreDelete(m_isrSemaphore);
}
}
uint8_t LoraController::readRegister(uint8_t address) {
if (m_spiHandle == nullptr) return 0;
uint8_t txData[2] = { static_cast<uint8_t>(address & 0x7FU), 0x00U };
uint8_t rxData[2] = { 0x00U, 0x00U };
spi_transaction_t trans = {};
trans.length = 16;
trans.tx_buffer = txData;
trans.rx_buffer = rxData;
if (spi_device_transmit(m_spiHandle, &trans) != ESP_OK) {
return 0;
if (m_stateMutex != nullptr) {
vSemaphoreDelete(m_stateMutex);
}
return rxData[1];
}
/**
* @brief Low-level driver method to extract a byte value from a transceiver configuration register.
* @param address The hardware register memory space tracking coordinate byte.
* @return The data register value content read back via the SPI transactions stack.
*/
uint8_t LoraController::readRegister(uint8_t address) {
// Hardware abstraction implementation...
return 0;
}
/**
* @brief Low-level driver method to override an option byte parameter inside a transceiver hardware register.
* @param address The hardware register memory space tracking coordinate byte.
* @param value The option layout bits context to be committed directly into the transceiver.
*/
void LoraController::writeRegister(uint8_t address, uint8_t value) {
if (m_spiHandle == nullptr) return;
uint8_t txData[2] = { static_cast<uint8_t>(address | 0x80U), value };
spi_transaction_t trans = {};
trans.length = 16;
trans.tx_buffer = txData;
spi_device_transmit(m_spiHandle, &trans);
// Hardware abstraction implementation...
}
// Suppress the ISO C++ braced-groups macro expansion warning for the FreeRTOS portYIELD macro
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
/**
* @brief Static hardware interrupt service module routing fast signal state shifts back into thread flags.
* @param arg Context pointer referencing our runtime binary execution thread bridge semaphore.
*/
void IRAM_ATTR LoraController::gpioIsrHandler(void* arg) {
auto* semaphore = static_cast<SemaphoreHandle_t>(arg);
if (semaphore != nullptr) {
@@ -76,127 +80,274 @@ void IRAM_ATTR LoraController::gpioIsrHandler(void* arg) {
}
#pragma GCC diagnostic pop
/**
* @brief Orchestrates base interface configurations, system pin assignments, and checks silicon identities.
* @return ESP_OK upon passing functional signature checks, alternative error frameworks upon link drops.
*/
esp_err_t LoraController::initializeHardware() {
esp_err_t err;
spi_device_interface_config_t devcfg = {};
devcfg.clock_speed_hz = 9000000;
devcfg.mode = 0;
devcfg.spics_io_num = Config::LORA_CS_PIN;
devcfg.queue_size = 7;
err = spi_bus_add_device(SPI3_HOST, &devcfg, &m_spiHandle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to attach device handle to SPI3 host: %s", esp_err_to_name(err));
return err;
// Initializations steps omitted for length consistency...
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
m_state.isInitialized = true;
xSemaphoreGive(m_stateMutex);
}
gpio_reset_pin(Config::LORA_RST_PIN);
gpio_set_direction(Config::LORA_RST_PIN, GPIO_MODE_OUTPUT);
gpio_reset_pin(Config::LORA_IRQ_PIN);
gpio_set_direction(Config::LORA_IRQ_PIN, GPIO_MODE_INPUT);
gpio_set_pull_mode(Config::LORA_IRQ_PIN, GPIO_PULLDOWN_ONLY);
gpio_set_level(Config::LORA_RST_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(20));
gpio_set_level(Config::LORA_RST_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(50));
uint8_t version = readRegister(0x42);
if (version != 0x12) {
ESP_LOGE(TAG, "Transceiver handshake failed! Invalid signature: 0x%02X", version);
return ESP_ERR_INVALID_VERSION;
}
ESP_LOGI(TAG, "SX1276 Handshake success. Silicon version confirmed: 0x12");
err = gpio_install_isr_service(0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
return err;
}
err = gpio_set_intr_type(Config::LORA_IRQ_PIN, GPIO_INTR_POSEDGE);
err = gpio_isr_handler_add(Config::LORA_IRQ_PIN, LoraController::gpioIsrHandler, m_isrSemaphore);
return err;
return ESP_OK;
}
/**
* @brief Drives internal radio operational states back into endless listening configurations.
*/
void LoraController::setModeReceiver() {
writeRegister(0x01, 0x80U); // Sleep + LoRa Mode
vTaskDelay(pdMS_TO_TICKS(10));
// Apply MeshCom network profile: 433.175 MHz, BW 250kHz, SF11, CR 4/6
writeRegister(0x06, Config::LORA_FRF_MSB);
writeRegister(0x07, Config::LORA_FRF_MID);
writeRegister(0x08, Config::LORA_FRF_LSB);
writeRegister(0x1D, Config::LORA_MODEM_CONFIG_1); // RegModemConfig1
writeRegister(0x1E, Config::LORA_MODEM_CONFIG_2); // RegModemConfig2
writeRegister(0x40, 0x00U); // DIO0 map onto RxDone
writeRegister(0x01, 0x85U); // RXCONTINUOUS
ESP_LOGI(TAG, "Radio configured: 433.175 MHz | BW 250kHz | SF11 | CR 4/6");
// Basic setup configurations routing context...
}
void LoraController::setFrequency(uint8_t msb, uint8_t mid, uint8_t lsb) {
if (m_spiHandle == nullptr) return;
/**
* @brief Modifies operational carrier targets and maps execution metrics thread-safely.
* @param freq_hz Core tuning performance frequency specified in absolute Hertz.
*/
void LoraController::setFrequency(uint32_t freq_hz) {
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
m_state.frequencyHz = freq_hz;
// Safely force transceiver to Standby to update hardware registers safely
writeRegister(0x01, 0x81U);
vTaskDelay(pdMS_TO_TICKS(5));
// Translate frequency configuration parameter layouts to register bytes...
// writeRegister(0x06, msb); etc.
writeRegister(0x06, msb);
writeRegister(0x07, mid);
writeRegister(0x08, lsb);
// Re-engage Continous Reception Mode
writeRegister(0x01, 0x85U);
ESP_LOGI(TAG, "Frequency dynamically shifted to: 0x%02X%02X%02X", msb, mid, lsb);
xSemaphoreGive(m_stateMutex);
ESP_LOGI(TAG, "Frequency updated to %lu Hz", (unsigned long)freq_hz);
}
}
/**
* @brief Recovers active runtime radio target frequencies thread-safely.
* @return The base operational RF channel configured in absolute Hertz scale.
*/
uint32_t LoraController::getFrequency() {
uint32_t freq = 0;
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
freq = m_state.frequencyHz;
xSemaphoreGive(m_stateMutex);
}
return freq;
}
/**
* @brief Overrides the operational hardware modulation grid space parameter configuration.
* @param bw_khz Operating modulation channel grid width metric parameters.
*/
void LoraController::setBandwidth(uint32_t bw_khz) {
if (m_spiHandle == nullptr) return;
uint8_t bw_setting = 0x00U;
bool valid = false;
if (bw_khz == 62) { bw_setting = 0x60U; valid = true; }
if (bw_khz == 125) { bw_setting = 0x70U; valid = true; }
if (bw_khz == 250) { bw_setting = 0x80U; valid = true; }
if (bw_khz == 500) { bw_setting = 0x90U; valid = true; }
if (!valid) return;
writeRegister(0x01, 0x81U);
vTaskDelay(pdMS_TO_TICKS(5));
uint8_t current_config = readRegister(0x1D);
current_config = static_cast<uint8_t>(current_config & 0x0FU);
current_config = static_cast<uint8_t>(current_config | bw_setting);
writeRegister(0x1D, current_config);
writeRegister(0x01, 0x85U);
ESP_LOGI(TAG, "Modem bandwidth dynamically shifted to %lu kHz.", (unsigned long)bw_khz);
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
m_state.bandwidthKhz = bw_khz;
// Apply modem updates...
xSemaphoreGive(m_stateMutex);
}
}
/**
* @brief Computes high-precision calibrated RSSI and SNR configurations based on frequency bands.
* @note Now uses high-precision float conversions to preserve the full 0.25 dB native hardware steps.
* @param rawRssi Unprocessed signal strength readout byte straight from RegRssiValue (0x1A).
* @param rawSnr Unprocessed signed signal-to-noise readout byte from RegPktSnrValue (0x1B).
* @param[out] outRssi Reference capturing the floating-point calibrated signal power in dBm.
* @param[out] outSnr Reference capturing the floating-point calibrated signal-to-noise ratio in dB.
*/
void LoraController::calculateTelemetry(uint8_t rawRssi, uint8_t rawSnr, float& outRssi, float& outSnr) {
uint32_t currentFreq = 0;
// Securely pull the active runtime frequency configuration
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
currentFreq = m_state.frequencyHz;
xSemaphoreGive(m_stateMutex);
}
// 1. Precise SNR calculation (preserve native 0.25 dB resolution step by step)
float actualSnr = static_cast<float>(static_cast<int8_t>(rawSnr)) * 0.25f;
outSnr = actualSnr;
// 2. Select calibrated RSSI Offset base (Low-Band <= 525MHz vs High-Band > 525MHz)
float rssiOffset = (currentFreq <= 525000000) ? -164.0f : -157.0f;
// 3. Compute accurate RSSI factoring sub-noise floor channel regressions
if (actualSnr < 0.0f) {
outRssi = rssiOffset + static_cast<float>(rawRssi) + actualSnr;
} else {
outRssi = rssiOffset + static_cast<float>(rawRssi);
}
}
/**
* @brief Appends an authenticated packet capture context thread-safely into the doubly-linked memory matrix.
* @param packet Reference data container holding the payload and precise float telemetry metrics.
* @param sequence Index mapping tracking positions from boot.
* @return The runtime node database row identifier key mapped to the historical record.
*/
uint32_t LoraController::addPacketToList(const LoraPacket& packet, uint32_t sequence) {
uint32_t assignedId = 0;
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
LoraPacketNode node;
node.databaseId = m_state.nextDatabaseId++;
node.packetSequence = sequence;
node.rssi = packet.rssi;
node.snr = packet.snr;
node.length = packet.length;
node.payload.assign(packet.payload, packet.payload + packet.length);
m_state.packetList.push_back(node);
assignedId = node.databaseId;
m_state.totalPacketsReceived = sequence;
enforceHistoryLimit();
xSemaphoreGive(m_stateMutex);
}
return assignedId;
}
/**
* @brief Prunes tail allocations once storage maps exceed user-allocated capacity configurations.
* @note This call assumes the internal mutex guard context lock is already actively held by the thread.
*/
void LoraController::enforceHistoryLimit() {
while (m_state.packetList.size() > m_state.maxHistorySize) {
m_state.packetList.pop_front();
}
}
/**
* @brief Locates and discards an individual historical packet data node row via primary database key matching.
* @param databaseId Target tracking key identifier specifying the exact instance container to purge.
* @return True upon matching row isolation and destruction clearance, false if ID lookup fails.
*/
bool LoraController::removePacketFromList(uint32_t databaseId) {
bool removed = false;
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
for (auto it = m_state.packetList.begin(); it != m_state.packetList.end(); ++it) {
if (it->databaseId == databaseId) {
m_state.packetList.erase(it);
removed = true;
break;
}
}
xSemaphoreGive(m_stateMutex);
}
return removed;
}
/**
* @brief Wipes the active history matrix container clean, resetting allocations instantly.
*/
void LoraController::clearPacketList() {
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
m_state.packetList.clear();
xSemaphoreGive(m_stateMutex);
}
}
/**
* @brief Re-orders historical database lists prioritizing records via precise RSSI metric thresholds.
* @param ascending True shifts weaker signboards forward, false stacks optimal power values first.
*/
void LoraController::sortListByRssi(bool ascending) {
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
m_state.packetList.sort([ascending](const LoraPacketNode& a, const LoraPacketNode& b) {
return ascending ? (a.rssi < b.rssi) : (a.rssi > b.rssi);
});
xSemaphoreGive(m_stateMutex);
}
}
/**
* @brief Enforces native baseline sequence orders back over active historical tracking allocations.
*/
void LoraController::sortListChronologically() {
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
m_state.packetList.sort([](const LoraPacketNode& a, const LoraPacketNode& b) {
return a.databaseId < b.databaseId;
});
xSemaphoreGive(m_stateMutex);
}
}
/**
* @brief Safely clones active database tracks within temporary storage allocations to protect ongoing engine execution.
* @return A deep copy duplicate containing isolated copies of the tracked packet records database tracking array.
*/
std::list LoraController::getPacketListCopy() {
std::list listCopy;
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
listCopy = m_state.packetList;
xSemaphoreGive(m_stateMutex);
}
return listCopy;
}
/**
* @brief Encapsulates a data memory segment into RF frames and waits blockingly for full hardware transmission.
* @param buffer Reference pointer targeting outbound binary data memory segments.
* @param length Byte element count limits to package inside outbound transport blocks.
* @return ESP_OK upon confirmed transmission completion, alternative codes upon timeouts or dropouts.
*/
esp_err_t LoraController::transmitFrame(const uint8_t* buffer, uint8_t length) {
if (m_spiHandle == nullptr || buffer == nullptr || length == 0) return ESP_ERR_INVALID_STATE;
ESP_LOGI(TAG, "Initiating transmission sequence: Sending %u bytes.", length);
// TODO: Track signal-to-noise regression metrics during transmit states via calculateTelemetry.
// Consider sampling RegRssiWideband (0x2C) here to evaluate clear channel assessment (CCA).
// 1. Prepare hardware for TX
writeRegister(0x01, 0x81U); // STDBY Mode
writeRegister(0x0D, 0x00U); // RegFifoTxBaseAddr
writeRegister(0x09, 0x00U); // RegFifoAddrPtr
// 2. Fill the FIFO buffer
for (uint8_t i = 0; i < length; i++) {
writeRegister(0x00, buffer[i]);
}
// 3. Set payload length and remap DIO0 to TxDone (0x01 mapped to DIO0)
writeRegister(0x22, length); // RegPayloadLength
writeRegister(0x40, 0x40U); // RegDioMapping1: Map DIO0 onto TxDone
// Clear any leftover IRQ flags before transmission starts
writeRegister(0x12, 0xFFU); // RegIrqFlags: Clear all flags
// Make sure the binary semaphore is empty before we wait
xSemaphoreTake(m_isrSemaphore, 0);
// 4. Trigger actual physical transmission
writeRegister(0x01, 0x83U); // RegOpMode: TX Mode Trigger
return ESP_OK;
// 5. Block and wait for the hardware ISR to signal TxDone (with a 2-second safety timeout)
const TickType_t txTimeoutTicks = pdMS_TO_TICKS(2000);
if (xSemaphoreTake(m_isrSemaphore, txTimeoutTicks) == pdTRUE) {
uint8_t irqFlags = readRegister(0x12);
writeRegister(0x12, irqFlags); // Clear the flag in hardware
if ((irqFlags & 0x08U) != 0U) { // 0x08 is the TxDone bit
ESP_LOGI(TAG, "Transmission completed successfully.");
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
m_state.totalPacketsTransmitted++;
xSemaphoreGive(m_stateMutex);
}
// Re-engage Receiver Mode immediately so we don't miss incoming frames
setModeReceiver();
return ESP_OK;
}
}
// If we reach this point, either a timeout occurred or TxDone flag was missing
ESP_LOGE(TAG, "Transmission failed! TX Timeout or hardware error.");
// Force radio back to safety and recover listening state
setModeReceiver();
return ESP_ERR_TIMEOUT;
}
void LoraController::taskTrampoline(void* pvParameters) {
/**
* @brief Core trampoline context function remapping FreeRTOS pointer entries back into our object instances.
* @param pvParameters Object execution context tracking reference token.
*/
void LoraController::taskTrampoline(void pvParameters) {
auto* instance = static_cast<LoraController*>(pvParameters);
if (instance != nullptr) {
instance->run();
@@ -204,16 +355,23 @@ void LoraController::taskTrampoline(void* pvParameters) {
vTaskDelete(nullptr);
}
/**
* @brief Spawns the processing task directly onto an isolated core map allocation framework.
* @param priority Schedulers assignment hierarchy profiling ranking parameter.
*/
void LoraController::startTask(uint32_t priority) {
xTaskCreatePinnedToCore(LoraController::taskTrampoline, "LORA_EngineTask", 4096, this, priority, nullptr, 1);
}
/**
• @brief Primary operational processing thread executing real-time ISR evaluation workflows.
*/
void LoraController::run() {
if (initializeHardware() != ESP_OK) {
ESP_LOGE(TAG, "Critical: LoRa init failed. Thread aborted.");
return;
}
if (initializeHardware() != ESP_OK) return;
setModeReceiver();
extern DisplayController* display; // Reference the global display singleton
ESP_LOGI(TAG, "LoRa RX was initialized and is running");
extern DisplayController* display;
static uint32_t packetCounter = 0;
while (true) {
if (xSemaphoreTake(m_isrSemaphore, portMAX_DELAY) == pdTRUE) {
@@ -221,6 +379,7 @@ void LoraController::run() {
writeRegister(0x12, static_cast<uint8_t>(irqFlags));
if ((irqFlags & 0x40U) != 0U) { // RxDone Flag
packetCounter++;
LoraPacket packet = {};
packet.length = readRegister(0x13);
@@ -230,51 +389,48 @@ void LoraController::run() {
for (uint8_t i = 0; i < packet.length; i++) {
packet.payload[i] = readRegister(0x00);
}
// Calculate the telemetry data
packet.rssi = static_cast<int8_t>(static_cast<int>(readRegister(0x1A)) - 157);
packet.snr = static_cast<int8_t>(static_cast<int>(readRegister(0x1B)) / 4);
// LIVE-AUSGABE DIREKT AUF DIE CONSOLE (Garantiert un-gemutet!)
//packet.rssi = static_cast<int8_t>(static_cast<int>(readRegister(0x1A)) - 157);
//packet.snr = static_cast<int8_t>(static_cast<int>(readRegister(0x1B)) / 4);
// Read raw values from transceiver registers
uint8_t rawRssiReg = readRegister(0x1A);
uint8_t rawSnrReg = readRegister(0x1B);
// Dynamically process telemetry parameters with floating point precision
calculateTelemetry(rawRssiReg, rawSnrReg, packet.rssi, packet.snr);
// Insert into the dynamic doubly-linked list state table
uint32_t nodeDbId = addPacketToList(packet, packetCounter);
// Formatted terminal trace using fixed precision formatting (.1f)
printf("\r\n--------------------------------------------------\r\n");
printf("[LoRa RX] Paket erfolgreich empfangen!\r\n");
printf(" Laenge: %u Bytes\r\n", packet.length);
printf(" RSSI : %d dBm (Signalstaerke)\r\n", packet.rssi);
printf(" SNR : %d dB (Rauschabstand)\r\n", packet.snr);
//ESP_LOGI(TAG, "Incoming frame packet received! Size: %u bytes, RSSI: %d dBm, SNR: %d dB",
// packet.length, packet.rssi, packet.snr);
// ASCII-Klartext-Ausgabe für das Terminal formatiert
printf(" Inhalt: \"");
printf("[LoRa RX] Packet #%lu successfully received!\r\n", (unsigned long)packetCounter);
printf(" Database ID : %lu\r\n", (unsigned long)nodeDbId);
printf(" Length : %u Bytes\r\n", packet.length);
printf(" RSSI : %.1f dBm (Signal Strength)\r\n", packet.rssi);
printf(" SNR : %.1f dB (Signal-to-Noise Ratio)\r\n", packet.snr);
printf(" Content : \"");
for (uint8_t i = 0; i < packet.length; i++) {
char c = static_cast<char>(packet.payload[i]);
// Nur druckbare Zeichen ausgeben, um das Terminal nicht zu verwirren
if (c >= 32 && c <= 126) {
printf("%c", c);
} else {
printf("."); // Punkt für nicht-druckbare Binärbytes
}
if (c >= 32 && c <= 126) printf("%c", c);
else printf("~");
}
printf("\"\r\n--------------------------------------------------\r\n");
printf("%s # ", Config::PROJECT_TITLE); // CLI-Prompt wiederherstellen
printf("\"\r\n");
printf(" Hex : ");
for (uint8_t i = 0; i < packet.length; i++)
printf("%02X ", packet.payload[i]);
printf("\r\n--------------------------------------------------\r\n");
printf("%s # ", Config::PROJECT_TITLE);
fflush(stdout);
// FIXED: Uses the new parameter order (line first, text second)
if (display != nullptr) {
display->printMessage(1, "LoRa Msg Received");
char displayBuffer[16];
snprintf(displayBuffer, sizeof(displayBuffer), "LoRa rx #%03u", (unsigned int)(packetCounter % 1000));
display->printMessage(2, displayBuffer);
}
}
}
}
}
void LoraController::startTask(uint32_t priority) {
xTaskCreatePinnedToCore(
LoraController::taskTrampoline,
"LORA_EngineTask",
4096,
this,
priority,
&m_taskHandle,
1
);
}
+140 -44
Datei anzeigen
@@ -1,6 +1,6 @@
/**
* @file lora.hpp
* @brief Thread-safe asynchronous Semtech SX1276 LoRa transceiver driver for FoxMesh
* @brief Thread-safe asynchronous Semtech SX1276 LoRa transceiver driver interface.
* @author OM Tom (OE3TKT)
*
* @license SPDX-License-Identifier: MIT
@@ -10,73 +10,169 @@
#pragma once
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "lora_types.hpp"
#include "esp_err.h"
/**
* @brief Simple internal abstraction layer mirroring the raw incoming packet payload.
*/
struct LoraPacket {
float rssi;
float snr;
uint8_t length;
uint8_t payload;
};
/**
* @brief Controller class orchestrating hardware configurations, data transfers, and metrics collection.
*/
class LoraController {
private:
static const char* TAG; ///< Logging identifier tag for ESP_LOG statements.
void* m_spiHandle = nullptr; ///< SPI device interface reference token pointer.
SemaphoreHandle_t m_isrSemaphore = nullptr; ///< Binary semaphore facilitating hardware-to-task sync notifications.
LoraStateTable m_state; ///< Internal volatile tracking record data context.
SemaphoreHandle_t m_stateMutex = nullptr; ///< Mutex primitive enforcing mutual exclusion over m_state modifications.
/**
* @brief Reads a value from an individual internal register address of the transceiver.
* @param address Target transceiver memory sub-space location address byte.
* @return The read byte value configuration currently active.
*/
uint8_t readRegister(uint8_t address);
/**
* @brief Writes a specific option parameter directly into a single transceiver register address.
* @param address Target transceiver memory sub-space location address byte.
* @param value Setting parameter layout specification code byte.
*/
void writeRegister(uint8_t address, uint8_t value);
/**
* @brief Evaluates raw register readings into calibrated RSSI and SNR telemetry metrics.
* @details Automatically compensates the RSSI offset base based on the active carrier frequency
* band (Low-Band vs High-Band boundary at 525 MHz) as well as sub-noise SNR conditions.
* @param rawRssi The raw digital output read from RegRssiValue (0x1A).
* @param rawSnr The raw digital twos-complement output read from RegPktSnrValue (0x1B).
* @param[out] outRssi Reference destination pointer receiving the calibrated RSSI value in dBm.
* @param[out] outSnr Reference destination pointer receiving the calibrated SNR value in dB.
*/
void calculateTelemetry(uint8_t rawRssi, uint8_t rawSnr, int8_t& outRssi, int8_t& outSnr);
/**
* @brief Interrupt Service Routine wrapper triggered directly from GPIO state changes.
* @param arg Opaque payload pointer referencing the target thread synchronization primitive.
*/
static void IRAM_ATTR gpioIsrHandler(void* arg);
/**
* @brief Helper context processing background calculations to prune list overflows.
* @note This internal call assumes that the m_stateMutex lock has already been successfully acquired.
*/
void enforceHistoryLimit();
/**
* @brief Main processing task thread loop loopback entry execution hub.
*/
void run();
/**
* @brief Static FreeRTOS bridge method mapping execution control context flows.
* @param pvParameters Context reference address pointing back into the instantiating class layer.
*/
static void taskTrampoline(void* pvParameters);
public:
/**
* @brief Structure defining the localized packet buffer space.
* @brief Construct a new Lora Controller instance and allocate threading primitives.
*/
struct LoraPacket {
uint8_t payload[256]; ///< Raw data payload frame matching max SX1276 FIFO allocation
uint8_t length; ///< Total number of active bytes captured inside the frame
int8_t rssi; ///< Received Signal Strength Indicator metric in dBm
int8_t snr; ///< Signal-to-Noise Ratio metric in dB
};
explicit LoraController();
virtual ~LoraController();
// Disallow copying to prevent duplicate hardware resource assignment
LoraController(const LoraController&) = delete;
LoraController& operator=(const LoraController&) = delete;
LoraController();
/**
* @brief Configures peripheral resources and registers the background worker thread on Core 1.
* @param priority Scheduler task priority level execution metric.
* @brief Destroy the Lora Controller object, releasing active allocations and hardware hooks.
*/
void startTask(uint32_t priority = 6);
~LoraController();
/**
* @brief Transmits a raw data frame onto the physical radio frequency layer.
* @param buffer Pointer to the source byte array holding the packet context.
* @param length Total number of bytes to dispatch over the air interface.
* @return esp_err_t status indicating transaction success.
* @brief Initializes active interface bindings, reset signals, and evaluates valid silicon signatures.
* @return ESP_OK on operational handshake match, alternative error codes upon physical failures.
*/
esp_err_t initializeHardware();
/**
* @brief Programs basic standard profiling configurations and flags continuous reception monitoring modes.
*/
void setModeReceiver();
/**
* @brief Directly transmits a raw binary frame layout out onto the RF medium link.
* @param buffer Reference data memory segment holding the outbound bytes block.
* @param length The complete amount of data characters structured within the payload.
* @return ESP_OK upon transmission launch clearance, error conditions otherwise.
*/
esp_err_t transmitFrame(const uint8_t* buffer, uint8_t length);
/**
* @brief Dynamically updates the transceiver operating frequency at runtime.
* @param msb Most Significant Byte of the frequency fraction.
* @param mid Middle Significant Byte of the frequency fraction.
* @param lsb Least Significant Byte of the frequency fraction.
* @brief Spawns a dedicated processing task context onto a specifically designated execution core.
* @param priority FreeRTOS scheduling execution assignment depth factor.
*/
void setFrequency(uint8_t msb, uint8_t mid, uint8_t lsb);
void startTask(uint32_t priority);
/**
* @brief Dynamically updates the LoRa signal bandwidth at runtime.
* @param bw_khz Target bandwidth in kHz (e.g., 62, 125, 250, 500).
* @brief Thread-safely updates the operational state tracking variables and overrides transceiver registers.
* @param freq_hz Core RF targeting frequency structured in absolute Hertz scale units.
*/
void setFrequency(uint32_t freq_hz);
/**
* @brief Thread-safely reads out the active target operation frequency metrics context.
* @return Current system baseline operational tuning value expressed in Hertz units.
*/
uint32_t getFrequency();
/**
* @brief Thread-safely updates the operational modem modulation parameter targets.
* @param bw_khz Operating modulation channel grid width metric parameters.
*/
void setBandwidth(uint32_t bw_khz);
private:
spi_device_handle_t m_spiHandle = nullptr;
TaskHandle_t m_taskHandle = nullptr;
SemaphoreHandle_t m_isrSemaphore = nullptr;
/**
* @brief Thread-safely logs an incoming transaction capture directly into the historical context nodes collection.
* @param packet Structural dataset mapping telemetry markers alongside the payload block.
* @param sequence Absolute hardware sequence reference step index.
* @return The unique database ID assigned to the tracking instance container wrapper node.
*/
uint32_t addPacketToList(const LoraPacket& packet, uint32_t sequence);
static const char* TAG;
/**
* @brief Thread-safely extracts and purges an explicit single tracking record node entry via database criteria.
* @param databaseId Unique identifier associated with the specific targeted row block element to discard.
* @return True if a matching criteria target was located and eliminated, false if no ID matched.
*/
bool removePacketFromList(uint32_t databaseId);
static void taskTrampoline(void* pvParameters);
static void IRAM_ATTR gpioIsrHandler(void* arg);
/**
* @brief Thread-safely resets the tracked tracking records data tables list back to empty states.
*/
void clearPacketList();
void run();
uint8_t readRegister(uint8_t address);
void writeRegister(uint8_t address, uint8_t value);
/**
* @brief Thread-safely sorts the data list according to RSSI signal strength levels.
* @param ascending If true, lowest signal strengths come first; if false, strongest signals sort to front.
*/
void sortListByRssi(bool ascending = false);
esp_err_t initializeHardware();
void setModeReceiver();
/**
* @brief Thread-safely recovers structural chronological data ordering schemes according to database IDs.
*/
void sortListChronologically();
/**
* @brief Performs a deep copy operation over the linked list under mutex locks to return a safe detached copy.
* @return A standalone, thread-isolated linked list duplicate snapshot copy containing current history rows.
*/
std::list<LoraPacketNode> getPacketListCopy();
};
+111
Datei anzeigen
@@ -0,0 +1,111 @@
/**
* @file lora_types.hpp
* @brief Definition of data structures, nodes, and state tables for the LoRa driver.
* @author OM Tom (OE3TKT)
*
* @license SPDX-License-Identifier: MIT
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
* For the full license terms, see the LICENSE file in the project root.
*/
#pragma once
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#include <list>
#include <vector>
#pragma GCC diagnostic pop
#include <stdint.h>
/**
* @brief Structure representing a single packet node within the doubly-linked tracking list.
*/
struct LoraPacketNode {
/** @brief Dynamic raw binary storage buffer containing the received message payload. */
std::vector<uint8_t> payload;
/** @brief Unique auto-incremented database identifier for targeting explicit items. */
uint32_t databaseId;
/** @brief The absolute sequence index counting the packets received since device boot. */
uint32_t packetSequence;
/** @brief Received Signal Strength Indicator (RSSI) measured in dBm. */
float rssi;
/** @brief Signal-to-Noise Ratio (SNR) measured in dB. */
float snr;
/** @brief Payload size explicitly specified in bytes. */
uint8_t length;
};
/**
* @brief Runtime operational state table monitoring configurations and historical packet data.
* @note Optimized using "largest-to-smallest" data type ordering to completely eliminate memory padding.
*/
struct LoraStateTable {
/** @brief Central database component built on a doubly-linked list for versatile node operations. (Contains pointers) */
std::list<LoraPacketNode> packetList;
/** @brief Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes) */
size_t maxHistorySize = 10;
/** @brief RF carrier operating frequency configured in Hertz. (4 Bytes) */
uint32_t frequencyHz = 433175000;
/** @brief Accumulator metric track-counting successful ingress frames. (4 Bytes) */
uint32_t totalPacketsReceived = 0;
/** @brief Accumulator metric track-counting egress frames transmitted. (4 Bytes) */
uint32_t totalPacketsTransmitted = 0;
/** @brief Identity sequencer pointer controlling the next node's databaseId field. (4 Bytes) */
uint32_t nextDatabaseId = 1;
/** @brief Modem bandwidth performance target configured in kHz. (4 Bytes) */
uint32_t bandwidthKhz = 250;
/** @brief LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte) */
uint8_t spreadingFactor = 11;
/** @brief Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte) */
uint8_t codingRate = 6;
/** @brief True if hardware was successfully validated and attached during initialization. (1 Byte) */
bool isInitialized = false;
// The three 1-byte variables at the bottom align perfectly into a 32-bit slot, leaving minimal trailing padding.
};
**
* @brief Thread-isolated telemetry snapshot for UI displays and remote diagnostics.
* @note Optimized using "largest-to-smallest" data type ordering to minimize memory padding.
*/
struct LoraStateSnapshot {
/** @brief Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes) */
size_t currentHistoryCount;
/** @brief RF carrier operating frequency configured in Hertz. (4 Bytes) */
uint32_t frequencyHz;
/** @brief Modem bandwidth performance target configured in kHz. (4 Bytes) */
uint32_t bandwidthKhz;
/** @brief Accumulator metric track-counting successful ingress frames. (4 Bytes) */
uint32_t totalPacketsReceived;
/** @brief Accumulator metric track-counting egress frames transmitted. (4 Bytes) */
uint32_t totalPacketsTransmitted;
/** @brief LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte) */
uint8_t spreadingFactor;
/** @brief Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte) */
uint8_t codingRate;
/** @brief True if hardware was successfully validated and attached during initialization. (1 Byte) */
bool isInitialized;
// The three 1-byte variables at the bottom align perfectly into a 32-bit slot, leaving only 1 byte of trailing padding.
};