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);

13 People Made This Project!


  • Clocks Speed Challenge

    Clocks Speed Challenge
  • Toys & Games Contest

    Toys & Games Contest
  • Big vs Small Challenge

    Big vs Small Challenge



1 day 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 19 hours 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.


2 months 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.


4 months 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 4 months 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 2 months 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 2 months 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 2 months 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%.


Reply 4 months ago

You are wesome thank you


Question 5 months 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?


Answer 5 months ago

Actually never mind, the driver software was installed as CH340g drivers for some reason, no wonder. After installing the actual drivers, however, it worked perfectly. Still don’t understand how I ever uploaded a program like that, but whatever. Thanks for the excellent program!


Reply 2 months ago

I'm still getting that same error message. I checked the driver, and it's using CP210x. I know it's working because I can see traffic on COM9 when I reset the watch using the power button. Which driver are you using? Thanks.


Reply 2 months ago

Thanks, Andrew. Yes, I did get the driver from Silicon Labs. I then tried it with both the original cable and others on another Windows laptop, then on a Mac laptop. Got the same result every time. On all of them I can see data from the watch, but can't upload anything. I put in a question to the vendor, Banggood.


Reply 2 months ago

Maybe try uninstalling and reinstalling it? Other than that I don’t know what to try. Maybe the seller could help. Good luck on that


Reply 2 months ago

Thanks for the idea, Andrew. I kind of did that by installing the right drivers and upgrades to Arduino on both the mac and the other windows laptop. Both other machines had to be brought up from scratch, since I hadn't done any esp32 development on them. I can now do it in my sleep. Banggood, the vendor, seems to be pretty responsive, having already asked me for more details about the failure. I'm making a youtube video about it now.


Reply 5 months ago

Ecellent. That would be the thing I would have looked for.


2 months ago

Thank you for watching this example watch. You can build on this example and try to expand yourself. I really liked that you embed the Void ... as an app because it makes the code clearer in the programming. Please continue.


Question 4 months ago

Hello at first i started not liking this watch. But the more i play with it the more Im loving it. TI followed all directions to setup and it worked. But my question is how can i make the watch go to sleep with your code? Because the screen is on all the time.


Answer 4 months ago

My watch is in light sleep most of the time, the battery lasts all day. I started with Dan's framework, my son and I love the simple retro watch look. I've now grabbed code from several other sources too (like Dudley, J Hershey, diyProjects). It connects to wifi, checks my local time (from Dan) and weather. Work in progress -