changeset 8:47795c3121f1

bufferless updates! mqtt message is sent over SPI and everything works
author drewp@bigasterisk.com
date Mon, 11 Mar 2024 01:37:57 -0700
parents b46679798c51
children 315f79d04c75
files esp/do-squib-touch-lcd.yaml esp/update_lcd_block.h lcd_simulator.py src/scheduleLcd.css tasks.py web_to_mqtt.py
diffstat 6 files changed, 291 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/esp/do-squib-touch-lcd.yaml	Sun Mar 10 15:03:53 2024 -0700
+++ b/esp/do-squib-touch-lcd.yaml	Mon Mar 11 01:37:57 2024 -0700
@@ -34,7 +34,7 @@
     topic: 'display/squib/updates'
     then:
     - lambda: |-
-       update_lcd_block(id(lcd), x);
+       update_lcd_block(static_cast<ili9xxx::ILI9481Direct*>(id(lcd)), x);
 light:
   - platform: status_led
     id: "status_out"
@@ -62,20 +62,14 @@
     pin: GPIO17
     id: lcd_bright
 
-display:
-  - platform: ili9xxx
-    id: lcd
-    model: ILI9481-18
-    dc_pin: GPIO22
-    reset_pin: GPIO25
-    cs_pin: GPIO27
-    dimensions:
-      width: 320
-      height: 320
-    data_rate: "10MHz"
-    update_interval: "never"
-    auto_clear_enabled: false
  
+custom_component:
+- lambda: |-
+    auto my_custom = new esphome::ili9xxx::ILI9481Direct(spi_spicomponent);
+    return {my_custom};
+  components:
+  - id: lcd
+
 # breakout kit esp board has these neighbors: gnd 13 12 14 26 27 25 33 32 35 34 *and* 21 19 18 5 17 16 4 2 15 gnd 3v3
 
 # esp # lcdboard
--- a/esp/update_lcd_block.h	Sun Mar 10 15:03:53 2024 -0700
+++ b/esp/update_lcd_block.h	Mon Mar 11 01:37:57 2024 -0700
@@ -1,16 +1,270 @@
 #include <string>
 
 #include "esphome.h"
