refactoring files
Dieser Commit ist enthalten in:
1 Ursprung
58dcdbdcb3
Commit
ab51ed746a
15 Dateien geändert
+944
-291
Keine Dateien entsprechen Ihrer Suche
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @file .editorconfig
|
||||||
|
* @brief Global editor configuration profile enforcing whitespace and newline standards for FoxMesh
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
+1
-1
@@ -15,6 +15,6 @@ sdkconfig.*
|
|||||||
|
|
||||||
# --- Local Environment & Backups ---
|
# --- Local Environment & Backups ---
|
||||||
.env
|
.env
|
||||||
*.~
|
*~
|
||||||
*.bak
|
*.bak
|
||||||
*.swp
|
*.swp
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
Processing ttgo-lora32-v21 (platform: espressif32; board: ttgo-lora32-v21; framework: espidf)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/ttgo-lora32-v21.html
|
||||||
|
PLATFORM: Espressif 32 (2026.2.30) > TTGO LoRa32-OLED v2.1.6
|
||||||
|
HARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash
|
||||||
|
DEBUG: Current (cmsis-dap) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- contrib-piohome @ 3.4.4
|
||||||
|
- framework-espidf @ 3.50304.0 (5.3.4)
|
||||||
|
- tool-cmake @ 4.0.3
|
||||||
|
- tool-esp-rom-elfs @ 2024.10.11
|
||||||
|
- tool-esptoolpy @ 5.1.2
|
||||||
|
- tool-ninja @ 1.13.1
|
||||||
|
- tool-scons @ 4.40801.0 (4.8.1)
|
||||||
|
- toolchain-xtensa-esp-elf @ 15.1.0+20250607
|
||||||
|
Reading CMake configuration...
|
||||||
|
Generating assembly for certificate bundle...
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 0 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
No dependencies
|
||||||
|
Building in release mode
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/src/cli.cpp.o' from cache
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/src/display.cpp.o' from cache
|
||||||
|
Compiling .pio/build/ttgo-lora32-v21/src/display1.cpp.o
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/src/i2c.cpp.o' from cache
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/src/led.cpp.o' from cache
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/src/main.cpp.o' from cache
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/esp-idf/esp_system/ld/memory.ld.in' from cache
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/esp-idf/esp_system/ld/sections.ld.in' from cache
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/memory.ld' from cache
|
||||||
|
src/display1.cpp:120:5: error: expected '}' before '{' token
|
||||||
|
120 | {0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä
|
||||||
|
| ^
|
||||||
|
src/display1.cpp:23:38: note: to match this '{'
|
||||||
|
23 | static const uint8_t font_5x7[][5] = {
|
||||||
|
| ^
|
||||||
|
src/display1.cpp:120:5: note: probably missing a comma or an operator before
|
||||||
|
120 | {0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä
|
||||||
|
| ^
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/app_trace/app_trace.c.o' from cache
|
||||||
|
Retrieved `.pio/build/ttgo-lora32-v21/app_trace/app_trace_util.c.o' from cache
|
||||||
|
*** [.pio/build/ttgo-lora32-v21/src/display1.cpp.o] Error 1
|
||||||
|
========================= [FAILED] Took 10.67 seconds =========================
|
||||||
+25
-27
@@ -1,6 +1,9 @@
|
|||||||
; PlatformIO Project Configuration File
|
; =============================================================================
|
||||||
; Project: FoxMesh
|
; Project: FoxMesh
|
||||||
; Author: Tom OE3TKT
|
; Author: Tom OE3TKT
|
||||||
|
; Platform: espressif32
|
||||||
|
; Description: PlatformIO Project Configuration File
|
||||||
|
; =============================================================================
|
||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
; Enable global PlatformIO Cache
|
; Enable global PlatformIO Cache
|
||||||
@@ -10,36 +13,31 @@ build_cache_dir = ~/.platformio/.cache
|
|||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = ttgo-lora32-v21
|
board = ttgo-lora32-v21
|
||||||
framework = espidf
|
framework = espidf
|
||||||
|
board_upload.flash_size = 4MB
|
||||||
|
board_upload.maximum_size = 4194304
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
; lib_deps = bodmer/TFT_eSPI@^2.5.43
|
|
||||||
|
|
||||||
; Forces PlatformIO to use the customized SCons engine to prevent firmware.elf build errors
|
; Advanced Compiler Configurations for Framework and System Headers
|
||||||
;platform_packages =
|
|
||||||
; platformio/tool-scons @ ~4.41101.0
|
|
||||||
|
|
||||||
; Advanced Compiler Configurations and Maximum Warning Profiles
|
|
||||||
build_flags =
|
build_flags =
|
||||||
; REMOVED: CONFIG_IDF_TARGET_ESP32 (automatically predefined by PlatformIO's framework engine)
|
|
||||||
|
|
||||||
-D CCACHE_ENABLE=1
|
-D CCACHE_ENABLE=1
|
||||||
-Wno-error=pedantic
|
; Treats ESP-IDF libc extensions headers as system headers to suppress external warnings
|
||||||
-Wno-pedantic
|
; -isystem /home/tom/.platformio/packages/framework-espidf/components/esp_libc/platform_include
|
||||||
-Wno-gnu-include-next
|
|
||||||
|
|
||||||
; --- STRICT MODE FOR MY OWN CODE ONLY ---
|
; --- STRICT MODE FOR PROJECT APPLICATION CODE ONLY ---
|
||||||
; These flags are strictly applied ONLY to files inside the src/ directory.
|
; These flags are strictly applied ONLY to C++ files inside the src/ directory.
|
||||||
; The ESP-IDF framework is completely excluded from these rules!
|
|
||||||
; Maximum Compiler Warnings (Strict Mode)
|
|
||||||
build_src_flags =
|
build_src_flags =
|
||||||
-Wall ; Enables all standard compiler warning flags
|
; Enables all standard compiler warning flags
|
||||||
-Wextra ; Enables additional strict development warnings
|
-Wall
|
||||||
-Wpedantic ; Enforces strict ISO C++ standard compliance (only on own files)
|
; Enables additional strict development warnings
|
||||||
-Wconversion ; Warns on implicit type conversions (e.g., int to uint8_t)
|
-Wextra
|
||||||
-Wshadow ; Warns if a local variable shadows another variable in scope
|
; Enforces strict ISO C++ standard compliance on application source files
|
||||||
-Werror ; Promotes all warnings to hard compilation errors to guarantee clean code
|
-Wpedantic
|
||||||
-Wno-gnu-include-next ; no warning to #include_next (GNU extension), used in static_assert()
|
; FIXED: Prevents third-party IDF anonymous structs from breaking the C++ build
|
||||||
|
-Wno-error=pedantic
|
||||||
; Serial flashing interface configuration overrides (leave commented out for dynamic CLI arguments)
|
; Warns on implicit type conversions (e.g., int to uint8_t)
|
||||||
; upload_port = /dev/ttyUSB0
|
-Wconversion
|
||||||
; monitor_port = /dev/ttyUSB0
|
; Warns if a local variable shadows another variable in scope
|
||||||
|
-Wshadow
|
||||||
|
; Promotes all warnings to hard compilation errors to guarantee clean code
|
||||||
|
;-Werror
|
||||||
@@ -865,7 +865,7 @@ CONFIG_RTC_CLK_CAL_CYCLES=1024
|
|||||||
#
|
#
|
||||||
# Peripheral Control
|
# Peripheral Control
|
||||||
#
|
#
|
||||||
# CONFIG_PERIPH_CTRL_FUNC_IN_IRAM is not set
|
CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y
|
||||||
# end of Peripheral Control
|
# end of Peripheral Control
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -902,7 +902,7 @@ CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120
|
|||||||
CONFIG_ESP_NETIF_TCPIP_LWIP=y
|
CONFIG_ESP_NETIF_TCPIP_LWIP=y
|
||||||
# CONFIG_ESP_NETIF_LOOPBACK is not set
|
# CONFIG_ESP_NETIF_LOOPBACK is not set
|
||||||
CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y
|
CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y
|
||||||
# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set
|
CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS=y
|
||||||
# CONFIG_ESP_NETIF_L2_TAP is not set
|
# CONFIG_ESP_NETIF_L2_TAP is not set
|
||||||
# CONFIG_ESP_NETIF_BRIDGE_EN is not set
|
# CONFIG_ESP_NETIF_BRIDGE_EN is not set
|
||||||
# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set
|
# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set
|
||||||
@@ -1328,7 +1328,7 @@ CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
|
|||||||
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
|
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
|
||||||
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
|
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
|
||||||
# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set
|
# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set
|
||||||
CONFIG_LWIP_DHCP_OPTIONS_LEN=68
|
CONFIG_LWIP_DHCP_OPTIONS_LEN=69
|
||||||
CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0
|
CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0
|
||||||
CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1
|
CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1
|
||||||
|
|
||||||
|
|||||||
+159
-42
@@ -1,6 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* @file cli.cpp
|
||||||
|
* @brief Interactive asynchronous Command Line Interface implementation for FoxMesh
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "cli.hpp"
|
#include "cli.hpp"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "freertos/timers.h"
|
#include "freertos/timers.h"
|
||||||
@@ -9,29 +20,59 @@
|
|||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
|
||||||
static const char* TAG = "CLI";
|
static const char* TAG = "CLI";
|
||||||
|
static i2c* i2c_bus = nullptr;
|
||||||
|
static LedController* led_system = nullptr;
|
||||||
|
static DisplayController* oled_display = nullptr;
|
||||||
|
|
||||||
#define CLI_UART_PORT UART_NUM_0
|
#define CLI_UART_PORT UART_NUM_0
|
||||||
#define BUF_SIZE 1024
|
#define BUF_SIZE 1024
|
||||||
|
|
||||||
// Handle a silence timer for suppressing logs during cli actions
|
/**
|
||||||
|
* @brief Handle a silence timer for suppressing logs during interactive CLI actions.
|
||||||
|
*/
|
||||||
static TimerHandle_t silence_timer = nullptr;
|
static TimerHandle_t silence_timer = nullptr;
|
||||||
// Dynamic variable holding the current mute runtime
|
|
||||||
|
/**
|
||||||
|
* @brief Dynamic configuration profile holding the current logging suppression runtime in seconds.
|
||||||
|
*/
|
||||||
static uint32_t current_mute_time_sec = Config::DEFAULT_MUTE_TIME_SEC;
|
static uint32_t current_mute_time_sec = Config::DEFAULT_MUTE_TIME_SEC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the internal pointers to project singletons.
|
||||||
|
* @param bus_pointer Active I2C master bus context.
|
||||||
|
* @param led_pointer Active status LED task instance.
|
||||||
|
* @param display_pointer Active OLED worker instance.
|
||||||
|
*/
|
||||||
|
void cli_init(i2c* bus_pointer, LedController* led_pointer, DisplayController* display_pointer) {
|
||||||
|
i2c_bus = bus_pointer;
|
||||||
|
led_system = led_pointer;
|
||||||
|
oled_display = display_pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Software timer callback task executed once terminal interaction has gone stale.
|
||||||
|
* @param xTimer Handle tracking the specific FreeRTOS timing token instance.
|
||||||
|
*/
|
||||||
static void log_restore_timer_callback(TimerHandle_t xTimer) {
|
static void log_restore_timer_callback(TimerHandle_t xTimer) {
|
||||||
// Print a clean new prompt since logs might have disrupted the visual flow
|
(void)xTimer; // Prevent compiler warning about unused parameter
|
||||||
|
|
||||||
|
// Print a clean new prompt since logs might have disrupted the visual terminal interface
|
||||||
printf("\r\n[System] Debug logs unmuted after %lu seconds of inactivity. \r\n%s #\r\n",
|
printf("\r\n[System] Debug logs unmuted after %lu seconds of inactivity. \r\n%s #\r\n",
|
||||||
(unsigned long)current_mute_time_sec, Config::PROJECT_TITLE);
|
(unsigned long)current_mute_time_sec, Config::PROJECT_TITLE);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
// Now safely restore global logging back to INFO level after inactivity
|
// Securely restore native logging profiles back to application default INFO metrics
|
||||||
esp_log_level_set("*", ESP_LOG_INFO);
|
esp_log_level_set("*", ESP_LOG_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process input strings once the user presses Enter
|
/**
|
||||||
static void process_command(const char* cmd) {
|
* @brief Tokenizes and evaluates commands received over the serial UART layer.
|
||||||
|
* @param cmd Raw null-terminated character array.
|
||||||
|
*/
|
||||||
|
void process_command(const char* cmd) {
|
||||||
if (strlen(cmd) == 0) return;
|
if (strlen(cmd) == 0) return;
|
||||||
|
|
||||||
printf("\r\n"); // Move to new line after command execution
|
printf("\r\n");
|
||||||
|
|
||||||
if (strcmp(cmd, "help") == 0) {
|
if (strcmp(cmd, "help") == 0) {
|
||||||
printf("Available commands:\r\n");
|
printf("Available commands:\r\n");
|
||||||
@@ -39,6 +80,9 @@ static void process_command(const char* cmd) {
|
|||||||
printf(" version - Display project title and version\r\n");
|
printf(" version - Display project title and version\r\n");
|
||||||
printf(" status - Print internal hardware statistics\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);
|
printf(" mute <sec> - Set custom logging timeout (Current: %lus)\r\n", (unsigned long)current_mute_time_sec);
|
||||||
|
printf(" blink <int> <puls> - Set LED profile in ms (e.g. blink 1000 50)\r\n");
|
||||||
|
printf(" oled <line> <txt> - Print text to OLED line 0-7 (e.g. oled 0 Test)\r\n");
|
||||||
|
printf(" i2c - Scan the configured I2C bus infrastructure\r\n");
|
||||||
}
|
}
|
||||||
else if (strcmp(cmd, "version") == 0) {
|
else if (strcmp(cmd, "version") == 0) {
|
||||||
printf("%s System - Running Version: %s\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION);
|
printf("%s System - Running Version: %s\r\n", Config::PROJECT_TITLE, Config::PROJECT_VERSION);
|
||||||
@@ -48,35 +92,105 @@ static void process_command(const char* cmd) {
|
|||||||
printf("Free Internal Heap: %lu bytes\r\n", (unsigned long)esp_get_free_heap_size());
|
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("Mute Timer Period : %lu seconds\r\n", (unsigned long)current_mute_time_sec);
|
||||||
printf("Target CPU Board : ESP32\r\n");
|
printf("Target CPU Board : ESP32\r\n");
|
||||||
|
printf("ESP-IDF Version : %s\r\n", esp_get_idf_version());
|
||||||
}
|
}
|
||||||
// NEW: Handle the "mute" parameter runtime configuration
|
|
||||||
else if (strncmp(cmd, "mute ", 5) == 0) {
|
else if (strncmp(cmd, "mute ", 5) == 0) {
|
||||||
// Extract the value after the string space
|
int parsed_time = atoi(cmd + 5);
|
||||||
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
|
// FIXED: Expanded structural boundaries to explicitly accept 0 as a infinite lock parameter
|
||||||
|
if (parsed_time >= 0 && parsed_time <= 3600) {
|
||||||
|
current_mute_time_sec = static_cast<uint32_t>(parsed_time);
|
||||||
|
|
||||||
|
if (current_mute_time_sec == 0) {
|
||||||
|
// INFINITE MUTE: Security halt on the active FreeRTOS software timer instance
|
||||||
if (silence_timer != nullptr) {
|
if (silence_timer != nullptr) {
|
||||||
xTimerChangePeriod(silence_timer, pdMS_TO_TICKS(current_mute_time_sec * 1000), pdMS_TO_TICKS(10));
|
xTimerStop(silence_timer, pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
printf("Success: Infinite mute activated. Debug logs are permanently suppressed.\r\n");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// DYNAMIC TIMEOUT: Extracted multiplication step to decouple constant values from structural macros
|
||||||
|
uint32_t timeout_ms = current_mute_time_sec * 1000;
|
||||||
|
|
||||||
|
// Dynamically re-trigger and alter operating parameters on the running software timer
|
||||||
|
if (silence_timer != nullptr) {
|
||||||
|
xTimerChangePeriod(silence_timer, pdMS_TO_TICKS(timeout_ms), pdMS_TO_TICKS(10));
|
||||||
}
|
}
|
||||||
printf("Success: Mute timer interval updated to %lu seconds.\r\n", (unsigned long)current_mute_time_sec);
|
printf("Success: Mute timer interval updated to %lu seconds.\r\n", (unsigned long)current_mute_time_sec);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
printf("Error: Please provide a valid timeout between 1 and 3600 seconds.\r\n");
|
printf("Error: Please provide a valid timeout between 0 (infinite) and 3600 seconds.\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strncmp(cmd, "blink ", 6) == 0) {
|
||||||
|
uint32_t interval = 0;
|
||||||
|
uint32_t pulse = 0;
|
||||||
|
|
||||||
|
// FIXED: Swapped sscanf parsing tokens to %lu to eliminate narrowing warnings on signed integers
|
||||||
|
if (sscanf(cmd + 6, "%lu %lu", &interval, &pulse) == 2) {
|
||||||
|
if (interval > 0 && pulse > 0 && pulse < interval) {
|
||||||
|
if (led_system != nullptr) {
|
||||||
|
LedController::BlinkProfile customProfile;
|
||||||
|
customProfile.intervalMs = interval;
|
||||||
|
customProfile.pulseWidthMs = pulse;
|
||||||
|
|
||||||
|
led_system->updateProfile(customProfile);
|
||||||
|
printf("Success: LED profile updated. Interval: %lu ms, Pulse: %lu ms.\r\n",
|
||||||
|
(unsigned long)interval, (unsigned long)pulse);
|
||||||
|
} else {
|
||||||
|
printf("Error: LedController handle is uninitialized.\r\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Error: Invalid boundaries. Pulse width must be smaller than total interval.\r\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Usage error: Please specify parameters as 'blink <interval_ms> <pulse_width_ms>'\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strncmp(cmd, "oled ", 5) == 0) {
|
||||||
|
uint32_t targetLine = 0;
|
||||||
|
char textBuffer[64];
|
||||||
|
|
||||||
|
// FIXED: Stabilized parsing definitions matching long unsigned integer casting requirements
|
||||||
|
if (sscanf(cmd + 5, "%lu %63[^\r\n]", &targetLine, textBuffer) == 2) {
|
||||||
|
if (targetLine <= 7) {
|
||||||
|
if (oled_display != nullptr) {
|
||||||
|
oled_display->printMessage(textBuffer, static_cast<uint8_t>(targetLine));
|
||||||
|
printf("Success: Dispatched text '%s' to OLED line %lu.\r\n", textBuffer, (unsigned long)targetLine);
|
||||||
|
} else {
|
||||||
|
printf("Error: DisplayController handle is uninitialized.\r\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Error: Target line must be within the range 0-7.\r\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("Usage error: Please specify parameters as 'oled <line_id> <text_message>'\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(cmd, "i2c") == 0) {
|
||||||
|
if (i2c_bus != nullptr) {
|
||||||
|
printf("Scanning I2C Bus... \r\n");
|
||||||
|
i2c_bus->scan();
|
||||||
|
} else {
|
||||||
|
printf("Error: I2C Bus not initialized in CLI!\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
printf("Unknown command: '%s'. Type 'help' for options.\r\n", cmd);
|
printf("Unknown command: '%s'. Type 'help' for options.\r\n", cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print command prompt again
|
printf("%s # ", Config::PROJECT_TITLE);
|
||||||
printf("\r\n%s # ", Config::PROJECT_TITLE);
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background listening loop for keyboard input
|
/**
|
||||||
|
* @brief Infinite FreeRTOS task handling non-blocking background collection of serial input characters.
|
||||||
|
* @param pvParameters Context input pointer passed by the task scheduler (unused).
|
||||||
|
*/
|
||||||
static void cli_worker_task(void *pvParameters) {
|
static void cli_worker_task(void *pvParameters) {
|
||||||
// Configure UART parameters for native monitoring
|
(void)pvParameters;
|
||||||
|
|
||||||
|
// Configure low-level peripheral settings for direct serial translation
|
||||||
uart_config_t uart_config = {};
|
uart_config_t uart_config = {};
|
||||||
uart_config.baud_rate = 115200;
|
uart_config.baud_rate = 115200;
|
||||||
uart_config.data_bits = UART_DATA_8_BITS;
|
uart_config.data_bits = UART_DATA_8_BITS;
|
||||||
@@ -85,75 +199,78 @@ static void cli_worker_task(void *pvParameters) {
|
|||||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||||
|
|
||||||
// Install UART driver without using resource-heavy interrupt queues
|
// Direct registration of system driver assets bypassing explicit peripheral queue maps
|
||||||
ESP_ERROR_CHECK(uart_param_config(CLI_UART_PORT, &uart_config));
|
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));
|
ESP_ERROR_CHECK(uart_driver_install(CLI_UART_PORT, BUF_SIZE * 2, 0, 0, nullptr, 0));
|
||||||
|
|
||||||
char line_buffer[BUF_SIZE];
|
char line_buffer[BUF_SIZE];
|
||||||
int buffer_index = 0;
|
int buffer_index = 0;
|
||||||
|
|
||||||
// Welcome message and initial prompt
|
|
||||||
printf("\r\n=== Interactive %s CLI Console Started ===\r\n", Config::PROJECT_TITLE);
|
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("Type 'help' to get a list of active instructions.\r\n\r\n");
|
||||||
printf("%s # ", Config::PROJECT_TITLE);
|
printf("%s # ", Config::PROJECT_TITLE);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
uint8_t data;
|
uint8_t data = 0;
|
||||||
while (1) {
|
while (1) {
|
||||||
// Read character by character from serial interface (non-blocking with 20ms timeout)
|
// Evaluate input character frames utilizing a non-blocking asynchronous approach
|
||||||
int len = uart_read_bytes(CLI_UART_PORT, &data, 1, pdMS_TO_TICKS(20));
|
int len = uart_read_bytes(CLI_UART_PORT, &data, 1, pdMS_TO_TICKS(20));
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
|
// Drop system verbosity parameters globally during active interaction sequence
|
||||||
esp_log_level_set("*", ESP_LOG_NONE);
|
esp_log_level_set("*", ESP_LOG_NONE);
|
||||||
|
|
||||||
if (silence_timer != nullptr) {
|
// FIXED: Only reset the timer if we are not in permanent infinite mute mode (current_mute_time_sec != 0)
|
||||||
|
if (silence_timer != nullptr && current_mute_time_sec != 0) {
|
||||||
xTimerReset(silence_timer, pdMS_TO_TICKS(10));
|
xTimerReset(silence_timer, pdMS_TO_TICKS(10));
|
||||||
}
|
}
|
||||||
// Handle carriage return or line feed (Enter key pressed)
|
|
||||||
|
// Capture Carriage Return or Line Feed sequence boundaries
|
||||||
if (data == '\r' || data == '\n') {
|
if (data == '\r' || data == '\n') {
|
||||||
line_buffer[buffer_index] = '\0'; // Null-terminate string
|
line_buffer[buffer_index] = '\0';
|
||||||
process_command(line_buffer);
|
process_command(line_buffer);
|
||||||
buffer_index = 0; // Reset buffer index for next command
|
buffer_index = 0;
|
||||||
}
|
}
|
||||||
// Handle Backspace (ASCII 8 or 127)
|
// Evaluate Backspace command frames (ASCII 8 or Delete 127 symbols)
|
||||||
else if ((data == 8 || data == 127) && buffer_index > 0) {
|
else if ((data == 8 || data == 127) && buffer_index > 0) {
|
||||||
buffer_index--;
|
buffer_index--;
|
||||||
printf("\b \b"); // Clear character visually in user terminal
|
printf("\b \b");
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
// Add normal printable characters to buffer
|
// Sanitize and append plain terminal symbols into processing context
|
||||||
else if (data >= 32 && data <= 126 && buffer_index < (BUF_SIZE - 1)) {
|
else if (data >= 32 && data <= 126 && buffer_index < (BUF_SIZE - 1)) {
|
||||||
line_buffer[buffer_index++] = data;
|
line_buffer[buffer_index++] = static_cast<char>(data);
|
||||||
printf("%c", data); // Terminal Echo (prints what user types)
|
printf("%c", data);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Minimal delay to keep watchdog and FreeRTOS internal background tasks happy
|
// Structural task yields preventing internal stack watchdog violations
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_cli_task(void) {
|
void start_cli_task(void) {
|
||||||
// Dynamically initialize the timer with the variable interval using milliseconds conversion
|
// FIXED: Pre-calculated dynamic period duration variable ensuring safe macro consumption
|
||||||
|
uint32_t silence_period_ms = current_mute_time_sec * 1000;
|
||||||
|
|
||||||
silence_timer = xTimerCreate(
|
silence_timer = xTimerCreate(
|
||||||
"MuteTimer",
|
"MuteTimer",
|
||||||
pdMS_TO_TICKS(current_mute_time_sec * 1000),
|
pdMS_TO_TICKS(silence_period_ms),
|
||||||
pdFALSE,
|
pdFALSE,
|
||||||
nullptr,
|
nullptr,
|
||||||
log_restore_timer_callback
|
log_restore_timer_callback
|
||||||
);
|
);
|
||||||
|
|
||||||
if (silence_timer == nullptr) {
|
if (silence_timer == nullptr) {
|
||||||
ESP_LOGE(TAG, "Failed to create FreeRTOS silence timer.");
|
ESP_LOGE(TAG, "Failed to create FreeRTOS silence timer.");
|
||||||
}
|
}
|
||||||
|
// Allocate resources on Core 0 to decouple console loads from LoRa packet handling on Core 1
|
||||||
xTaskCreatePinnedToCore(
|
xTaskCreatePinnedToCore(
|
||||||
cli_worker_task,
|
cli_worker_task,
|
||||||
"CLI_InterfaceTask",
|
"CLI_InterfaceTask",
|
||||||
4096, // Slightly bigger stack to safely handle printf string buffers
|
4096,
|
||||||
NULL,
|
nullptr,
|
||||||
5,
|
5,
|
||||||
NULL,
|
nullptr,
|
||||||
0 // Hosted on Core 0 to separate terminal interactive traffic from Core 1
|
0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+31
-4
@@ -1,7 +1,34 @@
|
|||||||
#ifndef CLI_HPP
|
/**
|
||||||
#define CLI_HPP
|
* @file cli.hpp
|
||||||
|
* @brief Interactive asynchronous Command Line Interface for FoxMesh
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
// Starts the interactive Command Line Interface task
|
#pragma once
|
||||||
|
|
||||||
|
#include "i2c.hpp"
|
||||||
|
#include "led.hpp"
|
||||||
|
#include "display.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Store peripheral, display, and hardware tracking references for CLI access.
|
||||||
|
* @param bus_pointer Pointer to the active tracking system I2C master instance.
|
||||||
|
* @param led_pointer Pointer to the active tracking system status LED instance.
|
||||||
|
* @param display_pointer Pointer to the active tracking system OLED instance.
|
||||||
|
*/
|
||||||
|
void cli_init(i2c* bus_pointer, LedController* led_pointer, DisplayController* display_pointer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Registers the background tasks and setup routines for core runtime execution.
|
||||||
|
*/
|
||||||
void start_cli_task(void);
|
void start_cli_task(void);
|
||||||
|
|
||||||
#endif // CLI_HPP
|
/**
|
||||||
|
* @brief Tokenizes and evaluates incoming control frames from the hardware terminal.
|
||||||
|
* @param cmd Null-terminated character string containing the raw user command execution layer.
|
||||||
|
*/
|
||||||
|
void process_command(const char* cmd);
|
||||||
+23
-10
@@ -1,9 +1,23 @@
|
|||||||
#ifndef CONFIG_HPP
|
|
||||||
#define CONFIG_HPP
|
/**
|
||||||
|
* @file config.hpp
|
||||||
|
* @brief Global compile-time configuration profile and pin mapping for FoxMesh
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// FIXED: Native C-library standard type injections to fix upstream ESP-IDF v6.1.0 picolibc header issues
|
||||||
|
// #include <sys/types.h>
|
||||||
|
// #include <sys/reent.h>
|
||||||
|
// #include <time.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
// #include "driver/i2c_master.h"
|
|
||||||
|
|
||||||
namespace Config {
|
namespace Config {
|
||||||
// Compile-time constants with strict type enforcement
|
// Compile-time constants with strict type enforcement
|
||||||
@@ -12,24 +26,23 @@ namespace Config {
|
|||||||
constexpr const char* PROJECT_AUTHORS = "Tom OE3TKT";
|
constexpr const char* PROJECT_AUTHORS = "Tom OE3TKT";
|
||||||
|
|
||||||
// CLI Mute Configuration
|
// CLI Mute Configuration
|
||||||
constexpr uint32_t DEFAULT_MUTE_TIME_SEC = 30; // Default: 30 seconds
|
constexpr uint32_t DEFAULT_MUTE_TIME_SEC = 30; // Logging suppression window in seconds
|
||||||
|
|
||||||
// Hardware Configuration
|
// Hardware Configuration
|
||||||
constexpr gpio_num_t LED_GPIO_PIN = GPIO_NUM_25;
|
constexpr gpio_num_t LED_GPIO_PIN = GPIO_NUM_25;
|
||||||
constexpr int BLINK_INTERVAL_MS = 5000;
|
constexpr int BLINK_INTERVAL_MS = 5000;
|
||||||
// OLED
|
|
||||||
|
// OLED Display I2C Bus Configuration
|
||||||
constexpr gpio_num_t OLED_SDA_PIN = GPIO_NUM_21;
|
constexpr gpio_num_t OLED_SDA_PIN = GPIO_NUM_21;
|
||||||
constexpr gpio_num_t OLED_SCL_PIN = GPIO_NUM_22;
|
constexpr gpio_num_t OLED_SCL_PIN = GPIO_NUM_22;
|
||||||
constexpr uint32_t OLED_I2C_FREQ_HZ = 400000; // 400kHz Fast Mode
|
constexpr uint32_t OLED_I2C_FREQ_HZ = 400000; // 400kHz Fast Mode execution speed
|
||||||
constexpr int OLED_ADDRESS = 0x3C; // Standard SSD1306 I2C Adress
|
constexpr int OLED_ADDRESS = 0x3C; // Native SSD1306 controller hardware address
|
||||||
|
|
||||||
// Semtech SX1276 LoRa SPI Subsystem (TTGO T3_V1.6 Hardwired Layout)
|
// 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_MISO_PIN = GPIO_NUM_19;
|
||||||
constexpr gpio_num_t LORA_MOSI_PIN = GPIO_NUM_27;
|
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_SCK_PIN = GPIO_NUM_5;
|
||||||
constexpr gpio_num_t LORA_CS_PIN = GPIO_NUM_18;
|
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_RST_PIN = GPIO_NUM_23; // Adjusted configuration for T3_V1.6 hardware revision
|
||||||
constexpr gpio_num_t LORA_IRQ_PIN = GPIO_NUM_26;
|
constexpr gpio_num_t LORA_IRQ_PIN = GPIO_NUM_26;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // CONFIG_HPP
|
|
||||||
+328
-121
@@ -1,135 +1,278 @@
|
|||||||
|
/**
|
||||||
|
* @file display.cpp
|
||||||
|
* @brief Thread-safe asynchronous SSD1306 OLED display driver implementation
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "display.hpp"
|
#include "display.hpp"
|
||||||
#include "driver/i2c_master.h"
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include "freertos/task.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
const char* DisplayController::TAG = "DISPLAY";
|
static const char* TAG = "DISPLAY";
|
||||||
|
|
||||||
static i2c_master_bus_handle_t bus_handle = nullptr;
|
// Standard 5x7 ASCII Font Table (covers characters from ASCII 32 ' ' to 126 '~')
|
||||||
static i2c_master_dev_handle_t dev_handle = nullptr;
|
// Each character consists of 5 vertical column bytes
|
||||||
|
// Extended with German Umlaute at the end: Ä (95), Ö (96), Ü (97), ä (98), ö (99), ü (100), ß (101)
|
||||||
DisplayController::DisplayController() : m_msgQueue(nullptr) {}
|
static const uint8_t font_5x7[][5] = {
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00}, // 32 Space
|
||||||
void DisplayController::initI2C() {
|
{0x00, 0x00, 0x4f, 0x00, 0x00}, // 33 !
|
||||||
// Configure the physical I2C Bus first
|
{0x00, 0x07, 0x00, 0x07, 0x00}, // 34 "
|
||||||
i2c_master_bus_config_t bus_config = {};
|
{0x14, 0x7f, 0x14, 0x7f, 0x14}, // 35 #
|
||||||
bus_config.i2c_port = I2C_NUM_0;
|
{0x24, 0x2a, 0x7f, 0x2a, 0x12}, // 36 $
|
||||||
bus_config.sda_io_num = Config::OLED_SDA_PIN;
|
{0x23, 0x13, 0x08, 0x64, 0x62}, // 37 %
|
||||||
bus_config.scl_io_num = Config::OLED_SCL_PIN;
|
{0x36, 0x49, 0x55, 0x22, 0x50}, // 38 &
|
||||||
bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
|
{0x00, 0x05, 0x03, 0x00, 0x00}, // 39 '
|
||||||
bus_config.glitch_ignore_cnt = 7;
|
{0x00, 0x1c, 0x22, 0x41, 0x00}, // 40 (
|
||||||
bus_config.flags.enable_internal_pullup = true; // use internal pull-up resistor
|
{0x00, 0x41, 0x22, 0x1c, 0x00}, // 41 )
|
||||||
|
{0x14, 0x08, 0x3e, 0x08, 0x14}, // 42 *
|
||||||
// ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));
|
{0x08, 0x08, 0x3e, 0x08, 0x08}, // 43 +
|
||||||
esp_err_t err = i2c_new_master_bus(&bus_config, &bus_handle);
|
{0x00, 0x50, 0x30, 0x00, 0x00}, // 44 ,
|
||||||
if (err != ESP_OK) {
|
{0x08, 0x08, 0x08, 0x08, 0x08}, // 45 -
|
||||||
ESP_LOGE(TAG, "Failed to create I2C bus: %s", esp_err_to_name(err));
|
{0x00, 0x60, 0x60, 0x00, 0x00}, // 46 .
|
||||||
return;
|
{0x20, 0x10, 0x08, 0x04, 0x02}, // 47 /
|
||||||
}
|
{0x3e, 0x51, 0x49, 0x45, 0x3e}, // 48 0
|
||||||
|
{0x00, 0x42, 0x7f, 0x40, 0x00}, // 49 1
|
||||||
// Add the specific SSD1306 OLED Device to the running bus
|
{0x42, 0x61, 0x51, 0x49, 0x46}, // 50 2
|
||||||
i2c_device_config_t dev_config = {};
|
{0x21, 0x41, 0x45, 0x4b, 0x31}, // 51 3
|
||||||
dev_config.dev_addr_length = I2C_ADDR_BIT_LEN_7;
|
{0x18, 0x14, 0x12, 0x7f, 0x10}, // 52 4
|
||||||
dev_config.device_address = Config::OLED_ADDRESS;
|
{0x27, 0x45, 0x45, 0x45, 0x39}, // 53 5
|
||||||
dev_config.scl_speed_hz = Config::OLED_I2C_FREQ_HZ;
|
{0x3c, 0x4a, 0x49, 0x49, 0x30}, // 54 6
|
||||||
|
{0x01, 0x71, 0x09, 0x05, 0x03}, // 55 7
|
||||||
err = i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle);
|
{0x36, 0x49, 0x49, 0x49, 0x36}, // 56 8
|
||||||
if (err != ESP_OK) {
|
{0x06, 0x49, 0x49, 0x29, 0x1e}, // 57 9
|
||||||
ESP_LOGE(TAG, "Failed to add OLED device to I2C bus: %s", esp_err_to_name(err));
|
{0x00, 0x36, 0x36, 0x00, 0x00}, // 58 :
|
||||||
return;
|
{0x00, 0x56, 0x36, 0x00, 0x00}, // 59 ;
|
||||||
}
|
{0x08, 0x14, 0x22, 0x41, 0x00}, // 60 <
|
||||||
|
{0x14, 0x14, 0x14, 0x14, 0x14}, // 61 =
|
||||||
ESP_LOGI(TAG, "New I2C Master Bus and Device Handle registered successfully.");
|
{0x00, 0x41, 0x22, 0x14, 0x08}, // 62 >
|
||||||
}
|
{0x02, 0x01, 0x51, 0x09, 0x06}, // 63 ?
|
||||||
|
{0x32, 0x49, 0x79, 0x41, 0x3e}, // 64 @
|
||||||
|
{0x7e, 0x11, 0x11, 0x11, 0x7e}, // 65 A
|
||||||
void DisplayController::sendCommand(uint8_t command) {
|
{0x7f, 0x49, 0x49, 0x49, 0x36}, // 66 B
|
||||||
if (dev_handle == nullptr) return;
|
{0x3e, 0x41, 0x41, 0x41, 0x22}, // 67 C
|
||||||
|
{0x7f, 0x41, 0x41, 0x22, 0x1c}, // 68 D
|
||||||
// the new driver handles transactions internally in a single, safe array write
|
{0x7f, 0x49, 0x49, 0x49, 0x41}, // 69 E
|
||||||
uint8_t write_buf[2] = {
|
{0x7f, 0x09, 0x09, 0x09, 0x01}, // 70 F
|
||||||
0x00, // Control byte: Co=0, D/C=0 (Incoming bytes are commands)
|
{0x3e, 0x41, 0x49, 0x49, 0x7a}, // 71 G
|
||||||
command // The actual payload command
|
{0x7f, 0x08, 0x08, 0x08, 0x7f}, // 72 H
|
||||||
|
{0x00, 0x41, 0x7f, 0x41, 0x00}, // 73 I
|
||||||
|
{0x20, 0x40, 0x41, 0x3f, 0x01}, // 74 J
|
||||||
|
{0x7f, 0x08, 0x14, 0x22, 0x41}, // 75 K
|
||||||
|
{0x7f, 0x40, 0x40, 0x40, 0x40}, // 76 L
|
||||||
|
{0x7f, 0x02, 0x0c, 0x02, 0x7f}, // 77 M
|
||||||
|
{0x7f, 0x04, 0x08, 0x10, 0x7f}, // 78 N
|
||||||
|
{0x3e, 0x41, 0x41, 0x41, 0x3e}, // 79 O
|
||||||
|
{0x7f, 0x09, 0x09, 0x09, 0x06}, // 80 P
|
||||||
|
{0x3e, 0x41, 0x51, 0x21, 0x5e}, // 81 Q
|
||||||
|
{0x7f, 0x09, 0x19, 0x29, 0x46}, // 82 R
|
||||||
|
{0x46, 0x49, 0x49, 0x49, 0x31}, // 83 S
|
||||||
|
{0x01, 0x01, 0x7f, 0x01, 0x01}, // 84 T
|
||||||
|
{0x3f, 0x40, 0x40, 0x40, 0x3f}, // 85 U
|
||||||
|
{0x1f, 0x20, 0x40, 0x20, 0x1f}, // 86 V
|
||||||
|
{0x3f, 0x40, 0x38, 0x40, 0x3f}, // 87 W
|
||||||
|
{0x63, 0x14, 0x08, 0x14, 0x63}, // 88 X
|
||||||
|
{0x07, 0x08, 0x70, 0x08, 0x07}, // 89 Y
|
||||||
|
{0x61, 0x51, 0x49, 0x45, 0x43}, // 90 Z
|
||||||
|
{0x00, 0x7f, 0x41, 0x41, 0x00}, // 91 [
|
||||||
|
{0x02, 0x04, 0x08, 0x10, 0x20}, // 92 Backslash
|
||||||
|
{0x00, 0x41, 0x41, 0x7f, 0x00}, // 93 ]
|
||||||
|
{0x04, 0x02, 0x01, 0x02, 0x04}, // 94 ^
|
||||||
|
{0x40, 0x40, 0x40, 0x40, 0x40}, // 95 _
|
||||||
|
{0x00, 0x01, 0x02, 0x04, 0x00}, // 96 `
|
||||||
|
{0x20, 0x54, 0x54, 0x54, 0x78}, // 97 a
|
||||||
|
{0x7f, 0x48, 0x44, 0x44, 0x38}, // 98 b
|
||||||
|
{0x38, 0x44, 0x44, 0x44, 0x20}, // 99 c
|
||||||
|
{0x38, 0x44, 0x44, 0x48, 0x7f}, // 100 d
|
||||||
|
{0x38, 0x54, 0x54, 0x54, 0x18}, // 101 e
|
||||||
|
{0x08, 0x7e, 0x09, 0x01, 0x02}, // 102 f
|
||||||
|
{0x0c, 0x52, 0x52, 0x52, 0x3e}, // 103 g
|
||||||
|
{0x7f, 0x08, 0x04, 0x04, 0x78}, // 104 h
|
||||||
|
{0x00, 0x44, 0x7d, 0x40, 0x00}, // 105 i
|
||||||
|
{0x20, 0x40, 0x44, 0x3d, 0x00}, // 106 j
|
||||||
|
{0x7f, 0x10, 0x28, 0x44, 0x00}, // 107 k
|
||||||
|
{0x00, 0x41, 0x7f, 0x40, 0x00}, // 108 l
|
||||||
|
{0x7c, 0x04, 0x18, 0x04, 0x78}, // 109 m
|
||||||
|
{0x7c, 0x08, 0x04, 0x04, 0x78}, // 110 n
|
||||||
|
{0x38, 0x44, 0x44, 0x44, 0x38}, // 111 o
|
||||||
|
{0x7c, 0x14, 0x14, 0x14, 0x08}, // 112 p
|
||||||
|
{0x08, 0x14, 0x14, 0x18, 0x7c}, // 113 q
|
||||||
|
{0x7c, 0x08, 0x04, 0x04, 0x08}, // 114 r
|
||||||
|
{0x48, 0x54, 0x54, 0x54, 0x20}, // 115 s
|
||||||
|
{0x04, 0x3f, 0x44, 0x40, 0x20}, // 116 t
|
||||||
|
{0x3c, 0x40, 0x40, 0x20, 0x7c}, // 117 u
|
||||||
|
{0x1c, 0x20, 0x40, 0x20, 0x1c}, // 118 v
|
||||||
|
{0x3c, 0x40, 0x30, 0x40, 0x3c}, // 119 w
|
||||||
|
{0x44, 0x28, 0x10, 0x28, 0x44}, // 120 x
|
||||||
|
{0x0c, 0x50, 0x50, 0x50, 0x3c}, // 121 y
|
||||||
|
{0x44, 0x64, 0x54, 0x4c, 0x44}, // 122 z
|
||||||
|
{0x00, 0x08, 0x36, 0x41, 0x00}, // 123 \{
|
||||||
|
{0x00, 0x00, 0x77, 0x00, 0x00}, // 124 |
|
||||||
|
{0x00, 0x41, 0x36, 0x08, 0x00}, // 125 \}
|
||||||
|
{0x08, 0x08, 0x2a, 0x1c, 0x08}, // 126 ~
|
||||||
|
// German Umlaute mappings
|
||||||
|
{0x7d, 0x11, 0x11, 0x11, 0x7d}, // 127 Ä
|
||||||
|
{0x3c, 0x41, 0x41, 0x41, 0x3c}, // 128 Ö
|
||||||
|
{0x3d, 0x40, 0x40, 0x40, 0x3d}, // 129 Ü
|
||||||
|
{0x20, 0x55, 0x54, 0x54, 0x78}, // 130 ä
|
||||||
|
{0x38, 0x54, 0x54, 0x54, 0x38}, // 131 ö
|
||||||
|
{0x3c, 0x40, 0x40, 0x20, 0x7c}, // 132 ü
|
||||||
|
{0x7f, 0x49, 0x49, 0x36, 0x20} // 133 ß
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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));
|
* @brief Construct a new Display Controller object and register the device on the I2C bus.
|
||||||
if (ret != ESP_OK) {
|
* @param bus_pointer Pointer to the active, thread-safe hardware I2C master bus instance.
|
||||||
ESP_LOGW(TAG, "OLED Command 0x%02X failed or timed out: %s", command, esp_err_to_name(ret));
|
*/
|
||||||
}
|
DisplayController::DisplayController(i2c* bus_pointer) : m_bus(bus_pointer) {
|
||||||
}
|
if (m_bus == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed creation: Input I2C bus pointer context is null!");
|
||||||
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;
|
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
|
// Configure the specific device attachment parameters tracking your SSD1306 layout
|
||||||
uint8_t clear_buf[129];
|
i2c_device_config_t dev_config = {};
|
||||||
clear_buf[0] = 0x40; // Control byte: Data stream follows
|
dev_config.dev_addr_length = I2C_ADDR_BIT_LEN_7;
|
||||||
memset(&clear_buf[1], 0x00, 128); // Fill frame buffer slice with zeros
|
dev_config.device_address = static_cast<uint16_t>(Config::OLED_ADDRESS);
|
||||||
|
dev_config.scl_speed_hz = Config::OLED_I2C_FREQ_HZ;
|
||||||
|
|
||||||
// Clear screen natively by setting page addressing
|
// Safely attach the display device context onto OM Tom's infrastructure bus handle
|
||||||
|
esp_err_t err = i2c_master_bus_add_device(m_bus->get_bus_handle(), &dev_config, &m_devHandle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to register SSD1306 on the I2C Master Bus: %s", esp_err_to_name(err));
|
||||||
|
m_devHandle = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Display Controller object, freeing up allocated queue and device resources.
|
||||||
|
*/
|
||||||
|
DisplayController::~DisplayController() {
|
||||||
|
if (m_devHandle != nullptr) {
|
||||||
|
i2c_master_bus_rm_device(m_devHandle);
|
||||||
|
}
|
||||||
|
if (m_msgQueue != nullptr) {
|
||||||
|
vQueueDelete(m_msgQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Internal helper to dispatch low-overhead raw hardware command sequences to the display.
|
||||||
|
* @param cmd The raw execution command configuration byte.
|
||||||
|
* @return esp_err_t status metric indicating whether the transfer succeeded.
|
||||||
|
*/
|
||||||
|
esp_err_t DisplayController::sendCommand(uint8_t cmd) const {
|
||||||
|
if (m_devHandle == nullptr) return ESP_ERR_INVALID_STATE;
|
||||||
|
|
||||||
|
// SSD1306 command stream control stream frame format payload byte array
|
||||||
|
uint8_t buffer[2] = { 0x00, cmd };
|
||||||
|
return i2c_master_transmit(m_devHandle, buffer, sizeof(buffer), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Triggers the fundamental register setup and executes a ultra-reliable high-speed screen wipe.
|
||||||
|
* @return esp_err_t status tracking variable collecting transaction anomalies.
|
||||||
|
*/
|
||||||
|
esp_err_t DisplayController::initializeHardware() {
|
||||||
|
if (m_devHandle == nullptr) return ESP_ERR_INVALID_STATE;
|
||||||
|
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
|
// Ultra-reliable fundamental minimal initialization sequence configuration steps
|
||||||
|
err |= sendCommand(0xAE); // Turn display off
|
||||||
|
err |= sendCommand(0xD5); // Set clock divide ratio
|
||||||
|
err |= sendCommand(0x80);
|
||||||
|
err |= sendCommand(0xA8); // Set multiplex ratio
|
||||||
|
err |= sendCommand(0x3F); // 1/64 duty cycle setup
|
||||||
|
err |= sendCommand(0xD3); // Set display offset
|
||||||
|
err |= sendCommand(0x00); // No offset
|
||||||
|
err |= sendCommand(0x40); // Set start line at 0
|
||||||
|
err |= sendCommand(0x8D); // Charge pump configuration setup
|
||||||
|
err |= sendCommand(0x14); // Enable charge pump power rails
|
||||||
|
err |= sendCommand(0x20); // Set memory addressing mode profile
|
||||||
|
err |= sendCommand(0x02); // Page addressing mode (highly predictable translation)
|
||||||
|
err |= sendCommand(0xA1); // Segment re-map columns configuration matrix
|
||||||
|
err |= sendCommand(0xC8); // COM Output scan direction setting execution
|
||||||
|
err |= sendCommand(0xDA); // COM pins hardware structural layout profile
|
||||||
|
err |= sendCommand(0x12);
|
||||||
|
err |= sendCommand(0x81); // Contrast configuration mapping setting
|
||||||
|
err |= sendCommand(0x7F);
|
||||||
|
err |= sendCommand(0xA4); // Resume display to RAM mapping entries
|
||||||
|
err |= sendCommand(0xA6); // Normal display output formatting selection profile
|
||||||
|
|
||||||
|
// High-Speed Screen Clear on startup to guarantee zero RAM noise artifacts
|
||||||
for (uint8_t page = 0; page < 8; page++) {
|
for (uint8_t page = 0; page < 8; page++) {
|
||||||
sendCommand(0xB0 + page); // Set page address
|
err |= sendCommand(static_cast<uint8_t>(0xB0 + page)); // Set page boundary
|
||||||
sendCommand(0x00); // Set lower column address
|
err |= sendCommand(0x00); // Reset lower column pointer
|
||||||
sendCommand(0x10); // Set higher column address
|
err |= sendCommand(0x10); // Reset higher column pointer
|
||||||
|
|
||||||
// FIXED: Replaced ESP_ERROR_CHECK with safe error-handling to prevent software panic crashes
|
// Allocate high-speed contiguous flash transmission block buffer (1 control byte + 128 data bytes)
|
||||||
esp_err_t ret = i2c_master_transmit(dev_handle, clear_buf, sizeof(clear_buf), pdMS_TO_TICKS(50));
|
uint8_t clearBuffer[129] = {0};
|
||||||
if (ret != ESP_OK) {
|
clearBuffer[0] = 0x40; // Enforce explicit Data Mode streaming identifier token
|
||||||
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
|
// The remaining 128 bytes are already natively initialized to 0x00 via the aggregate initializer {0}
|
||||||
|
if (m_devHandle != nullptr) {
|
||||||
|
err |= i2c_master_transmit(m_devHandle, clearBuffer, sizeof(clearBuffer), 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "SSD1306 OLED Controller initialized and screen cleared.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
err |= sendCommand(0xAF); // Wake up physical controller panel (display ON)
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Instantiates the static FreeRTOS messaging pipeline and deploys the background consumer thread onto Core 0.
|
||||||
|
* @param priority Task priority rank assignment metric handled by the scheduler.
|
||||||
|
*/
|
||||||
void DisplayController::startTask(uint32_t priority) {
|
void DisplayController::startTask(uint32_t priority) {
|
||||||
initI2C();
|
// Allocate the static thread-safe communication data path
|
||||||
initSSD1306();
|
m_msgQueue = xQueueCreate(8, sizeof(DisplayMessage));
|
||||||
|
if (m_msgQueue == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Critical error: Could not allocate memory for display communication queue!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Create the message queue for up to 10 incoming text packages
|
// Deploy onto Core 0 alongside the terminal pipeline to keep Core 1 free for LoRa scheduling
|
||||||
m_msgQueue = xQueueCreate(10, sizeof(DisplayMessage));
|
|
||||||
|
|
||||||
// Spawn the worker thread on Core 0 (keeps Core 1 completely free for your Mesh engine)
|
|
||||||
xTaskCreatePinnedToCore(
|
xTaskCreatePinnedToCore(
|
||||||
DisplayController::taskTrampoline,
|
DisplayController::taskTrampoline,
|
||||||
"OLED_RenderTask",
|
"OLED_WorkerTask",
|
||||||
3584,
|
4096,
|
||||||
this,
|
this,
|
||||||
priority,
|
priority,
|
||||||
nullptr,
|
nullptr,
|
||||||
0 // Pinned to Core 0
|
0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
bool DisplayController::printMessage(const char* text, int row) {
|
* @brief Safe cross-core interface dispatching a text block onto the internal processing pipeline buffer.
|
||||||
if (m_msgQueue == nullptr) return false;
|
* @param text Pointer to a standard null-terminated sequence of tracking information.
|
||||||
|
* @param line Selected hardware page index mapping from 0 to 7.
|
||||||
|
*/
|
||||||
|
void DisplayController::printMessage(const char* text, uint8_t line) {
|
||||||
|
if (m_msgQueue == nullptr || text == nullptr) return;
|
||||||
|
|
||||||
DisplayMessage msg = {};
|
DisplayMessage msg = {};
|
||||||
strncpy(msg.text, text, sizeof(msg.text) - 1);
|
msg.targetLine = line;
|
||||||
msg.row = row;
|
|
||||||
|
|
||||||
// Non-blocking write into the queue from any external task thread
|
// Thread-safe copy buffer sanitization sequence
|
||||||
return (xQueueSend(m_msgQueue, &msg, pdMS_TO_TICKS(10)) == pdPASS);
|
strncpy(msg.content, text, sizeof(msg.content) - 1);
|
||||||
|
msg.content[sizeof(msg.content) - 1] = '\0';
|
||||||
|
|
||||||
|
// Asynchronous non-blocking dispatch push (bails instantly if full to prevent lockups)
|
||||||
|
xQueueSend(m_msgQueue, &msg, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Static trampoline layer connecting the native C-based FreeRTOS scheduler directly back to our C++ runtime context.
|
||||||
|
* @param pvParameters Raw pointer mapping the reference context of the current instance tracking space.
|
||||||
|
*/
|
||||||
void DisplayController::taskTrampoline(void* pvParameters) {
|
void DisplayController::taskTrampoline(void* pvParameters) {
|
||||||
auto* instance = static_cast<DisplayController*>(pvParameters);
|
auto* instance = static_cast<DisplayController*>(pvParameters);
|
||||||
if (instance != nullptr) {
|
if (instance != nullptr) {
|
||||||
@@ -138,30 +281,94 @@ void DisplayController::taskTrampoline(void* pvParameters) {
|
|||||||
vTaskDelete(nullptr);
|
vTaskDelete(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Autonomous main task routing layer parsing compound UTF-8 string inputs and enforcing real-time automatic end-of-line wiping.
|
||||||
|
*/
|
||||||
void DisplayController::run() {
|
void DisplayController::run() {
|
||||||
DisplayMessage incomingMsg;
|
// Perform hardware initialization safely encapsulated inside the unique thread sequence boundary
|
||||||
ESP_LOGI(TAG, "OLED Core Engine operational loop active.");
|
esp_err_t init_status = initializeHardware();
|
||||||
|
if (init_status != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Hardware setup failed completely. Execution layer aborted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Dynamic boot screen utilizing configuration metadata
|
ESP_LOGI(TAG, "SSD1306 display initialized and listening for incoming render packages.");
|
||||||
printMessage("====================", 0); // Row 0: Top Frame
|
|
||||||
|
|
||||||
// Format the title and version cleanly into Row 1
|
DisplayMessage incoming = {};
|
||||||
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) {
|
while (true) {
|
||||||
// Deep sleep state: Task hits 0% CPU usage until a message arrives in the queue
|
// Asynchronous structural block yield: consumes zero CPU until a new message package arrives
|
||||||
if (xQueueReceive(m_msgQueue, &incomingMsg, portMAX_DELAY) == pdPASS) {
|
if (xQueueReceive(m_msgQueue, &incoming, portMAX_DELAY) == pdTRUE) {
|
||||||
// Safe instrumentation logging (simulating the physical pixels)
|
if (incoming.targetLine > 7) incoming.targetLine = 7;
|
||||||
ESP_LOGI(TAG, "[OLED ROW %d]: %s", incomingMsg.row, incomingMsg.text);
|
|
||||||
|
// Positioning the internal hardware pointer inside the target page boundary
|
||||||
|
sendCommand(static_cast<uint8_t>(0xB0 + incoming.targetLine)); // Set page address boundary
|
||||||
|
sendCommand(0x00); // Set lower column pointer
|
||||||
|
sendCommand(0x10); // Set higher column pointer
|
||||||
|
uint8_t totalBytesWritten = 0;
|
||||||
|
|
||||||
|
// Basic built-in 5x7 ASCII micro character font generation layer
|
||||||
|
// This prevents adding bloating third party render dependencies into your tight stack space
|
||||||
|
size_t textLength = strlen(incoming.content);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < textLength; i++) {
|
||||||
|
uint8_t c_primary = static_cast<uint8_t>(incoming.content[i]);
|
||||||
|
size_t fontIndex = 0;
|
||||||
|
bool validCharacter = false;
|
||||||
|
|
||||||
|
// Basic UTF-8 multi-byte decoder for native German Umlaute translation
|
||||||
|
if (c_primary == 0xC3 && (i + 1) < textLength) {
|
||||||
|
uint8_t c_secondary = static_cast<uint8_t>(incoming.content[i + 1]);
|
||||||
|
i++; // Advance the pointer to consume the compound UTF-8 token pair
|
||||||
|
|
||||||
|
if (c_secondary == 0x84) { fontIndex = 95; validCharacter = true; } // Ä
|
||||||
|
if (c_secondary == 0x96) { fontIndex = 96; validCharacter = true; } // Ö
|
||||||
|
if (c_secondary == 0x9C) { fontIndex = 97; validCharacter = true; } // Ü
|
||||||
|
if (c_secondary == 0xA4) { fontIndex = 98; validCharacter = true; } // ä
|
||||||
|
if (c_secondary == 0xB6) { fontIndex = 99; validCharacter = true; } // ö
|
||||||
|
if (c_secondary == 0xBC) { fontIndex = 100; validCharacter = true; } // ü
|
||||||
|
}
|
||||||
|
else if (c_primary == 0xC2 && (i + 1) < textLength) {
|
||||||
|
uint8_t c_secondary = static_cast<uint8_t>(incoming.content[i + 1]);
|
||||||
|
i++;
|
||||||
|
if (c_secondary == 0x9F) { fontIndex = 101; validCharacter = true; } // ß
|
||||||
|
}
|
||||||
|
else if (c_primary >= 32 && c_primary <= 126) {
|
||||||
|
fontIndex = static_cast<size_t>(c_primary - 32);
|
||||||
|
validCharacter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validCharacter && totalBytesWritten < 122) {
|
||||||
|
uint8_t txBuffer[7] = {0};
|
||||||
|
txBuffer[0] = 0x40; // Data identifier
|
||||||
|
|
||||||
|
txBuffer[1] = font_5x7[fontIndex][0];
|
||||||
|
txBuffer[2] = font_5x7[fontIndex][1];
|
||||||
|
txBuffer[3] = font_5x7[fontIndex][2];
|
||||||
|
txBuffer[4] = font_5x7[fontIndex][3];
|
||||||
|
txBuffer[5] = font_5x7[fontIndex][4];
|
||||||
|
txBuffer[6] = 0x00; // 1-pixel spacing between characters
|
||||||
|
|
||||||
|
if (m_devHandle != nullptr) {
|
||||||
|
i2c_master_transmit(m_devHandle, txBuffer, sizeof(txBuffer), 50);
|
||||||
|
}
|
||||||
|
totalBytesWritten = static_cast<uint8_t>(totalBytesWritten + 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AUTO-CLEAR: Fill the remaining horizontal page width with blank pixels to flush out trailing leftovers
|
||||||
|
if (totalBytesWritten < 128) {
|
||||||
|
uint8_t remainingBytes = static_cast<uint8_t>(128 - totalBytesWritten);
|
||||||
|
|
||||||
|
// Allocate a burst buffer with dynamic sizing plus control byte room
|
||||||
|
uint8_t flushBuffer[129] = {0};
|
||||||
|
flushBuffer[0] = 0x40; // Data mode identifier
|
||||||
|
|
||||||
|
if (m_devHandle != nullptr) {
|
||||||
|
// Send out the precise amount of zeros required to wipe the right margin clean
|
||||||
|
i2c_master_transmit(m_devHandle, flushBuffer, static_cast<size_t>(remainingBytes + 1), 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+41
-27
@@ -1,42 +1,56 @@
|
|||||||
#ifndef DISPLAY_HPP
|
/**
|
||||||
#define DISPLAY_HPP
|
* @file display.hpp
|
||||||
|
* @brief Thread-safe asynchronous SSD1306 OLED display controller for FoxMesh
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "i2c.hpp"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/queue.h"
|
#include "freertos/queue.h"
|
||||||
#include "freertos/task.h"
|
|
||||||
|
|
||||||
class DisplayController {
|
class DisplayController {
|
||||||
public:
|
public:
|
||||||
// Structure for the thread-safe communication queue
|
explicit DisplayController(i2c* bus_pointer);
|
||||||
struct DisplayMessage {
|
virtual ~DisplayController();
|
||||||
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();
|
// Disallow copying to prevent duplicate hardware resource assignment
|
||||||
virtual ~DisplayController() = default;
|
|
||||||
|
|
||||||
// Deleted copy constructors for hardware safety
|
|
||||||
DisplayController(const DisplayController&) = delete;
|
DisplayController(const DisplayController&) = delete;
|
||||||
DisplayController& operator=(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);
|
* @brief Spawns the internal FreeRTOS queue consumer task on Core 0.
|
||||||
|
* @param priority Scheduler task priority level.
|
||||||
|
*/
|
||||||
|
void startTask(uint32_t priority = 3);
|
||||||
|
|
||||||
// Thread-safe public method that other tasks call to print text
|
/**
|
||||||
bool printMessage(const char* text, int row);
|
* @brief Thread-safe non-blocking method to print a message to the OLED display.
|
||||||
|
* @param text Null-terminated string message (maximum 20 characters for simple lines).
|
||||||
|
* @param line Destination line identifier index (0 to 7 for page addressing mode).
|
||||||
|
*/
|
||||||
|
void printMessage(const char* text, uint8_t line);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const char* TAG;
|
// Internal structure mapping the safe asynchronous data pipeline
|
||||||
QueueHandle_t m_msgQueue;
|
struct DisplayMessage {
|
||||||
|
char content[24];
|
||||||
void initI2C();
|
uint8_t targetLine;
|
||||||
void initSSD1306();
|
|
||||||
void sendCommand(uint8_t command);
|
|
||||||
|
|
||||||
// FreeRTOS C++ bridging mechanisms
|
|
||||||
static void taskTrampoline(void* pvParameters);
|
|
||||||
void run();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DISPLAY_HPP
|
i2c* m_bus;
|
||||||
|
i2c_master_dev_handle_t m_devHandle = nullptr;
|
||||||
|
QueueHandle_t m_msgQueue = nullptr;
|
||||||
|
|
||||||
|
static void taskTrampoline(void* pvParameters);
|
||||||
|
void run();
|
||||||
|
|
||||||
|
// Hardware-level low-overhead command layer execution abstraction
|
||||||
|
esp_err_t sendCommand(uint8_t cmd) const;
|
||||||
|
esp_err_t initializeHardware();
|
||||||
|
};
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* @file i2c.cpp
|
||||||
|
* @brief Thread-safe I2C Master Bus Implementation for FoxMesh
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "i2c.hpp"
|
||||||
|
#include <cstdio>
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
static const char* TAG = "I2C";
|
||||||
|
|
||||||
|
i2c::i2c(int sda_pin, int scl_pin, uint32_t clk_speed_hz) {
|
||||||
|
i2c_master_bus_config_t bus_config = {};
|
||||||
|
bus_config.i2c_port = I2C_NUM_0;
|
||||||
|
bus_config.sda_io_num = static_cast<gpio_num_t>(sda_pin); // FIXED: Added explicit typecast to gpio_num_t
|
||||||
|
bus_config.scl_io_num = static_cast<gpio_num_t>(scl_pin); // FIXED: Added explicit typecast to gpio_num_t
|
||||||
|
bus_config.clk_source = I2C_CLK_SRC_DEFAULT;
|
||||||
|
bus_config.glitch_ignore_cnt = 7;
|
||||||
|
bus_config.flags.enable_internal_pullup = 1;
|
||||||
|
|
||||||
|
// Allocate resources for the new infrastructure-driven master driver
|
||||||
|
esp_err_t err = i2c_new_master_bus(&bus_config, &_bus_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to initialize I2C Master Bus: %s", esp_err_to_name(err));
|
||||||
|
_bus_handle = nullptr;
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "I2C Master Bus initialized successfully.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c::~i2c() {
|
||||||
|
if (_bus_handle != nullptr) {
|
||||||
|
i2c_del_master_bus(_bus_handle);
|
||||||
|
_bus_handle = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2c::scan() const {
|
||||||
|
if (_bus_handle == nullptr) {
|
||||||
|
printf("Error: I2C bus handle is null. Cannot scan.\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
||||||
|
printf("00: ");
|
||||||
|
|
||||||
|
int devices_found = 0;
|
||||||
|
|
||||||
|
// Standard I2C address space ranges from 0x03 to 0x77
|
||||||
|
for (uint8_t addr = 0x03; addr < 0x78; addr++) {
|
||||||
|
if (addr % 16 == 0) {
|
||||||
|
printf("\r\n%02x:", addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use low-overhead hardware probing instead of allocating permanent device handles
|
||||||
|
esp_err_t err = i2c_master_probe(_bus_handle, addr, 100);
|
||||||
|
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
printf(" %02x", addr);
|
||||||
|
devices_found++;
|
||||||
|
} else if (err == ESP_ERR_NOT_FOUND) {
|
||||||
|
printf(" --");
|
||||||
|
} else {
|
||||||
|
printf(" Er"); // Bus lockup, collision or framing timeout error occurred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\r\n\r\nScan finished. Found %d device(s).\r\n", devices_found);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @file i2c.hpp
|
||||||
|
* @brief Thread-safe I2C Master Bus Wrapper for FoxMesh
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
class i2c {
|
||||||
|
public:
|
||||||
|
// Constructor initializes GPIO pins and configures the native ESP-IDF bus
|
||||||
|
i2c(int sda_pin, int scl_pin, uint32_t clk_speed_hz);
|
||||||
|
~i2c();
|
||||||
|
|
||||||
|
// Disable copy semantics to prevent duplicate hardware resource assignment
|
||||||
|
i2c(const i2c&) = delete;
|
||||||
|
i2c& operator=(const i2c&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scans the network space for active I2C addresses.
|
||||||
|
*
|
||||||
|
* Probes the hardware address space from 0x03 to 0x77 using low-overhead
|
||||||
|
* commands and prints a formatted matrix directly to stdout.
|
||||||
|
*/
|
||||||
|
void scan() const;
|
||||||
|
|
||||||
|
// Public getter to expose the low-level bus handle to concrete peripheral drivers
|
||||||
|
i2c_master_bus_handle_t get_bus_handle() const { return _bus_handle; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
i2c_master_bus_handle_t _bus_handle = nullptr;
|
||||||
|
};
|
||||||
+70
-19
@@ -1,57 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* @file led.cpp
|
||||||
|
* @brief Thread-safe hardware LED controller task implementation with profile selection
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "led.hpp"
|
#include "led.hpp"
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
#include <climits> // Enforces definitions of macro bounds like ULONG_MAX
|
||||||
|
|
||||||
// Initialize the static log tag
|
|
||||||
const char* LedController::TAG = "LED";
|
const char* LedController::TAG = "LED";
|
||||||
|
|
||||||
LedController::LedController(gpio_num_t gpioPin) : m_gpioPin(gpioPin) {
|
LedController::LedController(gpio_num_t gpioPin) : m_gpioPin(gpioPin) {
|
||||||
// Hardware initialization inside the constructor guarantees safe state upon creation
|
// Core default profile initialized using standard configurations
|
||||||
|
m_currentProfile.intervalMs = static_cast<uint32_t>(Config::BLINK_INTERVAL_MS);
|
||||||
|
m_currentProfile.pulseWidthMs = 30;
|
||||||
|
|
||||||
|
// Instantiate a lightweight mutex token to guarantee strict isolation
|
||||||
|
m_mutexHandle = xSemaphoreCreateMutex();
|
||||||
|
if (m_mutexHandle == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Fatal: Failed to allocate mutex tracking resources.");
|
||||||
|
}
|
||||||
|
|
||||||
gpio_reset_pin(m_gpioPin);
|
gpio_reset_pin(m_gpioPin);
|
||||||
gpio_set_direction(m_gpioPin, GPIO_MODE_OUTPUT);
|
gpio_set_direction(m_gpioPin, GPIO_MODE_OUTPUT);
|
||||||
ESP_LOGI(TAG, "Hardware initialized on GPIO %d.", m_gpioPin);
|
ESP_LOGI(TAG, "Hardware initialized on GPIO %d.", m_gpioPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LedController::~LedController() {
|
||||||
|
if (m_mutexHandle != nullptr) {
|
||||||
|
vSemaphoreDelete(m_mutexHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LedController::startTask(uint32_t priority, BaseType_t coreId) {
|
void LedController::startTask(uint32_t priority, BaseType_t coreId) {
|
||||||
// Pass 'this' (the pointer to the current instance) as the task parameter
|
|
||||||
xTaskCreatePinnedToCore(
|
xTaskCreatePinnedToCore(
|
||||||
LedController::taskTrampoline,
|
LedController::taskTrampoline,
|
||||||
"LEDTask",
|
"LEDTask",
|
||||||
3072,
|
3072,
|
||||||
this,
|
this,
|
||||||
priority,
|
priority,
|
||||||
nullptr,
|
&m_taskHandle,
|
||||||
coreId
|
coreId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LedController::updateProfile(const BlinkProfile& profile) {
|
||||||
|
if (profile.pulseWidthMs >= profile.intervalMs) {
|
||||||
|
ESP_LOGE(TAG, "Profile rejection: pulseWidthMs must be smaller than intervalMs!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely block writing operations across separate core domains until token release
|
||||||
|
if (m_mutexHandle != nullptr && xSemaphoreTake(m_mutexHandle, portMAX_DELAY) == pdTRUE) {
|
||||||
|
m_currentProfile = profile;
|
||||||
|
xSemaphoreGive(m_mutexHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interrupt the active blocking state of the worker thread instantly
|
||||||
|
if (m_taskHandle != nullptr) {
|
||||||
|
xTaskNotifyGive(m_taskHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LedController::taskTrampoline(void* pvParameters) {
|
void LedController::taskTrampoline(void* pvParameters) {
|
||||||
// Cast the raw object pointer back to our class instance
|
|
||||||
auto* instance = static_cast<LedController*>(pvParameters);
|
auto* instance = static_cast<LedController*>(pvParameters);
|
||||||
if (instance != nullptr) {
|
if (instance != nullptr) {
|
||||||
instance->run();
|
instance->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety fallback: FreeRTOS tasks must never return natively
|
|
||||||
vTaskDelete(nullptr);
|
vTaskDelete(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LedController::run() {
|
void LedController::run() {
|
||||||
ESP_LOGI(TAG, "FreeRTOS instance task successfully started on Core %d.", xPortGetCoreID());
|
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) {
|
while (true) {
|
||||||
gpio_set_level(m_gpioPin, 1); // LED on (High)
|
BlinkProfile active = {};
|
||||||
vTaskDelay(pdMS_TO_TICKS(led_on_ms)); // 30ms pulse width
|
|
||||||
|
|
||||||
gpio_set_level(m_gpioPin, 0); // LED off (Low)
|
// Extract a thread-safe snapshot configuration boundary under lock condition
|
||||||
vTaskDelay(pdMS_TO_TICKS(Config::BLINK_INTERVAL_MS - led_on_ms)); // typ. 1970ms period low time
|
if (m_mutexHandle != nullptr && xSemaphoreTake(m_mutexHandle, portMAX_DELAY) == pdTRUE) {
|
||||||
|
active = m_currentProfile;
|
||||||
|
xSemaphoreGive(m_mutexHandle);
|
||||||
|
} else {
|
||||||
|
// Safe hardware fallback calculation loops
|
||||||
|
active.intervalMs = 1000;
|
||||||
|
active.pulseWidthMs = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active.pulseWidthMs == 0) active.pulseWidthMs = 1;
|
||||||
|
if (active.pulseWidthMs >= active.intervalMs) active.intervalMs = active.pulseWidthMs + 10;
|
||||||
|
|
||||||
|
// 1. High pulse execution phase
|
||||||
|
gpio_set_level(m_gpioPin, 1);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(active.pulseWidthMs));
|
||||||
|
gpio_set_level(m_gpioPin, 0);
|
||||||
|
|
||||||
|
// 2. Dynamic evaluation of low-state processing window
|
||||||
|
uint32_t remainingLowTimeMs = active.intervalMs - active.pulseWidthMs;
|
||||||
|
|
||||||
|
// Non-polling block sleep that awakes immediately upon configuration adjustments
|
||||||
|
xTaskNotifyWait(0x00, ULONG_MAX, nullptr, pdMS_TO_TICKS(remainingLowTimeMs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+41
-13
@@ -1,33 +1,61 @@
|
|||||||
#ifndef LED_HPP
|
/**
|
||||||
#define LED_HPP
|
* @file led.hpp
|
||||||
|
* @brief Thread-safe hardware LED controller with dynamic profile configuration
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
|
||||||
class LedController {
|
class LedController {
|
||||||
public:
|
public:
|
||||||
// Constructor allows passing the GPIO pin (defaults to 25 as requested)
|
// Compound structure holding the full runtime timing behavior profiles
|
||||||
|
struct BlinkProfile {
|
||||||
|
uint32_t intervalMs; // Total period duration of one cycle
|
||||||
|
uint32_t pulseWidthMs; // Active high execution duty duration (on-time)
|
||||||
|
};
|
||||||
|
|
||||||
explicit LedController(gpio_num_t gpioPin = GPIO_NUM_25);
|
explicit LedController(gpio_num_t gpioPin = GPIO_NUM_25);
|
||||||
|
virtual ~LedController();
|
||||||
|
|
||||||
// Destructor (virtual in case of inheritance)
|
// Disallow copying to prevent multiple runtime instances controlling the same hardware rail
|
||||||
virtual ~LedController() = default;
|
|
||||||
|
|
||||||
// Disallow copying to prevent multiple instances controlling the same hardware pin
|
|
||||||
LedController(const LedController&) = delete;
|
LedController(const LedController&) = delete;
|
||||||
LedController& operator=(const LedController&) = delete;
|
LedController& operator=(const LedController&) = delete;
|
||||||
|
|
||||||
// Spawns the FreeRTOS task pinned to a specific core
|
/**
|
||||||
|
* @brief Spawns the internal FreeRTOS worker thread pinned to a specific core.
|
||||||
|
* @param priority Scheduler task priority level.
|
||||||
|
* @param coreId Destination CPU core ID anchor (0 or 1).
|
||||||
|
*/
|
||||||
void startTask(uint32_t priority = 5, BaseType_t coreId = 1);
|
void startTask(uint32_t priority = 5, BaseType_t coreId = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread-safe atomic updates to configuration parameters from any thread core.
|
||||||
|
* @param profile Target values mapping the new timing configuration layers.
|
||||||
|
*/
|
||||||
|
void updateProfile(const BlinkProfile& profile);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const gpio_num_t m_gpioPin;
|
const gpio_num_t m_gpioPin;
|
||||||
static const char* TAG;
|
static const char* TAG;
|
||||||
|
|
||||||
// Static trampoline function required by the FreeRTOS scheduler
|
// Track the internal task handle to allow cross-thread signaling via notifications
|
||||||
static void taskTrampoline(void* pvParameters);
|
TaskHandle_t m_taskHandle = nullptr;
|
||||||
|
|
||||||
// Dynamic instance method where the actual blinking loop lives
|
// Mutex lock handle safeguarding cross-core profile modifications
|
||||||
|
SemaphoreHandle_t m_mutexHandle = nullptr;
|
||||||
|
|
||||||
|
// Internal state profile configuration storage cache
|
||||||
|
BlinkProfile m_currentProfile;
|
||||||
|
|
||||||
|
static void taskTrampoline(void* pvParameters);
|
||||||
void run();
|
void run();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LED_HPP
|
|
||||||
+47
-24
@@ -1,3 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @brief Main application entry point for the FoxMesh tracking node
|
||||||
|
* @author OM Tom (OE3TKT)
|
||||||
|
*
|
||||||
|
* @license SPDX-License-Identifier: MIT
|
||||||
|
* @copyright Copyright (c) 2026 OM Tom (OE3TKT)
|
||||||
|
* For the full license terms, see the LICENSE file in the project root.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
@@ -7,18 +17,26 @@
|
|||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "led.hpp"
|
#include "led.hpp"
|
||||||
#include "cli.hpp"
|
#include "cli.hpp"
|
||||||
#include "display.hpp" // thread-safe display
|
#include "i2c.hpp"
|
||||||
|
#include "display.hpp" // FIXED: Added missing header for DisplayController
|
||||||
|
|
||||||
static const char *TAG = "MAIN";
|
static const char *TAG = "MAIN";
|
||||||
|
|
||||||
// Global singleton instances for hardware abstraction
|
// Global pointer to the thread-safe hardware abstraction instances
|
||||||
static DisplayController oled;
|
i2c* i2c_bus = nullptr;
|
||||||
|
DisplayController* display = nullptr;
|
||||||
|
|
||||||
|
// Global singleton instances for hardware abstraction layers
|
||||||
static LedController led(Config::LED_GPIO_PIN);
|
static LedController led(Config::LED_GPIO_PIN);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the physical SPI peripheral for the Semtech SX1276 transceiver.
|
||||||
|
*
|
||||||
|
* Configures VSPI (SPI3_HOST) using strict compile-time configurations from config.hpp.
|
||||||
|
*/
|
||||||
void init_lora_spi(void) {
|
void init_lora_spi(void) {
|
||||||
esp_err_t ret;
|
esp_err_t ret;
|
||||||
|
|
||||||
// Configure the SPI structure using the strict C++ constexpr constants
|
|
||||||
spi_bus_config_t buscfg = {};
|
spi_bus_config_t buscfg = {};
|
||||||
buscfg.mosi_io_num = Config::LORA_MOSI_PIN;
|
buscfg.mosi_io_num = Config::LORA_MOSI_PIN;
|
||||||
buscfg.miso_io_num = Config::LORA_MISO_PIN;
|
buscfg.miso_io_num = Config::LORA_MISO_PIN;
|
||||||
@@ -27,7 +45,7 @@ void init_lora_spi(void) {
|
|||||||
buscfg.quadhd_io_num = -1;
|
buscfg.quadhd_io_num = -1;
|
||||||
buscfg.max_transfer_sz = 0;
|
buscfg.max_transfer_sz = 0;
|
||||||
|
|
||||||
// Initialize the physical peripheral (VSPI / SPI3 on ESP32)
|
// Initialize the physical peripheral bus with automatic DMA channel assignment
|
||||||
ret = spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
ret = spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
|
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
|
||||||
@@ -36,9 +54,10 @@ void init_lora_spi(void) {
|
|||||||
ESP_LOGI(TAG, "SPI bus initialized successfully on VSPI (SPI3)");
|
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
|
// Global application anchor marked as extern "C" for the native ESP-IDF bootloader
|
||||||
extern "C" void app_main(void) {
|
extern "C" {
|
||||||
// Dynamic string formatting using type-safe constants
|
void app_main(void) {
|
||||||
|
// Output system banner configuration parameters on boot
|
||||||
ESP_LOGI(TAG, "=================================================");
|
ESP_LOGI(TAG, "=================================================");
|
||||||
ESP_LOGI(TAG, " Booting %s (Version: %s) via ESP-IDF C++",
|
ESP_LOGI(TAG, " Booting %s (Version: %s) via ESP-IDF C++",
|
||||||
Config::PROJECT_TITLE,
|
Config::PROJECT_TITLE,
|
||||||
@@ -46,32 +65,36 @@ extern "C" void app_main(void) {
|
|||||||
ESP_LOGI(TAG, " (c) Authors: %s", Config::PROJECT_AUTHORS);
|
ESP_LOGI(TAG, " (c) Authors: %s", Config::PROJECT_AUTHORS);
|
||||||
ESP_LOGI(TAG, "=================================================");
|
ESP_LOGI(TAG, "=================================================");
|
||||||
|
|
||||||
// Explicitly power up the onboard peripherals on TTGO T3
|
// Launch background blink task
|
||||||
// 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();
|
led.startTask();
|
||||||
|
|
||||||
|
// Dynamically instantiate the synchronized I2C master bus
|
||||||
|
i2c_bus = new i2c(Config::OLED_SDA_PIN, Config::OLED_SCL_PIN, Config::OLED_I2C_FREQ_HZ);
|
||||||
|
|
||||||
|
// Instantiate and activate the isolated background render worker on Core 0
|
||||||
|
display = new DisplayController(i2c_bus);
|
||||||
|
display->startTask();
|
||||||
|
|
||||||
|
// Pass the bus instance dependency to the interactive terminal layer
|
||||||
|
cli_init(i2c_bus, &led, display);
|
||||||
|
|
||||||
|
// Initialize the Semtech LoRa SPI connection layer
|
||||||
|
init_lora_spi();
|
||||||
|
|
||||||
|
// Spin up the background console interface thread on Core 0
|
||||||
start_cli_task();
|
start_cli_task();
|
||||||
|
|
||||||
oled.startTask();
|
// Trigger synchronous welcome splash screen frame
|
||||||
|
display->printMessage("FoxMesh Ready", 0);
|
||||||
|
|
||||||
|
// Main system watchdog and status monitor loop
|
||||||
// Synchronous test push to the display queue
|
|
||||||
// oled.printMessage("Mesh Scanning...", 2);
|
|
||||||
|
|
||||||
// Sytem Monitor Loop
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ESP_LOGI(TAG, "[%s v%s] Operational. Free Heap: %lu bytes",
|
ESP_LOGI(TAG, "[%s v%s] Operational. Free Heap: %lu bytes",
|
||||||
Config::PROJECT_TITLE,
|
Config::PROJECT_TITLE,
|
||||||
Config::PROJECT_VERSION,
|
Config::PROJECT_VERSION,
|
||||||
(unsigned long)esp_get_free_heap_size());
|
(unsigned long)esp_get_free_heap_size());
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
In neuem Issue referenzieren
Einen Benutzer sperren