Files
cardputerfw/main/drivers/st7789.c
2026-05-08 15:09:47 +02:00

293 lines
6.7 KiB
C

#include "st7789.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "string.h"
#include "fonts.h"
#include "pins.h"
#include <stdint.h>
static spi_device_handle_t lcd_spi;
/* ---------------- LOW LEVEL SPI ---------------- */
static void spi_tx(const void *data, size_t len) {
spi_transaction_t t = {
.length = len * 8,
.tx_buffer = data,
.flags = 0,
};
spi_device_transmit(lcd_spi, &t);
}
void st7789_write_cmd(uint8_t cmd) {
gpio_set_level(LCD_RS, 0);
spi_tx(&cmd, 1);
}
void st7789_write_data(const void *data, size_t len) {
gpio_set_level(LCD_RS, 1);
spi_tx(data, len);
}
/* commands */
/* ---------------- WINDOW ---------------- */
void st7789_set_window(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye) {
uint8_t data[4];
st7789_write_cmd(0x2A);
data[0] = (xs + X_OFFSET) >> 8;
data[1] = (xs + X_OFFSET) & 0xFF;
data[2] = (xe + X_OFFSET) >> 8;
data[3] = (xe + X_OFFSET) & 0xFF;
st7789_write_data(data, 4);
st7789_write_cmd(0x2B);
data[0] = (ys + Y_OFFSET) >> 8;
data[1] = (ys + Y_OFFSET) & 0xFF;
data[2] = (ye + Y_OFFSET) >> 8;
data[3] = (ye + Y_OFFSET) & 0xFF;
st7789_write_data(data, 4);
st7789_write_cmd(0x2C);
}
/* ---------------- PIXELS ---------------- */
void st7789_push_pixels(const uint16_t *pixels, size_t count) {
gpio_set_level(LCD_RS, 1);
const uint8_t *data = (const uint8_t *)pixels;
size_t bytes = count * 2;
while (bytes > 0) {
size_t chunk = (bytes > SPI_MAX_CHUNK) ? SPI_MAX_CHUNK : bytes;
spi_transaction_t t = {
.length = chunk * 8,
.tx_buffer = data,
.flags = 0,
};
spi_device_transmit(lcd_spi, &t);
data += chunk;
bytes -= chunk;
}
}
/* ---------------- INIT ---------------- */
static void st7789_reset(void) {
gpio_set_level(LCD_RESET, 0);
vTaskDelay(pdMS_TO_TICKS(20));
gpio_set_level(LCD_RESET, 1);
vTaskDelay(pdMS_TO_TICKS(120));
}
void st7789_init(void) {
gpio_config_t io = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << LCD_RS) | (1ULL << LCD_RESET) | (1ULL << LCD_CS),
};
gpio_config(&io);
spi_bus_config_t buscfg = {
.mosi_io_num = LCD_DAT,
.miso_io_num = -1,
.sclk_io_num = LCD_SCK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO);
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 40000000,
.mode = 0,
.spics_io_num = LCD_CS,
.queue_size = 1,
};
spi_bus_add_device(SPI3_HOST, &devcfg, &lcd_spi);
st7789_reset();
st7789_write_cmd(0x01); // SWRESET
vTaskDelay(pdMS_TO_TICKS(150));
st7789_write_cmd(0x11); // SLPOUT
vTaskDelay(pdMS_TO_TICKS(120));
uint8_t colmod = 0x55; // RGB565 stable
st7789_write_cmd(0x3A);
st7789_write_data(&colmod, 1);
uint8_t madctl = 0x70; // typical working orientation (adjust if needed)
st7789_write_cmd(0x36);
st7789_write_data(&madctl, 1);
st7789_write_cmd(0x21); // INVERT ON (fixes many panels)
st7789_write_cmd(0x29); // DISPON
st7789_fill(0x0000);
st7789_flush_full();
gpio_set_level(LCD_BACKLIGHT_AND_RGB_POWER, 1);
}
/*rendering*/
uint16_t framebuffer[LCD_WIDTH * LCD_HEIGHT];
static uint16_t fb_dirty_x0 = LCD_WIDTH;
static uint16_t fb_dirty_y0 = LCD_HEIGHT;
static uint16_t fb_dirty_x1 = 0;
static uint16_t fb_dirty_y1 = 0;
static inline void fb_mark_dirty(uint16_t x, uint16_t y) {
if (x < fb_dirty_x0)
fb_dirty_x0 = x;
if (y < fb_dirty_y0)
fb_dirty_y0 = y;
if (x > fb_dirty_x1)
fb_dirty_x1 = x;
if (y > fb_dirty_y1)
fb_dirty_y1 = y;
}
void fb_set_pixel(uint16_t x, uint16_t y, uint16_t color) {
if (x >= LCD_WIDTH || y >= LCD_HEIGHT)
return;
framebuffer[y * LCD_WIDTH + x] = color;
fb_mark_dirty(x, y);
}
void st7789_flush_full(void) {
st7789_set_window(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);
st7789_push_pixels(framebuffer, LCD_WIDTH * LCD_HEIGHT);
}
void st7789_flush(void) {
if (fb_dirty_x1 < fb_dirty_x0 || fb_dirty_y1 < fb_dirty_y0)
return; // nothing changed
uint16_t x0 = fb_dirty_x0;
uint16_t y0 = fb_dirty_y0;
uint16_t x1 = fb_dirty_x1;
uint16_t y1 = fb_dirty_y1;
st7789_set_window(x0, y0, x1, y1);
for (uint16_t y = y0; y <= y1; y++) {
st7789_push_pixels(&framebuffer[y * LCD_WIDTH + x0], (x1 - x0 + 1));
}
// reset dirty region
fb_dirty_x0 = LCD_WIDTH;
fb_dirty_y0 = LCD_HEIGHT;
fb_dirty_x1 = 0;
fb_dirty_y1 = 0;
}
/* ---------------- DRAWING ---------------- */
void st7789_fill(uint16_t color) {
for (int i = 0; i < LCD_WIDTH * LCD_HEIGHT; i++)
framebuffer[i] = color;
}
void st7789_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
uint16_t color) {
for (uint16_t yy = 0; yy < h; yy++) {
for (uint16_t xx = 0; xx < w; xx++) {
fb_set_pixel(x + xx, y + yy, color);
}
}
}
void st7789_draw_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
uint16_t color) {
if (w == 0 || h == 0)
return;
uint16_t x2 = x + w - 1;
uint16_t y2 = y + h - 1;
// top + bottom
for (uint16_t i = x; i <= x2; i++) {
fb_set_pixel(i, y, color);
fb_set_pixel(i, y2, color);
}
// left + right
for (uint16_t j = y; j <= y2; j++) {
fb_set_pixel(x, j, color);
fb_set_pixel(x2, j, color);
}
}
void st7789_blit_rgb565(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
const uint16_t *data) {
for (uint16_t yy = 0; yy < h; yy++) {
uint16_t *dst = &framebuffer[(y + yy) * LCD_WIDTH + x];
const uint16_t *src = &data[yy * w];
memcpy(dst, src, w * sizeof(uint16_t));
}
fb_mark_dirty(x, y);
fb_mark_dirty(x + w - 1, y + h - 1);
}
/* ---------------- TEXT ---------------- */
void st7789_draw_char(uint16_t x, uint16_t y, char c, uint16_t fg, uint16_t bg,
bool drawBG, const uint8_t font[][5]) {
if (c < 32 || c > 127)
return;
const uint8_t *glyph = font[(uint8_t) c];
for (uint16_t xx = 0; xx < 5; xx++) {
uint8_t col = glyph[xx];
for (uint16_t yy = 0; yy < 8; yy++) {
uint16_t px = x + xx;
uint16_t py = y + yy;
if (col & (1 << yy)) {
fb_set_pixel(px, py, fg);
} else {
if (drawBG) { // TRANSPARENT
fb_set_pixel(px, py, bg);
}
}
}
}
fb_mark_dirty(x, y);
fb_mark_dirty(x + 5, y + 7);
}
void st7789_draw_string(uint16_t xOld, uint16_t yOld, const char *s,
uint16_t fg, uint16_t bg, bool drawBG, const uint8_t font[][5]) {
uint16_t x = xOld;
uint16_t y = yOld;
while (*s) {
if (*s == '\n') {
y += 5;
x = xOld;
s++;
continue;
}
st7789_draw_char(x, y, *s, fg, bg, drawBG, font);
x += 6;
s++;
}
}