554 Zeilen
22 KiB
C++
554 Zeilen
22 KiB
C++
/**
|
|
* @file lora.cpp
|
|
* @brief Thread-safe asynchronous Semtech SX1276 LoRa transceiver driver implementation
|
|
* @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.
|
|
*/
|
|
|
|
#include "lora.hpp"
|
|
#include "esp_log.h"
|
|
#include "config.hpp"
|
|
#include "display.hpp"
|
|
#include <cstring>
|
|
|
|
const char* LoraController::TAG = "LORA";
|
|
|
|
/**
|
|
* @brief Constructor initializing core FreeRTOS synchronization primitives.
|
|
*/
|
|
LoraController::LoraController() {
|
|
m_isrSemaphore = xSemaphoreCreateBinary();
|
|
m_stateMutex = xSemaphoreCreateMutex();
|
|
|
|
if (m_isrSemaphore == nullptr || m_stateMutex == nullptr) {
|
|
ESP_LOGE(TAG, "Fatal: Failed to allocate LoRa synchronization primitives.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
LoraController::~LoraController() {
|
|
if (m_spiHandle != nullptr) {
|
|
spi_bus_remove_device(m_spiHandle);
|
|
}
|
|
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;
|
|
|
|
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;
|
|
}
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
// Suppress the ISO C++ braced-groups macro expansion warning for the FreeRTOS portYIELD macro
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wpedantic"
|
|
void IRAM_ATTR LoraController::gpioIsrHandler(void* arg) {
|
|
auto* semaphore = static_cast<SemaphoreHandle_t>(arg);
|
|
if (semaphore != nullptr) {
|
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|
xSemaphoreGiveFromISR(semaphore, &xHigherPriorityTaskWoken);
|
|
if (xHigherPriorityTaskWoken == pdTRUE) {
|
|
portYIELD_FROM_ISR();
|
|
}
|
|
}
|
|
}
|
|
#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;
|
|
}
|
|
|
|
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");
|
|
|
|
// Invoke the modular modem configuration
|
|
applyModemConfig();
|
|
|
|
err = gpio_install_isr_service(0);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
return err;
|
|
}
|
|
|
|
// Configure interrupt triggering on the rising edge (PosEdge)
|
|
err = gpio_set_intr_type(Config::LORA_IRQ_PIN, GPIO_INTR_POSEDGE);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to set GPIO interrupt type: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
// Bind the static ISR handler to the physical pin context
|
|
err = gpio_isr_handler_add(Config::LORA_IRQ_PIN, LoraController::gpioIsrHandler, m_isrSemaphore);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to register GPIO ISR handler: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
|
|
// MARK STATE MATRIX READY (Thread-Safe Status Update)
|
|
// We use a safe timeout here. If the mutex is permanently blocked during boot,
|
|
// we must escalate this as a system fault instead of returning ESP_OK.
|
|
if (xSemaphoreTake(m_stateMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
|
|
m_state.isInitialized = true;
|
|
m_state.frequencyHz = Config::LORA_DEFAULT_FREQ_HZ;
|
|
xSemaphoreGive(m_stateMutex);
|
|
} else {
|
|
ESP_LOGE(TAG, "Fatal: State mutex acquisition timed out during hardware registration.");
|
|
return ESP_ERR_TIMEOUT; // Escalate the mutex block as a hard boot failure
|
|
}
|
|
|
|
ESP_LOGI(TAG, "SX1276 LoRa transceiver attached successfully via SPI (Version: 0x%02X).", version);
|
|
return ESP_OK;
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
|
|
#ifndef MATH_COMPARE
|
|
#define MATH_COMPARE 0
|
|
#endif
|
|
|
|
|
|
void LoraController::setFrequency(uint32_t freq_hz) {
|
|
if (m_spiHandle == nullptr) return;
|
|
|
|
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
|
|
m_state.frequencyHz = freq_hz;
|
|
|
|
// 1. Fetch current mode and shift transceiver into Standby (0x01)
|
|
// Changing RF carrier registers is strictly prohibited while actively radiating or sampling
|
|
const uint8_t previousMode = readRegister(0x01); // RegOpMode
|
|
writeRegister(0x01, static_cast<uint8_t>((previousMode & 0xF8U) | 0x01U));
|
|
vTaskDelay(pdMS_TO_TICKS(2));
|
|
|
|
// 2. High-speed fractional frequency calculation
|
|
// Formula optimized from: (freq_hz * 2^19) / 32,000,000 b/c 32,000,000 can be seen as 2^11 * 5^6:
|
|
// we see freq_hz * 2^(19-11)/5^6 which is: freq_hz * 2^8 / 15625, i.e.
|
|
// reduced to exact fraction: 256 / 15,625
|
|
// or even more using shift operation, we can switch on a compare of all three methods with #MATH_COMPARE 1
|
|
|
|
#if (MATH_COMPARE == 1)
|
|
// =================================================================
|
|
// BENCHMARK MODE (Runs all 3 variants and measures CPU cycles)
|
|
// =================================================================
|
|
uint32_t start_cycles = esp_cpu_get_cycle_count();
|
|
[[maybe_unused]] volatile uint32_t frf_long = static_cast<uint32_t>((static_cast<uint64_t>(freq_hz) << 19) / 32000000ULL);
|
|
uint32_t cycles_long = esp_cpu_get_cycle_count() - start_cycles;
|
|
|
|
start_cycles = esp_cpu_get_cycle_count();
|
|
[[maybe_unused]] volatile uint32_t frf_64bit = static_cast<uint32_t>((static_cast<uint64_t>(freq_hz) * 256ULL) / 15625ULL);
|
|
uint32_t cycles_64bit = esp_cpu_get_cycle_count() - start_cycles;
|
|
|
|
start_cycles = esp_cpu_get_cycle_count();
|
|
volatile uint32_t frf_shift = static_cast<uint32_t>((static_cast<uint64_t>(freq_hz) * 1125899907ULL) >> 36);
|
|
uint32_t cycles_shift = esp_cpu_get_cycle_count() - start_cycles;
|
|
|
|
printf("[MATH_COMPARE] Freq: %lu Hz -> long 64bit: %lu cycles | 64bit: %lu cycles | Shift: %lu cycles\n",
|
|
freq_hz, cycles_long, cycles_64bit, cycles_shift);
|
|
|
|
const uint32_t frf = frf_shift; // Fallback to the safe bit-shift variant
|
|
#else
|
|
// =================================================================
|
|
// PRODUCTION MODE (Highly optimized, zero benchmark overhead)
|
|
// =================================================================
|
|
const uint32_t frf = static_cast<uint32_t>((static_cast<uint64_t>(freq_hz) * 1125899907ULL) >> 36);
|
|
#endif
|
|
|
|
|
|
// 3. Shift the 24-bit frequency tracking token across the three consecutive hardware registers
|
|
writeRegister(0x06, static_cast<uint8_t>((frf >> 16) & 0xFFU)); // RegFrfMsb
|
|
writeRegister(0x07, static_cast<uint8_t>((frf >> 8) & 0xFFU)); // RegFrfMid
|
|
writeRegister(0x08, static_cast<uint8_t>(frf & 0xFFU)); // RegFrfLsb
|
|
|
|
// 4. Restore previous operational transceiver path configuration
|
|
writeRegister(0x01, previousMode);
|
|
xSemaphoreGive(m_stateMutex);
|
|
|
|
// 5. Force state synchronization and PLL locking verification
|
|
setModeReceiver();
|
|
|
|
// High-priority diagnosis logging bypasses CLI lock buffers uncensored
|
|
printf("\r\n[LoRa HW] Frequency successfully shifted to %lu Hz (FRF Word: 0x%06X)\r\n",
|
|
(unsigned long)freq_hz, (unsigned int)(frf & 0xFFFFFFU));
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
writeRegister(0x01, 0x81U); // STDBY Mode
|
|
writeRegister(0x0D, 0x00U); // RegFifoTxBaseAddr
|
|
writeRegister(0x09, 0x00U); // RegFifoAddrPtr
|
|
|
|
for (uint8_t i = 0; i < length; i++) {
|
|
writeRegister(0x00, buffer[i]);
|
|
}
|
|
|
|
writeRegister(0x22, length); // RegPayloadLength
|
|
writeRegister(0x01, 0x83U); // RegOpMode: TX Mode Trigger
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
void LoraController::taskTrampoline(void* pvParameters) {
|
|
auto* instance = static_cast<LoraController*>(pvParameters);
|
|
if (instance != nullptr) {
|
|
instance->run();
|
|
}
|
|
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<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);
|
|
}
|
|
}
|
|
|
|
void LoraController::run() {
|
|
// Lambda Object:
|
|
auto utf8_advance = [](const std::string& str, size_t start_byte, size_t num_chars) -> size_t {
|
|
size_t i = start_byte;
|
|
size_t chars_counted = 0;
|
|
while (i < str.length() && chars_counted < num_chars) {
|
|
if (((unsigned char)str[i] & 0xC0) != 0x80) chars_counted++;
|
|
i++;
|
|
}
|
|
while (i < str.length() && ((unsigned char)str[i] & 0xC0) == 0x80) i++;
|
|
return i;
|
|
};
|
|
|
|
if (initializeHardware() != ESP_OK) {
|
|
ESP_LOGE(TAG, "Critical: LoRa init failed. Thread aborted.");
|
|
return;
|
|
}
|
|
|
|
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");
|
|
|
|
while (true) {
|
|
// CHANGE: wait a maximum of 500 ms instead of infinity long
|
|
if (xSemaphoreTake(m_isrSemaphore, pdMS_TO_TICKS(500)) == pdTRUE) {
|
|
// if (xSemaphoreTake(m_isrSemaphore, portMAX_DELAY) == pdTRUE) {
|
|
uint32_t irqFlags = readRegister(0x12);
|
|
writeRegister(0x12, static_cast<uint8_t>(irqFlags));
|
|
|
|
if ((irqFlags & 0x40U) != 0U) { // RxDone Flag
|
|
packetCounter++;
|
|
LoraPacket packet = {};
|
|
packet.length = readRegister(0x13);
|
|
|
|
uint32_t currentFifoAddress = readRegister(0x10);
|
|
writeRegister(0x09, static_cast<uint8_t>(currentFifoAddress));
|
|
|
|
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);
|
|
// 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);
|
|
|
|
// Formatted terminal trace using fixed precision formatting (.1f)
|
|
printf("\r\n--------------------------------------------------\r\n");
|
|
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]);
|
|
// Only print printable characters to avoid confusing the terminal
|
|
if (c >= 32 && c <= 126)
|
|
printf("%c", c);
|
|
else
|
|
printf("~"); // not printable chars
|
|
}
|
|
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) {
|
|
char displayBuffer[85]; // worst case 21 chars with each 4 bytes + Nullbyte.
|
|
|
|
snprintf(displayBuffer, sizeof(displayBuffer), "#%03u %4.1fdBm %2.1fdB",
|
|
(unsigned int)(packetCounter % 1000),
|
|
packet.rssi,
|
|
packet.snr);
|
|
display->printMessage(2, displayBuffer);
|
|
// we don't care about UTF-8 for now
|
|
std::string text(reinterpret_cast<const char*>(packet.payload), static_cast<size_t>(packet.length));
|
|
|
|
size_t byte_pos = 0;
|
|
uint8_t linenr = 3; // start at line 3
|
|
|
|
while ( byte_pos < text.length() && linenr < 8) {
|
|
// The '::' ensures that the global/local function is called.
|
|
size_t next_byte_pos = utf8_advance(text, byte_pos, 21);
|
|
std::string line = text.substr(byte_pos, next_byte_pos - byte_pos);
|
|
|
|
std::strncpy(displayBuffer, line.c_str(), sizeof(displayBuffer ) - 1);
|
|
displayBuffer[sizeof(displayBuffer) - 1] ='\0';
|
|
|
|
display->printMessage(linenr++, displayBuffer);
|
|
byte_pos = next_byte_pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
// New: after work or after 500ms delay, the chip has usually changed its mode, we update the state for the m_state:
|
|
uint8_t currentHardwareMode = readRegister(0x01);
|
|
if (xSemaphoreTake(m_stateMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
|
|
m_state.currentMode = currentHardwareMode;
|
|
xSemaphoreGive(m_stateMutex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LoraController::startTask(uint32_t priority) {
|
|
xTaskCreatePinnedToCore(
|
|
LoraController::taskTrampoline,
|
|
"LORA_EngineTask",
|
|
4096,
|
|
this,
|
|
priority,
|
|
&m_taskHandle,
|
|
1
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @brief Generates an atomic, high-performance runtime snapshot of configuration parameters and metrics.
|
|
*/
|
|
LoraStateSnapshot LoraController::getSnapshot() {
|
|
LoraStateSnapshot snapshot = {};
|
|
|
|
// Sichere die thread-übergreifende Konsistenz mittels Mutex ab
|
|
if (xSemaphoreTake(m_stateMutex, portMAX_DELAY) == pdTRUE) {
|
|
|
|
// Kopiere die primitiven Werte aus dem internen Zustandstabellen-Struct (m_state)
|
|
snapshot.frequencyHz = m_state.frequencyHz;
|
|
snapshot.bandwidthKhz = m_state.bandwidthKhz;
|
|
snapshot.totalPacketsReceived = m_state.totalPacketsReceived;
|
|
snapshot.totalPacketsTransmitted = m_state.totalPacketsTransmitted;
|
|
snapshot.spreadingFactor = m_state.spreadingFactor;
|
|
snapshot.codingRate = m_state.codingRate;
|
|
snapshot.isInitialized = m_state.isInitialized;
|
|
|
|
// Zähle die Elemente in der Liste, ohne die schweren Payload-Vektoren zu kopieren
|
|
// TODO install the package list:
|
|
// snapshot.currentHistoryCount = m_state.packetList.size();
|
|
|
|
// Read Register 0x01 ONLY ONCE to avoid SPI race conditions
|
|
// uint8_t rohofmode = readRegister(0x01);
|
|
// uint8_t rawMode = rohofmode & 0x07U;
|
|
// use the cached mode from RAM instead live register read
|
|
uint8_t rawMode = m_state.currentMode & 0x07U;
|
|
|
|
// Print the real, unmanipulated hex value safely
|
|
//printf("Readregister actual raw hex: 0x%02X\r\n", rohofmode);
|
|
//fflush(stdout);
|
|
|
|
static const char* const MODE_STRINGS[] = {
|
|
"SLEEP", // 0x00
|
|
"STANDBY", // 0x01
|
|
"FSTX", // 0x02
|
|
"TX", // 0x03
|
|
"FSRX", // 0x04
|
|
"RXCONTINUOUS", // 0x05
|
|
"RXSINGLE", // 0x06
|
|
"CAD" // 0x07
|
|
};
|
|
|
|
strncpy(snapshot.currentModeStr, MODE_STRINGS[rawMode], sizeof(snapshot.currentModeStr) - 1);
|
|
snapshot.currentModeStr[sizeof(snapshot.currentModeStr) - 1] = '\0'; // guarantee NULL termination
|
|
|
|
// Release mutex immediately
|
|
xSemaphoreGive(m_stateMutex);
|
|
}
|
|
|
|
return snapshot;
|
|
}
|