Merge pull request #8101 from Links2004/reduce_cpu_load

reduce cpu load by optimizing OSThread runOnce calls
This commit is contained in:
Ben Meadors 2025-09-25 16:29:42 -05:00 committed by GitHub
commit c65dbe490e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 78 additions and 47 deletions

View File

@ -36,6 +36,7 @@ build_flags =
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
-DSERIAL_BUFFER_SIZE=4096 -DSERIAL_BUFFER_SIZE=4096
-DSERIAL_HAS_ON_RECEIVE
-DLIBPAX_ARDUINO -DLIBPAX_ARDUINO
-DLIBPAX_WIFI -DLIBPAX_WIFI
-DLIBPAX_BLE -DLIBPAX_BLE

View File

@ -56,6 +56,7 @@ build_flags = -Wno-missing-field-initializers
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-D OLED_PL=1 #-D OLED_PL=1
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs #-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
#-D DEBUG_LOOP_TIMING=1 ; uncomment to add main loop timing logs
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = direct monitor_filters = direct

View File

@ -6,6 +6,14 @@
#include "configuration.h" #include "configuration.h"
#include "time.h" #include "time.h"
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
#define IS_USB_SERIAL
#ifdef SERIAL_HAS_ON_RECEIVE
#undef SERIAL_HAS_ON_RECEIVE
#endif
#include "HWCDC.h"
#endif
#ifdef RP2040_SLOW_CLOCK #ifdef RP2040_SLOW_CLOCK
#define Port Serial2 #define Port Serial2
#else #else
@ -22,7 +30,12 @@ SerialConsole *console;
void consoleInit() void consoleInit()
{ {
new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread auto sc = new SerialConsole(); // Must be dynamically allocated because we are now inheriting from thread
#if defined(SERIAL_HAS_ON_RECEIVE)
// onReceive does only exist for HardwareSerial not for USB CDC serial
Port.onReceive([sc]() { sc->rxInt(); });
#endif
DEBUG_PORT.rpInit(); // Simply sets up semaphore DEBUG_PORT.rpInit(); // Simply sets up semaphore
} }
@ -65,14 +78,21 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
int32_t SerialConsole::runOnce() int32_t SerialConsole::runOnce()
{ {
#ifdef HELTEC_MESH_SOLAR #ifdef HELTEC_MESH_SOLAR
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module. // After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port if (moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port &&
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) {
{
return 250; return 250;
} }
#endif #endif
return runOncePart();
int32_t delay = runOncePart();
#if defined(SERIAL_HAS_ON_RECEIVE)
return Port.available() ? delay : INT32_MAX;
#elif defined(IS_USB_SERIAL)
return HWCDC::isPlugged() ? delay : (1000 * 20);
#else
return delay;
#endif
} }
void SerialConsole::flush() void SerialConsole::flush()
@ -80,6 +100,18 @@ void SerialConsole::flush()
Port.flush(); Port.flush();
} }
// trigger tx of serial data
void SerialConsole::onNowHasData(uint32_t fromRadioNum)
{
setIntervalFromNow(0);
}
// trigger rx of serial data
void SerialConsole::rxInt()
{
setIntervalFromNow(0);
}
// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages // For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
bool SerialConsole::checkIsConnected() bool SerialConsole::checkIsConnected()
{ {

View File

@ -32,11 +32,14 @@ class SerialConsole : public StreamAPI, public RedirectablePrint, private concur
virtual int32_t runOnce() override; virtual int32_t runOnce() override;
void flush(); void flush();
void rxInt();
protected: protected:
/// Check the current underlying physical link to see if the client is currently connected /// Check the current underlying physical link to see if the client is currently connected
virtual bool checkIsConnected() override; virtual bool checkIsConnected() override;
virtual void onNowHasData(uint32_t fromRadioNum) override;
/// Possibly switch to protobufs if we see a valid protobuf message /// Possibly switch to protobufs if we see a valid protobuf message
virtual void log_to_serial(const char *logLevel, const char *format, va_list arg); virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
}; };

View File

