#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 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++; } }