Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multithreading support for ESP32s #3310

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

# CI binaries
; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi
; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy to revert this part. I usually use platform_overrides. Is that the preferred method for adding new functionality?


# Release binaries
; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB
Expand Down Expand Up @@ -42,6 +42,7 @@ default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_
; default_envs = esp32s2_saola
; default_envs = esp32c3dev
; default_envs = lolin_s2_mini
default_envs = esp32dev_multi

src_dir = ./wled00
data_dir = ./wled00/data
Expand Down
17 changes: 17 additions & 0 deletions usermods/usermod_v2_background/platformio_override.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[env:esp32dev_multi]
extends = env:esp32dev
upload_speed = 460800
monitor_speed = 115200
build_flags = ${env:esp32dev.build_flags}
-D WLED_ENABLE_BACKGROUND
-D USERMOD_BACKGROUND_V2
; -D USERMOD_BACKGROUND_TDISPLAY ; For TTGO T-Display
; -D TFT_BRIGHTNESS = 100 ; Optional
; -D TFT_TIMEOUT = 10000 ; Optional

lib_deps =
${esp32.lib_deps}
; TFT_eSPI ; For TTGO T-Display

; board_build.partitions = tools/WLED_ESP32_4MB_MIN_SPIFFS.csv

36 changes: 36 additions & 0 deletions usermods/usermod_v2_background/usermod_v2_background.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include "wled.h"

class UsermodBackgroundDemo : public Usermod {
private:
int backgroundIndex = 0;
int mainIndex = 0;

public:
void setup() {
Serial.println("Setup - UsermodBackgroundDemo");
}

void doBackgroundWork(){
backgroundIndex++;
Serial.println("B: " + String(backgroundIndex));
}
Copy link
Collaborator

@softhack007 softhack007 Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please generally(!) avoid fastled EVERY_N_MILLIS as it does a very bad job .... for example, if the N_MILLIS time is missed by a single microsecond, it does nothing.

You might be able to utilize vTaskDelayUntil(). There are examples in the fourLineDisplay_ALT or audioreactive usermods.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought EVERY_N... was clean, I will move to vTaskDelayUntil, thank you for that.

Copy link
Collaborator

@softhack007 softhack007 Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The benefit of every_n_... is that it does not block as it avoids calling delay().
But you can easily substitute it with a few lines like this:

const unsigned long task1_period = 40;   // to run every 40 millisecond
static unsigned long task1_last_run = 0;   // "static" needed so the value is preserved
if ((millis() - task1_last_run) >= task1_period) { // activate after at least 40 millis have passed
   task1_last_run = millis();  // remember last runtime
   task1_do_work();              // do work
}
// do something else, or just wait

In contrast to 8266 or other small arduino platforms, on esp32 millis() and delay() do work as expected even when you are outside of the main looptask (i.e. in a seperate task). They are still "forbidden" for interrupt service routines (ISR).
vTaskDelayUntil() will suspend your main task until a specific time, its for "nothing to do" situations.


void backgroundLoop(){
EVERY_N_MILLISECONDS(5000){ doBackgroundWork(); }
}

void doMainWork(){
mainIndex++;
Serial.println("M: " + String(mainIndex));
}

void loop() {
EVERY_N_MILLISECONDS(1000) { doMainWork(); }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my previous comment

}

uint16_t getId() {
return USERMOD_ID_BACKGROUND;
}
};
250 changes: 250 additions & 0 deletions usermods/usermod_v2_background/usermod_v2_tdisplay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/*
REQUIRED SETUP

TFT_eSPI Library Adjustments (board selection)
You need to modify a file in the 'TFT_eSPI' library to select the correct board.
Locate the 'User_Setup_Select.h' file can be found in the '/.pio/libdeps/YOUR-BOARD/TFT_eSPI_ID1559' folder.

Modify the 'User_Setup_Select.h'
Comment out the following line
//#include <User_Setup.h> // Default setup is root library folder

Uncomment the following line
#include <User_Setups/Setup25_TTGO_T_Display.h> // Setup file for ESP32 and TTGO T-Display ST7789V SPI bus TFT
*/

#pragma once

#include "wled.h"
#include <TFT_eSPI.h>
#include <SPI.h>
#include "WiFi.h"
#include <Wire.h>

#define PRESET_NAME_BUFFER_SIZE 25

#define TFT_MOSI 19
#define TFT_SCLK 18
#define TFT_CS 5
#define TFT_DC 16
#define TFT_RST 23
#define TFT_BL 4 // Display backlight control pin
#define ADC_EN 14 // Used for enabling battery voltage measurements - not used in this program
#define TFT_CH 6

TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library

class UsermodBackgroundTDisplay : public Usermod {

private:
#ifdef TFT_BRIGHTNESS
uint16_t tftBrightness = TFT_BRIGHTNESS;
#else
// 0=OFF; 255=MAX
uint16_t tftBrightness = 50;
#endif

#ifdef TFT_TIMEOUT
uint16_t tftTimeout = TFT_TIMEOUT;
#else
uint16_t tftTimeout = 10000;
#endif

bool initDone = false;
bool needRedraw = true;

// TTGO T-Display
String knownSsid = "";
IPAddress knownIp;
uint8_t knownBrightness = 0;
uint8_t knownMode = 0;
uint8_t knownPalette = 0;
uint8_t tftcharwidth = 19; // Number of chars that fit on screen with text size set to 2
unsigned long tftNextTimeout = 0;

// strings to reduce flash memory usage (used more than twice)
static const char _strTag[];
static const char _strBrightness[];
static const char _strName[];
static const char _strTimeout[];

public:

void setBrightness(uint32_t newBrightness) {
ledcWrite(TFT_CH, newBrightness); // 0-15, 0-255 (with 8 bit resolution); 0=totally dark;255=totally shiny
}

void initDisplay(int rotation=3) {
pinMode(TFT_BL, OUTPUT);
ledcSetup(TFT_CH, 5000, 8); // 0-15, 5000, 8
ledcAttachPin(TFT_BL, TFT_CH); // TFT_BL, 0 - 15

tft.init();
tft.setRotation(rotation); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip.
tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
tft.setTextColor(TFT_WHITE);
tft.setCursor(1, 10);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(2);
tft.print("Init...");
}

void updateDisplay(){
// Check if values which are shown on display changed from the last time.
if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) {
needRedraw = true;
} else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) {
needRedraw = true;
} else if (knownBrightness != bri) {
needRedraw = true;
} else if (knownMode != strip.getMainSegment().mode) {
needRedraw = true;
} else if (knownPalette != strip.getMainSegment().palette) {
needRedraw = true;
}

if(tftNextTimeout < millis())
setBrightness(0);

if (!needRedraw) return;

knownSsid = WiFi.SSID();
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri;
knownMode = strip.getMainSegment().mode;
knownPalette = strip.getMainSegment().palette;

tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
tft.setTextColor(TFT_YELLOW);

// First row
tft.setCursor(1, 1);
tft.print(apSSID);
//tft.print(knownSsid.substring(0, tftcharwidth > 1 ? tftcharwidth - 1 : 0));
//if (knownSsid.length() > tftcharwidth)
// tft.print("~");

// Second row
tft.setTextSize(2);
tft.setCursor(1, 24);
tft.setTextColor(TFT_GREENYELLOW);

if (apActive) {
tft.print("AP IP: ");
tft.print(knownIp);
tft.setCursor(1,46);
tft.print("AP Pass:");
tft.print(apPass);
}
else {
tft.print("IP: ");
tft.print(knownIp);
tft.setCursor(1,46);
tft.setTextColor(TFT_GREEN);
tft.print("Bright: ");
tft.print(((float(bri)/255)*100));
tft.print("%");
}

// Third row
tft.setCursor(1, 68);
tft.setTextColor(TFT_SKYBLUE);
char lineBuffer[tftcharwidth+1];
extractModeName(knownMode, JSON_mode_names, lineBuffer, tftcharwidth);
tft.print(lineBuffer);

// Fourth row
tft.setCursor(1, 90);
tft.setTextColor(TFT_BLUE);
tft.print(strip.currentMilliamps);
tft.print("mA est.");

// Fifth row
tft.setCursor(1, 112);
tft.setTextColor(TFT_VIOLET);
extractModeName(knownPalette, JSON_palette_names, lineBuffer, tftcharwidth);
tft.print(lineBuffer);


needRedraw = false;
tftNextTimeout = tftTimeout + millis();
setBrightness(tftBrightness);
}

void setup() {
Serial.println("Setup - UsermodBackgroundTDisplay");

initDisplay();
setBrightness(tftBrightness);
needRedraw = true;
initDone = true;
}

// gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void connected() {
needRedraw = true;
}

void doWork(){
if(needRedraw)
updateDisplay();

EVERY_N_MILLISECONDS(5000){ updateDisplay(); }
}

void backgroundLoop(){
// Calling doWork on the background thread
// Only call it from one thread
doWork();
}

void loop(){
// Calling doWork on the main thread
// Only call it from one thread
// doWork();
}

// fired upon WLED state change
void onStateChange(uint8_t mode) {
needRedraw = true;
}