-#include "esphome/components/ili9xxx/ili9xxx_display.h"
-void update_lcd_block(esphome::ili9xxx::ILI9XXXDisplay *lcd, std::string &x) {
-  id(lcd).update();
+// some from esphome; other code from
+// https://github.com/lvgl/lvgl_esp32_drivers/blob/master/lvgl_tft/ili9481.c
+// #include "esphome/components/ili9xxx/ili9xxx_display.h"
+
+static const char *const TAG = "ili9481direct";
+
+namespace esphome {
+namespace ili9xxx {
+
+/* MIPI DCS Type1  */
+#define ILI9481_CMD_NOP 0x00
+#define ILI9481_CMD_SOFTWARE_RESET 0x01
+#define ILI9481_CMD_READ_DISP_POWER_MODE 0x0A
+#define ILI9481_CMD_READ_DISP_MADCTRL 0x0B  // bits 7:3 only
+#define ILI9481_CMD_READ_DISP_PIXEL_FORMAT 0x0C
+#define ILI9481_CMD_READ_DISP_IMAGE_MODE 0x0D
+#define ILI9481_CMD_READ_DISP_SIGNAL_MODE 0x0E
+#define ILI9481_CMD_READ_DISP_SELF_DIAGNOSTIC 0x0F  // bits 7:6 only
+#define ILI9481_CMD_ENTER_SLEEP_MODE 0x10
+#define ILI9481_CMD_SLEEP_OUT 0x11
+#define ILI9481_CMD_PARTIAL_MODE_ON 0x12
+#define ILI9481_CMD_NORMAL_DISP_MODE_ON 0x13
+#define ILI9481_CMD_DISP_INVERSION_OFF 0x20
+#define ILI9481_CMD_DISP_INVERSION_ON 0x21
+#define ILI9481_CMD_DISPLAY_OFF 0x28
+#define ILI9481_CMD_DISPLAY_ON 0x29
+#define ILI9481_CMD_COLUMN_ADDRESS_SET 0x2A
+#define ILI9481_CMD_PAGE_ADDRESS_SET 0x2B
+#define ILI9481_CMD_MEMORY_WRITE 0x2C
+#define ILI9481_CMD_MEMORY_READ 0x2E
+#define ILI9481_CMD_PARTIAL_AREA 0x30
+#define ILI9481_CMD_VERT_SCROLL_DEFINITION 0x33
+#define ILI9481_CMD_TEARING_EFFECT_LINE_OFF 0x34
+#define ILI9481_CMD_TEARING_EFFECT_LINE_ON 0x35
+#define ILI9481_CMD_MEMORY_ACCESS_CONTROL 0x36  // bits 7:3,1:0 only
+#define ILI9481_CMD_VERT_SCROLL_START_ADDRESS 0x37
+#define ILI9481_CMD_IDLE_MODE_OFF 0x38
+#define ILI9481_CMD_IDLE_MODE_ON 0x39
+#define ILI9481_CMD_COLMOD_PIXEL_FORMAT_SET 0x3A
+#define ILI9481_CMD_WRITE_MEMORY_CONTINUE 0x3C
+#define ILI9481_CMD_READ_MEMORY_CONTINUE 0x3E
+#define ILI9481_CMD_SET_TEAR_SCANLINE 0x44
+#define ILI9481_CMD_GET_SCANLINE 0x45
+
+#define ILI9481_DDB_START 0xA1
+#define ILI9481_DDB_CONTINUE 0xA8
+
+/* other */
+#define ILI9481_CMD_ACCESS_PROTECT 0xB0
+#define ILI9481_CMD_LOW_POWER_CONTROL 0xB1
+#define ILI9481_CMD_FRAME_MEMORY_ACCESS 0xB3
+#define ILI9481_CMD_DISPLAY_MODE 0xB4
+#define ILI9481_CMD_DEVICE_CODE 0xBF
+
+#define ILI9481_CMD_PANEL_DRIVE 0xC0
+#define ILI9481_CMD_DISP_TIMING_NORMAL 0xC1
+#define ILI9481_CMD_DISP_TIMING_PARTIAL 0xC2
+#define ILI9481_CMD_DISP_TIMING_IDLE 0xC3
+#define ILI9481_CMD_FRAME_RATE 0xC5
+#define ILI9481_CMD_INTERFACE_CONTROL 0xC6
+#define ILI9481_CMD_GAMMA_SETTING 0xC8
+
+#define ILI9481_CMD_POWER_SETTING 0xD0
+#define ILI9481_CMD_VCOM_CONTROL 0xD1
+#define ILI9481_CMD_POWER_CONTROL_NORMAL 0xD2
+#define ILI9481_CMD_POWER_CONTROL_IDEL 0xD3
+#define ILI9481_CMD_POWER_CONTROL_PARTIAL 0xD4
+
+#define ILI9481_CMD_NVMEM_WRITE 0xE0
+#define ILI9481_CMD_NVMEM_PROTECTION_KEY 0xE1
+#define ILI9481_CMD_NVMEM_STATUS_READ 0xE2
+#define ILI9481_CMD_NVMEM_PROTECTION 0xE3
+
+typedef struct {
+  uint8_t cmd;
+  uint8_t data[16];
+  uint8_t databytes;  // No of data in data; bit 7 = delay after set; 0xFF = end
+                      // of cmds.
+} lcd_init_cmd_t;
+
+lcd_init_cmd_t ili_init_cmds[] = {
+    {ILI9481_CMD_SLEEP_OUT, {0x00}, 0x80},
+
+    // {ILI9481_CMD_POWER_SETTING, {0x07, 0x42, 0x18}, 3},  // from lvgl code
+    {ILI9481_CMD_POWER_SETTING, {0x07, 0x41, 0x1D}, 3},  // from esphome code
+
+    // {ILI9481_CMD_VCOM_CONTROL, {0x00, 0x07, 0x10}, 3},  // from lvgl code
+    {ILI9481_CMD_VCOM_CONTROL, {0x00, 0x1c, 0x1f}, 3},  // from esphome code
+
+    // {ILI9481_CMD_POWER_CONTROL_NORMAL, {0x01, 0x02}, 2},  // from lvgl code
+    {ILI9481_CMD_POWER_CONTROL_NORMAL, {0x01, 0x11}, 2},  // from esphome code
+
+    {ILI9481_CMD_PANEL_DRIVE, {0x10, 0x3B, 0x00, 0x02, 0x11}, 5},
+    {ILI9481_CMD_FRAME_RATE, {0x03}, 1},
+    {ILI9481_CMD_FRAME_MEMORY_ACCESS, {0x0, 0x0, 0x0, 0x0}, 4},
+    //{ILI9481_CMD_DISP_TIMING_NORMAL, {0x10, 0x10, 0x22}, 3},
+    {ILI9481_CMD_GAMMA_SETTING,
+     {0x00, 0x32, 0x36, 0x45, 0x06, 0x16, 0x37, 0x75, 0x77, 0x54, 0x0C, 0x00},
+     12},
+
+#define ILI9481_ADDR_PAGE_ADDR_ORDER_B_TO_T 0x80
+#define ILI9481_ADDR_COL_ADDR_ORDER_R_TO_L 0x40
+#define ILI9481_ADDR_PAGE_COLUMN_ORDER_REVERSE 0x20
+#define ILI9481_ADDR_LCD_REFRESH_BOT_TO_TOP 0x10
+#define ILI9481_ADDR_BGR_ORDER 0x08
+#define ILI9481_ADDR_HFLIP 0x02
+#define ILI9481_ADDR_VFLIP 0x01
+
+    {ILI9481_CMD_MEMORY_ACCESS_CONTROL,
+     {ILI9481_ADDR_BGR_ORDER |
+      ILI9481_ADDR_HFLIP |
+      0},
+     1},
+
+    {ILI9481_CMD_COLMOD_PIXEL_FORMAT_SET, {0x66}, 1},
+    // {ILI9481_CMD_COLMOD_PIXEL_FORMAT_SET, {0x55}, 1},
+
+    {ILI9481_CMD_DISP_INVERSION_ON, {}, 0x80},
+    {ILI9481_CMD_NORMAL_DISP_MODE_ON, {}, 0x80},
+    {ILI9481_CMD_DISPLAY_ON, {}, 0x80},
+    {0, {0}, 0xff},
+};
 
-#if 1
-          std::string localPayload = x;
-          const char *buf = localPayload.data();
+class ILI9481Direct
+    : public Component,
+      public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
+                            spi::CLOCK_PHASE_LEADING,
+                            // spi::DATA_RATE_40MHZ
+                            spi::DATA_RATE_10MHZ> {
+ public:
+  ILI9481Direct(esphome::spi::SPIComponent *spi) { spi_ = spi; }
+  esphome::spi::SPIComponent *spi_;
+  int width_ = 320;
+  int height_ = 480;
+  esp32::ESP32InternalGPIOPin *reset_pin_;
+  esp32::ESP32InternalGPIOPin *dc_pin_;
+  esp32::ESP32InternalGPIOPin *cs_pin_;
+
+  void setup() override {
+    this->setup_pins_();
+    this->init_lcd_();
+  }
+  void loop() override {}
+
+  void setup_pins_() {
+    dc_pin_ = new esp32::ESP32InternalGPIOPin();
+    dc_pin_->set_pin(::GPIO_NUM_22);
+    dc_pin_->set_inverted(false);
+    dc_pin_->set_drive_strength(::GPIO_DRIVE_CAP_2);
+    dc_pin_->set_flags(gpio::Flags::FLAG_OUTPUT);
+
+    dc_pin_->setup();  // OUTPUT
+    dc_pin_->digital_write(false);
+
+    reset_pin_ = new esp32::ESP32InternalGPIOPin();
+    reset_pin_->set_pin(::GPIO_NUM_25);
+    reset_pin_->set_inverted(false);
+    reset_pin_->set_drive_strength(::GPIO_DRIVE_CAP_2);
+    reset_pin_->set_flags(gpio::Flags::FLAG_OUTPUT);
+
+    reset_pin_->setup();  // OUTPUT
+    reset_pin_->digital_write(true);
+
+    cs_pin_ = new esp32::ESP32InternalGPIOPin();
+    cs_pin_->set_pin(::GPIO_NUM_27);
+    cs_pin_->set_inverted(false);
+    cs_pin_->set_drive_strength(::GPIO_DRIVE_CAP_2);
+    cs_pin_->set_flags(gpio::Flags::FLAG_OUTPUT);
+
+    set_cs_pin(cs_pin_);
+    set_spi_parent(spi_);
+    spi_setup();
+    reset_();
+  }
+
+  void init_lcd_() {
+    command(ILI9481_CMD_SOFTWARE_RESET);
+    delay(150);
+
+    uint16_t cmd = 0;
+    while (ili_init_cmds[cmd].databytes != 0xff) {
+      command(ili_init_cmds[cmd].cmd);
+      {
+        start_data_();
+        write_array(ili_init_cmds[cmd].data,
+                    ili_init_cmds[cmd].databytes & 0x1F);
+        end_data_();
+      }
+      if (ili_init_cmds[cmd].databytes & 0x80) {
+        delay(150);
+      }
+      cmd++;
+    }
+    ESP_LOGI(TAG, "done init cmds");
+  }
+
+  void blit(uint16_t xp, uint16_t yp, uint16_t w, uint16_t h,
+            const unsigned char *&buf) {
+    set_addr_window_(xp, yp, xp + w - 1, yp + h - 1);
+
+    this->command(ILI9481_CMD_MEMORY_WRITE);
+    {
+      this->start_data_();
+      this->write_array(buf, static_cast<size_t>(h * w * 3));
+      this->end_data_();
+    }
+  }
+
+  void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
+    command(ILI9481_CMD_COLUMN_ADDRESS_SET);
+    {
+      start_data_();
+      write_byte(x1 >> 8);
+      write_byte(x1 & 0xFF);
+      write_byte(x2 >> 8);
+      write_byte(x2 & 0xFF);
+      end_data_();
+    }
+    command(ILI9481_CMD_PAGE_ADDRESS_SET);
+    {
+      start_data_();
+      write_byte(y1 >> 8);
+      write_byte(y1 & 0xFF);
+      write_byte(y2 >> 8);
+      write_byte(y2 & 0xFF);
+      end_data_();
+    }
+  }
+  void command(uint8_t value) {
+    this->start_command_();
+    this->write_byte(value);
+    this->end_command_();
+  }
+
+  void start_command_() {
+    this->dc_pin_->digital_write(false);
+    this->enable();
+  }
+  void end_command_() { this->disable(); }
+
+  void start_data_() {
+    this->dc_pin_->digital_write(true);
+    this->enable();
+  }
+  void end_data_() { this->disable(); }
+
+  void reset_() {
+    if (this->reset_pin_ != nullptr) {
+      this->reset_pin_->digital_write(false);
+      delay(20);
+      this->reset_pin_->digital_write(true);
+      delay(20);
+    }
+  }
+};
+
+void update_lcd_block(ILI9481Direct *lcd, std::string &x) {
+#if 0
+  std::string localPayload = x;
+  const unsigned char *buf = reinterpret_cast<const unsigned char*>(localPayload.data());
 #else
-  const char *buf = x.data();
+  const unsigned char *buf = reinterpret_cast<const unsigned char *>(x.data());
 #endif
+
   uint16_t seq, xp, yp, w, h;
 #define readu16()                          \
   (static_cast<const uint16_t>(*(buf++)) | \
@@ -25,27 +279,8 @@
   ESP_LOGD("dbg",
            "seq=%hu%s xp=%hu, yp=%hu, w=%hu, h=%hu, full payload=%d bytes", seq,
            seqDia, xp, yp, w, h, x.size());
-#if 1
-  ///// v1
-  id(lcd).draw_pixels_at(320+w-1-xp, yp, w, h,
-                         reinterpret_cast<const uint8_t*>(buf),
-                         COLOR_ORDER_BGR,
-                         COLOR_BITNESS_888,
-                         /*big_endian=*/false,
-                         /*x_offset=*/0,
-                         /*y_offset=*/0,
-                         /*x_pad=*/0);
-#elif 0
-  ///// v2
-  for (uint16_t y = 0; y < h; y++) {
-    uint16_t row = yp + y;
-    for (uint16_t x = 0; x < w; x++) {
-      id(lcd).draw_pixel_at(320-1-(xp + w-1 - x), row, Color(*buf++, *buf++, *buf++));
-    }
-    // App.feed_wdt();
-  }
-  #else
 
-#endif
-  id(lcd).update();
-}
\ No newline at end of file
+  lcd->blit(xp, yp, w, h, buf);
+}
+}  // namespace ili9xxx
+}  // namespace esphome
--- a/lcd_simulator.py	Sun Mar 10 15:03:53 2024 -0700
+++ b/lcd_simulator.py	Mon Mar 11 01:37:57 2024 -0700
@@ -4,7 +4,7 @@
 import aiomqtt
 import pygame
 
