291 lines
6.6 KiB
C
291 lines
6.6 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
|
|
vTaskDelay(pdMS_TO_TICKS(20));
|
|
}
|
|
|
|
/*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++;
|
|
}
|
|
} |