@ -5,7 +5,7 @@
BuzzerFeedbackThread *buzzerFeedbackThread; BuzzerFeedbackThread *buzzerFeedbackThread;
BuzzerFeedbackThread::BuzzerFeedbackThread() : OSThread("BuzzerFeedback") BuzzerFeedbackThread::BuzzerFeedbackThread()
{ {
if (inputBroker) if (inputBroker)
inputObserver.observe(inputBroker); inputObserver.observe(inputBroker);
@ -19,10 +19,6 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
return 0; // Let other handlers process the event return 0; // Let other handlers process the event
} }
// Track last event time for potential future use
lastEventTime = millis();
needsUpdate = true;
// Handle different input events with appropriate buzzer feedback // Handle different input events with appropriate buzzer feedback
switch (event->inputEvent) { switch (event->inputEvent) {
case INPUT_BROKER_USER_PRESS: case INPUT_BROKER_USER_PRESS:
@ -61,15 +57,4 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
} }
return 0; // Allow other handlers to process the event return 0; // Allow other handlers to process the event
} }
int32_t BuzzerFeedbackThread::runOnce()
{
// This thread is primarily event-driven, but we can use runOnce
// for any periodic tasks if needed in the future
needsUpdate = false;
// Run every 100ms when active, less frequently when idle
return needsUpdate ? 100 : 1000;
}

View File

@ -4,7 +4,7 @@
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
#include "input/InputBroker.h" #include "input/InputBroker.h"
class BuzzerFeedbackThread : public concurrency::OSThread class BuzzerFeedbackThread
{ {
CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver = CallbackObserver<BuzzerFeedbackThread, const InputEvent *> inputObserver =
CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent); CallbackObserver<BuzzerFeedbackThread, const InputEvent *>(this, &BuzzerFeedbackThread::handleInputEvent);
@ -12,13 +12,6 @@ class BuzzerFeedbackThread : public concurrency::OSThread
public: public:
BuzzerFeedbackThread(); BuzzerFeedbackThread();
int handleInputEvent(const InputEvent *event); int handleInputEvent(const InputEvent *event);
protected:
virtual int32_t runOnce() override;
private:
uint32_t lastEventTime = 0;
bool needsUpdate = false;
}; };
extern BuzzerFeedbackThread *buzzerFeedbackThread; extern BuzzerFeedbackThread *buzzerFeedbackThread;

View File

