initialize a multitasking framework
Dieser Commit ist enthalten in:
1 Ursprung
ffdfa486ff
Commit
198074e289
18 Dateien geändert
+2843
-54
Keine Dateien entsprechen Ihrer Suche
+7
-54
@@ -1,55 +1,8 @@
|
|||||||
# Prerequisites
|
# PlatformIO
|
||||||
*.d
|
.pio
|
||||||
|
|
||||||
# Object files
|
# VSCodium / VS Code
|
||||||
*.o
|
.vscode/.browse.c_cpp.db*
|
||||||
*.ko
|
.vscode/c_cpp_properties.json
|
||||||
*.obj
|
.vscode/launch.json
|
||||||
*.elf
|
.vscode/ipch
|
||||||
|
|
||||||
# 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
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16.0)
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
project(FoxMesh)
|
||||||
@@ -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.
|
||||||
@@ -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`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
@@ -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})
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
In neuem Issue referenzieren
Einen Benutzer sperren