0
\$\begingroup\$

I have a 2.6 in 320x320 display that I would like to drive with the Raspberry Pi Pico 2 but I am having some issues with my code that I am not sure how to fix. I am not quite familiar with this scanvideo library and there is not much documentation on it online. I will attach material such as the display pinout, my GPIO, my code, the display timings, and the SPI commands that are sent to initialize the display which I received from the manufacturer and also verified with the TFT ESPI library. I really wanted to use the TFT ESPI library but they use pins like WR that I don't have. All I have on the display is a SPI interface for setting the initialization and the DPI pins.

I tried using ChatGPT for some help but that didn't help and it really shows how much it doesn't know about embedded code.

Please note that for some of my GPIO I used an IO expander but I re-soldered those lines to the MCU momentarily just to make it easier to debug and write code for.

Main Code:

/* * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ #include <stdio.h> #include "hardware/i2c.h" #include "hardware/spi.h" #include <pico/bootrom.h> #include "hardware/pwm.h" #include "hardware/vreg.h" #define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 0u #define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 5u #define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 11u #define PICO_SCANVIDEO_DPI_PIXEL_RCOUNT 5 #define PICO_SCANVIDEO_DPI_PIXEL_GCOUNT 6 #define PICO_SCANVIDEO_DPI_PIXEL_BCOUNT 5 #define video_pio pio0 #define PICO_SCANVIDEO_ENABLE_CLOCK_PIN 1 #define PICO_SCANVIDEO_ENABLE_DEN_PIN 1 #define PICO_SCANVIDEO_COLOR_PIN_BASE 25 #define PICO_SCANVIDEO_COLOR_PIN_COUNT 16 #define PICO_SCANVIDEO_SYNC_PIN_BASE (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT) //41 // #include "pico.h" #include "pico/scanvideo.h" #include "pico/scanvideo/scanvideo_base.h" #include "pico/scanvideo/composable_scanline.h" #include "pico/multicore.h" #include "pico/sync.h" #include "pico/stdlib.h" #if PICO_ON_DEVICE #include "hardware/clocks.h" #endif #include "gpio.h" #include "display_config.h" // #define DUAL_CORE_RENDER // Custom timings for 320x320 TFT display // const scanvideo_timing_t tft_timing_320x320_60 = { // .clock_freq = 10000000, // 10 MHz DOTCLK frequency // .h_active = 320, // Horizontal active pixels // .v_active = 320, // Vertical active lines // .h_front_porch = 3, // Horizontal Front Porch // .h_pulse = 3, // Horizontal Sync Pulse // .h_total = 329, // Total Horizontal Time = HFP + HACT + HBP // .h_sync_polarity = 0, // .v_front_porch = 2, // Vertical Front Porch // .v_pulse = 1, // Vertical Sync Pulse // .v_total = 323, // Total Vertical Time = VFP + VACT + VBP // .v_sync_polarity = 0, // .enable_clock = 1, // .clock_polarity = 0, // .enable_den = 1 // }; //chatgpt const scanvideo_timing_t tft_timing_320x320_60 = { //pclk multiple of 2 in reference to system clock 150mhz .clock_freq = 18750000, // ↓ now 12 MHz (within your panel’s 20 MHz max) .h_active = 320, .v_active = 320, .h_front_porch = 10, .h_pulse = 3, .h_total = 320 + 10 + 3 + 10, // = 343 (adjust if your panel datasheet says otherwise) .h_sync_polarity = 0, .v_front_porch = 5, .v_pulse = 1, .v_total = 320 + 5 + 1 + 5, // = 331 .v_sync_polarity = 0, .enable_clock = 1, .clock_polarity = 0, .enable_den = 1 }; // Custom mode for 320x320 TFT LCD // const scanvideo_mode_t tft_mode_320x320_60 = { // .default_timing = &tft_timing_320x320_60, // .pio_program = &video_24mhz_composable, // .width = 320, // .height = 320, // .xscale = 1, // .yscale = 1, // .yscale_denominator = 1 // }; //chatgpt extern const struct scanvideo_pio_program video_24mhz_composable; // ← swap in 12 MHz const scanvideo_mode_t tft_mode_320x320_60 = { .default_timing = &tft_timing_320x320_60, .pio_program = &video_24mhz_composable, .width = 320, .height = 320, .xscale = 1, .yscale = 1, .yscale_denominator = 1 }; // const scanvideo_timing_t lcd_timing = // { // .clock_freq = 10000000, // .h_active = 320, // .v_active = 320, // .h_front_porch = 16, // .h_pulse = 64, // .h_total = 800, // .h_sync_polarity = 1, // .v_front_porch = 1, // .v_pulse = 2, // .v_total = 500, // .v_sync_polarity = 1, // .enable_clock = 1, // .clock_polarity = 0, // .enable_den = 1 // }; // const scanvideo_mode_t vga_mode_320x320_60 = // { // .default_timing = &lcd_timing, // .pio_program = &video_24mhz_composable, // .width = 320, // .height = 320, // .xscale = 1, // .yscale = 1, // }; // Display dimensions #define WIDTH 320 #define HEIGHT 320 // Function prototypes void setup_gpio(); void i2c_setup() { // MARK: - I2C INIT i2c_init(IOX_I2C_PORT, 400 * 1000); // 400 kHz gpio_set_function(GPIO_I2C_SDA, GPIO_FUNC_I2C); gpio_set_function(GPIO_I2C_SCL, GPIO_FUNC_I2C); gpio_pull_up(GPIO_I2C_SDA); gpio_pull_up(GPIO_I2C_SCL); } void my_setup() { stdio_init_all(); setup_gpio(); gpio_set_function(GPIO_DPI_DEN, GPIO_FUNC_SIO); gpio_set_dir(GPIO_DPI_DEN, true); gpio_put(GPIO_DPI_DEN, 1); gpio_set_function(IOX_IPS_nCS, GPIO_FUNC_SIO); gpio_set_dir(IOX_IPS_nCS, true); gpio_set_function(IOX_LCD_RST, GPIO_FUNC_SIO); gpio_set_dir(IOX_LCD_RST, true); // i2c_setup(); sleep_ms(500); set_up_select(); // config_iox_ports(); lcd_power_on_reset(); sleep_ms(500); init_spi_lcd(); sleep_ms(500); lcd_config(); sleep_ms(500); sleep_ms(1000); printf("SET UP"); } void setup_gpio() { // Initialize all GPIO pins for DPI config_led(GPIO_LCD_LED, 64, false); // Give PIO ownership of all DPI pins 25..44: for(int pin = GPIO_DPI_B0; pin <= GPIO_DPI_DEN; pin++) { gpio_init(pin); gpio_set_dir(pin, GPIO_OUT); gpio_set_function(pin, GPIO_FUNC_PIO0); printf("pin %d → PIO0\n", pin); } } #define VGA_MODE tft_mode_320x320_60 extern const struct scanvideo_pio_program video_24mhz_composable; // to make sure only one core updates the state when the frame number changes // todo note we should actually make sure here that the other core isn't still rendering (i.e. all must arrive before either can proceed - a la barrier) static struct mutex frame_logic_mutex; static void frame_update_logic(); static void render_scanline(struct scanvideo_scanline_buffer *dest, int core); // "Worker thread" for each core void render_loop() { static uint32_t last_frame_num = 0; int core_num = get_core_num(); printf("Rendering on core %d\n", core_num); while (true) { printf("Printing"); struct scanvideo_scanline_buffer *scanline_buffer = scanvideo_begin_scanline_generation(true); mutex_enter_blocking(&frame_logic_mutex); uint32_t frame_num = scanvideo_frame_number(scanline_buffer->scanline_id); // Note that with multiple cores we may have got here not for the first // scanline, however one of the cores will do this logic first before either // does the actual generation if (frame_num != last_frame_num) { last_frame_num = frame_num; frame_update_logic(); } mutex_exit(&frame_logic_mutex); render_scanline(scanline_buffer, core_num); // Release the rendered buffer into the wild scanvideo_end_scanline_generation(scanline_buffer); } } struct semaphore video_setup_complete; void core1_func() { sem_acquire_blocking(&video_setup_complete); render_loop(); } int vga_main(void) { mutex_init(&frame_logic_mutex); sem_init(&video_setup_complete, 0, 1); // Core 1 will wait for us to finish video setup, and then start rendering #ifdef DUAL_CORE_RENDER multicore_launch_core1(core1_func); #endif scanvideo_setup(&VGA_MODE); scanvideo_timing_enable(true); sem_release(&video_setup_complete); render_loop(); return 0; } void frame_update_logic() { } #define MIN_COLOR_RUN 3 int32_t single_color_scanline(uint32_t *buf, size_t buf_length, int width, uint32_t color16) { assert(buf_length >= 2); assert(width >= MIN_COLOR_RUN); // | jmp color_run | color | count-3 | buf[0] = buf[0] = COMPOSABLE_COLOR_RUN | (color16 << 16); buf[1] = (width - MIN_COLOR_RUN) | (COMPOSABLE_RAW_1P << 16); // note we must end with a black pixel buf[2] = 0 | (COMPOSABLE_EOL_ALIGN << 16); return 3; } // void render_scanline(struct scanvideo_scanline_buffer *dest, int core) { // uint32_t *buf = dest->data; // size_t buf_length = dest->data_max; // int l = scanvideo_scanline_number(dest->scanline_id); // uint16_t bgcolour = (uint16_t) l << 2; // dest->data_used = single_color_scanline(buf, buf_length, VGA_MODE.width, bgcolour); // dest->status = SCANLINE_OK; // } // void render_scanline(struct scanvideo_scanline_buffer *dest, int core) { // uint32_t *buf = dest->data; // size_t buf_length = dest->data_max; // int y = scanvideo_scanline_number(dest->scanline_id); // // Checkerboard configuration // const int tile_size = 40; // Each tile is 40x40 pixels // int row_toggle = (y / tile_size) % 2; // int used = 0; // // Initialize scanline with alternating color tiles // for (int x = 0; x < VGA_MODE.width; ) { // int col_toggle = ((x / tile_size) % 2) ^ row_toggle; // uint16_t color = col_toggle ? 0xFFFF : 0x0000; // White and black // // Determine run length until next tile boundary or end of scanline // int remaining_in_tile = tile_size - (x % tile_size); // int run_length = remaining_in_tile; // if (x + run_length > VGA_MODE.width) run_length = VGA_MODE.width - x; // if (run_length < MIN_COLOR_RUN) run_length = MIN_COLOR_RUN; // // Emit color run composable // if (used + 2 >= buf_length) break; // Safety check // buf[used++] = COMPOSABLE_COLOR_RUN | (color << 16); // buf[used++] = (run_length - MIN_COLOR_RUN) | (COMPOSABLE_RAW_1P << 16); // x += run_length; // } // // Add EOL // if (used + 1 < buf_length) { // buf[used++] = 0 | (COMPOSABLE_EOL_ALIGN << 16); // } // dest->data_used = used; // dest->status = SCANLINE_OK; // } void render_scanline(struct scanvideo_scanline_buffer *dest, int core) { uint32_t *buf = dest->data; size_t buf_length = dest->data_max; uint16_t color = 0xF800; // Red (RGB565) buf[0] = COMPOSABLE_COLOR_RUN | (color << 16); buf[1] = (VGA_MODE.width - MIN_COLOR_RUN) | (COMPOSABLE_RAW_1P << 16); buf[2] = 0 | (COMPOSABLE_EOL_ALIGN << 16); dest->data_used = 3; dest->status = SCANLINE_OK; } int main(void) { my_setup(); #if PICO_SCANVIDEO_48MHZ set_sys_clock_48mhz(); #endif // Re init uart now that clk_peri has changed setup_default_uart(); sleep_ms(4000); return vga_main(); } 

GPIO:

// DPI #define GPIO_DPI_B0 25 #define GPIO_DPI_B1 26 #define GPIO_DPI_B2 27 #define GPIO_DPI_B3 28 #define GPIO_DPI_B4 29 #define GPIO_DPI_G0 30 #define GPIO_DPI_G1 31 #define GPIO_DPI_G2 32 #define GPIO_DPI_G3 33 #define GPIO_DPI_G4 34 #define GPIO_DPI_G5 35 #define GPIO_DPI_R0 36 #define GPIO_DPI_R1 37 #define GPIO_DPI_R2 38 #define GPIO_DPI_R3 39 #define GPIO_DPI_R4 40 #define GPIO_DPI_HSYNC 41 #define GPIO_DPI_VSYNC 42 #define GPIO_DPI_PCLK 43 #define GPIO_DPI_DEN 44 #define GPIO_LCD_SCK 10 #define GPIO_LCD_MOSI 11 #define LCD_SPI spi1 #define GPIO_LCD_LED 24 #define IOX_IPS_nCS 46 #define IOX_LCD_RST 45 

Display config code:

// Delay between some initialisation commands #define TFT_INIT_DELAY 0x80 // Not used unless commandlist invoked // Generic commands used by TFT_eSPI.cpp #define TFT_NOP 0x00 #define TFT_SWRST 0x01 #define TFT_SLPIN 0x10 #define TFT_SLPOUT 0x11 #define TFT_INVOFF 0x20 #define TFT_INVON 0x21 #define TFT_DISPOFF 0x28 #define TFT_DISPON 0x29 #define TFT_CASET 0x2A #define TFT_PASET 0x2B #define TFT_RAMWR 0x2C #define TFT_RAMRD 0x2E #define TFT_MADCTL 0x36 #define TFT_MAD_MY 0x80 #define TFT_MAD_MX 0x40 #define TFT_MAD_MV 0x20 #define TFT_MAD_ML 0x10 #define TFT_MAD_RGB 0x00 #define TFT_MAD_BGR 0x08 #define TFT_MAD_MH 0x04 #define TFT_MAD_SS 0x02 #define TFT_MAD_GS 0x01 #define TFT_IDXRD 0x00 // ILI9341 only, indexed control register read #define TFT_PARALLEL_16_BIT // Function to initialize the SPI bus void init_spi_lcd() { // Set up GPIO functions for SPI gpio_set_function(GPIO_LCD_SCK, GPIO_FUNC_SPI); gpio_set_function(GPIO_LCD_MOSI, GPIO_FUNC_SPI); // Configure GPIO slew rates for faster signals gpio_set_slew_rate(GPIO_LCD_SCK, GPIO_SLEW_RATE_FAST); gpio_set_slew_rate(GPIO_LCD_MOSI, GPIO_SLEW_RATE_FAST); // Configure the clock for SPI to a high frequency clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, 125 * 1000 * 1000, 125 * 1000 * 1000); // Initialize the SPI interface spi_init(LCD_SPI, 10 * 1000 * 1000); // Set SPI baud rate to 30 MHz // Set SPI format: 8-bit data, CPOL=0, CPHA=0, MSB first spi_set_format(LCD_SPI, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); } void lcd_power_on_reset() { // Turn on the backlight gpio_write(IOX_LCD_RST, 0); // reset low before power on. gpio_write(IOX_IPS_nCS, 0); // gpio_write(IOX_n3V3_MCU_EN, 0); sleep_ms(10); // resetactive low //power on reset with power sleep_ms(4000); // take off reset gpio_write(IOX_LCD_RST, 1); gpio_write(GPIO_LCD_LED, 1); // Backlight ON sleep_ms(10); // b2 to 0 need to be 101 gpio_write(GPIO_DPI_B2, 1); gpio_write(GPIO_DPI_B1, 0); gpio_write(GPIO_DPI_B0, 1); } // // Function to write a single command to the SPI bus // void writecommand(uint8_t command) { // uint8_t dc_bit = 0x00; // Command mode (D/CX bit = 0) // spi_write_blocking(LCD_SPI, &dc_bit, 1); // Send the D/CX bit // spi_write_blocking(LCD_SPI, &command, 1); // Send the command byte // } // // Function to write data to the SPI bus // void writedata(uint8_t data) { // uint8_t dc_bit = 0x01; // Data mode (D/CX bit = 1) // spi_write_blocking(LCD_SPI, &dc_bit, 1); // Send the D/CX bit // spi_write_blocking(LCD_SPI, &data, 1); // Send the data byte // } void writecommand(uint8_t command) { uint8_t buf[2] = { 0x00, command }; // DC=0, then CMD gpio_write(IOX_IPS_nCS, 0); spi_write_blocking(LCD_SPI, buf, 2); gpio_write(IOX_IPS_nCS, 1); } void writedata(uint8_t data) { uint8_t buf[2] = { 0x01, data }; // DC=1, then DATA gpio_write(IOX_IPS_nCS, 0); spi_write_blocking(LCD_SPI, buf, 2); gpio_write(IOX_IPS_nCS, 1); } void lcd_config() { // INIT writecommand(0xE0); // Positive Gamma Control writedata(0x00); writedata(0x03); writedata(0x09); writedata(0x08); writedata(0x16); writedata(0x0A); writedata(0x3F); writedata(0x78); writedata(0x4C); writedata(0x09); writedata(0x0A); writedata(0x08); writedata(0x16); writedata(0x1A); writedata(0x0F); writecommand(0XE1); // Negative Gamma Control writedata(0x00); writedata(0x16); writedata(0x19); writedata(0x03); writedata(0x0F); writedata(0x05); writedata(0x32); writedata(0x45); writedata(0x46); writedata(0x04); writedata(0x0E); writedata(0x0D); writedata(0x35); writedata(0x37); writedata(0x0F); writecommand(0XC0); // Power Control 1 writedata(0x17); writedata(0x15); writecommand(0xC1); // Power Control 2 writedata(0x41); writecommand(0xC5); // VCOM Control writedata(0x00); writedata(0x12); writedata(0x80); writecommand(TFT_MADCTL); // Memory Access Control writedata(0x48); // MX, BGR writecommand(0x3A); // Pixel Interface Format #if defined (TFT_PARALLEL_8_BIT) || defined (TFT_PARALLEL_16_BIT) || defined (RPI_DISPLAY_TYPE) writedata(0x55); // 16-bit colour for parallel #else writedata(0x66); // 18-bit colour for SPI #endif //CHATGPT suggestion // writecommand(0xB0); // Interface Mode Control // writedata(0b10000000);//writedata(0x00); // Sets the 3 wire spi and polarities of vhsync pclk den // writecommand(0xB0); // Interface Mode Control // writedata(0b10000000); // //chatgpt // writecommand(0xB3); // writedata(0x02); // Enable DPI interface writecommand(0xB0); // Interface Mode Control writedata(0b10000000); // Bit7 = 1 → DPI, 3-wire SPI off, etc. writecommand(0xB3); // Interface Mode Setting writedata(0x02); // ??? (0x02 is typically “Enable DPI,” but some modules need 0x00 or 0x03) // writecommand(0xB1); // Frame Rate Control writedata(0xA0); // 60fps writecommand(0xB4); // Display Inversion Control writedata(0x02); writecommand(0xB6); // Display Function Control writedata(0b01110000); // writedata(0x02); // Sets the RCM, RM, DM writedata(0x02); // dont care writedata(0x3B); // dont care writecommand(0xB7); // Entry Mode Set writedata(0xC6); writecommand(0xF7); // Adjust Control 3 writedata(0xA9); writedata(0x51); writedata(0x2C); writedata(0x82); writecommand(TFT_SLPOUT); //Exit Sleep sleep_ms(120); writecommand(TFT_DISPON); //Display on sleep_ms(25); // End of ILI9488 display configuration } 

Manufacturer set up:

SPI_WriteComm(0XC0);SPI_WriteData(0x14);SPI_WriteData(0x14); SPI_WriteComm(0XC1);SPI_WriteData(0x66 ); //VGH = 4*VCI VGL = -4*VCI SPI_WriteComm(0XC5);SPI_WriteData(0x00);SPI_WriteData(0x43);SPI_WriteData(0x80 ); // SPI_WriteComm(0XB0);SPI_WriteData(0x00); //RGB SPI_WriteComm(0XB1);SPI_WriteData(0xA0); SPI_WriteComm(0XB4);SPI_WriteData(0x02); SPI_WriteComm(0XB6);SPI_WriteData(0x32);SPI_WriteData(0x02); //RGB SPI_WriteComm(0X36);SPI_WriteData(0x48); SPI_WriteComm(0X3A);SPI_WriteData(0x55); //55 66 SPI_WriteComm(0X21);SPI_WriteData(0x00); //IPS SPI_WriteComm(0XE9);SPI_WriteData(0x00); SPI_WriteComm(0XF7);SPI_WriteData(0xA9);SPI_WriteData(0x51);SPI_WriteData(0x2C);SPI_WriteData(0x82); SPI_WriteComm(0xE0);SPI_WriteData(0x00);SPI_WriteData(0x07);SPI_WriteData(0x0C);SPI_WriteData(0x03);SPI_WriteData(0x10);SPI_WriteData(0x06);SPI_WriteData(0x35);SPI_WriteData(0x37);SPI_WriteData(0x4C);SPI_WriteData(0x01);SPI_WriteData(0x0B);SPI_WriteData(0x08);SPI_WriteData(0x2E);SPI_WriteData(0x34);SPI_WriteData(0x0F); SPI_WriteComm(0xE1);SPI_WriteData(0x00);SPI_WriteData(0x0E);SPI_WriteData(0x14);SPI_WriteData(0x04);SPI_WriteData(0x12);SPI_WriteData(0x06);SPI_WriteData(0x37);SPI_WriteData(0x33);SPI_WriteData(0x4A);SPI_WriteData(0x06);SPI_WriteData(0x0F);SPI_WriteData(0x0C);SPI_WriteData(0x2E);SPI_WriteData(0x31);SPI_WriteData(0x0F); SPI_WriteComm(0X11); Delay(120); SPI_WriteComm(0X29); Delay(120); SPI_WriteComm(0X2C); 

I appreciate any help as this issue has been troubling me for a while and I'm not so experienced with the scanvideo library and I'd really love to use this display.

ILI9488 Datasheet: https://www.hpinfotech.ro/ILI9488.pdf

Display Datasheet: https://picopalgbc.wordpress.com/wp-content/uploads/2025/06/zw-t027boia-00.pdf

Here is the display timings and pinout:

timing

pinout

\$\endgroup\$
3
  • \$\begingroup\$ what is your specific question? ... please add it to your post \$\endgroup\$ Commented Jun 5 at 21:14
  • \$\begingroup\$ Well the LCD datasheet says the driver IC is the ILI9488. I'll attach the datasheet here: picopalgbc.wordpress.com/wp-content/uploads/2025/06/… \$\endgroup\$ Commented Jun 6 at 0:01
  • \$\begingroup\$ @peterk the link to the datasheet does not belong in a comment ... it belongs in the post ... please add it there \$\endgroup\$ Commented Jun 6 at 5:59

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.