Tech Blog

LVGL で ePaper に QR コードを描画する

ePaper に QR コードを LVGL (Light and Versatile Graphics Library) で表示する省電力 ESP デバイスを試作する

今回の環境

TL;DR

ファームウェアビルド環境の準備
brew tap espressif/eim
brew install eim
eim install -i v6.1
dependency の追加
idf.py add-dependency "dzarda7/esp_epaper^0.1.0"
idf.py add-dependency "lvgl/lvgl^9.3.0"
examples コード
../managed_components/dzarda7__esp_epaper/examples/epaper_lvgl/main/main.c .
panel 情報追加
  // gdem0154z91.c
+ #include "epd_private.h"
+
+ static const epd_init_cmd_t gdem0154z91_init[] = {
+     { 0x3C, 1, { 0x05 } },       /* border waveform: follow the LUT1 white level */
+     { 0x21, 2, { 0x00, 0x80 } }, /* 128 px of glass on 176 sources: use S8..S167 */
+     { 0x18, 1, { 0x80 } },       /* waveform compensation from the internal temperature sensor */
+ };
+
+ const epd_panel_desc_t epd_panel_gdem0154z91 = {
+     .name = "GDEM0154Z91",
+     .controller = &epd_controller_ssd1680,
+     .width = 152,
+     .height = 152,
+     .gate_lines = 152,
+     .init_cmds = gdem0154z91_init,
+     .init_cmd_count = sizeof(gdem0154z91_init) / sizeof(gdem0154z91_init[0]),
+     .busy_active_high = true,
+     .reset_low_ms = 10,
+     .reset_high_ms = 10,
+     .power_on_delay_ms = 10,
+     .full_refresh_ms = 14000,
+     .partial_refresh_ms = 500,
+     .supports_partial = false,
+ };
init
  // main/main.c
  #include <stdlib.h>
  #include <string.h>

  ...

  static esp_lcd_panel_handle_t display_init(void)
  {
      const spi_bus_config_t bus_cfg = {
-         .mosi_io_num = EPINK_SHELF_29_PIN_MOSI,
+         .mosi_io_num = GPIO_NUM_7,
          .miso_io_num = -1,
-         .sclk_io_num = EPINK_SHELF_29_PIN_SCLK,
+         .sclk_io_num = GPIO_NUM_6,
          .quadwp_io_num = -1,
          .quadhd_io_num = -1,
          .max_transfer_sz = 4096,
      };
-     ESP_ERROR_CHECK(spi_bus_initialize(EPINK_SHELF_29_SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO));
+     ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO));

      const esp_lcd_panel_io_spi_config_t io_cfg = {
-         .cs_gpio_num = EPINK_SHELF_29_PIN_CS,
-         .dc_gpio_num = EPINK_SHELF_29_PIN_DC,
+         .cs_gpio_num = GPIO_NUM_10,
+         .dc_gpio_num = GPIO_NUM_3,
          .spi_mode = 0,
-         .pclk_hz = EPINK_SHELF_29_PIXEL_CLK_HZ,
+         .pclk_hz = 2 * 1000 * 1000,
          .trans_queue_depth = 10,
          .lcd_cmd_bits = 8,
          .lcd_param_bits = 8,
      };
      esp_lcd_panel_io_handle_t io = NULL;
-     ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)EPINK_SHELF_29_SPI_HOST, &io_cfg, &io));
+     ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &io_cfg, &io));

      esp_lcd_epaper_config_t epd_cfg = {
          .panel = &epd_panel_gdey029t94,
-         .busy_gpio_num = EPINK_SHELF_29_PIN_BUSY,
-         .power_ctrl = epink_shelf_29_power_ctrl, /* board switches the panel rail */
-         .rotation = EPD_ROTATION_90,
-         .refresh_mode = EPD_REFRESH_AUTO,
+         .busy_gpio_num = GPIO_NUM_0,
+         .rotation = EPD_ROTATION_180,
+         .refresh_mode = EPD_REFRESH_FULL,
          .full_refresh_interval = 20,
      };
      const esp_lcd_panel_dev_config_t panel_cfg = {
-         .reset_gpio_num = EPINK_SHELF_29_PIN_RST,
+         .reset_gpio_num = GPIO_NUM_1,
          .bits_per_pixel = 1,
          .vendor_config = &epd_cfg,
      };
      esp_lcd_panel_handle_t panel = NULL;
      ESP_ERROR_CHECK(esp_lcd_new_panel_epaper(io, &panel_cfg, &panel));
      ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
      ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
      return panel;
  }

  ...
QR コード
  // main/main.c
  #include <stdlib.h>
  #include <string.h>

  ...
  static esp_lcd_panel_handle_t s_panel;
- static lv_obj_t *s_counter_label;
+ static lv_obj_t *s_qr_code;

  ...

  static void build_ui(void)
  {
      lv_obj_t *scr = lv_screen_active();
      lv_obj_set_style_bg_color(scr, lv_color_white(), 0);

-     lv_obj_t *title = lv_label_create(scr);
-     lv_label_set_text(title, "esp_epaper + LVGL");
-     lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
-     lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8);
-
-     s_counter_label = lv_label_create(scr);
-     lv_label_set_text(s_counter_label, "0");
-     lv_obj_set_style_text_font(s_counter_label, &lv_font_montserrat_24, 0);
-     lv_obj_align(s_counter_label, LV_ALIGN_CENTER, 0, 10);
-
-     lv_obj_t *line = lv_obj_create(scr);
-     lv_obj_set_size(line, DISP_WIDTH - 40, 3);
-     lv_obj_set_style_bg_color(line, lv_color_black(), 0);
-     lv_obj_set_style_border_width(line, 0, 0);
-     lv_obj_align(line, LV_ALIGN_TOP_MID, 0, 42);
+     s_qr_code = lv_qrcode_create(lv_scr_act());
+     lv_obj_align(s_qr_code, LV_ALIGN_CENTER, 0, 0);
+     lv_qrcode_set_size(s_qr_code, 152);
+     lv_qrcode_set_dark_color(s_qr_code, lv_color_black());
+     lv_qrcode_set_light_color(s_qr_code, lv_color_white());
  }

  ...

  void app_main(void)
  {
      s_panel = display_init();
  ...
      ESP_LOGI(TAG, "UI created, entering LVGL loop");
-     uint32_t counter = 0;
      uint32_t next_tick = 0;
      while (1) {
          uint32_t now = lvgl_tick_cb();
          if (now >= next_tick) {
              next_tick = now + 10000; /* one update every 10 s */
-             lv_label_set_text_fmt(s_counter_label, "%" LV_PRIu32, counter++);
+             const char * data = "Hello world";
+             lv_qrcode_set_data(s_qr_code, data);
          }
          uint32_t wait_ms = lv_timer_handler();
          if (wait_ms == LV_NO_TIMER_READY || wait_ms > LVGL_TICK_PERIOD_MS * 4) {
              wait_ms = LVGL_TICK_PERIOD_MS * 4;
          }
          vTaskDelay(pdMS_TO_TICKS(wait_ms ? wait_ms : LVGL_TICK_PERIOD_MS));
      }
  }
DEEPSLEEP
  // main/main.c
  ...
+ #include "esp_sleep.h"
+ #include "esp_attr.h"
...
+     ESP_LOGI(TAG, "Entering deep sleep...");
+     esp_sleep_enable_timer_wakeup(30 * 1000 * 1000);
+     esp_deep_sleep_start();
...
実行
idf.py set-target esp32c3
idf.py -p $PORT build
idf.py -p $PORT flash

Hello world

参考にしたページ