Mercurial > code > home > repos > front-door-display
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__":