diff --git a/src/DisplayConfig.h b/src/DisplayConfig.h index ab46f37..a63a340 100644 --- a/src/DisplayConfig.h +++ b/src/DisplayConfig.h @@ -33,6 +33,12 @@ enum DisplayBus : uint8_t { DISPLAY_BUS_RGB = 3, // Arduino_ESP32RGBPanel DISPLAY_BUS_DSI = 4, // Arduino_ESP32DSIPanel DISPLAY_BUS_PAR8 = 5, // Arduino_ESP32PAR8 + // Own-bus QSPI, like RGB/DSI above: the panel and its bus are one class + // (Arduino_ST77922_1L4), not an Arduino_DataBus, because this panel's + // command phase is single-line while Arduino_ESP32QSPI (bus 1) hardcodes + // every phase to 4 lines -- a real protocol mismatch, not a config gap. + // See Arduino_ST77922_1L4.h. + DISPLAY_BUS_QSPI_1L4 = 6, // LovyanGFX bus families, kept clear of the ArduinoGFX range. DISPLAY_BUS_LGFX_SPI = 16, DISPLAY_BUS_LGFX_I2C = 17, diff --git a/src/backends/ardgfx.cpp b/src/backends/ardgfx.cpp index 88c295c..d2db341 100644 --- a/src/backends/ardgfx.cpp +++ b/src/backends/ardgfx.cpp @@ -80,6 +80,18 @@ void tft_display::buildDisplay() { cfg.width, cfg.height, bus, 0, true, cfg.rst, static_cast(cfg.initOps), cfg.initOpsLen ); +#elif TFT_DATABUS_N == 6 + // Own-bus QSPI, same shape as the RGB/DSI cases above: this panel's + // command phase is single-line while Arduino_ESP32QSPI (bus 1) hardcodes + // every phase to 4 lines, so there is no generic Arduino_DataBus here -- + // Arduino_ST77922_1L4 drives its own ESP-IDF SPI bus and panel driver + // together. See Arduino_ST77922_1L4.h for why. + #if TFT_DISPLAY_DRIVER_N != 52 + #error "TFT_DATABUS_N=6 requires TFT_DISPLAY_DRIVER_N=52 (Arduino_ST77922_1L4)" + #endif + _gfx = new Arduino_ST77922_1L4( + cfg.width, cfg.height, cfg.cs, cfg.sclk, cfg.d[0], cfg.d[1], cfg.d[2], cfg.d[3], cfg.rst, drvRot + ); #else #if TFT_DATABUS_N == 0 bus = new TFT_DATABUS(cfg.dc, cfg.cs, cfg.sclk, cfg.mosi, cfg.miso, &SPI); diff --git a/src/backends/ardgfx.h b/src/backends/ardgfx.h index f8ccb55..5a27251 100644 --- a/src/backends/ardgfx.h +++ b/src/backends/ardgfx.h @@ -14,6 +14,9 @@ class tft_logger; #include "tft_defines.h" #include #include "../panels/Arduino_NV3031B.h" +#if defined(TFT_DATABUS_N) && TFT_DATABUS_N == 6 +#include "../panels/Arduino_ST77922_1L4.h" +#endif #include #include #include @@ -21,7 +24,7 @@ class tft_logger; #include // clang-format off // Check Data bus -#if !defined(TFT_DATABUS_N) || TFT_DATABUS_N > 5 +#if !defined(TFT_DATABUS_N) || TFT_DATABUS_N > 6 #warning "Please define the Data bus used for this board:\n \ - 0: Arduino_HWSPI: Shared SPI with other devices;\n \ - 1: Arduino_ESP32QSPI: Quad SPI, used in AMOLED and some new displays;\n \ @@ -29,6 +32,7 @@ class tft_logger; - 3: Arduino_ESP32RGBPanel: Parallel 16 bit;\n \ - 4: Arduino_ESP32DSIPanel: DSI Panel for P4 devices;\n \ - 5: Arduino_ESP32PAR8: Parallel 8 bit, D0-D7 spread over both GPIO banks;\n \ + - 6: Arduino_ST77922_1L4: QSPI with a single-line command phase (ST77916/ST77922 family);\n \ \n \ Using Arduino_HWSPI as default." #define TFT_DATABUS_N 0 @@ -141,6 +145,19 @@ class tft_logger; TFT_HEIGHT, TFT_COL_OFS1, TFT_ROW_OFS1, TFT_COL_OFS2,TFT_ROW_OFS2 " #endif +#elif TFT_DATABUS_N == 6 +// Own-bus QSPI (see DisplayConfig.h's DISPLAY_BUS_QSPI_1L4): no +// Arduino_DataBus here, Arduino_ST77922_1L4 owns its ESP-IDF SPI bus and +// panel driver together. No TFT_IPS/offset macros either -- this class +// doesn't use Arduino_GFX's generic MADCTL/window-offset mechanism at all. + #ifndef TFT_DISPLAY_DRIVER_N + #define TFT_DISPLAY_DRIVER_N 52 + #endif + #if !defined(TFT_CS) || !defined(TFT_SCLK) || !defined(TFT_D0) || !defined(TFT_D1) || !defined(TFT_D2) || \ + !defined(TFT_D3) || !defined(TFT_RST) || !defined(TFT_ROTATION) || !defined(TFT_WIDTH) || !defined(TFT_HEIGHT) + #error "Missing Definitions for: TFT_CS, TFT_SCLK, TFT_D0, TFT_D1, TFT_D2, TFT_D3, \ + TFT_RST, TFT_ROTATION, TFT_WIDTH, TFT_HEIGHT" + #endif #else #error "Invalid TFT_DATABUS_N\n Please define the Data bus used for this board:\n \ - 0: Arduino_HWSPI: Shared SPI with other devices;\n \ @@ -148,7 +165,8 @@ class tft_logger; - 2: Arduino_ESP32PAR8Q: Parallel 8 bit, D0-D7 on the same GPIO bank;\n \ - 3: Arduino_ESP32RGBPanel: Parallel 16 bit;\n \ - 4: Arduino_ESP32DSIPanel: DSI Panel for P4 devices;\n \ - - 5: Arduino_ESP32PAR8: Parallel 8 bit, D0-D7 spread over both GPIO banks;" + - 5: Arduino_ESP32PAR8: Parallel 8 bit, D0-D7 spread over both GPIO banks;\n \ + - 6: Arduino_ST77922_1L4: QSPI with a single-line command phase (ST77916/ST77922 family);" #endif // Set Databus Type for constructor @@ -224,6 +242,7 @@ class tft_logger; [bus,rst,w,h] 47 SSD1306; 48 SH1106;\n \ [RGB] 49 Arduino_RGB_Display (Arduino_ESP32RGBPanel: ST7262/ST7701);\n \ [DSI] 50 Arduino_DSI_Display;\n \ + [own bus, TFT_DATABUS_N=6 only] 52 Arduino_ST77922_1L4 (ST77916/ST77922 family, QSPI with a single-line command phase);\n \ " #endif diff --git a/src/backends/ardgfx_driver.inc b/src/backends/ardgfx_driver.inc index 287a588..21e54f1 100644 --- a/src/backends/ardgfx_driver.inc +++ b/src/backends/ardgfx_driver.inc @@ -32,6 +32,10 @@ #error "Arduino_DSI_Display (50) needs the compile-time TFT_DSI_INIT table and is built\n \ next to its bus, so it is not part of the runtime driver factory; selecting\n \ TFT_DATABUS_N=4 already pins it. Do not name 50 in TFT_DISPLAY_DRIVER_N_ALT*." +#elif DD_DRV_ID == 52 + #error "Arduino_ST77922_1L4 (52) owns its ESP-IDF SPI bus and panel driver together, so it\n \ + is not part of the runtime driver factory; selecting TFT_DATABUS_N=6 already pins it.\n \ + Do not name 52 in TFT_DISPLAY_DRIVER_N_ALT*." #endif #include "ardgfx_driver_class.inc" diff --git a/src/backends/ardgfx_driver_class.inc b/src/backends/ardgfx_driver_class.inc index a48ebad..45a7467 100644 --- a/src/backends/ardgfx_driver_class.inc +++ b/src/backends/ardgfx_driver_class.inc @@ -121,6 +121,8 @@ #define DD_DRV_CLASS Arduino_DSI_Display #elif DD_DRV_ID == 51 #define DD_DRV_CLASS Arduino_NV3031B // src/panels/, not part of Arduino_GFX +#elif DD_DRV_ID == 52 + #define DD_DRV_CLASS Arduino_ST77922_1L4 // src/panels/, not part of Arduino_GFX #else #error "Invalid driver id in TFT_DISPLAY_DRIVER_N / TFT_DISPLAY_DRIVER_N_ALT*" #endif diff --git a/src/panels/Arduino_ST77922_1L4.h b/src/panels/Arduino_ST77922_1L4.h new file mode 100644 index 0000000..299975c --- /dev/null +++ b/src/panels/Arduino_ST77922_1L4.h @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: 2026 bmorcelli +// +// SPDX-License-Identifier: MIT + +// Arduino_GFX driver for the ST77922 over QSPI, id 52 +// (TFT_DATABUS_N=6 / TFT_DISPLAY_DRIVER_N=52). +// +// Arduino_GFX's own Arduino_ESP32QSPI sends every command on all 4 QSPI lines +// ("4-4-4": see its writeCommand(), which sets SPI_TRANS_MULTILINE_CMD | +// SPI_TRANS_MULTILINE_ADDR unconditionally). This controller wants register +// writes on a single line and only the pixel data on four, so that bus cannot +// drive it. Like Arduino_RGB_Display/Arduino_DSI_Display (ids 49/50, see +// ardgfx.cpp), this class therefore owns its bus instead of taking an +// Arduino_DataBus. +// +// The transfer code below follows the board vendor's own Arduino example for +// this panel (LCDwiki ES3C35P, github.com/ydedox/st77922, Example_01) closely, +// because that is the known-good reference: +// - register write : opcode 0x02, 24-bit address = (register << 8), params on +// one line; +// - pixel write : opcode 0x32, address 0x003C00 ("write memory continue"), +// data on four lines, after the window is set with +// 0x2A / 0x2B; +// - no software reset, no RAMWR (0x2C) per write; +// - the window's X start and width are multiples of 4. +// +// The panel itself does not rotate (its MADCTL does not remap addressing), so +// rotation is done in software: Arduino_Canvas hands this driver native +// portrait frames, and the per-pixel/per-rect paths transform coordinates. +#ifndef ARDUINO_ST77922_1L4_H +#define ARDUINO_ST77922_1L4_H + +#include + +#include + +#include "st77922_1l4/st77922_1l4_init_table.h" + +class Arduino_ST77922_1L4 : public Arduino_GFX { +public: + Arduino_ST77922_1L4( + int16_t w, int16_t h, int8_t cs, int8_t sclk, int8_t d0, int8_t d1, int8_t d2, int8_t d3, int8_t rst, + uint8_t rotation + ) + : Arduino_GFX(w, h), _cs(cs), _sclk(sclk), _d0(d0), _d1(d1), _d2(d2), _d3(d3) { + (void)rst; // the panel's reset line is tied to the chip's own reset on this board + setRotation(rotation); + } + + // Pixels per SPI transaction; the vendor's own value. + static const int32_t CHUNK_PIXELS = 0x4000; + + bool begin(int32_t speed = 80000000) override { + // Callers reach this through an Arduino_GFX*, so the default above is + // never applied: they pass the base class's "not defined" (-1). + if (speed <= 0) speed = 80000000; + + // Internal, DMA-capable RAM for pixel data and for register parameters. + _buf = (uint16_t *)heap_caps_malloc(CHUNK_PIXELS * 2, MALLOC_CAP_DMA); + _cmd = (uint8_t *)heap_caps_malloc(64, MALLOC_CAP_DMA); + if (!_buf || !_cmd) return false; + + spi_bus_config_t buscfg = {}; + buscfg.data0_io_num = _d0; + buscfg.data1_io_num = _d1; + buscfg.sclk_io_num = _sclk; + buscfg.data2_io_num = _d2; + buscfg.data3_io_num = _d3; + buscfg.max_transfer_sz = CHUNK_PIXELS * 2 + 8; + buscfg.flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_IOMUX_PINS | SPICOMMON_BUSFLAG_QUAD; + if (spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO) != ESP_OK) return false; + + spi_device_interface_config_t devcfg = {}; + devcfg.command_bits = 0; + devcfg.address_bits = 0; + devcfg.mode = 0; + devcfg.clock_speed_hz = speed; + devcfg.spics_io_num = _cs; + devcfg.flags = SPI_DEVICE_HALFDUPLEX; + devcfg.queue_size = 17; + if (spi_bus_add_device(SPI2_HOST, &devcfg, &_dev) != ESP_OK) return false; + + const size_t n = sizeof(es3c35p_lcd_init_cmds) / sizeof(es3c35p_lcd_init_cmds[0]); + for (size_t i = 0; i < n; i++) { + writeReg((uint8_t)es3c35p_lcd_init_cmds[i].cmd, es3c35p_lcd_init_cmds[i].data, + es3c35p_lcd_init_cmds[i].data_bytes); + if (es3c35p_lcd_init_cmds[i].delay_ms) delay(es3c35p_lcd_init_cmds[i].delay_ms); + } + + return true; + } + + void writePixelPreclipped(int16_t x, int16_t y, uint16_t color) override { + writeFillRectPreclipped(x, y, 1, 1, color); + } + + // Fills a rectangle given in rotated (logical) coordinates. The panel needs + // X start and width in multiples of 4, so the rectangle is widened to the + // next multiple (the vendor's own point-drawing does the same). Whole-frame + // output goes through draw16bitRGBBitmap() instead. + void writeFillRectPreclipped(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override { + if (!_dev) return; + int32_t nx, ny, nw, nh; + toNative(x, y, w, h, nx, ny, nw, nh); + fillNative(nx, ny, nx + nw, ny + nh, color); + } + + // Whole-bitmap push for Arduino_Canvas::flush(). The canvas owns rotation + // and hands over a native-portrait bitmap; anything this path can't take + // (rotated driver, X or width not a multiple of 4) returns false so the + // caller falls back to the generic per-pixel path. + bool pushBitmap(int16_t x, int16_t y, const uint16_t *bitmap, int16_t w, int16_t h) { + if (!_dev || (_rotation & 3) != 0) return false; + if (x < 0 || y < 0 || w <= 0 || h <= 0 || x + w > WIDTH || y + h > HEIGHT) return false; + if ((x & 3) || (w & 3)) return false; + setWindow(x, y, x + w, y + h); + size_t total = (size_t)w * h; + const uint16_t *src = bitmap; + while (total) { + size_t n = total > CHUNK_PIXELS ? CHUNK_PIXELS : total; + for (size_t i = 0; i < n; i++) _buf[i] = swap565(src[i]); + writePixels(n); + src += n; + total -= n; + } + return true; + } + + void draw16bitRGBBitmap(int16_t x, int16_t y, uint16_t *bitmap, int16_t w, int16_t h) override { + if (!pushBitmap(x, y, bitmap, w, h)) Arduino_GFX::draw16bitRGBBitmap(x, y, bitmap, w, h); + } + + void draw16bitRGBBitmap(int16_t x, int16_t y, const uint16_t bitmap[], int16_t w, int16_t h) override { + if (!pushBitmap(x, y, bitmap, w, h)) Arduino_GFX::draw16bitRGBBitmap(x, y, bitmap, w, h); + } + +private: + // Logical (rotated) rect -> native portrait rect. A solid rectangle rotated + // by a multiple of 90 degrees is still a solid rectangle, so this closed + // form covers single pixels and whole rectangles alike. + void toNative(int16_t lx, int16_t ly, int16_t lw, int16_t lh, int32_t &nx, int32_t &ny, int32_t &nw, int32_t &nh) { + switch (_rotation & 3) { + default: // case 0 + nx = lx; ny = ly; nw = lw; nh = lh; + break; + case 1: + nx = WIDTH - ly - lh; ny = lx; nw = lh; nh = lw; + break; + case 2: + nx = WIDTH - lx - lw; ny = HEIGHT - ly - lh; nw = lw; nh = lh; + break; + case 3: + nx = ly; ny = HEIGHT - lx - lw; nw = lh; nh = lw; + break; + } + } + + // The controller is little-endian-hostile: 16bpp goes out MSB first. + static inline uint16_t swap565(uint16_t c) { return (c >> 8) | (c << 8); } + + // One register write: opcode 0x02, address (reg << 8), params on one line. + void writeReg(uint8_t reg, const void *data, size_t len) { + spi_transaction_ext_t t = {}; + t.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR; + t.base.cmd = 0x02; + t.base.addr = (uint32_t)reg << 8; + t.command_bits = 8; + t.address_bits = 24; + if (len && len <= 64) { + memcpy(_cmd, data, len); + t.base.tx_buffer = _cmd; + t.base.length = 8 * len; + } + spi_device_polling_transmit(_dev, (spi_transaction_t *)&t); + } + + // x1 / y1 are exclusive. + void setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { + uint8_t cx[4] = {(uint8_t)(x0 >> 8), (uint8_t)x0, (uint8_t)((x1 - 1) >> 8), (uint8_t)(x1 - 1)}; + uint8_t cy[4] = {(uint8_t)(y0 >> 8), (uint8_t)y0, (uint8_t)((y1 - 1) >> 8), (uint8_t)(y1 - 1)}; + writeReg(0x2A, cx, 4); + writeReg(0x2B, cy, 4); + } + + // n pixels from _buf (already byte-swapped): opcode 0x32, address 0x003C00, + // data on four lines. + void writePixels(size_t n) { + spi_transaction_ext_t t = {}; + t.base.flags = SPI_TRANS_MODE_QIO | SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR; + t.base.cmd = 0x32; + t.base.addr = 0x3C << 8; + t.command_bits = 8; + t.address_bits = 24; + t.base.tx_buffer = _buf; + t.base.length = n * 16; + spi_device_polling_transmit(_dev, (spi_transaction_t *)&t); + } + + // Solid fill of a native-portrait rectangle (x1 / y1 exclusive), widened to + // the panel's multiple-of-4 X alignment. + void fillNative(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t color) { + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 > WIDTH) x1 = WIDTH; + if (y1 > HEIGHT) y1 = HEIGHT; + if (x1 <= x0 || y1 <= y0) return; + x0 &= ~3; + x1 = (x1 + 3) & ~3; + if (x1 > WIDTH) x1 = WIDTH; + setWindow(x0, y0, x1, y1); + size_t total = (size_t)(x1 - x0) * (y1 - y0); + size_t first = total > CHUNK_PIXELS ? CHUNK_PIXELS : total; + uint16_t sc = swap565(color); + for (size_t i = 0; i < first; i++) _buf[i] = sc; + while (total) { + size_t n = total > CHUNK_PIXELS ? CHUNK_PIXELS : total; + writePixels(n); + total -= n; + } + } + + int8_t _cs, _sclk, _d0, _d1, _d2, _d3; + spi_device_handle_t _dev = nullptr; + uint16_t *_buf = nullptr; + uint8_t *_cmd = nullptr; +}; + +#endif // ARDUINO_ST77922_1L4_H diff --git a/src/panels/st77922_1l4/st77922_1l4_init_table.h b/src/panels/st77922_1l4/st77922_1l4_init_table.h new file mode 100644 index 0000000..29f1cb2 --- /dev/null +++ b/src/panels/st77922_1l4/st77922_1l4_init_table.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include + +typedef struct { + int cmd; // register + const void *data; // parameter bytes + size_t data_bytes; // number of parameter bytes + unsigned int delay_ms; // delay after this register write +} st77922_lcd_init_cmd_t; + +// Verbatim from the board vendor's own example (LCDwiki ES3C35P, +// github.com/ydedox/st77922 Example_01, the ACTIVE st77922_lcd_init[] table -- +// not the commented-out one above it), renamed only. Confirmed working on this +// board with the vendor's transport. Format: st77922_lcd_init_cmd_t. +static const st77922_lcd_init_cmd_t es3c35p_lcd_init_cmds[] = { + {0xF1, (uint8_t[]){0x00}, 1, 0}, + {0x60, (uint8_t[]){0x00, 0x00, 0x00}, 3, 0}, + {0x65, (uint8_t[]){0x80}, 1, 0}, + {0x79, (uint8_t[]){0x06}, 1, 0}, + {0x7B, (uint8_t[]){0x00, 0x08, 0x08}, 3, 0}, + {0x80, (uint8_t[]){0x55, 0x62, 0x2F, 0x17, 0xF0, 0x52, 0x70, 0xD2, 0x52, 0x62, 0xEA}, 11, 0}, + {0x81, (uint8_t[]){0x26, 0x52, 0x72, 0x27}, 4, 0}, + {0x84, (uint8_t[]){0x92, 0x25}, 2, 0}, + {0x87, (uint8_t[]){0x10, 0x10, 0x58, 0x00, 0x02, 0x3A}, 6, 0}, + {0x88, (uint8_t[]){0x00, 0x00, 0x2C, 0x10, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x06}, 15, 0}, + {0x89, (uint8_t[]){0x00, 0x00, 0x00}, 3, 0}, + {0x8A, (uint8_t[]){0x13, 0x00, 0x2C, 0x00, 0x00, 0x2C, 0x10, 0x10, 0x00, 0x3E, 0x19}, 11, 0}, + {0x8B, (uint8_t[]){0x15, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x97, 0x8E}, 9, 0}, + {0x8C, (uint8_t[]){0x1D, 0xB1, 0xB1, 0x44, 0x96, 0x2C, 0x10, 0x50, 0x0F, 0x01, 0xC5, 0x12, 0x09}, 13, 0}, + {0x8D, (uint8_t[]){0x0C}, 1, 0}, + {0x8E, (uint8_t[]){0x33, 0x01, 0x0C, 0x13, 0x01, 0x01}, 6, 0}, + {0xB3, (uint8_t[]){0x00, 0x30}, 2, 0}, + {0xF1, (uint8_t[]){0x00}, 1, 0}, + {0x71, (uint8_t[]){0xD0}, 1, 0}, + {0x66, (uint8_t[]){0x02, 0x3F}, 2, 0}, + {0xBE, (uint8_t[]){0x26, 0x00, 0x9D}, 3, 0}, + {0x70, (uint8_t[]){0x01, 0xA0, 0x11, 0x40, 0xE0, 0x00, 0x11, 0x69, 0x11, 0x00, 0x00, 0x1A}, 12, 0}, + {0x90, (uint8_t[]){0x04, 0x04, 0x55, 0x74, 0x00, 0x40, 0x43, 0x27, 0x27}, 9, 0}, + {0x91, (uint8_t[]){0x04, 0x04, 0x55, 0x75, 0x00, 0x40, 0x42, 0x27, 0x27}, 9, 0}, + {0x92, (uint8_t[]){0x04, 0x44, 0x55, 0xC0, 0x06, 0x00, 0x07, 0x05, 0x90, 0x27}, 10, 0}, + {0x93, (uint8_t[]){0x04, 0x43, 0x11, 0x00, 0x00, 0x00, 0x00, 0x05, 0x90, 0x27}, 10, 0}, + {0x94, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 6, 0}, + {0x95, (uint8_t[]){0x96, 0x16, 0x00, 0x00, 0xFF}, 5, 0}, + {0x96, (uint8_t[]){0x44, 0x53, 0x03, 0x12, 0x23, 0x24, 0x06, 0x05, 0x94, 0x27, 0x00, 0x44}, 12, 0}, + {0x97, (uint8_t[]){0x44, 0x53, 0x47, 0x56, 0x20, 0x20, 0x02, 0x01, 0x94, 0x27, 0x00, 0x44}, 12, 0}, + {0xBA, (uint8_t[]){0x55, 0x94, 0x2D, 0x94, 0x27}, 5, 0}, + {0x9A, (uint8_t[]){0x40, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9B, (uint8_t[]){0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x9C, (uint8_t[]){0x5C, 0x12, 0x00, 0x00, 0x10, 0x12, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00}, 13, 0}, + {0x9D, (uint8_t[]){0x8A, 0x51, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 8, 0}, + {0x9E, (uint8_t[]){0x51, 0x00, 0x00, 0x00, 0x80, 0x1E, 0x01}, 7, 0}, + {0xB4, (uint8_t[]){0x1D, 0x1C, 0x1E, 0x0B, 0x14, 0x02, 0x13, 0x09, 0x1E, 0x00, 0x1E, 0x10}, 12, 0}, + {0xB5, (uint8_t[]){0x1D, 0x1C, 0x1E, 0x0A, 0x15, 0x03, 0x11, 0x08, 0x1E, 0x01, 0x1E, 0x12}, 12, 0}, + {0xB6, (uint8_t[]){0x77, 0x77, 0x00, 0x0A, 0xFF, 0x0A, 0xFF}, 7, 0}, + {0x86, (uint8_t[]){0xCD, 0x04, 0xB1, 0x02, 0x58, 0x12, 0x58, 0x0C, 0x13, 0x01, 0xA5, 0x00, 0xA5, 0xA5}, 14, 0}, + {0xB7, (uint8_t[]){0x07, 0x0A, 0x0E, 0x06, 0x05, 0x03, 0x2B, 0x03, 0x03, 0x42, 0x07, 0x10, 0x10, 0x2E, 0x3F, 0x0D}, 16, 0}, + {0xB8, (uint8_t[]){0x07, 0x0A, 0x0D, 0x05, 0x05, 0x02, 0x2B, 0x02, 0x03, 0x42, 0x06, 0x10, 0x0F, 0x2E, 0x3F, 0x0D}, 16, 0}, + {0xB9, (uint8_t[]){0x23, 0x23}, 2, 0}, + {0xBF, (uint8_t[]){0x10, 0x14, 0x14, 0x0B, 0x0B, 0x0B}, 6, 0}, + {0xF2, (uint8_t[]){0x00}, 1, 0}, + {0x73, (uint8_t[]){0x04, 0xDA, 0x12, 0x54, 0x47}, 5, 0}, + {0x77, (uint8_t[]){0x6B, 0x5B, 0xFD, 0xC3, 0xC5}, 5, 0}, + {0x7A, (uint8_t[]){0x15, 0x27}, 2, 0}, + {0x7B, (uint8_t[]){0x04, 0x57}, 2, 0}, + {0x7E, (uint8_t[]){0x01, 0x0E}, 2, 0}, + {0xBF, (uint8_t[]){0x36}, 1, 0}, + {0xE3, (uint8_t[]){0x40, 0x40}, 2, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xD0, (uint8_t[]){0x00}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x3F}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xDF}, 4, 0}, + {0x21, (uint8_t[]){0x00}, 0, 0}, + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0x29, (uint8_t[]){0x00}, 0, 0}, + {0x2C, (uint8_t[]){0x00}, 0, 0}, + {0x3A, (uint8_t[]){0x01}, 1, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x35, (uint8_t[]){0x01}, 1, 20}, +};