Lilygo T-Watch 2020 Arduino Framework




Introduction: Lilygo T-Watch 2020 Arduino Framework

About: Lifelong Maker and Educator.

Lilygo has put out a new version of it's development platform TWatch. It is the T-Watch 2020. It is based on an ESP32 chip with a touch screen, accelerometer, real time clock, power controller, and more. For people who have been waiting for a commercial grade watch that can be programmed, this is a dream come true. It is available at

I was super excited so I ordered one as soon as they became available. When I received it, I got it to work with the Arduino IDE. But as I started to look at the sample code, I realized I needed some documentation to help me get started. As I searched, I didn't find much. The best sample app to show some of the functions of the watch is the SimpleWatch app in the LVGL section. Looking at the code, it is anything but simple. It is heavy in C++ and object oriented architecture and it runs under the RTOS (real-time operating system). If you are not familiar with these, they can be daunting.

So, not being an expert programmer, I set out to create a simple Arduino C-based framework for the watch that will get you up and running and able to add your own apps really easily.

The first thing to know about the watch (and this took me a while to figure out) is that there is no user accessible button. The button on the side of the watch turns the watch on and off.

Hold it for 2 seconds to power on the watch and 6 seconds to turn it off.

From the AXP202 Datasheet:

Power Enable Key (PEK)
The Power Enable/ Sleep/Wakeup Key can be connected between PWRON pin and GND of AXP202. AXP202 can automatically identify the “Long-press” and “Short-press” and then correspond respectively.

It looks like the power button is directly connected to the AXP202 and not the ESP32.

This turns out to be pretty useful. The AXP202 datasheet has pretty good information on this chip and you can do a little reverse engineering using it and the TWatch axp202 library. When the watch is shut down, it looks like it secures power to most of the hardware which does a pretty good job of reducing power. I have had the watch sitting on the counter for two days without operating and it only lost about 15% power. This prevented me from having to worry a lot about getting the watch into low power mode. I just turn it off when I am not using it.

Again, from the datasheet:

Power Off
When you push-and-hold PEK longer than IRQLEVEL, HOST can write “1” into“REG32H [7]” to inform AXP202 to shutdown, which can disable all power output except LDO1.

When you view the library, the functionint AXP20X_Class::shutdown() manipulates this bit 7 so just call the shutdown function and it will have the same effect as long pressing the button.

There is an IRQ input from the AXP202 to the ESP32 (AXP202_INT). There are a bunch of interrupts generated from the AXP202. If you need to use the button, you can poll the shortpress interrupt as follows:

ttgo->power->readIRQ(); // This reads the interupts<br>
if (ttgo->power->isPEKShortPressIRQ()) { // This will be true if the button was short pressed
      ttgo->power->clearIRQ(); // Clear the interrupts so you can receive some more
    } else {

See the example code in Examples->TTGO Watch Library->Basic Unit->AXP20X_IRQ.

Since most interaction happens on the touch screen, you may want to look into the LVGL framework. This allows you to make standard GUI elements (buttons, lists, etc) and respond to touches. You could probably use the accelerometer also as a gesture input.


1) T-Watch 2020

2) Arduino IDE

Step 1: The Framework

My code is borrowed heavily from the example code written by Lewis He.

The framework is set up to display the time. Every second the screen will update and flash the colon. This is a good visual that the watch is not hung up.

When you touch the screen, you will see the Menu. Touching up and down will allow you to cycle through menu items. When the desired item is displayed in the center, click the center to launch it.

Pretty straight forward, and that is the point.

The video shows the basic function and a couple of simple apps I included to let you see how to access the accelerometer, battery monitor, touch screen, and more.

Step 2: Get Your Watch Connected

The new Arduino method of loading libraries and boards makes this pretty straight forward.

If you don't already have the ESP32 board library loaded, in the File menu, select Preferences.

In the section Additional Boards Manager URLs, click the box on the right of the text block. This will bring up a box that has all of your current URLs for boards you are using. Add the following to the list:

Click OK until you are out of the Preferences menu.

