Skip to content

Dotclock enhancements #8430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Sep 27, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ CIRCUITPY_ESP_FLASH_FREQ = 80m

CIRCUITPY_ESP_PSRAM_SIZE = 8MB
CIRCUITPY_ESP_PSRAM_MODE = opi
CIRCUITPY_ESP_PSRAM_FREQ = 80m
CIRCUITPY_ESP_PSRAM_FREQ = 120m

CIRCUITPY_DOTCLOCKFRAMEBUFFER = 1
26 changes: 24 additions & 2 deletions ports/espressif/boards/adafruit_qualia_s3_rgb666/pins.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
#include "py/objtuple.h"
#include "shared-bindings/board/__init__.h"

#define MP_DEFINE_BYTES_OBJ(obj_name, bin) mp_obj_str_t obj_name = {{&mp_type_bytes}, 0, sizeof(bin) - 1, (const byte *)bin}

static const char i2c_bus_init_sequence[] = {
2, 3, 0x78, // set GPIO direction
2, 2, 0, // disable all output inversion
0, // trailing NUL for python bytes() representation
};
STATIC MP_DEFINE_BYTES_OBJ(i2c_init_byte_obj, i2c_bus_init_sequence);

STATIC const mp_rom_map_elem_t tft_io_expander_table[] = {
{ MP_ROM_QSTR(MP_QSTR_i2c_address), MP_ROM_INT(0x3f)},
{ MP_ROM_QSTR(MP_QSTR_gpio_address), MP_ROM_INT(1)},
{ MP_ROM_QSTR(MP_QSTR_gpio_data_len), MP_ROM_INT(1)},
{ MP_ROM_QSTR(MP_QSTR_gpio_data), MP_ROM_INT(0xFD)},
{ MP_ROM_QSTR(MP_QSTR_cs_bit), MP_ROM_INT(1)},
{ MP_ROM_QSTR(MP_QSTR_mosi_bit), MP_ROM_INT(7)},
{ MP_ROM_QSTR(MP_QSTR_clk_bit), MP_ROM_INT(0)},
{ MP_ROM_QSTR(MP_QSTR_reset_bit), MP_ROM_INT(2)},
{ MP_ROM_QSTR(MP_QSTR_i2c_init_sequence), &i2c_init_byte_obj},
};
MP_DEFINE_CONST_DICT(tft_io_expander_dict, tft_io_expander_table);

STATIC const mp_rom_obj_tuple_t tft_r_pins = {
{&mp_type_tuple},
5,
Expand Down Expand Up @@ -52,8 +74,8 @@ MP_DEFINE_CONST_DICT(tft_dict, tft_table);
STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS

{ MP_ROM_QSTR(MP_QSTR_TFT), MP_ROM_PTR(&tft_dict) },

{ MP_ROM_QSTR(MP_QSTR_TFT_PINS), MP_ROM_PTR(&tft_dict) },
{ MP_ROM_QSTR(MP_QSTR_TFT_IO_EXPANDER), MP_ROM_PTR(&tft_io_expander_dict) },
{ MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(MICROPY_HW_NEOPIXEL) },

{ MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO43) },
Expand Down
34 changes: 17 additions & 17 deletions ports/espressif/boards/espressif_esp32s3_lcd_ev/board.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
#include "shared-bindings/framebufferio/FramebufferDisplay.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-module/displayio/__init__.h"
#include "boards/espressif_esp32s3_lcd_ev/board.h"

#define MP_DEFINE_BYTES_OBJ(obj_name, bin) mp_obj_str_t obj_name = {{&mp_type_bytes}, 0, sizeof(bin) - 1, (const byte *)bin}

static const uint8_t display_init_sequence[] = {
0xf0, 5, 0x55, 0xaa, 0x52, 0x08, 0x00,
Expand Down Expand Up @@ -80,8 +83,17 @@ static const uint8_t display_init_sequence[] = {
0x3a, 1, 0x66,
0x3a, 1, 0x66,
0x11, 0x80, 120,
0x29, 0x0
0x29, 0x0,
0, // trailing NUL for Python bytes() representation
};
MP_DEFINE_BYTES_OBJ(display_init_byte_obj, display_init_sequence);

static const char i2c_bus_init_sequence[] = {
2, 3, 0xf1, // set GPIO direction
2, 2, 0, // disable all output inversion
0, // trailing NUL for Python bytes() representation
};
MP_DEFINE_BYTES_OBJ(i2c_init_byte_obj, i2c_bus_init_sequence);

static const mcu_pin_obj_t *red_pins[] = {
&pin_GPIO1, &pin_GPIO2, &pin_GPIO42, &pin_GPIO41, &pin_GPIO40
Expand All @@ -103,7 +115,7 @@ void board_init(void) {
/* hsync */ &pin_GPIO46,
/* dclk */ &pin_GPIO9,
/* data */ red_pins, MP_ARRAY_SIZE(red_pins), green_pins, MP_ARRAY_SIZE(green_pins), blue_pins, MP_ARRAY_SIZE(blue_pins),
/* frequency */ 6500000,
/* frequency */ 12000000,
/* width x height */ 480, 480,
/* horizontal: pulse, back & front porch, idle */ 13, 20, 40, false,
/* vertical: pulse, back & front porch, idle */ 15, 20, 40, false,
Expand All @@ -127,20 +139,6 @@ void board_init(void) {
common_hal_busio_i2c_construct(&i2c, DEFAULT_I2C_BUS_SCL, DEFAULT_I2C_BUS_SDA, 400000, 255);
const int i2c_device_address = 32;

common_hal_busio_i2c_try_lock(&i2c);

{
uint8_t buf[2] = {3, 0xf1}; // set GPIO direction
common_hal_busio_i2c_write(&i2c, i2c_device_address, buf, sizeof(buf));
}

{
uint8_t buf[2] = {2, 0}; // set all output pins low initially
common_hal_busio_i2c_write(&i2c, i2c_device_address, buf, sizeof(buf));
}

common_hal_busio_i2c_unlock(&i2c);

dotclockframebuffer_ioexpander_spi_bus spibus = {
.bus = &i2c,
.i2c_device_address = i2c_device_address,
Expand All @@ -151,7 +149,9 @@ void board_init(void) {
.clk_mask = 0x100 << 2,
};

dotclockframebuffer_ioexpander_send_init_sequence(&spibus, display_init_sequence, sizeof(display_init_sequence));
static const mp_buffer_info_t bufinfo_display_init = { (void *)display_init_sequence, sizeof(display_init_sequence) - 1 };
static const mp_buffer_info_t bufinfo_i2c_bus_init = { (void *)i2c_bus_init_sequence, sizeof(i2c_bus_init_sequence) - 1 };
dotclockframebuffer_ioexpander_send_init_sequence(&spibus, &bufinfo_i2c_bus_init, &bufinfo_display_init);

common_hal_busio_i2c_deinit(&i2c);
}
Expand Down
31 changes: 31 additions & 0 deletions ports/espressif/boards/espressif_esp32s3_lcd_ev/board.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2023 Jeff Epler for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#pragma once

#include "py/objstr.h"

extern mp_obj_str_t display_init_byte_obj, i2c_init_byte_obj;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ CIRCUITPY_ESP_FLASH_SIZE = 16MB

CIRCUITPY_ESP_PSRAM_SIZE = 8MB
CIRCUITPY_ESP_PSRAM_MODE = opi
CIRCUITPY_ESP_PSRAM_FREQ = 80m
CIRCUITPY_ESP_PSRAM_FREQ = 120m

CIRCUITPY_DOTCLOCKFRAMEBUFFER = 1
UF2_BOOTLOADER = 0
15 changes: 15 additions & 0 deletions ports/espressif/boards/espressif_esp32s3_lcd_ev/pins.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
#include "py/objtuple.h"
#include "boards/espressif_esp32s3_lcd_ev/board.h"
#include "shared-bindings/board/__init__.h"
#include "shared-module/displayio/__init__.h"

STATIC const mp_rom_map_elem_t tft_io_expander_table[] = {
{ MP_ROM_QSTR(MP_QSTR_i2c_address), MP_ROM_INT(0x20)},
{ MP_ROM_QSTR(MP_QSTR_gpio_address), MP_ROM_INT(1)},
{ MP_ROM_QSTR(MP_QSTR_gpio_data_len), MP_ROM_INT(1)},
{ MP_ROM_QSTR(MP_QSTR_gpio_data), MP_ROM_INT(0xF1)},
{ MP_ROM_QSTR(MP_QSTR_cs_bit), MP_ROM_INT(1)},
{ MP_ROM_QSTR(MP_QSTR_mosi_bit), MP_ROM_INT(3)},
{ MP_ROM_QSTR(MP_QSTR_clk_bit), MP_ROM_INT(2)},
{ MP_ROM_QSTR(MP_QSTR_i2c_init_sequence), &i2c_init_byte_obj},
};
MP_DEFINE_CONST_DICT(tft_io_expander_dict, tft_io_expander_table);

STATIC const mp_rom_obj_tuple_t tft_r_pins = {
{&mp_type_tuple},
5,
Expand Down Expand Up @@ -73,6 +86,8 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = {

{ MP_ROM_QSTR(MP_QSTR_TFT_PINS), MP_ROM_PTR(&tft_pins_dict) },
{ MP_ROM_QSTR(MP_QSTR_TFT_TIMINGS), MP_ROM_PTR(&tft_timings_dict) },
{ MP_ROM_QSTR(MP_QSTR_TFT_IO_EXPANDER), MP_ROM_PTR(&tft_io_expander_dict) },
{ MP_ROM_QSTR(MP_QSTR_TFT_INIT_SEQUENCE), &display_init_byte_obj},

{ MP_ROM_QSTR(MP_QSTR_I2S_SCK), MP_ROM_PTR(&pin_GPIO16) },
{ MP_ROM_QSTR(MP_QSTR_I2S_MCLK), MP_ROM_PTR(&pin_GPIO5) },
Expand Down
1 change: 1 addition & 0 deletions ports/espressif/common-hal/busio/I2C.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ void common_hal_busio_i2c_construct(busio_i2c_obj_t *self,
self->sda_pin = sda;
self->scl_pin = scl;
self->i2c_num = peripherals_i2c_get_free_num();
self->has_lock = 0;

if (self->i2c_num == I2C_NUM_MAX) {
mp_raise_ValueError(translate("All I2C peripherals are in use"));
Expand Down
11 changes: 11 additions & 0 deletions ports/espressif/esp-idf-config/sdkconfig-esp32s3.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ CONFIG_I2S_SUPPRESS_DEPRECATE_WARN=y

# end of Driver Configurations

#
# LCD and Touch Panel
#
#
# LCD Peripheral Configuration
#
CONFIG_LCD_RGB_RESTART_IN_VSYNC=y
# end of LCD Peripheral Configuration

# end of LCD and Touch Panel

#
# ESP System Settings
#
Expand Down
54 changes: 39 additions & 15 deletions shared-bindings/dotclockframebuffer/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@
//|
//| def ioexpander_send_init_sequence(
//| bus: busio.I2C,
//| init_sequence: ReadableBuffer,
//| *,
//| i2c_init_sequence: ReadableBuffer,
//| i2c_address: int,
//| reg_addr: int,
//| gpio_data_len: Length,
//| gpio_address: int,
//| gpio_data_len: Length,
//| gpio_data: int,
//| cs_bit: int,
//| mosi_bit: int,
//| clk_bit: int,
//| init_sequence: ReadableBuffer,
//| reset_bit: Optional[int],
//| ):
//| """Send a displayio-style initialization sequence over an I2C I/O expander
//|
Expand All @@ -60,30 +62,41 @@
//|
//| Normally this function is used via a convenience library that is specific to the display & I/O expander in use.
//|
//| If the board has an integrated I/O expander, ``**board.TFT_IO_EXPANDER`` expands to the proper arguments starting with ``gpio_address``.
//| Note that this may include the ``i2c_init_sequence`` argument which can change the direction & value of I/O expander pins.
//| If this is undesirable, take a copy of ``TFT_IO_EXPANDER`` and change or remove the ``i2c_init_sequence`` key.
//|
//| If the board has an integrated display that requires an initialization sequence, ``board.TFT_INIT_SEQUENCE`` is the initialization string for the display.
//|
//| :param busio.I2C bus: The I2C bus where the I/O expander resides
//| :param busio.i2c_address: int: The I2C bus address of the I/O expander
//| :param int busio.i2c_address: The I2C bus address of the I/O expander
//| :param ReadableBuffer init_sequence: The initialization sequence to send to the display
//| :param int gpio_address: The address portion of the I2C transaction (1 byte)
//| :param int gpio_data_len: The size of the data portion of the I2C transaction, 1 or 2 bytes
//| :param int gpio_data: The output value for all GPIO bits other than cs, mosi, and clk (needed because GPIO expanders may be unable to read back the current output value)
//| :param int cs_bit: The bit number (from 0 to 7, or from 0 to 15) of the chip select bit in the GPIO register
//| :param int mosi_value: The bit number (from 0 to 7, or from 0 to 15) of the data out bit in the GPIO register
//| :param int clk_value: The bit number (from 0 to 7, or from 0 to 15) of the clock out bit in the GPIO register
//| :param Optional[int] reset_value: The bit number (from 0 to 7, or from 0 to 15) of the display reset bit in the GPIO register
//| :param Optional[ReadableBuffer] i2c_init_sequence: An initialization sequence to send to the I2C expander
//| """
//|

STATIC mp_obj_t ioexpander_send_init_sequence(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_bus, ARG_i2c_address, ARG_gpio_address, ARG_gpio_data_len, ARG_gpio_data, ARG_cs_bit, ARG_mosi_bit, ARG_clk_bit, ARG_init_sequence, NUM_ARGS };
enum { ARG_bus, ARG_init_sequence, ARG_i2c_address, ARG_gpio_address, ARG_gpio_data_len, ARG_gpio_data, ARG_cs_bit, ARG_mosi_bit, ARG_clk_bit, ARG_reset_bit, ARG_i2c_init_sequence, NUM_ARGS };

static const mp_arg_t allowed_args[] = {
{ MP_QSTR_bus, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_i2c_address, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_gpio_address, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_gpio_data_len, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_gpio_data, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_cs_bit, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_mosi_bit, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_clk_bit, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_init_sequence, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_i2c_address, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_gpio_address, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_gpio_data_len, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_gpio_data, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_cs_bit, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_mosi_bit, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_clk_bit, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_reset_bit, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_ROM_NONE } },
{ MP_QSTR_i2c_init_sequence, MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_ROM_NONE } },
};

mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
Expand All @@ -99,8 +112,8 @@ STATIC mp_obj_t ioexpander_send_init_sequence(size_t n_args, const mp_obj_t *pos
mp_int_t mosi_bit = args[ARG_mosi_bit].u_int;
mp_int_t clk_bit = args[ARG_clk_bit].u_int;

mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[ARG_init_sequence].u_obj, &bufinfo, MP_BUFFER_READ);
mp_buffer_info_t bufinfo_display_init_sequence;
mp_get_buffer_raise(args[ARG_init_sequence].u_obj, &bufinfo_display_init_sequence, MP_BUFFER_READ);

mp_arg_validate_int_range(i2c_address, 0, 127, MP_QSTR_i2c_address);
mp_arg_validate_int_range(gpio_data_len, 1, 2, MP_QSTR_gpio_dat_len);
Expand All @@ -110,6 +123,16 @@ STATIC mp_obj_t ioexpander_send_init_sequence(size_t n_args, const mp_obj_t *pos
mp_arg_validate_int_range(mosi_bit, 0, max_bit, MP_QSTR_mosi_bit);
mp_arg_validate_int_range(clk_bit, 0, max_bit, MP_QSTR_clk_bit);
mp_arg_validate_int_range(gpio_data, 0, (1 << (max_bit * 8)) - 1, MP_QSTR_gpio_data);
mp_int_t reset_mask = 0;
if (args[ARG_reset_bit].u_obj != MP_ROM_NONE) {
mp_int_t reset_bit = mp_arg_validate_int_range(mp_arg_validate_type_int(args[ARG_reset_bit].u_obj, MP_QSTR_reset_bit), 0, max_bit, MP_QSTR_reset_bit);
reset_mask = 0x100 << reset_bit;
}

mp_buffer_info_t bufinfo_i2c_init_sequence = {};
if (args[ARG_i2c_init_sequence].u_obj != mp_const_none) {
mp_get_buffer_raise(args[ARG_i2c_init_sequence].u_obj, &bufinfo_i2c_init_sequence, MP_BUFFER_READ);
}

dotclockframebuffer_ioexpander_spi_bus b = {
.bus = bus_obj,
Expand All @@ -120,9 +143,10 @@ STATIC mp_obj_t ioexpander_send_init_sequence(size_t n_args, const mp_obj_t *pos
.cs_mask = 0x100 << cs_bit,
.mosi_mask = 0x100 << mosi_bit,
.clk_mask = 0x100 << clk_bit,
.reset_mask = reset_mask,
};

dotclockframebuffer_ioexpander_send_init_sequence(&b, bufinfo.buf, bufinfo.len);
dotclockframebuffer_ioexpander_send_init_sequence(&b, &bufinfo_i2c_init_sequence, &bufinfo_display_init_sequence);
return mp_const_none;
}

Expand Down
3 changes: 2 additions & 1 deletion shared-bindings/dotclockframebuffer/__init__.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ typedef struct {
uint32_t cs_mask;
uint32_t mosi_mask;
uint32_t clk_mask;
uint32_t reset_mask;
} dotclockframebuffer_ioexpander_spi_bus;

void dotclockframebuffer_ioexpander_send_init_sequence(dotclockframebuffer_ioexpander_spi_bus *bus, const uint8_t *init_sequence, uint16_t init_sequence_len);
void dotclockframebuffer_ioexpander_send_init_sequence(dotclockframebuffer_ioexpander_spi_bus *bus, const mp_buffer_info_t *i2c_bus_init, const mp_buffer_info_t *display_init);
8 changes: 8 additions & 0 deletions shared-module/displayio/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
#include "supervisor/spi_flash_api.h"
#include "py/mpconfig.h"

#if CIRCUITPY_DOTCLOCKFRAMEBUFFER
#include "shared-bindings/dotclockframebuffer/DotClockFramebuffer.h"
#endif

#if CIRCUITPY_SHARPDISPLAY
#include "shared-bindings/sharpdisplay/SharpMemoryFramebuffer.h"
#include "shared-module/sharpdisplay/SharpMemoryFramebuffer.h"
Expand Down Expand Up @@ -133,6 +137,10 @@ void common_hal_displayio_release_displays(void) {
common_hal_displayio_fourwire_deinit(&display_buses[i].fourwire_bus);
} else if (bus_type == &displayio_i2cdisplay_type) {
common_hal_displayio_i2cdisplay_deinit(&display_buses[i].i2cdisplay_bus);
#if CIRCUITPY_DOTCLOCKFRAMEBUFFER
} else if (bus_type == &dotclockframebuffer_framebuffer_type) {
common_hal_dotclockframebuffer_framebuffer_deinit(&display_buses[i].dotclock);
#endif
#if CIRCUITPY_PARALLELDISPLAY
} else if (bus_type == &paralleldisplay_parallelbus_type) {
common_hal_paralleldisplay_parallelbus_deinit(&display_buses[i].parallel_bus);
Expand Down
30 changes: 26 additions & 4 deletions shared-module/dotclockframebuffer/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,37 @@ static void ioexpander_bus_send(dotclockframebuffer_ioexpander_spi_bus *bus,
// * CPOL=CPHA=0
// * CS deasserted after each init sequence step, but not otherwise just like
// displayio fourwire bus without data_as_commands
void dotclockframebuffer_ioexpander_send_init_sequence(dotclockframebuffer_ioexpander_spi_bus *bus, const uint8_t *init_sequence, uint16_t init_sequence_len) {
void dotclockframebuffer_ioexpander_send_init_sequence(dotclockframebuffer_ioexpander_spi_bus *bus, const mp_buffer_info_t *i2c_bus_init, const mp_buffer_info_t *display_init) {
while (!common_hal_busio_i2c_try_lock(bus->bus)) {
RUN_BACKGROUND_TASKS;
}

// ensure deasserted CS and idle CLK
pin_change(bus, /* set */ bus->cs_mask, /* clear */ bus->clk_mask);
// send i2c init sequence
{
size_t init_sequence_len = i2c_bus_init->len;
const uint8_t *init_sequence = i2c_bus_init->buf;

for (uint32_t i = 0; i < init_sequence_len; /* NO INCREMENT */) {
for (size_t i = 0; i < init_sequence_len; /* NO INCREMENT */) {
uint8_t data_size = init_sequence[i];
const uint8_t *data_ptr = &init_sequence[i + 1];
(void)common_hal_busio_i2c_write(bus->bus, bus->i2c_device_address, data_ptr, data_size);
i = i + data_size + 1;
}
}

// ensure deasserted CS and idle CLK (and set other pins according to addr_reg_shadow); enter reset mode if applicable
pin_change(bus, /* set */ bus->cs_mask, /* clear */ bus->clk_mask | bus->reset_mask);

if (bus->reset_mask) {
mp_hal_delay_ms(10); // reset pulse length
pin_change(bus, /* set */ bus->reset_mask, /* clear */ 0);
mp_hal_delay_ms(100); // display start-up time
}

size_t init_sequence_len = display_init->len;
const uint8_t *init_sequence = display_init->buf;

for (size_t i = 0; i < init_sequence_len; /* NO INCREMENT */) {
const uint8_t *cmd = init_sequence + i;
uint8_t data_size = *(cmd + 1);
bool delay = (data_size & DELAY) != 0;
Expand Down