728x90

728x90

Tuesday, January 20, 2026

ESP32 ILI9341 2.8 inches TFT Example


Overview 

ESP32 is a SoC micro-controller with WiFi module. It's a low-cost and high speed 32-bit micro-controller suitable for most of hobby and commercial projects. It is very popular in IoT project since it's very to learn and program using various examples and libraries from many developers.

Arduino IDE is very common for most of programmers. PlatformIO plugin for Visual Studio Code is also a second option for experienced programmers. They can program this chip using its effective lower API ESP-IDF that able to access most of its hardware level layers.

Display interfacing is an interesting stuffs for electronic project especially for stand-alone device that has a built-in GUI. There are may newer and low-cost graphical displays especially the TFT display. They come with many physical sizes and screen resolutions, for instance a 128x160RGB, a 240x240RGB and a 240x320RGB etc.

ESP32 ILI9341 2.8 inches TFT Example
Arduino TFT_eSPI Meter Example

 

Some popular TFT LCD controllers the ST7735(128x160RGB), the ST7785(240x320RGB) and the ILI9341(240x320RGB) etc. There are many compatible TFT controllers for these controller chips. For example the ILI9341 and the ST7785 are equivalent.

ESP32 Arduino Programming and Interfacing

Arduino is very easy to use due to its readabilities excessive libraries. There are may TFT libraries for this TFT module especially from the Adafruit industry. However I use the TFT_eSPI libraries since its support many types of display controllers with various micro-controller modules.

I use the ESP32-WROOM-32U and a 2.8" ILI9341 with the XPT2046 resistive touch module to demo the example.

 

ESP32 ILI9341 2.8 inches TFT Example

I tested its Meter example using 240x320 TFT display module. Then the "meter.ino" is opened.

  1. /*
  2. Example animated analogue meters using a ILI9341 TFT LCD screen

  3. Needs Font 2 (also Font 4 if using large scale label)

  4. Make sure all the display driver and pin connections are correct by
  5. editing the User_Setup.h file in the TFT_eSPI library folder.

  6. #########################################################################
  7. ###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
  8. #########################################################################
  9. */

  10. #include <TFT_eSPI.h> // Hardware-specific library
  11. #include <SPI.h>

  12. TFT_eSPI tft = TFT_eSPI(); // Invoke custom library

  13. #define TFT_GREY 0x5AEB

  14. #define LOOP_PERIOD 35 // Display updates every 35 ms

  15. float ltx = 0; // Saved x coord of bottom of needle
  16. uint16_t osx = 120, osy = 120; // Saved x & y coords
  17. uint32_t updateTime = 0; // time for next update

  18. int old_analog = -999; // Value last displayed
  19. int old_digital = -999; // Value last displayed

  20. int value[6] = {0, 0, 0, 0, 0, 0};
  21. int old_value[6] = { -1, -1, -1, -1, -1, -1};
  22. int d = 0;

  23. void setup(void) {
  24. tft.init();
  25. tft.setRotation(0);
  26. Serial.begin(57600); // For debug
  27. tft.fillScreen(TFT_BLACK);

  28. analogMeter(); // Draw analogue meter

  29. // Draw 6 linear meters
  30. byte d = 40;
  31. plotLinear("A0", 0, 160);
  32. plotLinear("A1", 1 * d, 160);
  33. plotLinear("A2", 2 * d, 160);
  34. plotLinear("A3", 3 * d, 160);
  35. plotLinear("A4", 4 * d, 160);
  36. plotLinear("A5", 5 * d, 160);

  37. updateTime = millis(); // Next update time
  38. }


  39. void loop() {
  40. if (updateTime <= millis()) {
  41. updateTime = millis() + LOOP_PERIOD;

  42. d += 4; if (d >= 360) d = 0;

  43. //value[0] = map(analogRead(A0), 0, 1023, 0, 100); // Test with value form Analogue 0

  44. // Create a Sine wave for testing
  45. value[0] = 50 + 50 * sin((d + 0) * 0.0174532925);
  46. value[1] = 50 + 50 * sin((d + 60) * 0.0174532925);
  47. value[2] = 50 + 50 * sin((d + 120) * 0.0174532925);
  48. value[3] = 50 + 50 * sin((d + 180) * 0.0174532925);
  49. value[4] = 50 + 50 * sin((d + 240) * 0.0174532925);
  50. value[5] = 50 + 50 * sin((d + 300) * 0.0174532925);

  51. //unsigned long t = millis();

  52. plotPointer();

  53. plotNeedle(value[0], 0);

  54. //Serial.println(millis()-t); // Print time taken for meter update
  55. }
  56. }


  57. // #########################################################################
  58. // Draw the analogue meter on the screen
  59. // #########################################################################
  60. void analogMeter()
  61. {
  62. // Meter outline
  63. tft.fillRect(0, 0, 239, 126, TFT_GREY);
  64. tft.fillRect(5, 3, 230, 119, TFT_WHITE);

  65. tft.setTextColor(TFT_BLACK); // Text colour

  66. // Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing)
  67. for (int i = -50; i < 51; i += 5) {
  68. // Long scale tick length
  69. int tl = 15;

  70. // Coordinates of tick to draw
  71. float sx = cos((i - 90) * 0.0174532925);
  72. float sy = sin((i - 90) * 0.0174532925);
  73. uint16_t x0 = sx * (100 + tl) + 120;
  74. uint16_t y0 = sy * (100 + tl) + 140;
  75. uint16_t x1 = sx * 100 + 120;
  76. uint16_t y1 = sy * 100 + 140;

  77. // Coordinates of next tick for zone fill
  78. float sx2 = cos((i + 5 - 90) * 0.0174532925);
  79. float sy2 = sin((i + 5 - 90) * 0.0174532925);
  80. int x2 = sx2 * (100 + tl) + 120;
  81. int y2 = sy2 * (100 + tl) + 140;
  82. int x3 = sx2 * 100 + 120;
  83. int y3 = sy2 * 100 + 140;

  84. // Yellow zone limits
  85. //if (i >= -50 && i < 0) {
  86. // tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_YELLOW);
  87. // tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_YELLOW);
  88. //}

  89. // Green zone limits
  90. if (i >= 0 && i < 25) {
  91. tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREEN);
  92. tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREEN);
  93. }

  94. // Orange zone limits
  95. if (i >= 25 && i < 50) {
  96. tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_ORANGE);
  97. tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_ORANGE);
  98. }

  99. // Short scale tick length
  100. if (i % 25 != 0) tl = 8;

  101. // Recalculate coords incase tick lenght changed
  102. x0 = sx * (100 + tl) + 120;
  103. y0 = sy * (100 + tl) + 140;
  104. x1 = sx * 100 + 120;
  105. y1 = sy * 100 + 140;

  106. // Draw tick
  107. tft.drawLine(x0, y0, x1, y1, TFT_BLACK);

  108. // Check if labels should be drawn, with position tweaks
  109. if (i % 25 == 0) {
  110. // Calculate label positions
  111. x0 = sx * (100 + tl + 10) + 120;
  112. y0 = sy * (100 + tl + 10) + 140;
  113. switch (i / 25) {
  114. case -2: tft.drawCentreString("0", x0, y0 - 12, 2); break;
  115. case -1: tft.drawCentreString("25", x0, y0 - 9, 2); break;
  116. case 0: tft.drawCentreString("50", x0, y0 - 6, 2); break;
  117. case 1: tft.drawCentreString("75", x0, y0 - 9, 2); break;
  118. case 2: tft.drawCentreString("100", x0, y0 - 12, 2); break;
  119. }
  120. }

  121. // Now draw the arc of the scale
  122. sx = cos((i + 5 - 90) * 0.0174532925);
  123. sy = sin((i + 5 - 90) * 0.0174532925);
  124. x0 = sx * 100 + 120;
  125. y0 = sy * 100 + 140;
  126. // Draw scale arc, don't draw the last part
  127. if (i < 50) tft.drawLine(x0, y0, x1, y1, TFT_BLACK);
  128. }

  129. tft.drawString("%RH", 5 + 230 - 40, 119 - 20, 2); // Units at bottom right
  130. tft.drawCentreString("%RH", 120, 70, 4); // Comment out to avoid font 4
  131. tft.drawRect(5, 3, 230, 119, TFT_BLACK); // Draw bezel line

  132. plotNeedle(0, 0); // Put meter needle at 0
  133. }

  134. // #########################################################################
  135. // Update needle position
  136. // This function is blocking while needle moves, time depends on ms_delay
  137. // 10ms minimises needle flicker if text is drawn within needle sweep area
  138. // Smaller values OK if text not in sweep area, zero for instant movement but
  139. // does not look realistic... (note: 100 increments for full scale deflection)
  140. // #########################################################################
  141. void plotNeedle(int value, byte ms_delay)
  142. {
  143. tft.setTextColor(TFT_BLACK, TFT_WHITE);
  144. char buf[8]; dtostrf(value, 4, 0, buf);
  145. tft.drawRightString(buf, 40, 119 - 20, 2);

  146. if (value < -10) value = -10; // Limit value to emulate needle end stops
  147. if (value > 110) value = 110;

  148. // Move the needle util new value reached
  149. while (!(value == old_analog)) {
  150. if (old_analog < value) old_analog++;
  151. else old_analog--;

  152. if (ms_delay == 0) old_analog = value; // Update immediately id delay is 0

  153. float sdeg = map(old_analog, -10, 110, -150, -30); // Map value to angle
  154. // Calculate tip of needle coords
  155. float sx = cos(sdeg * 0.0174532925);
  156. float sy = sin(sdeg * 0.0174532925);

  157. // Calculate x delta of needle start (does not start at pivot point)
  158. float tx = tan((sdeg + 90) * 0.0174532925);

  159. // Erase old needle image
  160. tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_WHITE);
  161. tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_WHITE);
  162. tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_WHITE);

  163. // Re-plot text under needle
  164. tft.setTextColor(TFT_BLACK);
  165. tft.drawCentreString("%RH", 120, 70, 4); // // Comment out to avoid font 4

  166. // Store new needle end coords for next erase
  167. ltx = tx;
  168. osx = sx * 98 + 120;
  169. osy = sy * 98 + 140;

  170. // Draw the needle in the new postion, magenta makes needle a bit bolder
  171. // draws 3 lines to thicken needle
  172. tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_RED);
  173. tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_MAGENTA);
  174. tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_RED);

  175. // Slow needle down slightly as it approaches new postion
  176. if (abs(old_analog - value) < 10) ms_delay += ms_delay / 5;

  177. // Wait before next update
  178. delay(ms_delay);
  179. }
  180. }

  181. // #########################################################################
  182. // Draw a linear meter on the screen
  183. // #########################################################################
  184. void plotLinear(char *label, int x, int y)
  185. {
  186. int w = 36;
  187. tft.drawRect(x, y, w, 155, TFT_GREY);
  188. tft.fillRect(x + 2, y + 19, w - 3, 155 - 38, TFT_WHITE);
  189. tft.setTextColor(TFT_CYAN, TFT_BLACK);
  190. tft.drawCentreString(label, x + w / 2, y + 2, 2);

  191. for (int i = 0; i < 110; i += 10)
  192. {
  193. tft.drawFastHLine(x + 20, y + 27 + i, 6, TFT_BLACK);
  194. }

  195. for (int i = 0; i < 110; i += 50)
  196. {
  197. tft.drawFastHLine(x + 20, y + 27 + i, 9, TFT_BLACK);
  198. }

  199. tft.fillTriangle(x + 3, y + 127, x + 3 + 16, y + 127, x + 3, y + 127 - 5, TFT_RED);
  200. tft.fillTriangle(x + 3, y + 127, x + 3 + 16, y + 127, x + 3, y + 127 + 5, TFT_RED);

  201. tft.drawCentreString("---", x + w / 2, y + 155 - 18, 2);
  202. }

  203. // #########################################################################
  204. // Adjust 6 linear meter pointer positions
  205. // #########################################################################
  206. void plotPointer(void)
  207. {
  208. int dy = 187;
  209. byte pw = 16;

  210. tft.setTextColor(TFT_GREEN, TFT_BLACK);

  211. // Move the 6 pointers one pixel towards new value
  212. for (int i = 0; i < 6; i++)
  213. {
  214. char buf[8]; dtostrf(value[i], 4, 0, buf);
  215. tft.drawRightString(buf, i * 40 + 36 - 5, 187 - 27 + 155 - 18, 2);

  216. int dx = 3 + 40 * i;
  217. if (value[i] < 0) value[i] = 0; // Limit value to emulate needle end stops
  218. if (value[i] > 100) value[i] = 100;

  219. while (!(value[i] == old_value[i])) {
  220. dy = 187 + 100 - old_value[i];
  221. if (old_value[i] > value[i])
  222. {
  223. tft.drawLine(dx, dy - 5, dx + pw, dy, TFT_WHITE);
  224. old_value[i]--;
  225. tft.drawLine(dx, dy + 6, dx + pw, dy + 1, TFT_RED);
  226. }
  227. else
  228. {
  229. tft.drawLine(dx, dy + 5, dx + pw, dy, TFT_WHITE);
  230. old_value[i]++;
  231. tft.drawLine(dx, dy - 6, dx + pw, dy - 1, TFT_RED);
  232. }
  233. }
  234. }
  235. }



This libraries is compiled for the ESP8266 by default. So I need to modify its its User_Setup.h library header file in the TFT_eSPI library folder as follow.

 #define ILI9341_2_DRIVER     // Alternative ILI9341 driver, see https://github.com/Bodmer/TFT_eSPI/issues/1172

#define TFT_INVERSION_OFF 

// For NodeMCU - use pin numbers in the form PIN_Dx where Dx is the NodeMCU pin designation
#define TFT_MISO  PIN_D6  // Automatically assigned with ESP8266 if not defined
#define TFT_MOSI  PIN_D7  // Automatically assigned with ESP8266 if not defined
#define TFT_SCLK  PIN_D5  // Automatically assigned with ESP8266 if not defined

#define TFT_CS    PIN_D8  // Chip select control pin D8
#define TFT_DC    PIN_D3  // Data Command control pin
#define TFT_RST   PIN_D4  // Reset pin (could connect to NodeMCU RST, see next line)
//#define TFT_RST  -1     // Set TFT_RST to -1 if the display RESET is connected to NodeMCU RST or 3.3V

I need to un-commend other unused definitions.Then I need to save this file. 

ESP32 ILI9341 2.8 inches TFT Example

ESP32 ILI9341 2.8 inches TFT Example

 

 

320x50

Search This Blog

Labels

tyro-728x90