circuitpython/shared-module/dotclockframebuffer/__init__.c
Jeff Epler 04ad525c09
Re-work ioexpander_send_init_sequence
* 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
2023-09-25 11:31:16 -05:00

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);
}