Then go into Tools->Board->Boards Manager

Search for esp32 and install the latest version.

To load the library, go to the GitHub site, download the repository in zip format, and then import it into Arduino using the Library import ZIP function.

Plug in your watch, start Arduino IDE, Select the watch on the Board section, open a sample application in the TTGO section of Examples (try SimpleWatch in the LVGD section), compile and download. Hopefully all went well and your watch is ready to be programmed.

Step 3: Load the Framework and Add Your App

Download the Arduino Framework code in the individual INO files in this Instructable (sorry, I could not get the zip file to be accepted by Instrucables). Place them all into a folder in your Arduino sketch folder (Name the folder something you will remember). Open it in the IDE (may have to restart the IDE).

You will see that there are a bunch of separate tabs to the program. The Main tab is labeled TWatch_Framework_0. It is where the setup() and loop() subroutines are. The other important tab is the Menu tab.

If you have verified you can download the examples to your watch, you should now be able to download this to your watch.

To add your own app to this watch do the following:

1) In the Menu tab you will see the code:

const int maxApp = 6; // number of apps
String appName[maxApp] = {"Clock", "Jupiter", "Accel", "Battery", "Touch", "Set Time"}; // app names

Increment the constant maxApp by 1. This is the total number of apps.

Append the name of your app to the appName string array. It can be inserted anywhere. But be advised, the menu function returns the number of the selected app. It has to match the switch-case to select the correct app.

2) In the Main tab, add a call to your app as a case in the switch statement in the main loop().

switch (modeMenu()) { // Call modeMenu. The return is the desired app number

      case 0: // Zero is the clock, just exit the switch
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:

3) Add a tab, and write your code as a subroutine or function that is called from the case statement. See the appTouch tab as an example.

That is it. With the tabs I have already, you should be able to quickly code a new app that can be called from the menu. Take a look at the methods I use to exit apps. You want to make sure that the user is not pressing the touch screen when the app exits or you will immediately go back to the menu instead of the time display.

I so hope this helps. I saw a lot of people initially complaining about the lack of documentation and support. But this is a great product that will give you your own programmable watch with lots and lots of geek cred.

Just show people at a party the current orientation of Jupter's moons and you'll see.

******* Right now, Instructables will not upload the zipped code file. I uploaded the individual tabs. **********

******* Download them all, place them in a directory in the Arduino sketch folder and open the Framework ino file ****

Step 4: Some Helpful Apps

As I come up with apps for my watch, I'll post useful ones here:

This app sets the watch time from the internet. Just add your WiFi credentials.

#include <WiFi.h>
#include "time.h"

