Add a static packetCounter inside LoRa receiver and display in hex , refactoring LoRa driver
Dieser Commit ist enthalten in:
1 Ursprung
157c87fe89
Commit
fb542f9661
4 Dateien geändert
+568
-205
Keine Dateien entsprechen Ihrer Suche
+317
-161
@@ -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
@@ -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();
|
||||
};
|
||||
@@ -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.
|
||||
};
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren