Telegram! Link
Posts

Enhancing the ESP32 SmartConfig Project with Speed, Features, and Optimizations

 Great! I’ll explore enhancements you can add to your ESP32 SmartConfig OLED project. I’ll look into faster Wi-Fi reconnection methods, more efficient display updates, additional features like NTP time sync, storing credentials, and improving interactivity.

I’ll get back to you with a list of optimizations and new features you can integrate, along with code samples and explanations.

Enhancing the ESP32 SmartConfig Project with Speed, Features, and Optimizations

Wi-Fi Connection Improvements (Speed & Reliability)

  • Store Credentials to Skip SmartConfig: After using SmartConfig once, save the Wi-Fi SSID and password in non-volatile memory so that on next boot the ESP32 can connect directly without repeating SmartConfig. You can use the ESP32 Preferences library (NVS) for this. For example, after a successful SmartConfig connection:

    cpp
    #include <Preferences.h> Preferences prefs; // ... after WiFi connected via SmartConfig: String ssid = WiFi.SSID(); String pass = WiFi.psk(); prefs.begin("wifi", false); prefs.putString("ssid", ssid); prefs.putString("pass", pass); prefs.end();

    Then on startup, retrieve and use those credentials:

    cpp
    prefs.begin("wifi", true); String ssid = prefs.getString("ssid", ""); String pass = prefs.getString("pass", ""); prefs.end(); if (ssid.length() > 0) { WiFi.begin(ssid.c_str(), pass.c_str()); // connect with saved creds }

    This avoids SmartConfig unless no stored creds are found. The Preferences library stores key/value pairs in flash and is recommended over legacy EEPROM​

    . (By default, WiFi.begin(ssid, pass) on ESP32 also persists credentials in flash, so the ESP might auto-connect on reboot if WiFi mode is STA.)

  • Fast Auto-Reconnect: The ESP32 WiFi library has auto-reconnect enabled by default​

    . If the connection drops, it will try to re-connect automatically. To ensure reliability, you can implement your own retry logic or use Wi-Fi event handlers for immediate action. For instance, set up an event handler for disconnect events and call WiFi.begin() again:

    cpp
    // Global credentials (could come from Preferences) const char* ssid = "YourSSID"; const char* password = "YourPASS"; void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) { if(event == SYSTEM_EVENT_STA_DISCONNECTED) { Serial.println("WiFi disconnected, attempting reconnect..."); WiFi.begin(ssid, password); // reconnect with known credentials } } // In setup(): WiFi.onEvent(WiFiEvent); WiFi.begin(ssid, password);

    This tries to reconnect immediately on disconnect​

    . Alternatively, a simple loop check can be used: e.g. check WiFi.status() periodically and if not WL_CONNECTED, call WiFi.reconnect() (after a short delay to avoid tight loops)​. Combining both ensures faster retries.

  • WiFi Connection Optimizations: Set WiFi mode to station-only (WiFi.mode(WIFI_STA)) to reduce connection overhead. You can also set a static IP to skip DHCP if desired (use WiFi.config()). Ensure you handle edge cases like wrong credentials or SmartConfig timeouts by implementing a timeout (e.g. if not connected within ~30s, reset or enter SmartConfig mode again). Using WiFiMulti is an option if you want to register multiple AP credentials – it will connect to the strongest available network from a list​

    .

  • Feedback during Connection: Use the onboard or an external LED to indicate Wi-Fi status (e.g., blink while connecting, solid on when connected). This can improve perceived speed by informing the user. For example, toggle an LED in the loop until WiFi.status() == WL_CONNECTED, or use events to turn it on/off.

New Features and Display Enhancements

Auto-Reconnect & SmartConfig Improvements

(These were covered above in Wi-Fi Improvements, but ensure in your code that after SmartConfig provisioning, the credentials are saved and auto-reconnect is enabled for seamless usage.) When SmartConfig is needed (e.g., no saved creds), provide visual feedback: display a “Waiting for SmartConfig...” message on the OLED and maybe blink an LED. After SmartConfig, save the Wi-Fi credentials as shown above to speed up future connections​

. Implement a long-press on a button to “forget” Wi-Fi (clear stored creds and reboot to SmartConfig mode) if needed, like in the example code which resets credentials after holding a button for 3 seconds​.

OLED Display Improvements (Icons, Animations, Auto-Off)

  • Icons and Graphics: Enhance the OLED UI by drawing small icons (Wi-Fi signal, clock, etc.) or logos. The Adafruit_SSD1306 library (with Adafruit GFX) supports bitmaps via drawBitmap(). You define a bitmap array (in PROGMEM) and then call:

    cpp
    const uint8_t wifiIcon[] PROGMEM = { /* ... bitmap data ... */ }; display.drawBitmap(x, y, wifiIcon, width, height, WHITE);

    Each '1' bit in the bitmap will be drawn in the given color​

    . For example, you could display a Wi-Fi icon that changes (or a small animated spinner) while connecting. The Adafruit GFX guide provides a handy bitmap generation tool and notes that drawBitmap() expects PROGMEM data​.

  • Text and UI Elements: Use custom fonts or the built-in font for readability. You can draw basic shapes (lines, rectangles) to create separators or frames around information​

    . For instance, draw a horizontal line to separate the Wi-Fi status from other info. Also consider adding a subtle animation like a “connecting...” ellipsis that changes (e.g., 3 dots cycling) while Wi-Fi connects, to reassure the user that progress is ongoing.

  • Auto-Off Display: To prevent burn-in and reduce power, turn off the OLED after a period of inactivity. For example, if no button has been pressed for, say, 30 seconds, send the display to sleep. The SSD1306 controller has a command for this. Using Adafruit_SSD1306, you can send commands directly:

    cpp
    display.ssd1306_command(SSD1306_DISPLAYOFF); // turn off // ... later when needed: display.ssd1306_command(SSD1306_DISPLAYON); // turn on

    This uses the internal ssd1306_command to send the display OFF command​

    . You would track lastActivityTime = millis() whenever the user presses the button or new info is displayed, and in your loop check if millis() - lastActivityTime exceeds your timeout to turn off the display. When the user presses the button again (or on any relevant event), turn the display back on. (Note: Alternatively, you could physically cut power to the OLED via a MOSFET or a transistor on a GPIO for true power savings, but using the command is simpler.)

  • NTP Time Synchronization: Use an NTP server to get current time and display it on the OLED. The ESP32 Arduino core includes <time.h> which allows use of configTime(). For example:

    cpp
    configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org"); struct tm timeinfo; if(!getLocalTime(&timeinfo)) { Serial.println("Failed to obtain time"); } else { // use timeinfo char buf[9]; strftime(buf, sizeof(buf), "%H:%M:%S", &timeinfo); display.setCursor(0, 0); display.print(buf); // e.g., "14:25:36" }

    The configTime() function will contact the NTP server in the background and set the ESP32’s system clock​

    . After that, getLocalTime(&timeinfo) populates a tm struct with the current time (already adjusted for timezone offsets you passed)​. You should call configTime once after Wi-Fi connects. To keep time updated, you can periodically sync (e.g., call configTime again every 24 hours or so), but the ESP32’s internal RTC will keep time even if Wi-Fi goes down (it’s reasonably accurate for short durations).

  • Displaying Time and Uptime: Once NTP is set up, you can show the current time on the OLED (update every second). For instance, format the timeinfo into a string "HH:MM:SS" or a date. The %H:%M:%S format used above prints hours, minutes, seconds​

    . You might also show “Uptime” (time since last reboot). To get uptime, use millis() (which returns milliseconds since boot). Convert it to days, hours, minutes, seconds:

    cpp
    uint32_t ms = millis(); uint32_t sec = ms / 1000UL; uint32_t min = sec / 60UL; uint32_t hr = min / 60UL; uint32_t days = hr / 24UL; sec = sec % 60UL; min = min % 60UL; hr = hr % 24UL; display.printf("Uptime: %dD %02d:%02d:%02d", days, hr, min, sec);

    This will show uptime in the format 0D 00:00:00. Using millis() will overflow after ~49 days, so for very long uptimes you might want to use the ESP’s built-in time functions or track overflows. (There are libraries like <a href="https://github.com/harbac/ArduinoUptimeLibrary">ArduinoUptime</a> to handle this, but it may be overkill.) The code above illustrates the calculation using modular arithmetic​

    .

  • System Metrics (Heap, Chip Info, RSSI): You can display useful system info on the OLED as well:

    • Free Heap: Call ESP.getFreeHeap() to get available heap memory in bytes​

      . For example: display.print("Heap: "); display.println(ESP.getFreeHeap());.

    • Chip Info: The ESP32 Arduino ESP class provides methods to get chip details. For example, ESP.getChipModel() returns a string of the chip model, ESP.getChipCores() gives the core count, and ESP.getChipRevision() the revision​

      . You can do:

      cpp
      display.printf("CPU: %s Rev%d, %d cores\n", ESP.getChipModel(), ESP.getChipRevision(), ESP.getChipCores());

      This might print something like CPU: ESP32-D0WDQ6 Rev1, 2 cores. You could also show the chip frequency (ESP.getCpuFreqMHz()) or Flash size. (ESP32 doesn’t have a single “Chip ID” like ESP8266, but you can use ESP.getEfuseMac() to get the MAC address as a unique ID.)

    • Wi-Fi Signal: It’s often useful to display Wi-Fi RSSI (signal strength). Use WiFi.RSSI() to get the RSSI in dBm. You could display it as RSSI: -60 dBm or translate it into a signal bar icon (e.g., 4 bars). Since RSSI is negative (0 is max signal, around -90 is low), you can map it to a 0-100% or simple categories. For example: if > -50 dBm, 4 bars, if > -60, 3 bars, etc. This gives a quick visual on connection quality.

Example: Display Content Layout

Consider organizing the OLED like:

  • Line1: Wi-Fi status (icon or text), IP address if connected, or "Connecting..." animation.

  • Line2: Current Time (from NTP).

  • Line3: Perhaps Date or Uptime.

  • Line4: System info (free heap or chip temp if available, etc.), or use a button to cycle through different info screens (e.g., press to toggle between a “status” page and a “info” page).

Using small text (e.g., display.setTextSize(1)) on a 128x64 OLED, you can fit quite a lot. You could also use display.setTextSize(2) for the time to make it prominent, and smaller text for the rest.

Button Input and Debouncing Optimization

  • Stable Input Reading: Mechanical buttons often cause bounce, i.e., rapid on/off toggling when pressed or released. To handle this, use a debouncing technique so that a single press is registered once. Ensure the button is wired with a proper pull-up or pull-down. A common setup is to connect one side of the button to a GPIO and the other to ground, then use pinMode(buttonPin, INPUT_PULLUP) so that the input reads HIGH normally and LOW when pressed.

  • Software Debounce (Manual): One approach is to sample the button state and wait for it to stabilize. For example, the Arduino debounce algorithm:

    cpp
    int buttonPin = 0; // GPIO for button int buttonState = HIGH; // current stable state int lastButtonState = HIGH; // previous reading unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 50; // 50 ms debounce void loop() { int reading = digitalRead(buttonPin); if (reading != lastButtonState) { lastDebounceTime = millis(); // reset debounce timer on any change } if ((millis() - lastDebounceTime) > debounceDelay) { // if stable state for > debounceDelay, accept the new state if (reading != buttonState) { buttonState = reading; if (buttonState == LOW) { // Button just pressed, do something Serial.println("Button Pressed!"); } } } lastButtonState = reading; }

    In this code, we only update the buttonState after the reading has been stable for 50ms​

    . Adjust debounceDelay as needed (5-50ms is typical). This prevents flicker from noise. The check if (buttonState == LOW) detects a press (assuming LOW is pressed due to INPUT_PULLUP). You can similarly detect release if needed.

  • Using a Debounce Library: A simpler method is to use a library like Bounce2, which handles debouncing internally. For instance, Bounce2 has a Button object:

    cpp
    #include <Bounce2.h> Bounce2::Button button; void setup() { button.attach(BUTTON_PIN, INPUT_PULLUP); button.interval(5); // debounce interval 5 ms button.setPressedState(LOW); // the button reads LOW when pressed } void loop() { button.update(); if (button.pressed()) { // Button was pressed (debounced) Serial.println("Button pressed!"); } if (button.released()) { // Button was released } }

    The Bounce2 library will filter out the quick flickers. In the above example, button.pressed() returns true only once when the button transitions to the pressed state​

    . This is an easy way to handle single and multiple buttons without writing the timing logic yourself. It also provides button.changed() if you need to know when any change happens, or you can check button.currentState() at any time.

  • Interrupt vs Polling: It might be tempting to use hardware interrupts (attachInterrupt()) for button presses. In an ESP32, you can do this (if the button is on a GPIO that supports interrupts), but be careful: bouncing will trigger multiple interrupts. You can disable the interrupt for a short time or use the ISR to set a flag and then debounce in the main loop. Often, simply polling the button state in the main loop at a high frequency (every few milliseconds) is sufficient and simpler, given the ESP32 has plenty of CPU time for this in most applications. Use interrupts only if the button press needs to be captured during deep sleep or when the main loop might be blocked (which you should avoid anyway).

  • Additional Button Features: You can implement features like long-press detection or multi-click fairly easily once debouncing is in place. For a long press, note the millis() time when the button is pressed (on if (button.pressed()) event) and then if the button stays pressed for > X milliseconds, trigger a different action. The example code in the SmartConfig section used a long press (3 seconds) to reset Wi-Fi credentials​

    . With Bounce2, you could do if(button.pressed()) pressTime = millis(); if(button.isPressed() && millis() - pressTime > 3000) { /* long press action */ }.

Optimizing OLED Refresh (Reducing Flicker & CPU Usage)

Constantly redrawing the entire screen can cause noticeable flicker on an OLED, especially if done very frequently. Here are strategies to minimize unnecessary redraws and eliminate flicker:

  • Update Only When Needed: Rather than clearing and drawing the whole screen in every loop iteration, only update the display when your data changes or at a fixed interval. For example, if you display time, update it once per second. If showing sensor readings, update when the reading changes or say 10 times a second. You can maintain previous values of variables and compare. For instance, keep lastTimeStr and if the new time string differs, then redraw that part. This prevents redundant writes.

  • Partial Redraws: If only a portion of the display changes (e.g., one line), you don’t need to clear the full screen. You can erase just that region and draw again. The Adafruit GFX library allows filling rectangles. For example, to update a small area, you can do:

    cpp
    display.fillRect(x, y, w, h, BLACK); // clear a rect portion display.setCursor(x, y); display.print(newText); display.display();

    This way, other parts of the screen stay untouched and do not flicker. The Arduino forum suggests this technique: "To erase a section of the screen, draw a rectangle with the appropriate size and location, filled with the background color, and then draw new stuff over that."

    . Use this for things like changing numbers or icons.

  • Avoid Clear+Full Refresh Loops: The typical flicker happens if you clearDisplay() (turn all pixels off) and then immediately display() the new frame repeatedly. The blanking causes a blink. Instead, consider overwriting or erasing only small parts. The SSD1306 library uses an off-screen buffer, so you actually don’t see partial updates until display() is called. You can exploit this:

    • Draw static elements once (outside the loop or in setup).

    • In loop, only draw dynamic elements (text or bitmaps) and call display(). If you never clear the buffer fully, the static content remains. Just be careful to clear or overwrite the dynamic parts from their previous state.

    • If using the built-in 5x7 font, a convenient trick is to use display.setTextColor(WHITE, BLACK) when printing dynamic text. The second parameter is the background color, so the library will draw each character on a black rectangle, erasing what was there before​

      . This way, you can write over old text cleanly without clearing the whole screen. (Note: The background color only works with the fixed-size fonts, not custom GFX fonts​.) For example:

      cpp
      display.setTextColor(WHITE, BLACK); display.setCursor(0, 16); display.print("Temp: "); display.print(oldTemp); // this will overwrite oldTemp with black background display.setCursor(0, 16); display.print("Temp: "); display.print(newTemp); display.display();

      Here we printed the old value in black to erase it (or you could use fillRect over that number area), then printed the new value. This minimizes flicker to just that text.

  • Control Refresh Rate: If you have animations, try to limit the frame rate to something reasonable (e.g., 30 frames per second or less). The I2C bus speed (typically 400kHz) and OLED hardware can’t do very high frame rates smoothly for full screen updates. If you update too fast, you’ll tie up the CPU in I2C transfers and possibly still see flicker. Using a timer or delay(33) for ~30 FPS for animations is a good practice. For mostly static info (time, etc.), even 1 FPS is fine.

  • Double Buffer (Advanced): The Adafruit library already buffers internally. On more powerful chips, an advanced technique is to maintain two screen buffers and compute a “diff” to only send changed bytes to the display. This is complex and usually not necessary for a small OLED, but be aware it’s a technique to eliminate flicker completely. There are libraries like u8g2 that allow partial updates or have different drawing paradigms that can help reduce flicker by not clearing the screen by default.

  • Disable Splash Screen: As a minor startup improvement, if you haven’t already, you can disable the Adafruit splash logo (that appears at reset) by editing the library config or using the SSD1306 constructor with noSplash. This saves a bit of time on boot and prevents an unnecessary screen flash when your device resets.

By implementing these optimizations, your ESP32 project will connect to Wi-Fi more quickly and reliably, preserve the Wi-Fi credentials, and provide a richer user interface on the OLED without flicker. The button will be responsive and reliable thanks to debouncing, and the system will display useful realtime info like time, uptime, and system stats. These changes will make the device much more polished and responsive, approaching a commercial product level of robustness.

Sources:

Rate This Article :

Rate this article

Loading...

Post a Comment

© Pradeep's Blog. All rights reserved.

Cookies Consent

This website uses cookies to ensure you get the best experience on our website.

Cookies Policy

We employ the use of cookies. By accessing Lantro UI, you agreed to use cookies in agreement with the Lantro UI's Privacy Policy.

Most interactive websites use cookies to let us retrieve the user’s details for each visit. Cookies are used by our website to enable the functionality of certain areas to make it easier for people visiting our website. Some of our affiliate/advertising partners may also use cookies.