@ -90,7 +90,9 @@ void OSThread::run()
if (heap < newHeap) if (heap < newHeap)
LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap); LOG_HEAP("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
#endif #endif
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("====== Thread next run in: %d", newDelay);
#endif
runned(); runned();
if (newDelay >= 0) if (newDelay >= 0)

View File

@ -274,7 +274,12 @@ int32_t ButtonThread::runOnce()
} }
} }
btnEvent = BUTTON_EVENT_NONE; btnEvent = BUTTON_EVENT_NONE;
return 50;
// only pull when the button is pressed, we get notified via IRQ on a new press
if (!userButton.isIdle() || waitingForLongPress) {
return 50;
}
return INT32_MAX;
} }
/* /*

View File

@ -1002,6 +1002,7 @@ void setup()
config.pullupSense = INPUT_PULLUP; config.pullupSense = INPUT_PULLUP;
config.intRoutine = []() { config.intRoutine = []() {
UserButtonThread->userButton.tick(); UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1022,6 +1023,7 @@ void setup()
touchConfig.pullupSense = pullup_sense; touchConfig.pullupSense = pullup_sense;
touchConfig.intRoutine = []() { touchConfig.intRoutine = []() {
TouchButtonThread->userButton.tick(); TouchButtonThread->userButton.tick();
TouchButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1041,6 +1043,7 @@ void setup()
cancelConfig.pullupSense = pullup_sense; cancelConfig.pullupSense = pullup_sense;
cancelConfig.intRoutine = []() { cancelConfig.intRoutine = []() {
CancelButtonThread->userButton.tick(); CancelButtonThread->userButton.tick();
CancelButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1061,6 +1064,7 @@ void setup()
backConfig.pullupSense = pullup_sense; backConfig.pullupSense = pullup_sense;
backConfig.intRoutine = []() { backConfig.intRoutine = []() {
BackButtonThread->userButton.tick(); BackButtonThread->userButton.tick();
BackButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1095,6 +1099,7 @@ void setup()
userConfig.pullupSense = pullup_sense; userConfig.pullupSense = pullup_sense;
userConfig.intRoutine = []() { userConfig.intRoutine = []() {
UserButtonThread->userButton.tick(); UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1112,6 +1117,7 @@ void setup()
userConfigNoScreen.pullupSense = pullup_sense; userConfigNoScreen.pullupSense = pullup_sense;
userConfigNoScreen.intRoutine = []() { userConfigNoScreen.intRoutine = []() {
UserButtonThread->userButton.tick(); UserButtonThread->userButton.tick();
UserButtonThread->setIntervalFromNow(0);
runASAP = true; runASAP = true;
BaseType_t higherWake = 0; BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake); mainDelay.interruptFromISR(&higherWake);
@ -1607,6 +1613,9 @@ void loop()
// We want to sleep as long as possible here - because it saves power // We want to sleep as long as possible here - because it saves power
if (!runASAP && loopCanSleep()) { if (!runASAP && loopCanSleep()) {
#ifdef DEBUG_LOOP_TIMING
LOG_DEBUG("main loop delay: %d", delayMsec);
#endif
mainDelay.delay(delayMsec); mainDelay.delay(delayMsec);
} }
} }

View File

@ -69,7 +69,7 @@ bool ascending = true;
#endif #endif
#define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000 #define EXT_NOTIFICATION_MODULE_OUTPUT_MS 1000
#define EXT_NOTIFICATION_DEFAULT_THREAD_MS 25 #define EXT_NOTIFICATION_FAST_THREAD_MS 25
#define ASCII_BELL 0x07 #define ASCII_BELL 0x07
@ -88,7 +88,7 @@ int32_t ExternalNotificationModule::runOnce()
if (!moduleConfig.external_notification.enabled) { if (!moduleConfig.external_notification.enabled) {
return INT32_MAX; // we don't need this thread here... return INT32_MAX; // we don't need this thread here...
} else { } else {
uint32_t delay = EXT_NOTIFICATION_MODULE_OUTPUT_MS;
bool isPlaying = rtttl::isPlaying(); bool isPlaying = rtttl::isPlaying();
#ifdef HAS_I2S #ifdef HAS_I2S
isPlaying = rtttl::isPlaying() || audioThread->isPlaying(); isPlaying = rtttl::isPlaying() || audioThread->isPlaying();
@ -116,21 +116,16 @@ int32_t ExternalNotificationModule::runOnce()
// If the output is turned on, turn it back off after the given period of time. // If the output is turned on, turn it back off after the given period of time.
if (isNagging) { if (isNagging) {
if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms delay = (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) < : EXT_NOTIFICATION_MODULE_OUTPUT_MS);
millis()) { if (externalTurnedOn[0] + delay < millis()) {
setExternalState(0, !getExternal(0)); setExternalState(0, !getExternal(0));
} }
if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms if (externalTurnedOn[1] + delay < millis()) {
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
setExternalState(1, !getExternal(1)); setExternalState(1, !getExternal(1));
} }
// Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL) // Only toggle buzzer output if not using PWM mode (to avoid conflict with RTTTL)
if (!moduleConfig.external_notification.use_pwm && if (!moduleConfig.external_notification.use_pwm && externalTurnedOn[2] + delay < millis()) {
externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
millis()) {
LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms, LOG_DEBUG("EXTERNAL 2 %d compared to %d", externalTurnedOn[2] + moduleConfig.external_notification.output_ms,
millis()); millis());
setExternalState(2, !getExternal(2)); setExternalState(2, !getExternal(2));
@ -181,6 +176,8 @@ int32_t ExternalNotificationModule::runOnce()
colorState = 1; colorState = 1;
} }
} }
// we need fast updates for the color change
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif #endif
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
@ -206,9 +203,11 @@ int32_t ExternalNotificationModule::runOnce()
// start the song again if we have time left // start the song again if we have time left
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
} }
// we need fast updates to play the RTTTL
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
} }
return EXT_NOTIFICATION_DEFAULT_THREAD_MS; return delay;
} }
} }

View File

@ -53,7 +53,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
hasChecked = true; hasChecked = true;
} }
return 100; // the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
return INT32_MAX;
} }
/** /**
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)