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

+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));
}
}