#include "shared-bindings/dotclockframebuffer/__init__.h" #include #include #include #define DELAY (0x80) static void pin_change(dotclockframebuffer_ioexpander_spi_bus *bus, uint32_t set_bits, uint32_t clear_bits) { uint32_t data = (bus->addr_reg_shadow.u32 & ~clear_bits) | set_bits; // no way to signal failure to caller! (void)common_hal_busio_i2c_write(bus->bus, bus->i2c_device_address, (uint8_t *)&data, bus->i2c_write_size); bus->addr_reg_shadow.u32 = data; } static void ioexpander_bus_send(dotclockframebuffer_ioexpander_spi_bus *bus, bool is_command, const uint8_t *data, uint32_t data_length) { int dc_mask = is_command ? 0 : 0x100; for (uint32_t i = 0; i < data_length; i++) { int bits = data[i] | dc_mask; for (uint32_t j = 0; j < 9; j++) { // CPOL=CPHA=0: output fresh data on falling edge of clk or cs if (bits & 0x100) { pin_change(bus, /* set */ bus->mosi_mask, /* clear */ bus->clk_mask | bus->cs_mask); } else { pin_change(bus, /* set */ 0, /* clear */ bus->mosi_mask | bus->clk_mask | bus->cs_mask); } // Display latches bit on rising edge of CLK pin_change(bus, /* set */ bus->clk_mask, /* clear */ 0); // next bit bits <<= 1; } } } // Send a circuitpython-style display initialization sequence over an i2c-attached bus expander // This always assumes // * 9-bit SPI (no DC pin) // * 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 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; } // send i2c init sequence { size_t init_sequence_len = i2c_bus_init->len; const uint8_t *init_sequence = i2c_bus_init->buf; 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; data_size &= ~DELAY; const uint8_t *data = cmd + 2; ioexpander_bus_send(bus, true, cmd, 1); ioexpander_bus_send(bus, false, data, data_size); // idle CLK pin_change(bus, 0, /* clear */ bus->clk_mask); // deassert CS pin_change(bus, /* set */ bus->cs_mask, 0); if (delay) { data_size++; uint16_t delay_length_ms = *(cmd + 1 + data_size); if (delay_length_ms == 255) { delay_length_ms = 500; } mp_hal_delay_ms(delay_length_ms); } i += 2 + data_size; } common_hal_busio_i2c_unlock(bus->bus); }