#include "lcd.h" #include "pins.h" #include "font5x7.h" #include "pico/stdlib.h" #include "hardware/spi.h" #include "hardware/gpio.h" #include "hardware/sync.h" #include "tusb.h" #include #define ILI_SWRESET 0x01 #define ILI_SLPOUT 0x11 #define ILI_NORON 0x13 #define ILI_INVOFF 0x20 #define ILI_DISPON 0x29 #define ILI_CASET 0x2A #define ILI_PASET 0x2B #define ILI_RAMWR 0x2C #define ILI_MADCTL 0x36 #define ILI_VSCRSADD 0x37 #define ILI_PIXFMT 0x3A #define ILI_FRMCTR1 0xB1 #define ILI_DFUNCTR 0xB6 #define ILI_PWCTR1 0xC0 #define ILI_PWCTR2 0xC1 #define ILI_VMCTR1 0xC5 #define ILI_VMCTR2 0xC7 #define ILI_GAMMASET 0x26 #define ILI_GMCTRP1 0xE0 #define ILI_GMCTRN1 0xE1 #define MADCTL_MY 0x80 #define MADCTL_MX 0x40 #define MADCTL_MV 0x20 #define MADCTL_BGR 0x08 #define LCD_SPI_HZ (16 * 1000 * 1000) /* * MV is required for full glass coverage on this panel, and CASET/PASET must * stay in native 240x320 range. MV also transposes the image, so the logical * landscape axes are swapped when programming the window. * BGR is left off: with it set, yellow showed up as cyan (R/B swapped). */ #define MADCTL_FULL (MADCTL_MX | MADCTL_MV) static uint8_t s_madctl = MADCTL_FULL; static uint8_t s_line[LCD_WIDTH * 2]; /* MV makes the address counter walk the logical y axis first, so glyph pixels * are streamed column by column. Solid fills are scan-order agnostic. */ static uint16_t s_glyph_pix[28 * 24]; static uint8_t s_glyph_stream[28 * 24 * 2]; static inline void cs_low(void) { gpio_put(PIN_LCD_CS, 0); } static inline void cs_high(void) { gpio_put(PIN_LCD_CS, 1); } static void usb_pump(void) { tud_task(); } static void sleep_usb(uint32_t ms) { absolute_time_t t = make_timeout_time_ms(ms); while (!time_reached(t)) { tud_task(); sleep_ms(1); } } static void spi_write_raw(const uint8_t *data, size_t len) { spi_write_blocking(LCD_SPI_INST, data, len); } static void write_cmd_data(uint8_t cmd, const uint8_t *data, size_t len) { cs_low(); gpio_put(PIN_LCD_DC, 0); spi_write_raw(&cmd, 1); if (data && len) { gpio_put(PIN_LCD_DC, 1); spi_write_raw(data, len); } cs_high(); } static void write_cmd(uint8_t cmd) { write_cmd_data(cmd, NULL, 0); } static void write_cmd_byte(uint8_t cmd, uint8_t d) { write_cmd_data(cmd, &d, 1); } /* Touch and SD share the SPI bus on TPM408 — keep them deselected. */ static void spi_bus_idle_peripherals(void) { gpio_init(PIN_TOUCH_CS); gpio_init(PIN_SD_CS); gpio_set_dir(PIN_TOUCH_CS, GPIO_OUT); gpio_set_dir(PIN_SD_CS, GPIO_OUT); gpio_put(PIN_TOUCH_CS, 1); gpio_put(PIN_SD_CS, 1); } /* Logical landscape (x,y) → native window (col=y, row=x). */ static void window_begin(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { uint8_t caset[4] = { (uint8_t)(y0 >> 8), (uint8_t)(y0 & 0xFF), (uint8_t)(y1 >> 8), (uint8_t)(y1 & 0xFF), }; uint8_t paset[4] = { (uint8_t)(x0 >> 8), (uint8_t)(x0 & 0xFF), (uint8_t)(x1 >> 8), (uint8_t)(x1 & 0xFF), }; uint8_t c; cs_low(); gpio_put(PIN_LCD_DC, 0); c = ILI_CASET; spi_write_raw(&c, 1); gpio_put(PIN_LCD_DC, 1); spi_write_raw(caset, 4); gpio_put(PIN_LCD_DC, 0); c = ILI_PASET; spi_write_raw(&c, 1); gpio_put(PIN_LCD_DC, 1); spi_write_raw(paset, 4); gpio_put(PIN_LCD_DC, 0); c = ILI_RAMWR; spi_write_raw(&c, 1); gpio_put(PIN_LCD_DC, 1); } void lcd_set_backlight(uint8_t percent) { gpio_put(PIN_LCD_BL, percent > 0 ? 1 : 0); } static void hw_reset(void) { gpio_put(PIN_LCD_RES, 1); sleep_ms(10); gpio_put(PIN_LCD_RES, 0); sleep_ms(50); gpio_put(PIN_LCD_RES, 1); sleep_ms(150); usb_pump(); } uint32_t lcd_read_id(void) { return 0; } static void ili_init_regs(void) { write_cmd(ILI_SWRESET); sleep_ms(150); usb_pump(); write_cmd_data(0xEF, (const uint8_t[]){0x03, 0x80, 0x02}, 3); write_cmd_data(0xCF, (const uint8_t[]){0x00, 0xC1, 0x30}, 3); write_cmd_data(0xED, (const uint8_t[]){0x64, 0x03, 0x12, 0x81}, 4); write_cmd_data(0xE8, (const uint8_t[]){0x85, 0x00, 0x78}, 3); write_cmd_data(0xCB, (const uint8_t[]){0x39, 0x2C, 0x00, 0x34, 0x02}, 5); write_cmd_byte(0xF7, 0x20); write_cmd_data(0xEA, (const uint8_t[]){0x00, 0x00}, 2); write_cmd_byte(ILI_PWCTR1, 0x23); write_cmd_byte(ILI_PWCTR2, 0x10); write_cmd_data(ILI_VMCTR1, (const uint8_t[]){0x3E, 0x28}, 2); write_cmd_byte(ILI_VMCTR2, 0x86); write_cmd_byte(ILI_MADCTL, s_madctl); write_cmd_byte(ILI_VSCRSADD, 0x00); write_cmd_byte(ILI_PIXFMT, 0x55); write_cmd_data(ILI_FRMCTR1, (const uint8_t[]){0x00, 0x18}, 2); write_cmd_data(ILI_DFUNCTR, (const uint8_t[]){0x08, 0x82, 0x27}, 3); write_cmd_byte(0xF2, 0x00); write_cmd_byte(ILI_GAMMASET, 0x01); write_cmd_data(ILI_GMCTRP1, (const uint8_t[]){ 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00 }, 15); write_cmd_data(ILI_GMCTRN1, (const uint8_t[]){ 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F }, 15); write_cmd(ILI_SLPOUT); sleep_ms(150); usb_pump(); write_cmd(ILI_DISPON); sleep_ms(150); usb_pump(); write_cmd(ILI_INVOFF); write_cmd(ILI_NORON); write_cmd_byte(ILI_MADCTL, s_madctl); } void lcd_fill_rect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { if (w <= 0 || h <= 0) { return; } if (x >= LCD_WIDTH || y >= LCD_HEIGHT) { return; } if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x + w > LCD_WIDTH) { w = LCD_WIDTH - x; } if (y + h > LCD_HEIGHT) { h = LCD_HEIGHT - y; } if (w <= 0 || h <= 0) { return; } uint8_t hi = (uint8_t)(color >> 8); uint8_t lo = (uint8_t)(color & 0xFF); for (int16_t i = 0; i < w; i++) { s_line[i * 2] = hi; s_line[i * 2 + 1] = lo; } uint32_t irq = save_and_disable_interrupts(); window_begin((uint16_t)x, (uint16_t)y, (uint16_t)(x + w - 1), (uint16_t)(y + h - 1)); for (int16_t row = 0; row < h; row++) { spi_write_raw(s_line, (size_t)(w * 2)); } cs_high(); restore_interrupts(irq); usb_pump(); } void lcd_fill(uint16_t color) { lcd_fill_rect(0, 0, LCD_WIDTH, LCD_HEIGHT, color); } void lcd_draw_char(int16_t x, int16_t y, char c, uint16_t fg, uint16_t bg, uint8_t scale) { if (scale == 0) { scale = 1; } if (c < 32 || c > 126) { c = '?'; } if (scale > 4) { scale = 4; } const uint8_t *glyph = font5x7[c - 32]; const int16_t cw = (int16_t)(6 * scale); const int16_t ch = (int16_t)(7 * scale); if (x >= LCD_WIDTH || y >= LCD_HEIGHT || x + cw <= 0 || y + ch <= 0) { return; } for (int16_t gy = 0; gy < ch; gy++) { int row = gy / scale; for (int16_t gx = 0; gx < cw; gx++) { int col = gx / scale; bool on = (col < 5) && ((glyph[col] & (1u << row)) != 0); s_glyph_pix[gy * cw + gx] = on ? fg : bg; } } uint8_t *stream = s_glyph_stream; size_t n = 0; for (int16_t gx = 0; gx < cw; gx++) { for (int16_t gy = 0; gy < ch; gy++) { uint16_t px = s_glyph_pix[gy * cw + gx]; stream[n++] = (uint8_t)(px >> 8); stream[n++] = (uint8_t)(px & 0xFF); } } uint32_t irq = save_and_disable_interrupts(); window_begin((uint16_t)x, (uint16_t)y, (uint16_t)(x + cw - 1), (uint16_t)(y + ch - 1)); spi_write_raw(stream, n); cs_high(); restore_interrupts(irq); } void lcd_draw_string(int16_t x, int16_t y, const char *s, uint16_t fg, uint16_t bg, uint8_t scale) { if (!s) { return; } int16_t cx = x; while (*s) { lcd_draw_char(cx, y, *s, fg, bg, scale); cx += 6 * scale; s++; } } void lcd_selftest(void) { lcd_fill(UI_BG); lcd_fill_rect(0, 0, LCD_WIDTH, 3, UI_ACCENT); lcd_draw_string(62, 100, "Ausiomixer", UI_ACCENT, UI_BG, 3); lcd_draw_string(100, 140, "ready", UI_MUTED, UI_BG, 2); sleep_usb(700); } void lcd_init(void) { spi_bus_idle_peripherals(); gpio_init(PIN_LCD_CS); gpio_init(PIN_LCD_DC); gpio_init(PIN_LCD_RES); gpio_init(PIN_LCD_BL); gpio_set_dir(PIN_LCD_CS, GPIO_OUT); gpio_set_dir(PIN_LCD_DC, GPIO_OUT); gpio_set_dir(PIN_LCD_RES, GPIO_OUT); gpio_set_dir(PIN_LCD_BL, GPIO_OUT); gpio_put(PIN_LCD_CS, 1); gpio_put(PIN_LCD_DC, 1); gpio_put(PIN_LCD_RES, 1); gpio_put(PIN_LCD_BL, 0); spi_deinit(LCD_SPI_INST); uint baud = spi_init(LCD_SPI_INST, LCD_SPI_HZ); spi_set_format(LCD_SPI_INST, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); gpio_set_function(PIN_LCD_SCK, GPIO_FUNC_SPI); gpio_set_function(PIN_LCD_MOSI, GPIO_FUNC_SPI); printf("LCD 320x240 madctl=0x%02X baud~%u\n", MADCTL_FULL, baud); stdio_flush(); hw_reset(); ili_init_regs(); lcd_set_backlight(100); lcd_selftest(); lcd_fill(LCD_BLACK); }