04ad525c09
* can now send the I2C bus initialization code * can now reset the display on an I/O expander pin * parameters re-ordered to enable easy use with **board.TFT_IO_EXPANDER
103 lines
3.7 KiB
C
103 lines
3.7 KiB
C
#include "shared-bindings/dotclockframebuffer/__init__.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
|
|
#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);
|
|
}
|