-screen = pygame.display.set_mode((320, 320))
+screen = pygame.display.set_mode((320, 480))
 clock = pygame.time.Clock()
 
 
--- a/src/scheduleLcd.css	Sun Mar 10 15:03:53 2024 -0700
+++ b/src/scheduleLcd.css	Mon Mar 11 01:37:57 2024 -0700
@@ -7,6 +7,7 @@
 }
 .area {
   border: 1px solid gray;
-  background: rgb(39, 45, 103);
+  background: rgb(39, 45, 103, .9);
   position: absolute;
+  box-shadow: 5px 5px 20px black;
 }
--- a/tasks.py	Sun Mar 10 15:03:53 2024 -0700
+++ b/tasks.py	Mon Mar 11 01:37:57 2024 -0700
@@ -5,3 +5,13 @@
 def run(ctx, device="OTA"):
     ctx.run(
         f'pdm run esphome run esp/do-squib-touch-lcd.yaml --device {device}')
+
+
+@task()
+def lcd_simulator(ctx):
+    ctx.run('pdm run python lcd_simulator.py')
+
+
+@task()
+def web_to_mqtt(ctx):
+    ctx.run('pdm run python web_to_mqtt.py')
--- a/web_to_mqtt.py	Sun Mar 10 15:03:53 2024 -0700
+++ b/web_to_mqtt.py	Mon Mar 11 01:37:57 2024 -0700
@@ -20,7 +20,7 @@
         screenshot_command = [
             "google-chrome",
             "--headless",
-            "--window-size=320,320",
+            "--window-size=320,480",
             f"--screenshot={out.name}",
             url,
         ]
@@ -32,7 +32,7 @@
 
 blockX = 32
 blockY = 32
-msgDelay = .12
+msgDelay = .05
 dirtyQueue = {}
 
 
@@ -55,8 +55,7 @@
     while True:
         if dirtyQueue:
             # pos = random.choice(list(dirtyQueue.keys()))
-            # pos = min(list(dirtyQueue.keys()))
-            pos = random.choice(list(dirtyQueue.keys()))
+            pos = min(list(dirtyQueue.keys()))
             img = dirtyQueue.pop(pos)
             await tell_lcd(client, pos[0], pos[1], img)
         await asyncio.sleep(msgDelay)  # too fast and esp restarts
@@ -82,11 +81,11 @@
     renderer = WebRenderer()
     async with aiomqtt.Client("mqtt2") as client:
         asyncio.create_task(sendDirty(client))
-        last_image = Image.new('RGB', (320, 320))
+        last_image = Image.new('RGB', (320, 480))
         while True:
             last_image = await check_for_changes(renderer, client, last_image)
             # we could get the web page to tell us when any dom changes
-            await asyncio.sleep(5)
+            await asyncio.sleep(1)
 
 
 if __name__ == "__main__":