Files
AudioMixer/ili9341.c
T
2026-09-07 16:53:18 +03:00

379 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);
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);
}