void appWiFiTime() {

  // WiFi settings ******* Use your network values **********
  const char* ssid     = "put your ssid here";
  const char* password = "put your passcode here";

  const char* ntpServer = "";
  const long  gmtOffset_sec = -18000;
  const int   daylightOffset_sec = 3600;

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {}

  configTime(-18000, 3600 , "", "");

  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    ttgo->tft->drawString("Failed",  5, 30, 1);
  } else {
    ttgo->tft->setCursor(0, 130);
    ttgo->tft->print(&timeinfo, "%A, %B %d %Y\n%H:%M:%S");
    ttgo->rtc->setDateTime(timeinfo.tm_year, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);



I set up a call to give a quick motor vibration. Make sure you setup the motor pin in your setup() routine to be an OUTPUT (pinMode(4, OUTPUT); )

void quickBuzz() {
  digitalWrite(4, HIGH);
  digitalWrite(4, LOW);

Here is the code to retrieve the current Bitcoin price from the server. Again, I am not an expert programmer so you will see there is little error checking and waiting for conformation, but this works on my watch so it must be close. Remember to put in your own wifi credentials.

#include <ArduinoJson.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
void appBitcoin() {

  // WiFi settings
  const char* ssid     = "ssid";
  const char* password = "passcode";

  // API server
  const char* host = "";

  int16_t x, y;

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {}

  while (ttgo->getTouch(x, y)) {}

  WiFiClient client;
  const int httpPort = 80;
  client.connect(host, httpPort);

  // We now create a URI for the request
  String url = "/v1/bpi/currentprice.json";

  // This will send the request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n\r\n");

  // Read all the lines of the reply from server and print them to Serial
  String answer;
  while (!client.available()) {}
  while (client.available()) {
    String line = client.readStringUntil('\r');
    answer += line;


  // Convert to JSON
  String jsonAnswer;
  int jsonIndex;

  for (int i = 0; i < answer.length(); i++) {
    if (answer[i] == '{') {
      jsonIndex = i;

  // Get JSON data
  jsonAnswer = answer.substring(jsonIndex);

  // Get rate as float
  int rateIndex = jsonAnswer.indexOf("rate_float");
  String priceString = jsonAnswer.substring(rateIndex + 12, rateIndex + 19);
  float price = priceString.toFloat();

  ttgo->tft->setCursor(40, 90);

  ttgo->tft->println("Bitcoin Price");
  ttgo->tft->setCursor(70, 130);


  while (!ttgo->getTouch(x, y)) {} // wait until you touching
  while (ttgo->getTouch(x, y)) {}

This app returns the analog value proportional to the AXP202 temperature. It is a raw value and the data sheet I saw didn't have any discussion on the conversion to actual temperature so you will have to calibrate it yourself. My value is about 170 when the watch has been off for a while and I just turn it on and 190 when it has been running for a while.

void appTemp() {<br>  int16_t x, y;
  while (ttgo->getTouch(x, y)) { }
  while (!ttgo->getTouch(x, y)) { // Wait for press
    float temp = ttgo->power->getTemp();
    ttgo->tft->setTextColor(TFT_YELLOW, TFT_BLACK);
    ttgo->tft->setCursor(40, 100);
    ttgo->tft->print("Temp: ");
  while (ttgo->getTouch(x, y)) {} // wait until you stop touching

Step 5: A STTNG-like Drop-in Replacement for DisplayTime()

I wrote a replacement function for the displayTime() subroutine to mimic a ST-like screen display. The easy way would have been to use a bitmap image and overwrite the text. But, I just created it with display paint commands. If you use the above files, this would just replace the displayTime() tab. Here is the code:

// An advanced Time Display
// This is a drop-in replacement for the displayTime()
// In the original Instructable. s such, it redraws GUI
// every minute so you will see a little flicker.

void displayTime(boolean fullUpdate) {
  ttgo->power->adc1Enable(AXP202_VBUS_VOL_ADC1 | AXP202_VBUS_CUR_ADC1 | AXP202_BATT_CUR_ADC1 | AXP202_BATT_VOL_ADC1, true);

  // Get the current data
  RTC_Date tnow = ttgo->rtc->getDateTime();

  hh = tnow.hour;
  mm = tnow.minute;
  ss = tnow.second;
  dday =;
  mmonth = tnow.month;
  yyear = tnow.year;


  if (fullUpdate) {

    //Draw the back graphics - Top of display
    ttgo->tft->fillRoundRect(0, 0, 239, 120, 40, TFT_PURPLE);
    ttgo->tft->fillRoundRect(40, 20, 196, 80, 20, TFT_BLACK);
    ttgo->tft->fillRect(80, 20, 159, 80, TFT_BLACK);
    ttgo->tft->fillRect(170, 0, 45, 20, TFT_BLACK);
    ttgo->tft->fillRect(110, 0, 4, 20, TFT_BLACK);
    ttgo->tft->fillRect(0, 45, 50, 7, TFT_BLACK);
    ttgo->tft->fillRect(0, 70, 50, 7, TFT_BLACK);
    ttgo->tft->fillRect(215, 0, 24, 20, TFT_DARKCYAN);

    //Draw the back graphics - Bottom of display
    ttgo->tft->fillRoundRect(0, 130, 239, 109, 40, TFT_MAROON);
    ttgo->tft->fillRoundRect(40, 150, 199, 88, 20, TFT_BLACK);
    ttgo->tft->fillRect(0, 179, 50, 10, TFT_BLACK);
    ttgo->tft->fillRect(100, 160, 40, 10, TFT_YELLOW);
    ttgo->tft->fillRect(140, 160, 40, 10, TFT_DARKGREEN);
    ttgo->tft->fillRect(180, 160, 40, 10, TFT_RED);
    ttgo->tft->setTextColor(TFT_WHITE, TFT_BLACK);
    ttgo->tft->drawString("Temp", 66, 158, 2);
    ttgo->tft->fillRoundRect(119, 210, 120, 29, 15, TFT_DARKCYAN);

    // Display Temp Marker - you may need to adjust the x value based on your normal ADC results
    float temp = ttgo->power->getTemp();
    ttgo->tft->fillRoundRect(int(temp) - 20, 170, 10, 20, 5, TFT_WHITE);

    // Display Time
    // Font 7 is a 7-seg display but only contains
    // characters [space] 0 1 2 3 4 5 6 7 8 9 0 : .

    ttgo->tft->setTextColor(0xFBE0, TFT_BLACK);
    int xpos = 55;
    if (hh < 10) xpos += ttgo->tft->drawChar('0', xpos, 35, 7);
    xpos += ttgo->tft->drawNumber(hh, xpos, 35, 7);
    xpos += 3;
    xpos += ttgo->tft->drawChar(':', xpos, 35, 7);
    if (mm < 10) xpos += ttgo->tft->drawChar('0', xpos, 35, 7);
    ttgo->tft->drawNumber(mm, xpos, 35, 7);

    // Display Battery Level
    ttgo->tft->drawString("Power", 124, 2, 2);
    int per = ttgo->power->getBattPercentage();
    per = ttgo->power->getBattPercentage();
    ttgo->tft->drawString(String(per) + "%", 179, 2, 2);
    ttgo->tft->drawString(String(dday), 50, 188, 6);

    // Turn off the battery adc
    ttgo->power->adc1Enable(AXP202_VBUS_VOL_ADC1 | AXP202_VBUS_CUR_ADC1 | AXP202_BATT_CUR_ADC1 | AXP202_BATT_VOL_ADC1, false);

    // Draw Month
    String mStr;
    switch (mmonth) {
      case 1: mStr = "Jan"; break;
      case 2: mStr = "Feb"; break;
      case 3: mStr = "Mar"; break;
      case 4: mStr = "Apr"; break;
      case 5: mStr = "May"; break;
      case 6: mStr = "Jun"; break;
      case 7: mStr = "Jul"; break;
      case 8: mStr = "Aug"; break;
      case 9: mStr = "Sep"; break;
      case 10: mStr = "Oct"; break;
      case 11: mStr = "Nov"; break;
      case 12: mStr = "Dec"; break;
    ttgo->tft->drawString(mStr, 9, 194, 2);

  // Build a bargraph every 10 seconds
  int secmod = ss % 10;
  if (secmod) { // Show growing bar every 10 seconds
    ttgo->tft->fillRect(126 + secmod * 10, 215, 6, 15, TFT_ORANGE);
  } else {
    ttgo->tft->fillRoundRect(119, 210, 120, 29, 15, TFT_DARKCYAN);

15 People Made This Project!


  • Science Fair Challenge

    Science Fair Challenge
  • Trash to Treasure Contest

    Trash to Treasure Contest
  • Electronics Contest

    Electronics Contest



4 months ago

Dan, thanks a lot for sharing the code!!! but i have one small issue it looks like that some variables are not define on TWatch_framework i have some errors with
TTGOClass -> Unknown type name 'TTGOClass'clang(unknown_typename)
ttgo = TTGOClass::getWatch(); -> ttgo = TTGOClass::getWatch();
TFT_BLACK -> Use of undeclared identifier 'TFT_BLACK'clang(undeclared_var_use)
TFT_YELLOW -> Use of undeclared identifier 'TFT_YELLOW'clang(undeclared_var_use)

i think I'm missing something, and this is strange because the errors are on AppAccel and appBattery all the others .ino files can find this variables. I just started with Arduino so apologies for the question maybe is really straight forward to fix. Many thanks for the help.

UPDATE: Fix it! i was missing the config.h file!

Question 5 months ago

Hi Dan,
I am a few weeks ago very interested in buying this Lilygo T-Watch, or the WS2812 RGB module for an application in which I only intend to use with the Accelerometer, Bluetooth, Wiff or GPS to light up some led strips. WS2812 RGB on the following page promote the module.
The supplier's website
I would like to know your opinion of how reliable is the Accelerometer. From what I see in your video I would personally remove many functions, which do not appeal to me for my application. And how much more features would it have and what would I lose?
Thank you very much.
Helbert Ramirez.


Reply 5 months ago

The accelerometer is a BMA423. You can download the specs from the internet. It is a pretty standard accelerometer with some special features like step detection and detection of single and double taps. The response is nice too. I am not sure what you mean by "how much more features would it have and what would I lose?" If you look at the framework, you can get rid of anything you don't want and add any features that you program in.


Reply 5 months ago

ok gracias por su respuesta revisare detalladamente el marco


9 months ago

Using the code from the example "SimpleWatch". I got an additional module for switching to an economical power mode. In this mode, according to my estimates, the watch uses less than 10 mAh of power. I did not fully understand the purpose of the commands and just copied all the commands for disabling the elements integrated into the clock.
In active mode, the watch consumes about 90mAh.
I do not use Wi-Fi, everything works well this way.

<<< TWatch_framework_0.ino >>>
bool irq = false;
void loop() {

<<< appLowEnergy.ino >>>
void low_energy() { // Sleep mode
ttgo->power->readIRQ(); // Reading the state of the power controller
if (ttgo->power->isPEKShortPressIRQ()) { // if the button is pressed
while (irq){
// Serial.println("SLEEEP MODE");
if (ttgo->bl->isOn()) { // If ** backlight is on **
ttgo->closeBL(); // turn off the screen backlight
ttgo->stopLvglTick(); // stop working with BMA423
ttgo->displaySleep(); // turn off the Display
WiFi.mode(WIFI_OFF); // turn off WiFi
rtc_clk_cpu_freq_set(RTC_CPU_FREQ_2M); // set the clock mode
setCpuFrequencyMhz(10); // We set the clock frequency of the processor 10 MHz
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);
gpio_wakeup_enable ((gpio_num_t)BMA423_INT1, GPIO_INTR_HIGH_LEVEL);
esp_sleep_enable_gpio_wakeup ();
if (ttgo->power->isPEKShortPressIRQ()) {
irq = false;
setCpuFrequencyMhz(80); // Set the clock frequency of the processor to 80 MHz

Question 11 months ago

Download the Arduino Framework code in the individual INO files in this Instructable (sorry, I could not get the zip file to be accepted by Instrucables). Place them all into a folder in your Arduino sketch folder (Name the folder something you will remember). Open it in the IDE (may have to restart the IDE).
You will see that there are a bunch of separate tabs to the program.

I got a problem...

The " Bunch of seperate tabs to the program" are not showing for me..

I can open only the single .ino's and for every other .Ino I open it will open a whole new IDE widow

things I tried;
installed all updates to all the files
Added all seperate .Ino files into a .zip in the hope it would then open all the tabs (it didn' t, doesnt even find the zipped file)
upon opening the single .Ino's, the message ".ino needs it's own map, want to create it?"
created all maps and it still doesn' t work

I must admit this is all pretty new stuff for me, but I did manage to install the latest TTGO T watch library and updated my watch (so it's only showing the button and toggle button now)

I'm sure it's a minor thing, but it is beyond me...
All help is much appreciated!


Answer 10 months ago

I created a folder named "TWatch_framework_0".
Then all 9 files were copied to this folder. Using Arduino IDE I opened a file named TWatch_framework_0.ino and all 9 files were available for use. Of course, I did this by accident, since I am using the ESP32 for the first time and, in general, a beginner in this business.


Reply 11 months ago

The most probable is that you don't have all the files in the same directory. Make sure you put all of them in one directory and then open the main file and the others should load as tabs.

If that doesn't work, load the main one in the Arduino IDE, click on the drop down box in the upper right of the IDE and select "Add Tab". Then copy and paste one of the files into it. Repeat that for the others.


Answer 11 months ago

Anybody please answer my question??


1 year ago

Hello in step 4 wifi time how do i set that up? I'm still having problem with the manual time select. Everytime i try to set it for 7:23 for am it goes to 17:23.


Reply 11 months ago

Change in appSetTime in function prtTime(curnum) the line
if (wl != -1) {
if ((wl > -1) and (wl < 10)) {

Then it will work as expected. It seems that otherwise a wrong value of wl will be processed.


1 year ago

Great write up really good walk through.

I have a few questions, one of the codes I'm trying to use has set up and loop which it redefined due to the initial start page which is fine..

But I have a question iv re labeled the loop as void loppy the added loppy(); at the end of the loopy section to make this selection keep running over and over again is this the right way to do it ?

Also I have always though that void setup runs just once and establishes the grounds for the code when I change this am right in thinking that the app name I change it to, still runs the same as void setup ?


Reply 1 year ago

If I understand you right, you can get rid of the loopy. The code I have is the complete code, so if you just paste it into a new program, you will have my setup() and loop() and your setup() and loop() that you start with when you select NEW in the menu. Since I provide the setup and loop routines, delete the other ones. Setup() is run once and loop() repeats forever. If nothing is calling your loopy() routine, it is not executing at all.


1 year ago

hello i setup the Clock with the menu. I didn't add the Startrek theme as of yet. I came into a small problem. When i go to set the time and set it for sat 3:55am my time it goes to 13:55. I'm not sure what is wrong with it. Can someone please help me on this?

Edit: Also the other problem I'm having in the power at the top area. The percentage of the battery sometimes it reads stead of like 97% it reads 197% why is that?

One last thing. There is no year on the date. And the Temp area doens't seem to tell me what the temperature is. Just a yellow,green and a red bar but nothing under it or what shows me the temperature is.


Reply 1 year ago

There were a couple of problems with the original software and people in the comments have offered code to fix them. The time set function was one of them.
I never figured out why the power sometimes reads right and not.
For the temperature, the code prints a small icon under the color block. If you look in the code, there is a comment that you need to set one of the variables based on the temperature reading of your specific watch.


Reply 1 year ago

Hi Dan,
Can you please explain the temperature to me in more detail?
// Show Temp Markers - You may need to adjust the x value based on your normal ADC results
float temp = ttgo-> power-> getTemp ();
ttgo-> tft-> fillRoundRect (int (temp) - 20, 170, 10, 20, 5, TFT_WHITE);
Which values do you have to change where and how?


Reply 1 year ago

The temperature is a value that is returned from the getTemp() routine. To get the temperature to display under my temperature scale, I had to use the temperature value - 20. This is the x-axis value of the small oval that will be printed under the temperature scale. If your run the program and your rectangle is far off to the right or left, add or subtract a value to the -20 in the term int(temp) - 20 to get it under the temperature scale. This is going to vary from watch to watch so I don't know what it is for yours. I would suggest changing the value and see it's effect on the display.


Reply 1 year ago

If the power reads 197%, that's likely because it started as 100%, then the routine failed to clear the 3rd digit before writing the 97%.


Question 1 year ago

Hey Dan, this is a really neat project, and I managed to upload it to the watch. However every time I modify it and go to upload it, it’s kind of hit-or-miss whether it will upload successfully. I am not sure why exactly, but my settings that “worked” are as follows:
Board: TTGO T-Watch
Upload Speed:115200
Partition scheme: Large SPIFFS(7mb)
Debug: None

As for the error message it states:
“A fatal error occurred: Failed to connect to ESP32: timed out waiting for packet header”
Any ideas what might be going on?