Extending a TFT_eSPI Example With TTGO T-Display Using PlatformIO, With DumbDisplay
by Trevor Lee in Circuits > Arduino
2257 Views, 0 Favorites, 0 Comments
Extending a TFT_eSPI Example With TTGO T-Display Using PlatformIO, With DumbDisplay
In this post I will demonstrate how I extend a TFT_eSPI library example, namely TFT_Clock.ino, to be a bit more customizable:
- able to synchronize the clock's time with that of your phone
- customize the clock's background color
- enable putting the clock to "sleep" when idle
TTGO T-Display will be the target microcontroller board (ESP32), which is internally connected to an IPS ST7789V 1.14 Inch (160x128) TFT LCD.
VSCode with PlatformIO will be used for the development using Arduino framework.
DumbDisplay will be used for the UI of the said customizations -- a little remote control panel on your phone, driven by the microcontroller program.
Create PlatformIO Project
First, create a PlatformIO project with the following initial project settings
- Name: TDisplayClock
- Board: Expressif ESP32 Dev Module
- Framework: Arduino
Edit the generated platformio.ini configuration file, adding dependency declaration to the end of the file
lib_deps =
bodmer/TFT_eSPI @ ^2.5.30
SPI
FS
SPIFFS
Build the PlatformIO Project
Since the initially created PlatformIO project is already a buildable project. Try building it so that all the specified dependencies are pulled.
After successful building, the TFT_eSPI dependency folder (.pio/libdeps/esp32dev/TFT_eSPI) should be populated with files of the TFT_eSPI library.
Modify the Code As the Sample TFT_Clock.ino
The simplest way to modify your project to be the TFT_Clock.ino sample is
- Open .pio/libdeps/esp32dev/TFT_eSPI/examples/160 x 128/TFT_Clock.ino, which is a sample sketch that comes with TFT_eSPI library
- Save it as the src/main.cpp [of the project]
Now, your project has just become the sample.
Nevertheless, one more step before the sample (or should I say your project) will work correctly -- configure TFT_eSPI library for TTGOT T-Display LCD:
Configure TFT_eSPI Library for TTGO T-Display
Notice that the TFT_eSPI library is pulled as an independent copy for your PlatformIO project. Hence, it is perfectly fine to modify it for your project.
To tailor TFT_eSPI library for TTGOT T-Display LCD, open .pio/libdeps/esp32dev/TFT_eSPI/User_Setup_Select.h
Modify its content
- comment out the line
//#include <User_Setup.h> // Default setup is root library folder
- uncomment out the line
#include <User_Setups/Setup25_TTGO_T_Display.h> // Setup file for ESP32 and TTGO T-Display ST7789V SPI bus TFT
It is that easy.
Now, you are ready to upload the program to your TTGO T-Display.
Upload the Project (The Sample)
After uploading the project (the sample) to your TTGO T-Display. You should see a clock face displayed on the TFT LCD of TTGO T-Display. The clock looks synchronized with real-time; since the clock's time is coded to start with the time the program is built.
Next, I will show how to modify the program for the customizations mentioned previously. Before that, I guess is a good time to backup your work on src/main.cpp, by making a copy of it and renaming the copy to src/main-ori.txt (you need to rename it to extension .txt since otherwise it will be treated as source file and be involved in compilation).
Set Up the Project for Extending It
Here is the recap of what customizations we will be adding to this project, extending the TFT_Clock.ino sample:
- able to synchronize the clock's time with that of your phone
- customize the clock's background color
- enable putting the clock to "sleep" when idle
The core of these customizations will be using some help from DumbDisplay. Hence, the first step is to add DumbDisplay Arduino library as a dependency to this PlatformIO project
lib_deps =
bodmer/TFT_eSPI @ ^2.5.30
SPI
FS
SPIFFS
https://github.com/trevorwslee/Arduino-DumbDisplay
Note that most of the dependency entries were there previously for driving the TFT LCD. The new one is
https://github.com/trevorwslee/Arduino-DumbDisplay
Code to Make Use of DumbDisplay
Now, let's make use of DumbDisplay Arduino library to drive DumbDisplay Android app as UI for the said customizations.
The previous step already installed the DumbDisplay Arduino Library to the project, and you can download and install the DumbDisplay Android app from Google Play Store.
First, modify the code to bring DumbDisplay into the "picture" of the project.
At the beginning of src/main.cpp, add the code segment
#include "esp32dumbdisplay.h"
DumbDisplay dumbdisplay(new DDBluetoothSerialIO("TTGO"));
LcdDDLayer* syncButton = NULL;
- The code segment includes the needed DumbDisplay header file specific for using ESP Bluetooth connectivity IO object -- esp32dumbdisplay.h
- Indeed, Bluetooth is how your TTGO T-Display will be connecting to your DumbDisplay app. Notice that the Bluetooth device name for your TTGO T-Display is set to "TTGO"
- The code segment also declares the global variable syncButton -- a pointer to an LCD layer object, that will be created when connected to DumbDisplay app
At the beginning of the loop() block, add the code segment
void loop() {
// "passively" make connection with DumbDisplay app non-block
DDConnectPassiveStatus connectStatus;
dumbdisplay.connectPassive(&connectStatus);
if (connectStatus.connected) {
if (syncButton == NULL) {
syncButton = dumbdisplay.createLcdLayer(14, 1);
syncButton->border(2, DD_COLOR_darkgreen, "raised");
syncButton->writeLine(" 🔄 Sync Time");
syncButton->enableFeedback("fl");
}
}
...
}
- At the beginning of every loop, first try to make connection to DumbDisplay app non-block by calling connectPassive()
- If connected and syncButton is not yet assigned with a created LCD layer, create one by calling createLcdLayer() and assign it to the syncButton global variable
The code modification so far only enables connection to DumbDisplay app; once connected, a "Sync Time" button is shown. Try uploading the program to your TTGO T-Display and make connection with DumbDisplay app, and see that it is working.
Synchronize the Time of the Clock
The "Sync Time" button is already clickable; but will not do anything yet.
To actually perform synchronization of the clock's time with that of DumbDisplay app, you need to further modify the code -- will need a [DumbDisplay] "tunnel" to make a "get current time" service request to DumbDisplay app.
First, after declaring the global variable syncButton, declare one more global variable for the "tunnel" object
LcdDDLayer* syncButton = NULL;
BasicDDTunnel* datetimeTunnel = NULL;
- The newly added global variable is datetimeTunnel, which is a pointer for pointing to a created "tunnel" object
In the loop() block, after checked connected, and syncButton assigned with an actual LCD layer object, add the code
if (connectStatus.connected) {
if (syncButton == NULL) {
...
}
if (syncButton->getFeedback()) {
// "sync" button clicked ==> create a "tunnel" to get current date time
dumbdisplay.logToSerial("getting time for sync ...");
datetimeTunnel = dumbdisplay.createDateTimeServiceTunnel();
datetimeTunnel->reconnectTo("now:hhmmss"); // ask DumbDisplay app for current time in "hhmmss" format
}
if (datetimeTunnel != NULL) {
String nowStr;
if (datetimeTunnel->readLine(nowStr)) {
// got current time "feedback" from DumbDisplay app => use it to sync hh/mm/ss
dumbdisplay.logToSerial("... got sync time " + nowStr);
int now = nowStr.toInt();
hh = now / 10000;
mm = (now / 100) % 100;
ss = now % 100;
dumbdisplay.deleteTunnel(datetimeTunnel);
datetimeTunnel = NULL;
dumbdisplay.tone(2000, 100);
initial = 1;
}
}
}
...
- When "Sync Time" button -- syncButton LCD layer -- is clicked, syncButton->getFeedback() will return something (actually a "feedback" object) to indicate that the layer is clicked. In such a case, create a "get current time" "tunnel" by calling createDateTimeServiceTunnel(), and assign the created "tunnel" object to datetimeTunnel. Note that 1) the request is asynchronous; and 2) the "current time" reply will be returned as a string with pattern hhmmss (for only the time part)
- In case datetimeTunnel is not NULL, i.e. [asynchronous] request made to get "current time" from DumbDisplay app, check that can read the "current time" reply. If so, turn the "current time" reply, which is a string with pattern hhmmss, into an integer and extract components from it to update the hh, mm and ss global variables. Note that the hh, mm and ss global variables are used by the TFT_Clock.ino sample code to keep track of the clock's time.
- After updating the clock's time, 1) the "tunnel" is deleted and datetimeTunnel set to NULL; 2) a tone is played to DumbDisplay app; and 3) initial is set to 1 tricking the TFT_Clock.ino sample code to completely redraw the clock face.
After you build and upload the program, you should be able to really synchronize the clock on your TTGO T-Display with the time of your phone, by clicking the "Sync Time" button.
Make It Able to Reconnect
At this point, your code can already connect with your phone to synchronize the clock (with the help of DumbDisplay app). However, you can only connect one time. More precisely, reconnection (after disconnect) is not working correctly, unless you reset your TTGO T-Display.
In fact, the way connection is made with DumbDisplay app is specially called "passive":
- it is non-blocking
- it is catered for reconnection
There is just a little bit more code change needed -- after checked connected, add code like
if (connectStatus.connected) {
if (connectStatus.reconnecting) {
// if reconnecting (i.e. lost previous connection, "master reset" DumbDisplay)
dumbdisplay.masterReset();
syncButton = NULL;
datetimeTunnel = NULL;
tft.setTextColor(TFT_GREY, TFT_GREY);
tft.drawCentreString("Connected",66,160,4);
return; // exit this loop
}
if (syncButton == NULL) {
...
tft.setTextColor(TFT_RED, TFT_GREY);
tft.drawCentreString("Connected",66,160,4);
}
...
}
...
- If it is reconnecting (i.e. waiting for connection after disconnect), "master reset" DumbDisplay. After "master reset", all layer objects and "tunnel" objects previously created are no longer valid, and can simply be set to NULL (to indicate not-yet-created). Before exiting the loop call, erase the "Connected" message below the clock face.
- Note that after creating the syncButton layer object, code is added to print the "Connected" message below the clock face.
That's all the code needed to make the program reconnectable with DumbDisplay app.
Build it, upload it, and try it.
Adding Idle Sleep
Since ESP32 supports sleep mode, why not add idle sleep to the project?
OK. For this simple project, a few things:
- Idle means not connected to DumbDisplay app
- Pressing the left button (pin 0) of TTGO T-Display wakes it from idle sleep
- Clock states (like clock time) should be kept unchanged after awaken up
To add idle sleep to the project, declare a few more global things
LcdDDLayer* syncButton = NULL;
BasicDDTunnel* datetimeTunnel = NULL;
int maxIdleMillis = 30 * 1000; // -1 means disable idle sleep
long idleStartMillis;
#define WAKE_BUTTON_PIN_NUM GPIO_NUM_0
- maxIdleMillis is the maximum idle milliseconds before going to sleep;
- idleStartMillis is used to keep track of how long it has been idle for
- WAKE_BUTTON_PIN_NUM defines the pin used to wake the board from sleep
Change the hh/mm/ss variable to use the RTC memory
//uint8_t hh=conv2d(__TIME__), mm=conv2d(__TIME__+3), ss=conv2d(__TIME__+6); // Get H, M, S from compile time
RTC_DATA_ATTR uint8_t hh = 0;
RTC_DATA_ATTR uint8_t mm = 0;
RTC_DATA_ATTR uint8_t ss = 0;
It is necessary to put them in the RTC memory so that the values of the variables can be preserved during sleep. Notice that all hh, mm and ss are set to 0 initially.
At the beginning of the setup() block
void setup(void) {
idleStartMillis = millis();
...
}
- Initially set the maxIdleMillis to the current time
To actually check and put TTGO T-Display to sleep, the code around checking DumbDisplay app connected need modifications like
if (connectStatus.connected) {
idleStartMillis = millis();
...
} else {
// not connected to DumbDisplay app ==> check for idle; go to sleep if idle for too long
if (maxIdleMillis != -1) {
long diffMillis = millis() - idleStartMillis;
if (diffMillis >= maxIdleMillis) {
esp_sleep_enable_ext0_wakeup(WAKE_BUTTON_PIN_NUM, 0);
esp_deep_sleep_start();
// the above call will not return
}
}
}
- It is important that idleStartMillis be set to the current time every loop cycle if checked connected
- If not connected, check it has been idle for how long; if too long, put TTGO T-Display to sleep.
- esp_sleep_enable_ext0_wakeup() is called to set up that pin 0 will be signal source for waking up from sleep
- esp_deep_sleep_start() is called to put TTGO T-Display to sleep
- After waking up, the global variables will be initialized again and the setup() block will be called again, like a fresh run of the program. However, those global variables in the RTC memory will keep their values during sleep.
During sleep, you would not be able to make connection between TTGO T-Display and DumbDisplay app. However, you should be able to make connection once TTGO T-Display is awakened.
Note that when DumbDisplay app is connected and is in the foreground, your phone will not go to sleep. If DumbDisplay is put to the background, connection will still be kept.
Build it, upload it, and try it.
Customize Clock's Background Color
The remaining customization is to customize the clock's background color. To enable this customization
- On TTGO T-Display coding clock face side, a few places need to be modified.
- On the UI side, a DumbDisplay "slider" layer is used to adjust the clock's background color, ranging from 0 to 127 (i.e. color range close to the blue spectrum)
First, add the following global declarations
JoystickDDLayer* blueSlider;
RTC_DATA_ATTR uint32_t clockBackgroundColor = 0;
- blueSlider is used to point to the "slider" layer object for adjusting the clock's background color; note that the "slider" is actually a Joystick layer
- clockBackgrounColor is the clock's background color
The actual "slider" layer object is created and set up like
if (syncButton == NULL) {
...
blueSlider = dumbdisplay.createJoystickLayer(127, "hori");
blueSlider->moveToPos(clockBackgroundColor, 0);
blueSlider->border(5, DD_COLOR_darkgray, "round", 3);
dumbdisplay.configAutoPin(DDAutoPinConfig('V').build());
dumbdisplay.backgroundColor(DD_INT_COLOR(clockBackgroundColor));
}
- a "slider" object is created by calling createJoystickLayer() giving it 1) maximum value (127); and 2) its orientation ("hori" in this case)
- update the "slider" position according to colorBackgroundColor
- Auto pin the created layers vertically by calling configAutoPin()
Handing of "feedback" due to sliding blueSlider "slider" is like
if (connectStatus.connected) {
...
const DDFeedback* fb = blueSlider->getFeedback();
if (fb != NULL) {
// got "feedback" from the "slider" => use it's x value as color background color
clockBackgroundColor = fb->x;
dumbdisplay.backgroundColor(DD_INT_COLOR(clockBackgroundColor));
initial = 1;
}
} else {
...
}
- When the blueSlider "slider" is dragged, check for "feedback" by calling getFeedback()
- If "feedback" got (DDFeedback object), check the "feedback" x value (from 0 to 127)
- Set colorBackgroundColor to the x value of the "feedback"
- Also set the "slider" background color accordingly as well
- Set initial to 1 to force redraw of the clock face
Several places of the original clock face code need be modified as well
if (ss==0 || initial) {
tft.fillCircle(64, 64, 48, clockBackgroundColor); // draw the clock background circle
...
// Erase hour and minute hand positions every minute
tft.drawLine(ohx, ohy, 65, 65, clockBackgroundColor/*TFT_BLACK*/);
...
tft.drawLine(omx, omy, 65, 65, clockBackgroundColor/*TFT_BLACK*/);
...
}
...
// Redraw new hand positions, hour and minute hands not erased here to avoid flicker
tft.drawLine(osx, osy, 65, 65, clockBackgroundColor/*TFT_BLACK*/);
- Notice that the clock's background is actually a circle additionally drawn
That is it. Build, upload and try the updated program.
Further Extensions
There are further extensions not mentioned in the previous sections. You can copy the complete code from here, which is a sketch for the Arduino IDE. Yes, even though Arduino IDE is not referred to in this post, the project can be developed using Arduino IDE in a similar fashion. For a briefing on installing DumbDisplay Arduino library, you may refer to my previous post -- Blink Test With Virtual Display, DumbDisplay
Other than the previously mentioned customizations, the additional extensions are:
- the clock's time is also shown in the UI
- can use the UI to disable idle sleep
You can simply copy the complete code to the project's src/main.cpp. Build it, upload it, and try it.
Enjoy!
Even as a simple clock implementation like this, there are still at least two desirable capabilities not there
- Should somehow keep the clock time advancing during sleep. This might call for a hardware counter or a real-time clock???
- It is desirable to wake TTGO T-Display from sleep when DumbDisplay app tries to establish connection. This might call for hardware feature like PC's "wake-on-LAN"???
Anyway. Hope you also find the development journey interesting. Enjoy!
Peace be with you! May God bless you! Jesus loves you!