381 lines
10 KiB
C
381 lines
10 KiB
C
#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 <stdio.h>
|
|
|
|
#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_blit(int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *px,
|
|
int32_t src_stride_bytes) {
|
|
if (!px || w <= 0 || h <= 0) {
|
|
return;
|
|
}
|
|
if (x < 0 || y < 0 || x + w > LCD_WIDTH || y + h > LCD_HEIGHT) {
|
|
return;
|
|
}
|
|
if (src_stride_bytes <= 0) {
|
|
src_stride_bytes = w * 2;
|
|
}
|
|
/* One logical column is at most LCD_HEIGHT pixels; s_line is wide enough. */
|
|
if (h * 2 > (int16_t)sizeof(s_line)) {
|
|
return;
|
|
}
|
|
|
|
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));
|
|
/*
|
|
* MADCTL_MV: GRAM walks logical Y first inside the window (same as
|
|
* lcd_draw_char). LVGL gives row-major — transpose column by column.
|
|
*/
|
|
for (int16_t col = 0; col < w; col++) {
|
|
for (int16_t row = 0; row < h; row++) {
|
|
const uint8_t *src = px + (size_t)row * (size_t)src_stride_bytes
|
|
+ (size_t)col * 2u;
|
|
s_line[row * 2] = src[0];
|
|
s_line[row * 2 + 1] = src[1];
|
|
}
|
|
spi_write_raw(s_line, (size_t)h * 2u);
|
|
}
|
|
cs_high();
|
|
restore_interrupts(irq);
|
|
usb_pump();
|
|
}
|
|
|
|
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);
|
|
/* MISO optional for LCD; kept for future touch on same bus */
|
|
gpio_set_function(PIN_LCD_MISO, 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);
|
|
}
|