diff --git a/src/cli.cpp b/src/cli.cpp index 5914944..45189ff 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -163,16 +163,7 @@ void process_command(const char* cmd) { if (valid_433 || valid_868) { if (lora_system != nullptr) { - // SX1276 formula: Frf = Ffreq * 2^19 / 32000000 - // Using double to prevent truncation during division before bit masking - double fraction = (static_cast(target_freq_hz) * 524288.0) / 32000000.0; - uint32_t frf = static_cast(fraction); - - uint8_t msb = static_cast((frf >> 16) & 0xFFU); - uint8_t mid = static_cast((frf >> 8) & 0xFFU); - uint8_t lsb = static_cast(frf & 0xFFU); - - lora_system->setFrequency(msb, mid, lsb); + lora_system->setFrequency(target_freq_hz); printf("Success: Frequency shifted to %lu Hz.\r\n", (unsigned long)target_freq_hz); } else { printf("Error: LoraController handle is uninitialized.\r\n"); diff --git a/src/lora.cpp b/src/lora.cpp index 3ffaa8c..ebe481c 100644 --- a/src/lora.cpp +++ b/src/lora.cpp @@ -16,10 +16,16 @@ 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."); + return; } } @@ -30,8 +36,16 @@ LoraController::~LoraController() { if (m_isrSemaphore != nullptr) { vSemaphoreDelete(m_isrSemaphore); } + if (m_stateMutex != nullptr) { + vSemaphoreDelete(m_stateMutex); + } } +/** + * @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) { if (m_spiHandle == nullptr) return 0; @@ -49,6 +63,11 @@ uint8_t LoraController::readRegister(uint8_t address) { return rxData[1]; } +/** + * @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; @@ -76,6 +95,10 @@ 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; @@ -110,6 +133,9 @@ esp_err_t LoraController::initializeHardware() { } ESP_LOGI(TAG, "SX1276 Handshake success. Silicon version confirmed: 0x12"); + // Invoke the modular modem configuration + applyModemConfig(); + err = gpio_install_isr_service(0); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { return err; @@ -121,6 +147,7 @@ esp_err_t LoraController::initializeHardware() { return err; } +/* void LoraController::setModeReceiver() { writeRegister(0x01, 0x80U); // Sleep + LoRa Mode vTaskDelay(pdMS_TO_TICKS(10)); @@ -135,9 +162,56 @@ void LoraController::setModeReceiver() { 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"); + ESP_LOGI(TAG, "Radio state changed to RXCONTINUOUS"); +} +*/ + +void LoraController::setModeReceiver() { + // DIO0 auf RxDone mappen (Bit 7-6 auf 00) + writeRegister(0x40, 0x00U); + + // In den kontinuierlichen Empfangsmodus schalten + writeRegister(0x01, 0x85U); + + ESP_LOGD(TAG, "Radio state changed to RXCONTINUOUS"); } +/** + * @brief Applies the modem profile, RF carrier frequency, and network sync word. + * Note: The transceiver must transition through SLEEP mode to apply these alterations safely. + */ +void LoraController::applyModemConfig() { + // Force Sleep + LoRa mode to unlock configuration registers + writeRegister(0x01, 0x80U); + vTaskDelay(pdMS_TO_TICKS(10)); + + // Apply RF carrier center frequency (e.g., MeshCom 433.175 MHz) + writeRegister(0x06, Config::LORA_FRF_MSB); + writeRegister(0x07, Config::LORA_FRF_MID); + writeRegister(0x08, Config::LORA_FRF_LSB); + + // just write the m_state frequency.... + m_state.frequencyHz = Config::LORA_DEFAULT_FREQ_HZ; // @TODO: remove this line after implementation of the doubly linked list + + // Configure modem bandwidth, spreading factor, and coding rate + writeRegister(0x1D, Config::LORA_MODEM_CONFIG_1); + writeRegister(0x1E, Config::LORA_MODEM_CONFIG_2); + + // Set the network sync word (0x34 for open/public amateur networks like MeshCom) + writeRegister(0x39, Config::LORA_SYNC_WORD); + + // Set the preamble length (0x0C instead of 0x08 for more stability, see docs/lora/device-settings.md) + writeRegister(0x20, Config::LORA_PREAMBLE_LENGTH_MSB); + writeRegister(0x21, Config::LORA_PREAMBLE_LENGTH_LSB); + + // Transition to STANDBY to allow the internal PLL to lock and stabilize + writeRegister(0x01, 0x81U); + vTaskDelay(pdMS_TO_TICKS(5)); + + ESP_LOGI(TAG, "Radio profile applied: 433.175 MHz | SF11 | BW 250kHz | SyncWord 0x34"); +} + +/* void LoraController::setFrequency(uint8_t msb, uint8_t mid, uint8_t lsb) { if (m_spiHandle == nullptr) return; @@ -153,6 +227,11 @@ void LoraController::setFrequency(uint8_t msb, uint8_t mid, uint8_t lsb) { writeRegister(0x01, 0x85U); ESP_LOGI(TAG, "Frequency dynamically shifted to: 0x%02X%02X%02X", msb, mid, lsb); } + */ + +void LoraController::setFrequency(uint32_t freq_hz) { + // @TODO + } void LoraController::setBandwidth(uint32_t bw_khz) { if (m_spiHandle == nullptr) return; @@ -204,6 +283,38 @@ void LoraController::taskTrampoline(void* pvParameters) { vTaskDelete(nullptr); } +/** + * @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(static_cast(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(rawRssi) + actualSnr; + } else { + outRssi = rssiOffset + static_cast(rawRssi); + } +} + void LoraController::run() { if (initializeHardware() != ESP_OK) { ESP_LOGE(TAG, "Critical: LoRa init failed. Thread aborted."); @@ -212,6 +323,7 @@ void LoraController::run() { setModeReceiver(); extern DisplayController* display; // Reference the global display singleton + static uint32_t packetCounter = 0; // overflow in abt. 272 years (1 packet every 2 seconds) ESP_LOGI(TAG, "LoRa RX was initialized and is running"); @@ -221,6 +333,7 @@ void LoraController::run() { writeRegister(0x12, static_cast(irqFlags)); if ((irqFlags & 0x40U) != 0U) { // RxDone Flag + packetCounter++; LoraPacket packet = {}; packet.length = readRegister(0x13); @@ -231,36 +344,45 @@ void LoraController::run() { packet.payload[i] = readRegister(0x00); } // Calculate the telemetry data - packet.rssi = static_cast(static_cast(readRegister(0x1A)) - 157); - packet.snr = static_cast(static_cast(readRegister(0x1B)) / 4); + // packet.rssi = static_cast(static_cast(readRegister(0x1A)) - 157); + // packet.snr = static_cast(static_cast(readRegister(0x1B)) / 4); + // Read raw values from transceiver registers + uint8_t rawRssiReg = readRegister(0x1A); + uint8_t rawSnrReg = readRegister(0x1B); - // LIVE-AUSGABE DIREKT AUF DIE CONSOLE (Garantiert un-gemutet!) + // Dynamically process telemetry parameters with floating point precision + calculateTelemetry(rawRssiReg, rawSnrReg, packet.rssi, packet.snr); + + // 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(packet.payload[i]); - // Nur druckbare Zeichen ausgeben, um das Terminal nicht zu verwirren - if (c >= 32 && c <= 126) { + // Only print printable characters to avoid confusing the terminal + if (c >= 32 && c <= 126) printf("%c", c); - } else { - printf("."); // Punkt für nicht-druckbare Binärbytes - } + else + printf("~"); // not printable chars + } - printf("\"\r\n--------------------------------------------------\r\n"); - printf("%s # ", Config::PROJECT_TITLE); // CLI-Prompt wiederherstellen + printf("\"\r\n 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); // CLI prompt 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); } } } diff --git a/src/lora.hpp b/src/lora.hpp index 9437950..2fdeada 100644 --- a/src/lora.hpp +++ b/src/lora.hpp @@ -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 @@ -15,19 +15,26 @@ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" +#include "lora_types.hpp" + +/** + * @brief Controller class orchestrating hardware configurations, data transfers, and metrics collection. + */ class LoraController { public: + /** - * @brief Structure defining the localized packet buffer space. + * @brief Simple internal abstraction layer mirroring the raw incoming packet payload. */ 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 + float rssi; /**< High-precision calibrated received signal power in dBm. */ + float snr; /**< High-precision calibrated signal-to-noise ratio in dB. */ + uint8_t length; /**< Payload size explicitly specified in bytes. */ + uint8_t payload[256]; /**< Fixed internal memory buffer capturing raw ingress bytes. Max SX1276 FIFO is 256. */ }; + explicit LoraController(); virtual ~LoraController(); @@ -55,7 +62,13 @@ public: * @param mid Middle Significant Byte of the frequency fraction. * @param lsb Least Significant Byte of the frequency fraction. */ - void setFrequency(uint8_t msb, uint8_t mid, uint8_t lsb); + // void setFrequency(uint8_t msb, uint8_t mid, uint8_t lsb); + + /** + * @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 Dynamically updates the LoRa signal bandwidth at runtime. @@ -64,11 +77,15 @@ public: void setBandwidth(uint32_t bw_khz); private: + static const char* TAG; + spi_device_handle_t m_spiHandle = nullptr; TaskHandle_t m_taskHandle = nullptr; SemaphoreHandle_t m_isrSemaphore = nullptr; - static const char* TAG; + LoraStateTable m_state; ///< Internal volatile tracking record data context. + SemaphoreHandle_t m_stateMutex = nullptr; ///< Mutex primitive enforcing mutual exclusion over m_state modifications. + static void taskTrampoline(void* pvParameters); static void IRAM_ATTR gpioIsrHandler(void* arg); @@ -79,4 +96,18 @@ private: esp_err_t initializeHardware(); void setModeReceiver(); + + void applyModemConfig(); + + /** + * @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, float& outRssi, float& outSnr); + }; diff --git a/src/lora_types.hpp b/src/lora_types.hpp new file mode 100644 index 0000000..75ea56b --- /dev/null +++ b/src/lora_types.hpp @@ -0,0 +1,28 @@ +/** + * @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. + */ + +/** + * @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 { +// std::list packetList; ///< Central database component built on a doubly-linked list for versatile node operations. (Contains pointers) + size_t maxHistorySize = 10; ///< Soft-limit threshold capping the maximum number of history records stored before auto-purge. (4 or 8 Bytes) + uint32_t frequencyHz = 433175000; ///< RF carrier operating frequency configured in Hertz. (4 Bytes) + uint32_t totalPacketsReceived = 0; ///< Accumulator metric track-counting successful ingress frames. (4 Bytes) + uint32_t totalPacketsTransmitted = 0; ///< Accumulator metric track-counting egress frames transmitted. (4 Bytes) + uint32_t nextDatabaseId = 1; ///< Identity sequencer pointer controlling the next node's databaseId field. (4 Bytes) + uint32_t bandwidthKhz = 250; ///< Modem bandwidth performance target configured in kHz. (4 Bytes) + uint8_t spreadingFactor = 11; ///< LoRa Spreading Factor configuration (typically ranges between 6 and 12). (1 Byte) + uint8_t codingRate = 6; ///< Coding Rate denominator setting (e.g., value 6 yields a 4/6 coding protection overhead). (1 Byte) + bool isInitialized = false; ///< True if hardware was successfully validated and attached during initialization. (1 Byte) + + // The three 1-byte variables at the bottom align perfectly into a 32-bit slot, leaving minimal trailing padding. +};