mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 19:05:07 +00:00
- 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)
174 lines
4.8 KiB
C
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
|
|
}
|
|
}
|