Feature/add sx1276 support #2
Keine Dateien entsprechen Ihrer Suche
+1
-10
@@ -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<double>(target_freq_hz) * 524288.0) / 32000000.0;
|
||||
uint32_t frf = static_cast<uint32_t>(fraction);
|
||||
|
||||
uint8_t msb = static_cast<uint8_t>((frf >> 16) & 0xFFU);
|
||||
uint8_t mid = static_cast<uint8_t>((frf >> 8) & 0xFFU);
|
||||
uint8_t lsb = static_cast<uint8_t>(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");
|
||||
|
||||
+145
-23
@@ -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<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() {
|
||||
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<uint8_t>(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<int8_t>(static_cast<int>(readRegister(0x1A)) - 157);
|
||||
packet.snr = static_cast<int8_t>(static_cast<int>(readRegister(0x1B)) / 4);
|
||||
// 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);
|
||||
|
||||
// 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<char>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-8
@@ -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);
|
||||
|
||||
};
|
||||
@@ -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<LoraPacketNode> 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.
|
||||
};
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren