DropDown Menu in SquareLine Studio LVGL Arduino IDE
Table of Contents
Dropdown Menu in SquareLine Studio:
DropDown Menu in SquareLine Studio LVGL Arduino IDE– This is my 5th article in the Smartwatch programming series using the CrowPanel ESP32-C3 1.28-inch IPS Capacitive Touch Display. In part 3 and part 4 of this series, we designed two GUIs: one for a digital watch and the other for an analog watch.
It displays the correct time because, before uploading the actual code, I had already set the custom time using this set of instructions.
1 2 3 4 5 |
// I2C_BM8563_TimeTypeDef timeStruct; // timeStruct.hours = 11; // Hour (0 - 23) // timeStruct.minutes = 59; // Minute (0 - 59) // timeStruct.seconds = 0; // Second (0 - 59) // rtc.setTime(&timeStruct); |
Now, let’s say I move to a different time zone and need to change the time. I would have to go through that tiring process again: connecting the smart watch to the laptop, uncommenting these instructions to set the custom time, uploading the code, then commenting these lines again, and finally uploading the actual code. It’s a long, tedious process.
Clearly, this method isn’t practical for everyday use, and it’s time to explore a better, more user-friendly approach to setting the time.
We can do it the easy way by using the DropDown Menu.
With it, we can individually set the hours, minutes, and seconds without any need to reconnect the smartwatch to a laptop or modify the code repeatedly. The DropDown Menu provides an intuitive and flexible way to input custom values directly from the device itself.
You can not only use the DropDown Menu for setting the date and time, but you can also use it in thousands of other projects. For example, you could use it to select options such as modes for a device, a list of numbers for configuration settings, names for assigning tasks, or even pre-defined text messages to send quickly. Its versatility makes it a valuable component in GUI design for various applications.
In this article, we will focus on using the DropDown Menu to set a custom time. By the end of this article, you will see how easy and efficient it is to add this functionality to your projects, saving time and effort while making your smartwatch much more user-friendly.
So, without any further delay let’s get started!
Adding a Dropdown Menu:
Let’s continue with the same project. While Screen2 is selected add two DropDown Widgets, one for setting the hours and the other for setting the minutes.
If you want you can also add a third one for setting the seconds.
Next, name the DropDown widgets so that we can easily find them in the variables list on the Arduino side. I set the names as DropdownMinutes and DropdownHours. You can see in the image above.
Next, while the DropDown widget is selected, on the inspector tab go to the Dropdown and write the options. For the minutes dropdown widget I entered 60 options from 0 to 59.
Follow the same steps for the other Dropdown widget “0 to 23”.
Next, we need to add two events for the two Dropdown widgets. For this, while the Dropdown widget is selected, click on the ADD EVENT Button.
Set the Trigger type to VALUE_CHANGE
Set the Action type to Call Function.
Next, click the ADD button.
Write the function name “SetMinutes”.
Now, follow the same steps for the other Dropdown widget. And this time write the function name “SetHours”.
Now, let’s save the project and export the UI files.
Then go to the UI files folder, copy all the files and paste them in the same Arduino project folder. Then go ahead and open the Arduino file.
Complete Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
#define LGFX_USE_V1 #include <WiFi.h> #include "Arduino.h" #include <lvgl.h> #include <LovyanGFX.hpp> #include <Ticker.h> #include "CST816D.h" #include "do_mian.h" #include "ui.h" #include <Preferences.h> #include "I2C_BM8563.h" #define I2C_SDA 4 #define I2C_SCL 5 #define TP_INT 0 #define TP_RST -1 int counter = 0; // For analog watch int h = 0; int m = 0; int s = 0; //encoder #define ENCODER_A_PIN 19 #define ENCODER_B_PIN 18 #define SWITCH_PIN 8 //Custom key pins #define Custom_PIN 1 long position = 0; long position_tmp = 0; bool switchPressed = false; #define PI4IO_I2C_ADDR 0x43 I2C_BM8563 rtc(I2C_BM8563_DEFAULT_ADDRESS, Wire); I2C_BM8563_DateTypeDef dateStruct; I2C_BM8563_TimeTypeDef timeStruct; #define off_pin 35 #define buf_size 120 //Alarm switch sign int fal = 0; //Indicates whether the alarm has gone off int fal1 = 0; uint32_t hourValue = 0; uint32_t minuteValue = 0; class LGFX : public lgfx::LGFX_Device { lgfx::Panel_GC9A01 _panel_instance; lgfx::Bus_SPI _bus_instance; public: LGFX(void) { { auto cfg = _bus_instance.config(); cfg.spi_host = SPI2_HOST; cfg.spi_mode = 0; cfg.freq_write = 80000000; cfg.freq_read = 20000000; cfg.spi_3wire = true; cfg.use_lock = true; cfg.dma_channel = SPI_DMA_CH_AUTO; cfg.pin_sclk = 6; cfg.pin_mosi = 7; cfg.pin_miso = -1; cfg.pin_dc = 2; _bus_instance.config(cfg); _panel_instance.setBus(&_bus_instance); } { auto cfg = _panel_instance.config(); cfg.pin_cs = 10; cfg.pin_rst = -1; cfg.pin_busy = -1; cfg.memory_width = 240; cfg.memory_height = 240; cfg.panel_width = 240; cfg.panel_height = 240; cfg.offset_x = 0; cfg.offset_y = 0; cfg.offset_rotation = 0; cfg.dummy_read_pixel = 8; cfg.dummy_read_bits = 1; cfg.readable = false; cfg.invert = true; cfg.rgb_order = false; cfg.dlen_16bit = false; cfg.bus_shared = false; _panel_instance.config(cfg); } setPanel(&_panel_instance); } }; LGFX tft; CST816D touch(I2C_SDA, I2C_SCL, TP_RST, TP_INT); /*Change to your screen resolution*/ static const uint32_t screenWidth = 240; static const uint32_t screenHeight = 240; static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[2][screenWidth * buf_size]; #if LV_USE_LOG != 0 /* Serial debugging */ void my_print(lv_log_level_t level, const char *file, uint32_t line, const char *fn_name, const char *dsc) { Serial.printf("%s(%s)@%d->%s\r\n", file, fn_name, line, dsc); Serial.flush(); } #endif /* Display flushing */ void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { if (tft.getStartCount() == 0) { tft.endWrite(); } tft.pushImageDMA(area->x1, area->y1, area->x2 - area->x1 + 1, area->y2 - area->y1 + 1, (lgfx::swap565_t *)&color_p->full); lv_disp_flush_ready(disp); /* tell lvgl that flushing is done */ } /*Read the touchpad*/ void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) { bool touched; uint8_t gesture; uint16_t touchX, touchY; touched = touch.getTouch(&touchX, &touchY, &gesture); if (!touched) { data->state = LV_INDEV_STATE_REL; } else { data->state = LV_INDEV_STATE_PR; /*Set the coordinates*/ data->point.x = touchX; data->point.y = touchY; } } Ticker ticker; //Extended IO function void init_IO_extender() { Wire.beginTransmission(PI4IO_I2C_ADDR); Wire.write(0x01); // test register Wire.endTransmission(); Wire.requestFrom(PI4IO_I2C_ADDR, 1); uint8_t rxdata = Wire.read(); Serial.print("Device ID: "); Serial.println(rxdata, HEX); Wire.beginTransmission(PI4IO_I2C_ADDR); Wire.write(0x03); // IO direction register Wire.write((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4)); // set pins 0, 1, 2 as outputs Wire.endTransmission(); Wire.beginTransmission(PI4IO_I2C_ADDR); Wire.write(0x07); // Output Hi-Z register Wire.write(~((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4))); // set pins 0, 1, 2 low Wire.endTransmission(); } void set_pin_io(uint8_t pin_number, bool value) { Wire.beginTransmission(PI4IO_I2C_ADDR); Wire.write(0x05); // test register Wire.endTransmission(); Wire.requestFrom(PI4IO_I2C_ADDR, 1); uint8_t rxdata = Wire.read(); Serial.print("Before the change: "); Serial.println(rxdata, HEX); Wire.beginTransmission(PI4IO_I2C_ADDR); Wire.write(0x05); // Output register if (!value) Wire.write((~(1 << pin_number)) & rxdata); // set pin low else Wire.write((1 << pin_number) | rxdata); // set pin high Wire.endTransmission(); Wire.beginTransmission(PI4IO_I2C_ADDR); Wire.write(0x05); // test register Wire.endTransmission(); Wire.requestFrom(PI4IO_I2C_ADDR, 1); rxdata = Wire.read(); Serial.print("after the change: "); Serial.println(rxdata, HEX); } //RTC function void RTC_init() { rtc.begin(); // Set custom time // I2C_BM8563_TimeTypeDef timeStruct; // timeStruct.hours = 11; // Hour (0 - 23) // timeStruct.minutes = 59; // Minute (0 - 59) // timeStruct.seconds = 0; // Second (0 - 59) // rtc.setTime(&timeStruct); // // I2C_BM8563_DateTypeDef dateStruct; // dateStruct.weekDay = 3; // Weekday (0 - 6, where 0 is Sunday) // dateStruct.month = 1; // Month (1 - 12) // dateStruct.date = 24; // Day of the month (1 - 31) // dateStruct.year = 2024; // Year // rtc.setDate(&dateStruct); } //Encoder function void updateEncoder() { static int previousState = 0; static int flag_A = 0; static int flag_C = 0; int currentState = (digitalRead(ENCODER_A_PIN) << 1) | digitalRead(ENCODER_B_PIN); if ((currentState == 0b00 && previousState == 0b01) || (currentState == 0b01 && previousState == 0b11) || (currentState == 0b11 && previousState == 0b10) || (currentState == 0b10 && previousState == 0b00)) { // foreward // if (switchPressed) { flag_A++; if (flag_A == 50) { flag_A = 0; flag_C = 0; // position++; // position_tmp=position; position_tmp = 1; } // flag_C=0; // } } else if ((currentState == 0b01 && previousState == 0b00) || (currentState == 0b11 && previousState == 0b01) || (currentState == 0b10 && previousState == 0b11) || (currentState == 0b00 && previousState == 0b10)) { // reversal // if (switchPressed) { flag_C++; if (flag_C == 50) { // position--; flag_C = 0; flag_A = 0; // position_tmp=position; position_tmp = 0; } // flag_A=0; // } } previousState = currentState; } void switchPressedInterrupt() { switchPressed = !switchPressed; } void setup() { Serial.begin(115200); /* prepare for possible serial debug */ Serial.println("I am LVGL_Arduino"); Wire.begin(4, 5); init_IO_extender(); delay(100); set_pin_io(3, true); set_pin_io(4, true); pinMode(ENCODER_A_PIN, INPUT_PULLUP); pinMode(ENCODER_B_PIN, INPUT_PULLUP); pinMode(SWITCH_PIN, INPUT_PULLUP); pinMode(Custom_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(ENCODER_A_PIN), updateEncoder, CHANGE); attachInterrupt(digitalPinToInterrupt(ENCODER_B_PIN), updateEncoder, CHANGE); attachInterrupt(digitalPinToInterrupt(SWITCH_PIN), switchPressedInterrupt, FALLING); // ticker.attach(1, tcr1s); tft.init(); tft.initDMA(); tft.startWrite(); tft.setColor(0, 0, 0); tft.fillScreen(TFT_BLACK); delay(200); if (is_touch == 1) { touch.begin(); } lv_init(); #if LV_USE_LOG != 0 //lv_log_register_print_cb(my_print); /* register print function for debugging */ #endif lv_disp_draw_buf_init(&draw_buf, buf[0], buf[1], screenWidth * buf_size); /*Initialize the display*/ static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); /*Change the following line to your display resolution*/ disp_drv.hor_res = screenWidth; disp_drv.ver_res = screenHeight; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); /*Initialize the (dummy) input device driver*/ if (is_touch == 1) { static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; lv_indev_drv_register(&indev_drv); } #if 0 /* Create simple label */ lv_obj_t *label = lv_label_create( lv_scr_act() ); lv_label_set_text( label, "Hello Arduino! (V8.0.X)" ); lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 ); #else /* Try an example from the lv_examples Arduino library make sure to include it as written above. lv_example_btn_1(); */ // uncomment one of these demos // lv_demo_widgets(); // OK // lv_demo_benchmark(); // OK // lv_demo_keypad_encoder(); // works, but I haven't an encoder // lv_demo_music(); // NOK // lv_demo_printer(); // lv_demo_stress(); // seems to be OK ui_mian(); // watch #endif Serial.println("Setup done"); // delay(200); set_pin_io(2, true); pinMode(3, OUTPUT); digitalWrite(3, LOW); // pinMode(0, INPUT); // Get RTC RTC_init(); rtc.getDate(&dateStruct); rtc.getTime(&timeStruct); } //void Watch_Function(void *param) void loop() { lv_timer_handler(); /* let the GUI do its work */ lv_label_set_text(ui_lblCounter, String(counter).c_str()); rtc.getTime(&timeStruct); lv_label_set_text(ui_lblHour, String(timeStruct.hours).c_str()); lv_label_set_text(ui_lblminutes, String(timeStruct.minutes).c_str()); lv_label_set_text(ui_lblseconds, String(timeStruct.seconds).c_str()); /****************** For Analog Watch ****************/ h = timeStruct.hours; m = timeStruct.minutes; // s = timeStruct.seconds * 60 % 3600; //Initializing the alarm page from the RTC // lv_img_set_angle(ui_shand, s); // lv_img_set_angle(ui_shand2, s); lv_img_set_angle(ui_imageminutehand, m * 60); lv_img_set_angle(ui_imagehourhand, h * 300 + m / 12 * 60); lv_img_set_angle(ui_imageminutehand, m * 60); lv_img_set_angle(ui_imagehourhand, h * 300 + m / 12 * 60); } void ResetCounter(lv_event_t * e) { counter = 0; } void incrementCounter(lv_event_t * e) { counter++; } void SetHours(lv_event_t * e) { // Buffer to store the selected option text char myHour[25]; // Adjust size based on the longest dropdown option ui_DropdownHours = lv_event_get_target(e); lv_dropdown_get_selected_str(ui_DropdownHours, myHour, sizeof(myHour)); timeStruct.hours = atoi(myHour); rtc.setTime(&timeStruct); } void SetMinutes(lv_event_t * e) { char myMinutes[61]; // Adjust size based on the longest dropdown option ui_DropdownMinutes = lv_event_get_target(e); lv_dropdown_get_selected_str(ui_DropdownMinutes, myMinutes, sizeof(myMinutes)); timeStruct.minutes = atoi(myMinutes); rtc.setTime(&timeStruct); } |
In Part 3, I already explained where to find the screen properties, how to enable support for large fonts, and how to locate all the variables. I recommend reading Part 3 to learn how to avoid errors.
There is just one thing you need to do every time you generate new UI files: change LV_COLOR_16_SWAP from 0 to 1.
Let me tell you, if you want to make everything easier, simply download this template folder.
It includes all the files with all errors already fixed for you. You can download this template folder for free from my Patreon page.
Practical Demonstration:
Now, you can see, the Dropdown menus are added to the digital watch. Now, I can set the hours and minutes in just a few seconds.
So, that’s all for now!
Watch Video Tutorial: