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
@@ -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));
|
||||
}
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren