Ken Van Hoeylandt 65ee223876
CL-32 drivers & more (#652)
- CL-32 features added:
  - Hardware revision detection
  - v0.3 and v0.4 keyboard 
  - v0.4 power supply
  - Inlined epd library (was a module) and made changes to the device definition (don't do full update after 5 small updates)
- Statusbar: add support for inverted colors
- e-paper driver improvements and warning fixes
- Implemented gpio_controller_get_level()
- Keyboard support improvements (unicode for tca drivers)
2026-09-14 00:06:55 +02:00

174 lines
4.8 KiB
C

#include "epaper_spi.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "epd_spi";
#define SPI_MAX_CHUNK_SIZE 4096 // 4KB per transfer (safe for all ESP32 variants)
esp_err_t epd_spi_init(epd_spi_t *spi, const epd_pin_config_t *pins, const epd_spi_config_t *spi_cfg)
{
// Configure GPIO pins
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
// DC, RST, CS as output
io_conf.pin_bit_mask = (1ULL << pins->dc) | (1ULL << pins->rst) | (1ULL << pins->cs);
gpio_config(&io_conf);
// BUSY as input
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << pins->busy);
gpio_config(&io_conf);
// Store pins
spi->pin_dc = pins->dc;
spi->pin_rst = pins->rst;
spi->pin_busy = pins->busy;
spi->pin_cs = pins->cs;
// Set initial state
gpio_set_level(pins->cs, 1);
gpio_set_level(pins->dc, 1);
gpio_set_level(pins->rst, 1);
// Configure SPI bus (large buffer for big displays like 800x480)
spi_bus_config_t buscfg = {
.mosi_io_num = pins->mosi,
.miso_io_num = -1, // Not used
.sclk_io_num = pins->sck,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = SPI_MAX_CHUNK_SIZE, // Match chunk size
};
spi->host = spi_cfg->host;
esp_err_t ret = spi_bus_initialize(spi_cfg->host, &buscfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "SPI bus init failed: %s", esp_err_to_name(ret));
return ret;
}
// ESP_ERR_INVALID_STATE means the host was already initialized by someone else - this
// component doesn't own it and must not free it on deinit.
spi->owns_bus = (ret == ESP_OK);
// Add SPI device
spi_device_interface_config_t devcfg = {
.clock_speed_hz = spi_cfg->speed_hz,
.mode = 0,
.spics_io_num = -1, // Manual CS control
.queue_size = 1,
};
ret = spi_bus_add_device(spi_cfg->host, &devcfg, &spi->spi);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI device add failed: %s", esp_err_to_name(ret));
if (spi->owns_bus) {
spi_bus_free(spi->host);
spi->owns_bus = false;
}
return ret;
}
ESP_LOGI(TAG, "SPI initialized");
return ESP_OK;
}
esp_err_t epd_spi_deinit(epd_spi_t *spi)
{
if (spi->spi) {
spi_bus_remove_device(spi->spi);
spi->spi = NULL;
}
// spi_bus_free() requires all devices removed first, and only the owner of a host may free
// it - see spi_master.h's spi_bus_free() docs.
if (spi->owns_bus) {
spi_bus_free(spi->host);
spi->owns_bus = false;
}
return ESP_OK;
}
void epd_spi_write_cmd(epd_spi_t *spi, uint8_t cmd)
{
gpio_set_level(spi->pin_cs, 0);
gpio_set_level(spi->pin_dc, 0); // Command mode
spi_transaction_t t = {
.length = 8,
.tx_buffer = &cmd,
};
spi_device_polling_transmit(spi->spi, &t);
gpio_set_level(spi->pin_cs, 1);
}
void epd_spi_write_data(epd_spi_t *spi, uint8_t data)
{
gpio_set_level(spi->pin_cs, 0);
gpio_set_level(spi->pin_dc, 1); // Data mode
spi_transaction_t t = {
.length = 8,
.tx_buffer = &data,
};
spi_device_polling_transmit(spi->spi, &t);
gpio_set_level(spi->pin_cs, 1);
}
void epd_spi_write_data_bulk(epd_spi_t *spi, const uint8_t *data, uint32_t len)
{
gpio_set_level(spi->pin_cs, 0);
gpio_set_level(spi->pin_dc, 1); // Data mode
// Send in chunks to avoid SPI transfer limit
uint32_t offset = 0;
while (offset < len) {
uint32_t chunk_size = (len - offset > SPI_MAX_CHUNK_SIZE) ? SPI_MAX_CHUNK_SIZE : (len - offset);
spi_transaction_t t = {
.length = chunk_size * 8,
.tx_buffer = data + offset,
};
spi_device_polling_transmit(spi->spi, &t);
offset += chunk_size;
}
gpio_set_level(spi->pin_cs, 1);
}
void epd_spi_reset(epd_spi_t *spi)
{
gpio_set_level(spi->pin_rst, 0);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(spi->pin_rst, 1);
vTaskDelay(pdMS_TO_TICKS(10));
}
int epd_spi_is_busy(epd_spi_t *spi)
{
return gpio_get_level(spi->pin_busy);
}
void epd_spi_wait_busy(epd_spi_t *spi, uint32_t timeout_ms)
{
uint32_t start = xTaskGetTickCount();
while (gpio_get_level(spi->pin_busy)) {
if (timeout_ms > 0) {
uint32_t elapsed = (xTaskGetTickCount() - start) * portTICK_PERIOD_MS;
if (elapsed >= timeout_ms) {
ESP_LOGW(TAG, "Wait busy timeout");
return;
}
}
vTaskDelay(pdMS_TO_TICKS(5)); // 5ms like reference
}
}