initialize a multitasking framework

Dieser Commit ist enthalten in:
tom committed 2026-10-16 02:02:35 +02:00
1 Ursprung ffdfa486ff
Commit 198074e289
18 Dateien geändert
+2843 -54

Keine Dateien entsprechen Ihrer Suche

+7 -54
Datei anzeigen
@@ -1,55 +1,8 @@
# Prerequisites
*.d
# PlatformIO
.pio
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# debug information files
*.dwo
# VSCodium / VS Code
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
+3
Datei anzeigen
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.16.0)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(FoxMesh)
+78
Datei anzeigen
@@ -0,0 +1,78 @@
# Developer´s Installation with PlatformIO
## Installation of VSCodium (Visual Studio Code)
I prefer the installation of VSCodium, it is an excellent, privacy-focused alternative to Microsoft Visual Studio (VS Code) as it completely eliminatess telemtry, tracking, and proprietary Microsoft licenses.
However, setting up VSCodium with PlatformIO introduces a major technical hurdle:
Microsoft explicitly restricts the use of its official C/C++ extension (ms-vscode.cpptools) to official Microsoft product distributions.
Because PlatformIO relies heavily on this extension for code completion, debugging, and naviagation, you cannot simply download everything directly from VSCodium's default extension marketplace.
The following guide walks you through the proven workflows to successfully install and run PlatformIO on VSCodium.
## Method 1: Manual Extension Installation (Recommended for Privacy)
This method keeps your VSCodium completely decoupled from Microsoft's marketplace servers by manually sideloading the extension files (.vsix).
### 1. Check System Prerequisites
Ensure **Python 3** and **Git** are installed on your system.
PlatformIO uses them under the hood to manage libraries and compile code.
* Linux Users: You must also install the `python3-venv` packatge via your package manager (e.g. `sudo apt install python3-venv`).
On **Arch Linux** the core `python` package (installed via `sudo pacman -S python`) **already includes the standard libryre `venv` module by default**.
### 2. Download the Required Extensions
Because of license restrictions, the required versions are not available on VSCodium's default *Open-VSX* registry.
You need to grap them from the official Visual Studio Marketplace:
1. Go to the Microsoft C/C++ Extension Page.
On the right-hand side under *Resources*, click **Download Extensions** to save the `.vsix` file.
2. Go to the PlatformIO IDE Extension Page and download its `.vsix` file as well.
### 3. Sideload into VSCodium
1. Open VSCodium.
2. Click on the **Extension** icon in the left Activity Bar (or press `Ctrl+Shift+X` / `Cmd+Shift+X`).
3. Click the `three dots (...)` at the top right of the Extensions view.
4. Select and install the C/C++ Tools `.vsix` file first.
5. Repeat the process to install the PlatformIO IDE `.vsix` file.
### 4. Initialize PlatformIO Core
1. **Restart VSCodium** completely after both installations finish.
2. Upon restarting, PlatformIO will automatically start installing the **PlatformIO Core** framework in the background.
This may take a few minutes depending on your internet connection.
3. Once completed, you will see the iconic **PlatformIO Alien/Ant icon** appear in your left sidebar.
## Method 2: Switching to the Microsoft Marketplace (Easier Updates)
If you prefer a seamless one-click installation and automatic updates directly inside VSCodium, you can modify VSCodium's configuration to pull extensions directly from Microsoft's official marketplace instead of Open-VSX.
*Note: This connects your editor to Microsoft's gallery infrastructure, which slightly reduces the strict "no-Microsoft-servers" privacy model of VSCodium.*
1. Locate and open the product.json file for your VSCodium installation.
• Windows: %APPDATA%\VSCodium or inside the installation directory.
• Linux: /usr/share/vscodium/resources/app/product.json (requires root privileges to edit) or inside your Flatpak/Snap path.
• macOS: /Applications/VSCodium.app/Contents/Resources/app/product.json.
2. Look for the extensionsGallery block and replace it with the following configuration:
```json
json
"extensionsGallery": {
"serviceUrl": "https://visualstudio.com",
"itemUrl": "https://visualstudio.com"
}
```
3. Save the file and restart VSCodium.
4. You can now simply search for PlatformIO IDE and C/C++ in the Extensions tab and click Install.
+19
Datei anzeigen
@@ -0,0 +1,19 @@
# Create New Project
## PlatformIO
1. Open VSCodium.
2. Click on the **Alien Head** (ant head) icon of the PlatformIO in the left Activity bar.
Continue clicking on "Create New Project", PlatformIO Core -- **PIO Home** opens.
3. Again, press the button **+ New Project** and fill in the *Name* of the new project, e.g. `FoxMesh`.
4. Select the board `TTGO LoRa32-OLED v2.1.6`
5. Choose the Framework either `Arduino` or `Espidf`, we select `Espidf` i.e. **ESP-IDF**.
6. We do not use the default location but `$HOME/github/FoxMesh`.
+37
Datei anzeigen
@@ -0,0 +1,37 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
+46
Datei anzeigen
@@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html
+33
Datei anzeigen
@@ -0,0 +1,33 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
; Enable global PlatformIO Cache
build_cache_dir = ~/.platformio/.cache
[env:ttgo-lora32-v21]
platform = espressif32
board = ttgo-lora32-v21
framework = espidf
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
; lib_deps = bodmer/TFT_eSPI@^2.5.43
; Zwingt PlatformIO, die mit Core 6.2.0 kompatible SCons-Version zu nutzen
platform_packages =
platformio/tool-scons @ ~4.41101.0
; Hardware-specific macro definitions
build_flags =
;-D CONFIG_IDF_TARGET_ESP32=y
-D CCACHE_ENABLE=1
; upload_port = /dev/ttyUSB0
; monitor_port = /dev/ttyUSB0
Datei-Diff unterdrückt, da er zu groß ist. Diff laden
+6
Datei anzeigen
@@ -0,0 +1,6 @@
# This file was automatically generated for projects
# without default 'CMakeLists.txt' file.
FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
idf_component_register(SRCS ${app_sources})
+160
Datei anzeigen
@@ -0,0 +1,160 @@
#include "cli.hpp"
#include <cstring>
#include <cstdio>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "driver/uart.h"
#include "esp_log.h"
#include "config.hpp"
static const char* TAG = "CLI";
#define CLI_UART_PORT UART_NUM_0
#define BUF_SIZE 1024
// Handle a silence timer for suppressing logs during cli actions
static TimerHandle_t silence_timer = nullptr;
// Dynamic variable holding the current mute runtime
static uint32_t current_mute_time_sec = Config::DEFAULT_MUTE_TIME_SEC;
static void log_restore_timer_callback(TimerHandle_t xTimer) {
// Print a clean new prompt since logs might have disrupted the visual flow
printf("\r\n[System] Debug logs unmuted after %lu seconds of inactivity. \r\n%s # ",
(unsigned long)current_mute_time_sec, Config::PROJECT_TITLE);
fflush(stdout);
// Now safely restore global logging back to INFO level after inactivity
esp_log_level_set("*", ESP_LOG_INFO);
}
// Process input strings once the user presses Enter
static void process_command(const char* cmd) {
if (strlen(cmd) == 0) return;
printf("\r\n"); // Move to new line after command execution
if (strcmp(cmd, "help") == 0) {
printf("Available commands:\r\n");
printf(" help - Show this command overview\r\n");
printf(" version - Display project title and version\r\n");
printf(" status - Print internal hardware statistics\r\n");
printf(" mute <sec> - Set custom logging timeout (Current: %lus)\r\n", (unsigned long)current_mute_time_sec);
}
else if (strcmp(cmd, "version") == 0) {
printf("%s System - Running Version: %s\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION);
}
else if (strcmp(cmd, "status") == 0) {
printf("--- %s Status ---\r\n", Config::PROJECT_TITLE);
printf("Free Internal Heap: %lu bytes\r\n", (unsigned long)esp_get_free_heap_size());
printf("Mute Timer Period : %lu seconds\r\n", (unsigned long)current_mute_time_sec);
printf("Target CPU Board : ESP32\r\n");
}
// NEW: Handle the "mute" parameter runtime configuration
else if (strncmp(cmd, "mute ", 5) == 0) {
// Extract the value after the string space
int new_time = atoi(cmd + 5);
if (new_time > 0 && new_time <= 3600) { // Limit between 1 second and 1 hour
current_mute_time_sec = new_time;
// Instantly update the running FreeRTOS timer period
if (silence_timer != nullptr) {
xTimerChangePeriod(silence_timer, pdMS_TO_TICKS(current_mute_time_sec * 1000), pdMS_TO_TICKS(10));
}
printf("Success: Mute timer interval updated to %lu seconds.\r\n", (unsigned long)current_mute_time_sec);
} else {
printf("Error: Please provide a valid timeout between 1 and 3600 seconds.\r\n");
}
}
else {
printf("Unknown command: '%s'. Type 'help' for options.\r\n", cmd);
}
// Print command prompt again
printf("\r\n%s # ", Config::PROJECT_TITLE);
fflush(stdout);
}
// Background listening loop for keyboard input
static void cli_worker_task(void *pvParameters) {
// Configure UART parameters for native monitoring
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
// Install UART driver without using resource-heavy interrupt queues
ESP_ERROR_CHECK(uart_param_config(CLI_UART_PORT, &uart_config));
ESP_ERROR_CHECK(uart_driver_install(CLI_UART_PORT, BUF_SIZE * 2, 0, 0, NULL, 0));
char line_buffer[BUF_SIZE];
int buffer_index = 0;
// Welcome message and initial prompt
printf("\r\n=== Interactive %s CLI Console Started ===\r\n", Config::PROJECT_TITLE);
printf("Type 'help' to get a list of active instructions.\r\n\r\n");
printf("%s # ", Config::PROJECT_TITLE);
fflush(stdout);
uint8_t data;
while (1) {
// Read character by character from serial interface (non-blocking with 20ms timeout)
int len = uart_read_bytes(CLI_UART_PORT, &data, 1, pdMS_TO_TICKS(20));
if (len > 0) {
esp_log_level_set("*", ESP_LOG_NONE);
if (silence_timer != nullptr) {
xTimerReset(silence_timer, pdMS_TO_TICKS(10));
}
// Handle carriage return or line feed (Enter key pressed)
if (data == '\r' || data == '\n') {
line_buffer[buffer_index] = '\0'; // Null-terminate string
process_command(line_buffer);
buffer_index = 0; // Reset buffer index for next command
}
// Handle Backspace (ASCII 8 or 127)
else if ((data == 8 || data == 127) && buffer_index > 0) {
buffer_index--;
printf("\b \b"); // Clear character visually in user terminal
fflush(stdout);
}
// Add normal printable characters to buffer
else if (data >= 32 && data <= 126 && buffer_index < (BUF_SIZE - 1)) {
line_buffer[buffer_index++] = data;
printf("%c", data); // Terminal Echo (prints what user types)
fflush(stdout);
}
}
// Minimal delay to keep watchdog and FreeRTOS internal background tasks happy
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void start_cli_task(void) {
// Dynamically initialize the timer with the variable interval using milliseconds conversion
silence_timer = xTimerCreate(
"MuteTimer",
pdMS_TO_TICKS(current_mute_time_sec * 1000),
pdFALSE,
nullptr,
log_restore_timer_callback
);
if (silence_timer == nullptr) {
ESP_LOGE(TAG, "Failed to create FreeRTOS silence timer.");
}
xTaskCreatePinnedToCore(
cli_worker_task,
"CLI_InterfaceTask",
4096, // Slightly bigger stack to safely handle printf string buffers
NULL,
5,
NULL,
0 // Hosted on Core 0 to separate terminal interactive traffic from Core 1
);
}
+7
Datei anzeigen
@@ -0,0 +1,7 @@
#ifndef CLI_HPP
#define CLI_HPP
// Starts the interactive Command Line Interface task
void start_cli_task(void);
#endif // CLI_HPP
+35
Datei anzeigen
@@ -0,0 +1,35 @@
#ifndef CONFIG_HPP
#define CONFIG_HPP
#include <cstdint>
#include "driver/gpio.h"
// #include "driver/i2c_master.h"
namespace Config {
// Compile-time constants with strict type enforcement
constexpr const char* PROJECT_TITLE = "FoxMesh";
constexpr const char* PROJECT_VERSION = "1.0.2";
constexpr const char* PROJECT_AUTHORS = "Tom OE3TKT";
// CLI Mute Configuration
constexpr uint32_t DEFAULT_MUTE_TIME_SEC = 30; // Default: 30 seconds
// Hardware Configuration
constexpr gpio_num_t LED_GPIO_PIN = GPIO_NUM_25;
constexpr int BLINK_INTERVAL_MS = 2000;
// OLED
constexpr gpio_num_t OLED_SDA_PIN = GPIO_NUM_21;
constexpr gpio_num_t OLED_SCL_PIN = GPIO_NUM_22;
constexpr uint32_t OLED_I2C_FREQ_HZ = 400000; // 400kHz Fast Mode
constexpr int OLED_ADDRESS = 0x3C; // Standard SSD1306 I2C Adress
// Semtech SX1276 LoRa SPI Subsystem (TTGO T3_V1.6 Hardwired Layout)
constexpr gpio_num_t LORA_MISO_PIN = GPIO_NUM_19;
constexpr gpio_num_t LORA_MOSI_PIN = GPIO_NUM_27;
constexpr gpio_num_t LORA_SCK_PIN = GPIO_NUM_5;
constexpr gpio_num_t LORA_CS_PIN = GPIO_NUM_18;
constexpr gpio_num_t LORA_RST_PIN = GPIO_NUM_23; // Adjusted for T3_V1.6 hardware revision
constexpr gpio_num_t LORA_IRQ_PIN = GPIO_NUM_26;
}
#endif // CONFIG_HPP
+167
Datei anzeigen
@@ -0,0 +1,167 @@
#include "display.hpp"
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "config.hpp"
#include <cstring>
const char* DisplayController::TAG = "DISPLAY";
static i2c_master_bus_handle_t bus_handle = nullptr;
static i2c_master_dev_handle_t dev_handle = nullptr;
DisplayController::DisplayController() : m_msgQueue(nullptr) {}
void DisplayController::initI2C() {
// Configure the physical I2C Bus first
i2c_master_bus_config_t bus_config = {};
bus_config.i2c_port = I2C_NUM_0;
bus_config.sda_io_num = Config::OLED_SDA_PIN;
bus_config.scl_io_num = Config::OLED_SCL_PIN;
bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
bus_config.glitch_ignore_cnt = 7;
bus_config.flags.enable_internal_pullup = true; // use internal pull-up resistor
// ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));
esp_err_t err = i2c_new_master_bus(&bus_config, &bus_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to create I2C bus: %s", esp_err_to_name(err));
return;
}
// Add the specific SSD1306 OLED Device to the running bus
i2c_device_config_t dev_config = {};
dev_config.dev_addr_length = I2C_ADDR_BIT_LEN_7;
dev_config.device_address = Config::OLED_ADDRESS;
dev_config.scl_speed_hz = Config::OLED_I2C_FREQ_HZ;
err = i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to add OLED device to I2C bus: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "New I2C Master Bus and Device Handle registered successfully.");
}
void DisplayController::sendCommand(uint8_t command) {
if (dev_handle == nullptr) return;
// the new driver handles transactions internally in a single, safe array write
uint8_t write_buf[2] = {
0x00, // Control byte: Co=0, D/C=0 (Incoming bytes are commands)
command // The actual payload command
};
// Increased timeout to 50ms and replaced hard ESP_ERROR_CHECK with a safe warning log
esp_err_t ret = i2c_master_transmit(dev_handle, write_buf, sizeof(write_buf), pdMS_TO_TICKS(50));
if (ret != ESP_OK) {
ESP_LOGW(TAG, "OLED Command 0x%02X failed or timed out: %s", command, esp_err_to_name(ret));
}
}
void DisplayController::initSSD1306() {
// If the hardware setup failed, don't run commands against a null handle
if (dev_handle == nullptr) {
ESP_LOGE(TAG, "Skipping SSD1306 init sequence because dev_handle is null.");
return;
}
// Minimalistic SSD1306 initialization sequence for 128x64 OLEDs
sendCommand(0xAE); // Turn display off
sendCommand(0xD5); // Set display clock divide ratio
sendCommand(0x80);
sendCommand(0xA8); // Set multiplex ratio
sendCommand(0x3F); // 64MUX
sendCommand(0xD3); // Set display offset
sendCommand(0x00);
sendCommand(0x40); // Set display start line
sendCommand(0x8D); // Charge pump charge
sendCommand(0x14); // Enable charge pump
sendCommand(0xAF); // Turn display on
// clear screen via modern buffer transfer
uint8_t clear_buf[129];
clear_buf[0] = 0x40; // Control byte: Data stream follows
memset(&clear_buf[1], 0x00, 128); // Fill frame buffer slice with zeros
// Clear screen natively by setting page addressing
for (uint8_t page = 0; page < 8; page++) {
sendCommand(0xB0 + page); // Set page address
sendCommand(0x00); // Set lower column address
sendCommand(0x10); // Set higher column address
// FIXED: Replaced ESP_ERROR_CHECK with safe error-handling to prevent software panic crashes
esp_err_t ret = i2c_master_transmit(dev_handle, clear_buf, sizeof(clear_buf), pdMS_TO_TICKS(50));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to clear page %d buffer: %s. Aborting sequence.", page, esp_err_to_name(ret));
return; // Exit cleanly, allowing the CPU to boot other tasks
}
}
ESP_LOGI(TAG, "SSD1306 OLED Controller initialized and screen cleared.");
}
void DisplayController::startTask(uint32_t priority) {
initI2C();
initSSD1306();
// Create the message queue for up to 10 incoming text packages
m_msgQueue = xQueueCreate(10, sizeof(DisplayMessage));
// Spawn the worker thread on Core 0 (keeps Core 1 completely free for your Mesh engine)
xTaskCreatePinnedToCore(
DisplayController::taskTrampoline,
"OLED_RenderTask",
3584,
this,
priority,
nullptr,
0 // Pinned to Core 0
);
}
bool DisplayController::printMessage(const char* text, int row) {
if (m_msgQueue == nullptr) return false;
DisplayMessage msg = {};
strncpy(msg.text, text, sizeof(msg.text) - 1);
msg.row = row;
// Non-blocking write into the queue from any external task thread
return (xQueueSend(m_msgQueue, &msg, pdMS_TO_TICKS(10)) == pdPASS);
}
void DisplayController::taskTrampoline(void* pvParameters) {
auto* instance = static_cast<DisplayController*>(pvParameters);
if (instance != nullptr) {
instance->run();
}
vTaskDelete(nullptr);
}
void DisplayController::run() {
DisplayMessage incomingMsg;
ESP_LOGI(TAG, "OLED Core Engine operational loop active.");
// Dynamic boot screen utilizing configuration metadata
printMessage("====================", 0); // Row 0: Top Frame
// Format the title and version cleanly into Row 1
char titleBuffer[32];
snprintf(titleBuffer, sizeof(titleBuffer), "%s v%s", Config::PROJECT_TITLE, Config::PROJECT_VERSION);
printMessage(titleBuffer, 1);
// Format the copyright and authors into Row 2
char authorBuffer[32];
snprintf(authorBuffer, sizeof(authorBuffer), "(c) %s", Config::PROJECT_AUTHORS);
printMessage(authorBuffer, 2);
printMessage("====================", 3); // Row 3: Bottom Frame
while (true) {
// Deep sleep state: Task hits 0% CPU usage until a message arrives in the queue
if (xQueueReceive(m_msgQueue, &incomingMsg, portMAX_DELAY) == pdPASS) {
// Safe instrumentation logging (simulating the physical pixels)
ESP_LOGI(TAG, "[OLED ROW %d]: %s", incomingMsg.row, incomingMsg.text);
}
}
}
+42
Datei anzeigen
@@ -0,0 +1,42 @@
#ifndef DISPLAY_HPP
#define DISPLAY_HPP
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
class DisplayController {
public:
// Structure for the thread-safe communication queue
struct DisplayMessage {
char text[32]; // Buffer for short status messages (e.g., "LED ON", "Mesh Packet")
int row; // Target row on the screen (e.g., 0 to 3)
};
DisplayController();
virtual ~DisplayController() = default;
// Deleted copy constructors for hardware safety
DisplayController(const DisplayController&) = delete;
DisplayController& operator=(const DisplayController&) = delete;
// Initializes I2C and spawns the background worker task on Core 0
void startTask(uint32_t priority = 4);
// Thread-safe public method that other tasks call to print text
bool printMessage(const char* text, int row);
private:
static const char* TAG;
QueueHandle_t m_msgQueue;
void initI2C();
void initSSD1306();
void sendCommand(uint8_t command);
// FreeRTOS C++ bridging mechanisms
static void taskTrampoline(void* pvParameters);
void run();
};
#endif // DISPLAY_HPP
+57
Datei anzeigen
@@ -0,0 +1,57 @@
#include "led.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "config.hpp"
// Initialize the static log tag
const char* LedController::TAG = "LED";
LedController::LedController(gpio_num_t gpioPin) : m_gpioPin(gpioPin) {
// Hardware initialization inside the constructor guarantees safe state upon creation
gpio_reset_pin(m_gpioPin);
gpio_set_direction(m_gpioPin, GPIO_MODE_OUTPUT);
ESP_LOGI(TAG, "Hardware initialized on GPIO %d.", m_gpioPin);
}
void LedController::startTask(uint32_t priority, BaseType_t coreId) {
// Pass 'this' (the pointer to the current instance) as the task parameter
xTaskCreatePinnedToCore(
LedController::taskTrampoline,
"LEDTask",
3072,
this,
priority,
nullptr,
coreId
);
}
void LedController::taskTrampoline(void* pvParameters) {
// Cast the raw object pointer back to our class instance
auto* instance = static_cast<LedController*>(pvParameters);
if (instance != nullptr) {
instance->run();
}
// Safety fallback: FreeRTOS tasks must never return natively
vTaskDelete(nullptr);
}
void LedController::run() {
ESP_LOGI(TAG, "FreeRTOS instance task successfully started on Core %d.", xPortGetCoreID());
// Define the fixed ON-time as a local constant
constexpr uint32_t led_on_ms = 30;
// Compile-time assertion to ensure the interval is always larger than the flash duration
static_assert(Config::BLINK_INTERVAL_MS > led_on_ms, "Global blink interval must be greater than 30ms!");
while (true) {
gpio_set_level(m_gpioPin, 1); // LED on (High)
vTaskDelay(pdMS_TO_TICKS(led_on_ms)); // 30ms pulse width
gpio_set_level(m_gpioPin, 0); // LED off (Low)
vTaskDelay(pdMS_TO_TICKS(Config::BLINK_INTERVAL_MS - led_on_ms)); // typ. 1970ms period low time
}
}
+33
Datei anzeigen
@@ -0,0 +1,33 @@
#ifndef LED_HPP
#define LED_HPP
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
class LedController {
public:
// Constructor allows passing the GPIO pin (defaults to 25 as requested)
explicit LedController(gpio_num_t gpioPin = GPIO_NUM_25);
// Destructor (virtual in case of inheritance)
virtual ~LedController() = default;
// Disallow copying to prevent multiple instances controlling the same hardware pin
LedController(const LedController&) = delete;
LedController& operator=(const LedController&) = delete;
// Spawns the FreeRTOS task pinned to a specific core
void startTask(uint32_t priority = 5, BaseType_t coreId = 1);
private:
const gpio_num_t m_gpioPin;
static const char* TAG;
// Static trampoline function required by the FreeRTOS scheduler
static void taskTrampoline(void* pvParameters);
// Dynamic instance method where the actual blinking loop lives
void run();
};
#endif // LED_HPP
+77
Datei anzeigen
@@ -0,0 +1,77 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "config.hpp"
#include "led.hpp"
#include "cli.hpp"
#include "display.hpp" // thread-safe display
static const char *TAG = "MAIN";
// Global singleton instances for hardware abstraction
static DisplayController oled;
static LedController led(Config::LED_GPIO_PIN);
void init_lora_spi(void) {
esp_err_t ret;
// Configure the SPI structure using the strict C++ constexpr constants
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = Config::LORA_MOSI_PIN;
buscfg.miso_io_num = Config::LORA_MISO_PIN;
buscfg.sclk_io_num = Config::LORA_SCK_PIN;
buscfg.quadwp_io_num = -1;
buscfg.quadhd_io_num = -1;
buscfg.max_transfer_sz = 0;
// Initialize the physical peripheral (VSPI / SPI3 on ESP32)
ret = spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
return;
}
ESP_LOGI(TAG, "SPI bus initialized successfully on VSPI (SPI3)");
}
// Fixed: Only ONE app_main exists now, marked as extern "C" for ESP-IDF bootloader
extern "C" void app_main(void) {
// Dynamic string formatting using type-safe constants
ESP_LOGI(TAG, "=================================================");
ESP_LOGI(TAG, " Booting %s (Version: %s) via ESP-IDF C++",
Config::PROJECT_TITLE,
Config::PROJECT_VERSION);
ESP_LOGI(TAG, " (c) Authors: %s", Config::PROJECT_AUTHORS);
ESP_LOGI(TAG, "=================================================");
// Explicitly power up the onboard peripherals on TTGO T3
// gpio_reset_pin(GPIO_NUM_16);
// gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT);
// Hard reset of the OLED display
// gpio_set_level(GPIO_NUM_16, 0);
// vTaskDelay(pdMS_TO_TICKS(50)); // 50ms in Reset
// gpio_set_level(GPIO_NUM_16, 1); // Set HIGH to enable OLED power rails
// vTaskDelay(pdMS_TO_TICKS(100)); // Give the hardware 100ms to stabilize voltage
led.startTask();
start_cli_task();
oled.startTask();
// Synchronous test push to the display queue
// oled.printMessage("Mesh Scanning...", 2);
// Sytem Monitor Loop
while (true) {
ESP_LOGI(TAG, "[%s v%s] Operational. Free Heap: %lu bytes",
Config::PROJECT_TITLE,
Config::PROJECT_VERSION,
(unsigned long)esp_get_free_heap_size());
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
+11
Datei anzeigen
@@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html