lego-esp32s3-gameboy/components/st7789/st7789.c

339 lines
9.1 KiB
C

/**
* @file st7789.c
* @brief ST7789 Display Driver Implementation - COMPLETE!
*
* Full implementation with:
* - Complete initialization sequence
* - Fast DMA transfers
* - Framebuffer rendering
* - Backlight PWM control
*/
#include <string.h>
#include "esp_log.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "st7789.h"
#include "hardware_config.h"
static const char *TAG = "ST7789";
static spi_device_handle_t spi_handle = NULL;
// ST7789 Commands
#define ST7789_NOP 0x00
#define ST7789_SWRESET 0x01
#define ST7789_RDDID 0x04
#define ST7789_RDDST 0x09
#define ST7789_SLPIN 0x10
#define ST7789_SLPOUT 0x11
#define ST7789_PTLON 0x12
#define ST7789_NORON 0x13
#define ST7789_INVOFF 0x20
#define ST7789_INVON 0x21
#define ST7789_DISPOFF 0x28
#define ST7789_DISPON 0x29
#define ST7789_CASET 0x2A
#define ST7789_RASET 0x2B
#define ST7789_RAMWR 0x2C
#define ST7789_RAMRD 0x2E
#define ST7789_PTLAR 0x30
#define ST7789_COLMOD 0x3A
#define ST7789_MADCTL 0x36
// MADCTL bits
#define MADCTL_MY 0x80
#define MADCTL_MX 0x40
#define MADCTL_MV 0x20
#define MADCTL_ML 0x10
#define MADCTL_RGB 0x00
#define MADCTL_BGR 0x08
#define MADCTL_MH 0x04
/**
* @brief Send command to ST7789
*/
static void st7789_send_cmd(uint8_t cmd)
{
gpio_set_level(LCD_PIN_DC, 0); // Command mode
spi_transaction_t t = {
.length = 8,
.tx_buffer = &cmd,
.flags = SPI_TRANS_USE_TXDATA
};
t.tx_data[0] = cmd;
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_handle, &t));
}
/**
* @brief Send data byte to ST7789
*/
static void st7789_send_data(uint8_t data)
{
gpio_set_level(LCD_PIN_DC, 1); // Data mode
spi_transaction_t t = {
.length = 8,
.tx_buffer = &data,
.flags = SPI_TRANS_USE_TXDATA
};
t.tx_data[0] = data;
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_handle, &t));
}
/**
* @brief Send data buffer to ST7789
*/
static void st7789_send_data_buf(const uint8_t *data, size_t len)
{
if (len == 0) return;
gpio_set_level(LCD_PIN_DC, 1); // Data mode
spi_transaction_t t = {
.length = len * 8,
.tx_buffer = data
};
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_handle, &t));
}
/**
* @brief Set address window for drawing
*/
static void st7789_set_address_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
// Column address set
st7789_send_cmd(ST7789_CASET);
st7789_send_data(x0 >> 8);
st7789_send_data(x0 & 0xFF);
st7789_send_data(x1 >> 8);
st7789_send_data(x1 & 0xFF);
// Row address set
st7789_send_cmd(ST7789_RASET);
st7789_send_data(y0 >> 8);
st7789_send_data(y0 & 0xFF);
st7789_send_data(y1 >> 8);
st7789_send_data(y1 & 0xFF);
// Write to RAM
st7789_send_cmd(ST7789_RAMWR);
}
/**
* @brief Initialize backlight PWM
*/
static void st7789_backlight_init(void)
{
ledc_timer_config_t timer_conf = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_8_BIT,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&timer_conf));
ledc_channel_config_t channel_conf = {
.gpio_num = LCD_PIN_BCKL,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&channel_conf));
}
esp_err_t st7789_init(void)
{
ESP_LOGI(TAG, "Initializing ST7789 display...");
// Configure GPIO pins
gpio_config_t io_conf = {};
io_conf.pin_bit_mask = (1ULL << LCD_PIN_DC) | (1ULL << LCD_PIN_RST);
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&io_conf);
// Configure SPI bus
spi_bus_config_t buscfg = {
.mosi_io_num = LCD_PIN_MOSI,
.miso_io_num = LCD_PIN_MISO,
.sclk_io_num = LCD_PIN_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = LCD_WIDTH * LCD_HEIGHT * 2 + 8,
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
// Configure SPI device
spi_device_interface_config_t devcfg = {
.clock_speed_hz = LCD_PIXEL_CLOCK_HZ,
.mode = 0,
.spics_io_num = LCD_PIN_CS,
.queue_size = 7,
.flags = SPI_DEVICE_NO_DUMMY,
};
ESP_ERROR_CHECK(spi_bus_add_device(LCD_SPI_HOST, &devcfg, &spi_handle));
// Initialize backlight
st7789_backlight_init();
st7789_set_backlight(0); // Start with backlight off
// Hardware reset
gpio_set_level(LCD_PIN_RST, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(LCD_PIN_RST, 1);
vTaskDelay(pdMS_TO_TICKS(100));
// ============================================
// ST7789 Initialization Sequence
// ============================================
// Software reset
st7789_send_cmd(ST7789_SWRESET);
vTaskDelay(pdMS_TO_TICKS(150));
// Out of sleep mode
st7789_send_cmd(ST7789_SLPOUT);
vTaskDelay(pdMS_TO_TICKS(10));
// Color mode: 16-bit/pixel (RGB565)
st7789_send_cmd(ST7789_COLMOD);
st7789_send_data(0x55); // 16-bit
vTaskDelay(pdMS_TO_TICKS(10));
// Memory access control (rotation, RGB/BGR)
st7789_send_cmd(ST7789_MADCTL);
#if LCD_ROTATION == 0
st7789_send_data(MADCTL_MX | MADCTL_MY | MADCTL_RGB);
#elif LCD_ROTATION == 1
st7789_send_data(MADCTL_MY | MADCTL_MV | MADCTL_RGB);
#elif LCD_ROTATION == 2
st7789_send_data(MADCTL_RGB);
#elif LCD_ROTATION == 3
st7789_send_data(MADCTL_MX | MADCTL_MV | MADCTL_RGB);
#endif
// Inversion mode on
st7789_send_cmd(ST7789_INVON);
vTaskDelay(pdMS_TO_TICKS(10));
// Normal display mode on
st7789_send_cmd(ST7789_NORON);
vTaskDelay(pdMS_TO_TICKS(10));
// Display on
st7789_send_cmd(ST7789_DISPON);
vTaskDelay(pdMS_TO_TICKS(100));
ESP_LOGI(TAG, "✓ ST7789 initialized (%dx%d)", LCD_WIDTH, LCD_HEIGHT);
// Set default backlight
st7789_set_backlight(LCD_BCKL_DEFAULT);
// Clear screen to black
st7789_fill_screen(0x0000);
return ESP_OK;
}
void st7789_set_backlight(uint8_t brightness)
{
if (brightness > 100) brightness = 100;
uint32_t duty = (brightness * 255) / 100;
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}
void st7789_fill_screen(uint16_t color)
{
ESP_LOGD(TAG, "Filling screen with color 0x%04X", color);
// Set full screen window
st7789_set_address_window(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);
// Prepare color buffer (1 line)
uint16_t line_buf[LCD_WIDTH];
for (int i = 0; i < LCD_WIDTH; i++) {
// Convert to big-endian for ST7789
line_buf[i] = (color >> 8) | (color << 8);
}
// Send line by line
gpio_set_level(LCD_PIN_DC, 1); // Data mode
for (int y = 0; y < LCD_HEIGHT; y++) {
spi_transaction_t t = {
.length = LCD_WIDTH * 16,
.tx_buffer = line_buf
};
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_handle, &t));
}
}
void st7789_draw_pixel(int16_t x, int16_t y, uint16_t color)
{
if (x < 0 || x >= LCD_WIDTH || y < 0 || y >= LCD_HEIGHT) {
return; // Out of bounds
}
st7789_set_address_window(x, y, x, y);
// Convert to big-endian
uint8_t data[2] = {color >> 8, color & 0xFF};
gpio_set_level(LCD_PIN_DC, 1); // Data mode
spi_transaction_t t = {
.length = 16,
.tx_buffer = data
};
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_handle, &t));
}
void st7789_draw_buffer(const uint16_t *buffer, int16_t x, int16_t y,
int16_t width, int16_t height)
{
if (buffer == NULL || width <= 0 || height <= 0) {
return;
}
// Bounds checking
if (x < 0 || y < 0 || x + width > LCD_WIDTH || y + height > LCD_HEIGHT) {
ESP_LOGW(TAG, "Buffer out of bounds: (%d,%d) %dx%d", x, y, width, height);
return;
}
ESP_LOGD(TAG, "Drawing buffer at (%d,%d) size %dx%d", x, y, width, height);
// Set drawing window
st7789_set_address_window(x, y, x + width - 1, y + height - 1);
// Prepare buffer with byte-swapped colors (ST7789 is big-endian)
size_t buf_size = width * height;
uint16_t *swap_buf = heap_caps_malloc(buf_size * 2, MALLOC_CAP_DMA);
if (swap_buf == NULL) {
ESP_LOGE(TAG, "Failed to allocate swap buffer!");
return;
}
// Swap bytes for ST7789
for (size_t i = 0; i < buf_size; i++) {
swap_buf[i] = (buffer[i] >> 8) | (buffer[i] << 8);
}
// Send via DMA
gpio_set_level(LCD_PIN_DC, 1); // Data mode
spi_transaction_t t = {
.length = buf_size * 16,
.tx_buffer = swap_buf
};
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_handle, &t));
heap_caps_free(swap_buf);
}