// Add JSON entries that go to cfg.json
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject(FPSTR(_strTag));
top[FPSTR(_strBrightness)] = tftBrightness;
top[FPSTR(_strTimeout)] = tftTimeout;
}

// Read JSON entries that go to cfg.json
bool readFromConfig(JsonObject &root)
{
JsonObject top = root[FPSTR(_strTag)];

if (top.isNull()) {
DEBUG_PRINTLN(FPSTR(_strTag));
return false;
}

bool configComplete = !top.isNull();
configComplete &= getJsonValue(top[FPSTR(_strBrightness)], tftBrightness);
configComplete &= getJsonValue(top[FPSTR(_strTimeout)], tftTimeout);

return configComplete;
}

/*
* getId() allows you to optionally give your V2 usermod an unique ID.
*/
uint16_t getId() {
return USERMOD_ID_TDISPLAY;
}
};

// strings to reduce flash memory usage (used more than twice)
const char UsermodBackgroundTDisplay::_strTag[] PROGMEM = "tdesp32";
const char UsermodBackgroundTDisplay::_strBrightness[] PROGMEM = "TFT Brightness";
const char UsermodBackgroundTDisplay::_strTimeout[] PROGMEM = "TFT Timeout";
3 changes: 3 additions & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@
#define USERMOD_ID_KLIPPER 40 // Usermod Klipper percentage
#define USERMOD_ID_WIREGUARD 41 //Usermod "wireguard.h"

#define USERMOD_ID_BACKGROUND 50 //Usermod "usermod_v2_background.h"
#define USERMOD_ID_TDISPLAY 51 //Usermod "usermod_v2_tdisplay.h"

//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
#define AP_BEHAVIOR_NO_CONN 1 //Open when no connection (either after boot or if connection is lost)
Expand Down
8 changes: 8 additions & 0 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ class Usermod {
virtual void onUpdateBegin(bool) {} // fired prior to and after unsuccessful firmware update
virtual void onStateChange(uint8_t mode) {} // fired upon WLED state change
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}

#if defined(WLED_ENABLE_BACKGROUND)
virtual void backgroundLoop() {}
#endif
};

class UsermodManager {
Expand Down Expand Up @@ -323,6 +327,10 @@ class UsermodManager {
bool add(Usermod* um);
Usermod* lookup(uint16_t mod_id);
byte getModCount() {return numMods;};

#if defined(WLED_ENABLE_BACKGROUND)
void backgroundLoop();
#endif
};

//usermods_list.cpp
Expand Down
5 changes: 5 additions & 0 deletions wled00/um_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ void UsermodManager::connected() { for (byte i = 0; i < numMods; i++) um
void UsermodManager::loop() { for (byte i = 0; i < numMods; i++) ums[i]->loop(); }
void UsermodManager::handleOverlayDraw() { for (byte i = 0; i < numMods; i++) ums[i]->handleOverlayDraw(); }
void UsermodManager::appendConfigData() { for (byte i = 0; i < numMods; i++) ums[i]->appendConfigData(); }

#if defined(WLED_ENABLE_BACKGROUND)
void UsermodManager::backgroundLoop() { for (byte i = 0; i < numMods; i++) ums[i]->backgroundLoop(); }
#endif

bool UsermodManager::handleButton(uint8_t b) {
bool overrideIO = false;
for (byte i = 0; i < numMods; i++) {
Expand Down
16 changes: 16 additions & 0 deletions wled00/usermods_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
* \/ \/ \/
*/
//#include "../usermods/EXAMPLE_v2/usermod_v2_example.h"
#ifdef USERMOD_BACKGROUND_V2
#include "../usermods/usermod_v2_background/usermod_v2_background.h"
#endif

#ifdef USERMOD_BACKGROUND_TDISPLAY
#include "../usermods/usermod_v2_background/usermod_v2_tdisplay.h"
#endif

#ifdef USERMOD_BATTERY
#include "../usermods/Battery/usermod_v2_Battery.h"
Expand Down Expand Up @@ -202,6 +209,15 @@ void registerUsermods()
* \/ \/ \/
*/
//usermods.add(new MyExampleUsermod());
#ifdef USERMOD_BACKGROUND_V2
usermods.add(new UsermodBackgroundDemo());
#endif

#ifdef USERMOD_BACKGROUND_TDISPLAY
#include <TFT_eSPI.h>
usermods.add(new UsermodBackgroundTDisplay());
#endif

#ifdef USERMOD_BATTERY
usermods.add(new UsermodBattery());
#endif
Expand Down
Loading
Loading