daily update, add lora files
Dieser Commit ist enthalten in:
1 Ursprung
0ab736b338
Commit
19345c3b4b
8 Dateien geändert
+408
-76
Keine Dateien entsprechen Ihrer Suche
@@ -1,46 +0,0 @@
|
|||||||
Processing ttgo-lora32-v21 (platform: espressif32; board: ttgo-lora32-v21; framework: espidf)
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
Verbose mode can be enabled via `-v, --verbose` option
|
|
||||||
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/ttgo-lora32-v21.html
|
|
||||||
PLATFORM: Espressif 32 (2026.2.30) > TTGO LoRa32-OLED v2.1.6
|
|
||||||
HARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash
|
|
||||||
DEBUG: Current (cmsis-dap) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
|
||||||
PACKAGES:
|
|
||||||
- contrib-piohome @ 3.4.4
|
|
||||||
- framework-espidf @ 3.50304.0 (5.3.4)
|
|
||||||
- tool-cmake @ 4.0.3
|
|
||||||
- tool-esp-rom-elfs @ 2024.10.11
|
|
||||||
- tool-esptoolpy @ 5.1.2
|
|
||||||
- tool-ninja @ 1.13.1
|
|
||||||
- tool-scons @ 4.40801.0 (4.8.1)
|
|
||||||
- toolchain-xtensa-esp-elf @ 15.1.0+20250607
|
|
||||||
Reading CMake configuration...
|
|
||||||
Generating assembly for certificate bundle...
|
|
||||||
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
|
||||||
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
|
||||||
Found 0 compatible libraries
|
|
||||||
Scanning dependencies...
|
|
||||||
No dependencies
|
|
||||||
Building in release mode
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/src/cli.cpp.o' from cache
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/src/display.cpp.o' from cache
|
|
||||||
Compiling .pio/build/ttgo-lora32-v21/src/display1.cpp.o
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/src/i2c.cpp.o' from cache
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/src/led.cpp.o' from cache
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/src/main.cpp.o' from cache
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/esp-idf/esp_system/ld/memory.ld.in' from cache
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/esp-idf/esp_system/ld/sections.ld.in' from cache
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/memory.ld' from cache
|
|
||||||
src/display1.cpp:120:5: error: expected '}' before '{' token
|
|
||||||
120 | {0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä
|
|
||||||
| ^
|
|
||||||
src/display1.cpp:23:38: note: to match this '{'
|
|
||||||
23 | static const uint8_t font_5x7[][5] = {
|
|
||||||
| ^
|
|
||||||
src/display1.cpp:120:5: note: probably missing a comma or an operator before
|
|
||||||
120 | {0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä
|
|
||||||
| ^
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/app_trace/app_trace.c.o' from cache
|
|
||||||
Retrieved `.pio/build/ttgo-lora32-v21/app_trace/app_trace_util.c.o' from cache
|
|
||||||
*** [.pio/build/ttgo-lora32-v21/src/display1.cpp.o] Error 1
|
|
||||||
========================= [FAILED] Took 10.67 seconds =========================
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# LoRa Mesh & Device Settings (Austria / EU)
|
||||||
|
|
||||||
|
This document provides a comprehensive overview of the **MeshCom** parameters, frequency structures, and alternative LoRa options according to the official Austrian (**ÖVSV**) 70cm band plan.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📻 Part 1: MeshCom Frequency & Required Parameters
|
||||||
|
|
||||||
|
In Austria and the entire D-A-CH region, the amateur radio project **MeshCom** (MeshCom 4.0) operates on the primary standard frequency of **433.175 MHz**.
|
||||||
|
|
||||||
|
> ⚠️ **Requirement:** Since MeshCom runs within the 70cm amateur radio band, a **valid amateur radio license** and a personal **call sign** are strictly required to participate.
|
||||||
|
|
||||||
|
### LoRa & System Parameters
|
||||||
|
To successfully communicate with the Austrian and European mesh network, your LoRa modulation parameters must match the standard exactly. In the current MeshCom firmware, these are set automatically as soon as you configure your country/region code to **EU** (or **EU8**):
|
||||||
|
|
||||||
|
| Parameter | Value | Description / Command |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Frequency** | **433.175 MHz** | Main frequency for data and text messages |
|
||||||
|
| **Bandwidth (BW)** | **250 kHz** | Signal width in the spectrum |
|
||||||
|
| **Spreading Factor (SF)** | **11** | Defines the chip-to-symbol rate |
|
||||||
|
| **Coding Rate (CR)** | **4/6** | Error correction rate |
|
||||||
|
| **LoRa Profile** | **MediumSlow** | The standardized transmission profile |
|
||||||
|
| **Track Frequency** | **433.775 MHz** | Optional secondary frequency for pure position data (LoRa-APRS) |
|
||||||
|
|
||||||
|
> 🔍 **Important Note on SF11 vs. SF12:**
|
||||||
|
> * **SF11 (BW 250 kHz)** is the hardcoded standard for the primary MeshCom text/data channel (433.175 MHz). It uses the optimized "MediumSlow" profile to ensure a balanced time-on-air.
|
||||||
|
> * **SF12 (BW 125 kHz)** is *only* utilized on the separate **Tracker Frequency (433.775 MHz)** for LoRa-APRS position telemetry packets. Manually forcing the main channel to SF12 will break compatibility with the primary MeshCom grid and regional gateways.
|
||||||
|
|
||||||
|
### Device Configuration
|
||||||
|
When setting up your node for the first time (via the MeshCom Web Installer or the smartphone app), you must manually configure the following parameters:
|
||||||
|
|
||||||
|
* **Call Sign (`--setcall`):** Your unique amateur radio call sign combined with an APRS-compliant SSID (e.g., `OE3XXX-9` for mobile nodes or `OE1XXX-12` for internet gateways).
|
||||||
|
* **Country Code (`--setctry`):** Must be set to **EU** to automatically apply the correct LoRa profile.
|
||||||
|
* **Fixed Coordinates (`--setlat`, `--setlon`, `--setalt`):** If your node lacks a GPS module or functions as a static home gateway, you must enter its latitude, longitude, and altitude manually so it displays correctly on the official ÖVSV MeshCom map.
|
||||||
|
|
||||||
|
> 🌐 **For Internet Gateways:** You will additionally need to provide your local Wi-Fi credentials via `--setssid [Name]` / `--setpwd [Password]` and enable the gateway routing using `--gateway on` and `--setinet`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Part 2: Alternative Frequencies in the 433 MHz Band
|
||||||
|
|
||||||
|
According to the official **70cm band plan of the ÖVSV** (Austrian Experimental Transmitter Association), there are alternative options within the 433 MHz range. However, a strict distinction must be made between **Amateur Radio services** (MeshCom) and **license-free ISM use** (such as Meshtastic).
|
||||||
|
|
||||||
|
### 1. Options Within MeshCom (Amateur Radio)
|
||||||
|
Because a mesh network relies on all nodes sharing the same channel to forward packets, changing your frequency will disconnect you from the main Austrian network and its gateways. However, for isolated, experimental, or local group networks, the band plan offers structural alternatives:
|
||||||
|
|
||||||
|
* **The "All Modes" Segment (434.000 – 434.9875 MHz):** This range is designated for all types of transmission modes. If local clubs or emergency groups want to set up an independent, autarkic LoRa network separate from the primary MeshCom grid, frequencies within this block are typically selected.
|
||||||
|
* **The Standard Tracker Frequency (433.775 MHz):** As noted above, this secondary frequency is already integrated into the standard for offloading pure positioning data via LoRa-APRS.
|
||||||
|
|
||||||
|
### 2. The License-Free Alternative (ISM Band)
|
||||||
|
The frequency block from **433.050 to 434.790 MHz** is shared globally as an **ISM** (Industrial, Scientific, and Medical) band.
|
||||||
|
|
||||||
|
* **No License Required:** Anyone can transmit here without a call sign or an amateur radio license.
|
||||||
|
* **Meshtastic instead of MeshCom:** Unlicensed users looking for mesh networking in this band typically use the **Meshtastic** protocol instead of MeshCom.
|
||||||
|
* **Strict Regulatory Constraints:** Under ISM rules, maximum transmission power is legally limited to a tiny **10 mW ERP**, and strict duty-cycle limits apply. Licensed amateur radio operators using MeshCom are allowed significantly higher output power (typically 100–500 mW native hardware output on mobile nodes).
|
||||||
|
|
||||||
|
### 3. Moving Out of the 433 MHz Noise (The 868 MHz Option)
|
||||||
|
The 433 MHz ISM band in Europe is heavily congested by consumer devices like car key fobs, weather stations, and garage door openers. If you want to escape this interference completely, the European standard is to switch to the **868 MHz band**.
|
||||||
|
|
||||||
|
* **Note:** MeshCom itself is historically tied to 433 MHz in Austria. Shifting to 868 MHz effectively means migrating away from MeshCom infrastructure into the license-free **Meshtastic** ecosystem, which features a massive, highly dense node network across Austria on 868 MHz.
|
||||||
+6
-4
@@ -155,7 +155,7 @@ void process_command(const char* cmd) {
|
|||||||
if (sscanf(cmd + 5, "%lu %63[^\r\n]", &targetLine, textBuffer) == 2) {
|
if (sscanf(cmd + 5, "%lu %63[^\r\n]", &targetLine, textBuffer) == 2) {
|
||||||
if (targetLine <= 7) {
|
if (targetLine <= 7) {
|
||||||
if (oled_display != nullptr) {
|
if (oled_display != nullptr) {
|
||||||
oled_display->printMessage(textBuffer, static_cast<uint8_t>(targetLine));
|
oled_display->printMessage(static_cast<uint8_t>(targetLine), textBuffer);
|
||||||
printf("Success: Dispatched text '%s' to OLED line %lu.\r\n", textBuffer, (unsigned long)targetLine);
|
printf("Success: Dispatched text '%s' to OLED line %lu.\r\n", textBuffer, (unsigned long)targetLine);
|
||||||
} else {
|
} else {
|
||||||
printf("Error: DisplayController handle is uninitialized.\r\n");
|
printf("Error: DisplayController handle is uninitialized.\r\n");
|
||||||
@@ -237,11 +237,13 @@ static void cli_worker_task(void *pvParameters) {
|
|||||||
printf("\b \b");
|
printf("\b \b");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
// Sanitize and append plain terminal symbols into processing context
|
// Sanitize and append terminal symbols (including UTF-8 multi-byte characters like German Umlaute)
|
||||||
else if (data >= 32 && data <= 126 && buffer_index < (BUF_SIZE - 1)) {
|
else if (((data >= 32 && data <= 126) || data >= 128) && buffer_index < (BUF_SIZE - 1)) {
|
||||||
line_buffer[buffer_index++] = static_cast<char>(data);
|
line_buffer[buffer_index++] = static_cast<char>(data);
|
||||||
printf("%c", data);
|
printf("%c", data);
|
||||||
fflush(stdout);
|
//fflush(stdout);
|
||||||
|
// direct write into uart buffer, used for utf-8
|
||||||
|
//uart_write_bytes(CLI_UART_PORT, &data, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Structural task yields preventing internal stack watchdog violations
|
// Structural task yields preventing internal stack watchdog violations
|
||||||
|
|||||||
+31
-18
@@ -112,18 +112,30 @@ static const uint8_t font_5x7[][5] = {
|
|||||||
{0x44, 0x28, 0x10, 0x28, 0x44}, // 120 x
|
{0x44, 0x28, 0x10, 0x28, 0x44}, // 120 x
|
||||||
{0x0c, 0x50, 0x50, 0x50, 0x3c}, // 121 y
|
{0x0c, 0x50, 0x50, 0x50, 0x3c}, // 121 y
|
||||||
{0x44, 0x64, 0x54, 0x4c, 0x44}, // 122 z
|
{0x44, 0x64, 0x54, 0x4c, 0x44}, // 122 z
|
||||||
{0x00, 0x08, 0x36, 0x41, 0x00}, // 123 \{
|
{0x00, 0x08, 0x36, 0x41, 0x00}, // 123 {
|
||||||
{0x00, 0x00, 0x77, 0x00, 0x00}, // 124 |
|
{0x00, 0x00, 0x77, 0x00, 0x00}, // 124 |
|
||||||
{0x00, 0x41, 0x36, 0x08, 0x00}, // 125 \}
|
{0x00, 0x41, 0x36, 0x08, 0x00}, // 125 }
|
||||||
{0x08, 0x08, 0x2a, 0x1c, 0x08}, // 126 ~
|
{0x08, 0x08, 0x2a, 0x1c, 0x08}, // 126 ~
|
||||||
// German Umlaute mappings
|
// German Umlaute mappings
|
||||||
{0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä
|
// {0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä
|
||||||
{0x3c, 0x41, 0x41, 0x41, 0x3c}, // 128 Ö
|
// {0x3c, 0x41, 0x41, 0x41, 0x3c}, // 128 Ö
|
||||||
{0x3d, 0x40, 0x40, 0x40, 0x3d}, // 129 Ü
|
// {0x3d, 0x40, 0x40, 0x40, 0x3d}, // 129 Ü
|
||||||
{0x20, 0x55, 0x54, 0x54, 0x78}, // 130 ä
|
// {0x20, 0x55, 0x54, 0x54, 0x78}, // 130 ä
|
||||||
{0x38, 0x54, 0x54, 0x54, 0x38}, // 131 ö
|
// {0x38, 0x54, 0x54, 0x54, 0x38}, // 131 ö
|
||||||
{0x3c, 0x40, 0x40, 0x20, 0x7c}, // 132 ü
|
// {0x3c, 0x40, 0x40, 0x20, 0x7c}, // 132 ü
|
||||||
{0x7f, 0x49, 0x49, 0x36, 0x20} // 133 ß
|
// {0x7f, 0x49, 0x49, 0x36, 0x20} // 133 ß
|
||||||
|
// new German Umlaute mapping (5x7/5x8 Font Matrix)
|
||||||
|
{0,0,0,0,0}, //127 not allowed
|
||||||
|
{0x7d, 0x13, 0x13, 0x13, 0x7d}, // 128 Ä
|
||||||
|
{0x3c, 0x43, 0x43, 0x43, 0x3c}, // 129 Ö
|
||||||
|
{0x3d, 0x42, 0x42, 0x42, 0x3d}, // 130 Ü
|
||||||
|
{0x20, 0x55, 0x54, 0x54, 0x78}, // 131 ä
|
||||||
|
{0x38, 0x55, 0x55, 0x55, 0x38}, // 132 ö
|
||||||
|
{0x3c, 0x42, 0x42, 0x22, 0x7c}, // 133 ü
|
||||||
|
{0x7f, 0x49, 0x49, 0x36, 0x00}, // 134 ß (Letzte Spalte auf 0x00 bereinigt)
|
||||||
|
// {0x3c, 0x42, 0x51, 0x4a, 0x34} // 134 © Copyright
|
||||||
|
// {0x3c, 0x42, 0x4d, 0x4d, 0x32} // 135
|
||||||
|
{0x3c, 0x42, 0x95, 0x95, 0x52}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -255,7 +267,7 @@ void DisplayController::startTask(uint32_t priority) {
|
|||||||
* @param text Pointer to a standard null-terminated sequence of tracking information.
|
* @param text Pointer to a standard null-terminated sequence of tracking information.
|
||||||
* @param line Selected hardware page index mapping from 0 to 7.
|
* @param line Selected hardware page index mapping from 0 to 7.
|
||||||
*/
|
*/
|
||||||
void DisplayController::printMessage(const char* text, uint8_t line) {
|
void DisplayController::printMessage(uint8_t line, const char* text) {
|
||||||
if (m_msgQueue == nullptr || text == nullptr) return;
|
if (m_msgQueue == nullptr || text == nullptr) return;
|
||||||
|
|
||||||
DisplayMessage msg = {};
|
DisplayMessage msg = {};
|
||||||
@@ -321,19 +333,20 @@ void DisplayController::run() {
|
|||||||
uint8_t c_secondary = static_cast<uint8_t>(incoming.content[i + 1]);
|
uint8_t c_secondary = static_cast<uint8_t>(incoming.content[i + 1]);
|
||||||
i++; // Advance the pointer to consume the compound UTF-8 token pair
|
i++; // Advance the pointer to consume the compound UTF-8 token pair
|
||||||
|
|
||||||
if (c_secondary == 0x84) { fontIndex = 95; validCharacter = true; } // Ä
|
if (c_secondary == 0x84) { fontIndex = 96; validCharacter = true; } // Ä
|
||||||
if (c_secondary == 0x96) { fontIndex = 96; validCharacter = true; } // Ö
|
if (c_secondary == 0x96) { fontIndex = 97; validCharacter = true; } // Ö
|
||||||
if (c_secondary == 0x9C) { fontIndex = 97; validCharacter = true; } // Ü
|
if (c_secondary == 0x9C) { fontIndex = 98; validCharacter = true; } // Ü
|
||||||
if (c_secondary == 0xA4) { fontIndex = 98; validCharacter = true; } // ä
|
if (c_secondary == 0xA4) { fontIndex = 99; validCharacter = true; } // ä
|
||||||
if (c_secondary == 0xB6) { fontIndex = 99; validCharacter = true; } // ö
|
if (c_secondary == 0xB6) { fontIndex = 100; validCharacter = true; } // ö
|
||||||
if (c_secondary == 0xBC) { fontIndex = 100; validCharacter = true; } // ü
|
if (c_secondary == 0xBC) { fontIndex = 101; validCharacter = true; } // ü
|
||||||
}
|
}
|
||||||
else if (c_primary == 0xC2 && (i + 1) < textLength) {
|
else if (c_primary == 0xC2 && (i + 1) < textLength) {
|
||||||
uint8_t c_secondary = static_cast<uint8_t>(incoming.content[i + 1]);
|
uint8_t c_secondary = static_cast<uint8_t>(incoming.content[i + 1]);
|
||||||
i++;
|
i++;
|
||||||
if (c_secondary == 0x9F) { fontIndex = 101; validCharacter = true; } // ß
|
if (c_secondary == 0x9F) { fontIndex = 102; validCharacter = true; } // ß
|
||||||
|
if (c_secondary == 0xA9) { fontIndex = 103; validCharacter = true; } // ©
|
||||||
}
|
}
|
||||||
else if (c_primary >= 32 && c_primary <= 126) {
|
else if ((c_primary >= 32 && c_primary <= 126) || (c_primary > 127 && c_primary <= 135)) {
|
||||||
fontIndex = static_cast<size_t>(c_primary - 32);
|
fontIndex = static_cast<size_t>(c_primary - 32);
|
||||||
validCharacter = true;
|
validCharacter = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Thread-safe non-blocking method to print a message to the OLED display.
|
* @brief Thread-safe non-blocking method to print a message to the OLED display.
|
||||||
* @param text Null-terminated string message (maximum 20 characters for simple lines).
|
|
||||||
* @param line Destination line identifier index (0 to 7 for page addressing mode).
|
* @param line Destination line identifier index (0 to 7 for page addressing mode).
|
||||||
|
* @param text Null-terminated string message (maximum 20 characters for simple lines).
|
||||||
*/
|
*/
|
||||||
void printMessage(const char* text, uint8_t line);
|
void printMessage(uint8_t line, const char* text);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Internal structure mapping the safe asynchronous data pipeline
|
// Internal structure mapping the safe asynchronous data pipeline
|
||||||
struct DisplayMessage {
|
struct DisplayMessage {
|
||||||
char content[24];
|
|
||||||
uint8_t targetLine;
|
uint8_t targetLine;
|
||||||
|
char content[24];
|
||||||
};
|
};
|
||||||
|
|
||||||
i2c* m_bus;
|
i2c* m_bus;
|
||||||
|
|||||||
@@ -0,0 +1,210 @@
|
|||||||
|
/**
|
||||||
|
* @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";
|
||||||
|
|
||||||
|
LoraController::LoraController() {
|
||||||
|
m_isrSemaphore = xSemaphoreCreateBinary();
|
||||||
|
if (m_isrSemaphore == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Fatal: Failed to allocate LoRa ISR synchronization semaphore.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoraController::~LoraController() {
|
||||||
|
if (m_spiHandle != nullptr) {
|
||||||
|
spi_bus_remove_device(m_spiHandle);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return rxData[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraController::setModeReceiver() {
|
||||||
|
writeRegister(0x01, 0x80U); // Sleep + LoRa Mode
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
|
||||||
|
// Hardwired for 868.1 MHz fallback channel
|
||||||
|
writeRegister(0x06, 0xD9U);
|
||||||
|
writeRegister(0x07, 0x06U);
|
||||||
|
writeRegister(0x08, 0x66U);
|
||||||
|
|
||||||
|
writeRegister(0x40, 0x00U); // DIO0 map onto RxDone
|
||||||
|
writeRegister(0x01, 0x85U); // RXCONTINUOUS
|
||||||
|
ESP_LOGI(TAG, "Radio base configuration applied. Listening on 868.1 MHz.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraController::run() {
|
||||||
|
if (initializeHardware() != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Critical: LoRa init failed. Thread aborted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setModeReceiver();
|
||||||
|
extern DisplayController* display; // Reference the global display singleton
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (xSemaphoreTake(m_isrSemaphore, portMAX_DELAY) == pdTRUE) {
|
||||||
|
uint32_t irqFlags = readRegister(0x12);
|
||||||
|
writeRegister(0x12, static_cast<uint8_t>(irqFlags));
|
||||||
|
|
||||||
|
if ((irqFlags & 0x40U) != 0U) { // RxDone
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.rssi = static_cast<int8_t>(static_cast<int>(readRegister(0x1A)) - 157);
|
||||||
|
packet.snr = static_cast<int8_t>(static_cast<int>(readRegister(0x1B)) / 4);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Incoming frame packet received! Size: %u bytes, RSSI: %d dBm, SNR: %d dB",
|
||||||
|
packet.length, packet.rssi, packet.snr);
|
||||||
|
|
||||||
|
// FIXED: Uses the new parameter order (line first, text second)
|
||||||
|
if (display != nullptr) {
|
||||||
|
display->printMessage(1, "LoRa Msg Received");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoraController::startTask(uint32_t priority) {
|
||||||
|
xTaskCreatePinnedToCore(
|
||||||
|
LoraController::taskTrampoline,
|
||||||
|
"LORA_EngineTask",
|
||||||
|
4096,
|
||||||
|
this,
|
||||||
|
priority,
|
||||||
|
&m_taskHandle,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* @file lora.hpp
|
||||||
|
* @brief Thread-safe asynchronous Semtech SX1276 LoRa transceiver driver for FoxMesh
|
||||||
|
* @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
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
class LoraController {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Structure defining the localized packet buffer space.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configures peripheral resources and registers the background worker thread on Core 1.
|
||||||
|
* @param priority Scheduler task priority level execution metric.
|
||||||
|
*/
|
||||||
|
void startTask(uint32_t priority = 6);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
esp_err_t transmitFrame(const uint8_t* buffer, uint8_t length);
|
||||||
|
|
||||||
|
private:
|
||||||
|
spi_device_handle_t m_spiHandle = nullptr;
|
||||||
|
TaskHandle_t m_taskHandle = nullptr;
|
||||||
|
SemaphoreHandle_t m_isrSemaphore = nullptr;
|
||||||
|
|
||||||
|
static const char* TAG;
|
||||||
|
|
||||||
|
static void taskTrampoline(void* pvParameters);
|
||||||
|
static void IRAM_ATTR gpioIsrHandler(void* arg);
|
||||||
|
|
||||||
|
void run();
|
||||||
|
uint8_t readRegister(uint8_t address);
|
||||||
|
void writeRegister(uint8_t address, uint8_t value);
|
||||||
|
|
||||||
|
esp_err_t initializeHardware();
|
||||||
|
void setModeReceiver();
|
||||||
|
};
|
||||||
+30
-5
@@ -20,19 +20,31 @@
|
|||||||
#include "i2c.hpp"
|
#include "i2c.hpp"
|
||||||
#include "display.hpp" // FIXED: Added missing header for DisplayController
|
#include "display.hpp" // FIXED: Added missing header for DisplayController
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Global log categorization tag context for the main application loader.
|
||||||
|
*/
|
||||||
static const char *TAG = "MAIN";
|
static const char *TAG = "MAIN";
|
||||||
|
|
||||||
// Global pointer to the thread-safe hardware abstraction instances
|
/**
|
||||||
|
* @brief Global pointer tracking the synchronized thread-safe I2C master bus instance.
|
||||||
|
*/
|
||||||
i2c* i2c_bus = nullptr;
|
i2c* i2c_bus = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Global pointer tracking the asynchronous isolated OLED render controller instance.
|
||||||
|
*/
|
||||||
DisplayController* display = nullptr;
|
DisplayController* display = nullptr;
|
||||||
|
|
||||||
// Global singleton instances for hardware abstraction layers
|
/**
|
||||||
|
* @brief Static singleton instance governing hardware status LED blink patterns.
|
||||||
|
*/
|
||||||
static LedController led(Config::LED_GPIO_PIN);
|
static LedController led(Config::LED_GPIO_PIN);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initializes the physical SPI peripheral for the Semtech SX1276 transceiver.
|
* @brief Initializes the physical SPI peripheral for the Semtech SX1276 transceiver.
|
||||||
*
|
*
|
||||||
* Configures VSPI (SPI3_HOST) using strict compile-time configurations from config.hpp.
|
* Configures the VSPI (SPI3_HOST) back-end utilizing strict compile-time layouts
|
||||||
|
* mapped inside config.hpp and configures automatic DMA channel routing.
|
||||||
*/
|
*/
|
||||||
void init_lora_spi(void) {
|
void init_lora_spi(void) {
|
||||||
esp_err_t ret;
|
esp_err_t ret;
|
||||||
@@ -56,6 +68,12 @@ void init_lora_spi(void) {
|
|||||||
|
|
||||||
// Global application anchor marked as extern "C" for the native ESP-IDF bootloader
|
// Global application anchor marked as extern "C" for the native ESP-IDF bootloader
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
/**
|
||||||
|
* @brief System core entry point executing synchronous initialization loops.
|
||||||
|
*
|
||||||
|
* Spawns the decoupled system worker threads, establishes thread-safe hardware
|
||||||
|
* bus routing layers, and boots up the interactive terminal environment on Core 0.
|
||||||
|
*/
|
||||||
void app_main(void) {
|
void app_main(void) {
|
||||||
// Output system banner configuration parameters on boot
|
// Output system banner configuration parameters on boot
|
||||||
ESP_LOGI(TAG, "=================================================");
|
ESP_LOGI(TAG, "=================================================");
|
||||||
@@ -73,7 +91,9 @@ extern "C" {
|
|||||||
|
|
||||||
// Instantiate and activate the isolated background render worker on Core 0
|
// Instantiate and activate the isolated background render worker on Core 0
|
||||||
display = new DisplayController(i2c_bus);
|
display = new DisplayController(i2c_bus);
|
||||||
|
if (display != nullptr) {
|
||||||
display->startTask();
|
display->startTask();
|
||||||
|
}
|
||||||
|
|
||||||
// Pass the bus instance dependency to the interactive terminal layer
|
// Pass the bus instance dependency to the interactive terminal layer
|
||||||
cli_init(i2c_bus, &led, display);
|
cli_init(i2c_bus, &led, display);
|
||||||
@@ -85,8 +105,13 @@ extern "C" {
|
|||||||
start_cli_task();
|
start_cli_task();
|
||||||
|
|
||||||
// Trigger synchronous welcome splash screen frame
|
// Trigger synchronous welcome splash screen frame
|
||||||
display->printMessage("FoxMesh Ready", 0);
|
if (display != nullptr) {
|
||||||
|
char msg[32];
|
||||||
|
//display->printMessage(0, "FoxMesh " Config::PROJECT_VERSION " \x86" "OE3TKT");
|
||||||
|
snprintf(msg, sizeof(msg), "FoxMesh %s OE3TKT", Config::PROJECT_VERSION);
|
||||||
|
display->printMessage(1, msg);
|
||||||
|
display->printMessage(2, "Bärbel frühstückt ©");
|
||||||
|
}
|
||||||
// Main system watchdog and status monitor loop
|
// Main system watchdog and status monitor loop
|
||||||
while (true) {
|
while (true) {
|
||||||
ESP_LOGI(TAG, "[%s v%s] Operational. Free Heap: %lu bytes",
|
ESP_LOGI(TAG, "[%s v%s] Operational. Free Heap: %lu bytes",
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren