//////////////////////////////////////////////////// // TFT_eSPI generic driver functions // //////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// // Global variables //////////////////////////////////////////////////////////////////////////////////////// #if !defined (RP2040_PIO_INTERFACE) // SPI // Select the SPI port and board package to use #ifdef ARDUINO_ARCH_MBED // Arduino RP2040 board package MbedSPI spi = MbedSPI(TFT_MISO, TFT_MOSI, TFT_SCLK); #else // Community RP2040 board package by Earle Philhower //SPIClass& spi = SPI; // will use board package default pins SPIClassRP2040 spi = SPIClassRP2040(SPI_X, TFT_MISO, -1, TFT_SCLK, TFT_MOSI); #endif #else // PIO interface used (8 bit parallel or SPI) #ifdef RP2040_PIO_SPI #if defined (SPI_18BIT_DRIVER) // SPI PIO code for 18 bit colour transmit #include "pio_SPI_18bit.pio.h" #else // SPI PIO code for 16 bit colour transmit #include "pio_SPI.pio.h" #endif #elif defined (TFT_PARALLEL_8_BIT) // SPI PIO code for 8 bit parallel interface (16 bit colour) #include "pio_8bit_parallel.pio.h" #else // must be TFT_PARALLEL_16_BIT // SPI PIO code for 16 bit parallel interface (16 bit colour) #include "pio_16bit_parallel.pio.h" #endif // Board package specific differences #ifdef ARDUINO_ARCH_MBED // Not supported at the moment #error The Arduino RP2040 MBED board package is not supported when PIO is used. Use the community package by Earle Philhower. #endif // Community RP2040 board package by Earle Philhower PIO tft_pio = pio0; // Code will try both pio's to find a free SM int8_t pio_sm = 0; // pioinit will claim a free one // Updated later with the loading offset of the PIO program. uint32_t program_offset = 0; // SM stalled mask uint32_t pull_stall_mask = 0; // SM jump instructions to change SM behaviour uint32_t pio_instr_jmp8 = 0; uint32_t pio_instr_fill = 0; uint32_t pio_instr_addr = 0; // SM "set" instructions to control DC control signal uint32_t pio_instr_set_dc = 0; uint32_t pio_instr_clr_dc = 0; #endif #ifdef RP2040_DMA int32_t dma_tx_channel; dma_channel_config dma_tx_config; #endif //////////////////////////////////////////////////////////////////////////////////////// #if defined (TFT_SDA_READ) && !defined (RP2040_PIO_INTERFACE) //////////////////////////////////////////////////////////////////////////////////////// /*************************************************************************************** ** Function name: tft_Read_8 ** Description: Bit bashed SPI to read bidirectional SDA line ***************************************************************************************/ uint8_t TFT_eSPI::tft_Read_8(void) { uint8_t ret = 0; /* for (uint8_t i = 0; i < 8; i++) { // read results ret <<= 1; SCLK_L; if (digitalRead(TFT_MOSI)) ret |= 1; SCLK_H; } */ ret = spi.transfer(0x00); return ret; } /*************************************************************************************** ** Function name: beginSDA ** Description: Detach SPI from pin to permit software SPI ***************************************************************************************/ void TFT_eSPI::begin_SDA_Read(void) { // Release configured SPI port for SDA read spi.end(); } /*************************************************************************************** ** Function name: endSDA ** Description: Attach SPI pins after software SPI ***************************************************************************************/ void TFT_eSPI::end_SDA_Read(void) { // Configure SPI port ready for next TFT access spi.begin(); } //////////////////////////////////////////////////////////////////////////////////////// #endif // #if defined (TFT_SDA_READ) //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// #if defined (RP2040_PIO_INTERFACE) //////////////////////////////////////////////////////////////////////////////////////// #ifdef RP2040_PIO_SPI void pioinit(uint32_t clock_freq) { // Find a free SM on one of the PIO's tft_pio = pio0; /* pio_sm = pio_claim_unused_sm(tft_pio, false); // false means don't panic // Try pio1 if SM not found if (pio_sm < 0) { tft_pio = pio1; pio_sm = pio_claim_unused_sm(tft_pio, true); // panic this time if no SM is free } */ // Find enough free space on one of the PIO's tft_pio = pio0; if (!pio_can_add_program(tft_pio, &tft_io_program)) { tft_pio = pio1; if (!pio_can_add_program(tft_pio, &tft_io_program)) { Serial.println("No room for PIO program!"); return; } } pio_sm = pio_claim_unused_sm(tft_pio, false); // Load the PIO program program_offset = pio_add_program(tft_pio, &tft_io_program); // Associate pins with the PIO pio_gpio_init(tft_pio, TFT_DC); pio_gpio_init(tft_pio, TFT_SCLK); pio_gpio_init(tft_pio, TFT_MOSI); // Configure the pins to be outputs pio_sm_set_consecutive_pindirs(tft_pio, pio_sm, TFT_DC, 1, true); pio_sm_set_consecutive_pindirs(tft_pio, pio_sm, TFT_SCLK, 1, true); pio_sm_set_consecutive_pindirs(tft_pio, pio_sm, TFT_MOSI, 1, true); // Configure the state machine pio_sm_config c = tft_io_program_get_default_config(program_offset); sm_config_set_set_pins(&c, TFT_DC, 1); // Define the single side-set pin sm_config_set_sideset_pins(&c, TFT_SCLK); // Define the pin used for data output sm_config_set_out_pins(&c, TFT_MOSI, 1); // Set clock divider, frequency is set up to 2% faster than specified, or next division down uint16_t clock_div = 0.98 + clock_get_hz(clk_sys) / (clock_freq * 2.0); // 2 cycles per bit sm_config_set_clkdiv(&c, clock_div); // Make a single 8 words FIFO from the 4 words TX and RX FIFOs sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); // The OSR register shifts to the left, sm designed to send MS byte of a colour first, autopull off sm_config_set_out_shift(&c, false, false, 0); // Now load the configuration pio_sm_init(tft_pio, pio_sm, program_offset + tft_io_offset_start_tx, &c); // Start the state machine. pio_sm_set_enabled(tft_pio, pio_sm, true); // Create the pull stall bit mask pull_stall_mask = 1u << (PIO_FDEBUG_TXSTALL_LSB + pio_sm); // Create the assembler instruction for the jump to byte send routine pio_instr_jmp8 = pio_encode_jmp(program_offset + tft_io_offset_start_8); pio_instr_fill = pio_encode_jmp(program_offset + tft_io_offset_block_fill); pio_instr_addr = pio_encode_jmp(program_offset + tft_io_offset_set_addr_window); pio_instr_set_dc = pio_encode_set((pio_src_dest)0, 1); pio_instr_clr_dc = pio_encode_set((pio_src_dest)0, 0); } #else // 8 or 16 bit parallel void pioinit(uint16_t clock_div, uint16_t fract_div) { // Find a free SM on one of the PIO's tft_pio = pio0; pio_sm = pio_claim_unused_sm(tft_pio, false); // false means don't panic // Try pio1 if SM not found if (pio_sm < 0) { tft_pio = pio1; pio_sm = pio_claim_unused_sm(tft_pio, true); // panic this time if no SM is free } /* // Find enough free space on one of the PIO's tft_pio = pio0; if (!pio_can_add_program(tft_pio, &tft_io_program) { tft_pio = pio1; if (!pio_can_add_program(tft_pio, &tft_io_program) { Serial.println("No room for PIO program!"); while(1) delay(100); return; } } */ #if defined (TFT_PARALLEL_8_BIT) uint8_t bits = 8; #else // must be TFT_PARALLEL_16_BIT uint8_t bits = 16; #endif // Load the PIO program program_offset = pio_add_program(tft_pio, &tft_io_program); // Associate pins with the PIO pio_gpio_init(tft_pio, TFT_DC); pio_gpio_init(tft_pio, TFT_WR); for (int i = 0; i < bits; i++) { pio_gpio_init(tft_pio, TFT_D0 + i); } // Configure the pins to be outputs pio_sm_set_consecutive_pindirs(tft_pio, pio_sm, TFT_DC, 1, true); pio_sm_set_consecutive_pindirs(tft_pio, pio_sm, TFT_WR, 1, true); pio_sm_set_consecutive_pindirs(tft_pio, pio_sm, TFT_D0, bits, true); // Configure the state machine pio_sm_config c = tft_io_program_get_default_config(program_offset); // Define the set pin sm_config_set_set_pins(&c, TFT_DC, 1); // Define the single side-set pin sm_config_set_sideset_pins(&c, TFT_WR); // Define the consecutive pins that are used for data output sm_config_set_out_pins(&c, TFT_D0, bits); // Set clock divider and fractional divider sm_config_set_clkdiv_int_frac(&c, clock_div, fract_div); // Make a single 8 words FIFO from the 4 words TX and RX FIFOs sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); // The OSR register shifts to the left, sm designed to send MS byte of a colour first sm_config_set_out_shift(&c, false, false, 0); // Now load the configuration pio_sm_init(tft_pio, pio_sm, program_offset + tft_io_offset_start_tx, &c); // Start the state machine. pio_sm_set_enabled(tft_pio, pio_sm, true); // Create the pull stall bit mask pull_stall_mask = 1u << (PIO_FDEBUG_TXSTALL_LSB + pio_sm); // Create the instructions for the jumps to send routines pio_instr_jmp8 = pio_encode_jmp(program_offset + tft_io_offset_start_8); pio_instr_fill = pio_encode_jmp(program_offset + tft_io_offset_block_fill); pio_instr_addr = pio_encode_jmp(program_offset + tft_io_offset_set_addr_window); // Create the instructions to set and clear the DC signal pio_instr_set_dc = pio_encode_set((pio_src_dest)0, 1); pio_instr_clr_dc = pio_encode_set((pio_src_dest)0, 0); } #endif /*************************************************************************************** ** Function name: pushBlock - for generic processor and parallel display ** Description: Write a block of pixels of the same colour ***************************************************************************************/ #ifdef RP2040_PIO_PUSHBLOCK // PIO handles pixel block fill writes void TFT_eSPI::pushBlock(uint16_t color, uint32_t len) { #if defined (SPI_18BIT_DRIVER) uint32_t col = ((color & 0xF800)<<8) | ((color & 0x07E0)<<5) | ((color & 0x001F)<<3); if (len) { WAIT_FOR_STALL; tft_pio->sm[pio_sm].instr = pio_instr_fill; TX_FIFO = col; TX_FIFO = --len; // Decrement first as PIO sends n+1 } #else if (len) { WAIT_FOR_STALL; tft_pio->sm[pio_sm].instr = pio_instr_fill; TX_FIFO = color; TX_FIFO = --len; // Decrement first as PIO sends n+1 } #endif } #else void TFT_eSPI::pushBlock(uint16_t color, uint32_t len){ while (len > 4) { // 5 seems to be the optimum for maximum transfer rate WAIT_FOR_FIFO_FREE(5); TX_FIFO = color; TX_FIFO = color; TX_FIFO = color; TX_FIFO = color; TX_FIFO = color; len -= 5; } if (len) { // There could be a maximum of 4 words left to send WAIT_FOR_FIFO_FREE(4); while (len--) TX_FIFO = color; } } #endif /*************************************************************************************** ** Function name: pushPixels - for generic processor and parallel display ** Description: Write a sequence of pixels ***************************************************************************************/ void TFT_eSPI::pushPixels(const void* data_in, uint32_t len){ #if defined (SPI_18BIT_DRIVER) uint16_t *data = (uint16_t*)data_in; if (_swapBytes) { while ( len-- ) { uint32_t col = *data++; tft_Write_16(col); } } else { while ( len-- ) { uint32_t col = *data++; tft_Write_16S(col); } } #else const uint16_t *data = (uint16_t*)data_in; // PIO sends MS byte first, so bytes are already swapped on transmit if(_swapBytes) { while (len > 4) { WAIT_FOR_FIFO_FREE(5); TX_FIFO = data[0]; TX_FIFO = data[1]; TX_FIFO = data[2]; TX_FIFO = data[3]; TX_FIFO = data[4]; data += 5; len -= 5; } if (len) { WAIT_FOR_FIFO_FREE(4); while(len--) TX_FIFO = *data++; } } else { while (len > 4) { WAIT_FOR_FIFO_FREE(5); TX_FIFO = data[0] << 8 | data[0] >> 8; TX_FIFO = data[1] << 8 | data[1] >> 8; TX_FIFO = data[2] << 8 | data[2] >> 8; TX_FIFO = data[3] << 8 | data[3] >> 8; TX_FIFO = data[4] << 8 | data[4] >> 8; data += 5; len -= 5; } if (len) { WAIT_FOR_FIFO_FREE(4); while(len--) { TX_FIFO = *data << 8 | *data >> 8; data++; } } } #endif } /*************************************************************************************** ** Function name: GPIO direction control - supports class functions ** Description: Set parallel bus to INPUT or OUTPUT ***************************************************************************************/ void TFT_eSPI::busDir(uint32_t mask, uint8_t mode) { // Avoid warnings mask = mask; mode = mode; /* // mask is unused for generic processor // Arduino native functions suited well to a generic driver pinMode(TFT_D0, mode); pinMode(TFT_D1, mode); pinMode(TFT_D2, mode); pinMode(TFT_D3, mode); pinMode(TFT_D4, mode); pinMode(TFT_D5, mode); pinMode(TFT_D6, mode); pinMode(TFT_D7, mode); */ } /*************************************************************************************** ** Function name: GPIO direction control - supports class functions ** Description: Faster GPIO pin input/output switch ***************************************************************************************/ void TFT_eSPI::gpioMode(uint8_t gpio, uint8_t mode) { // Avoid warnings gpio = gpio; mode = mode; } /*************************************************************************************** ** Function name: read byte - supports class functions ** Description: Read a byte - parallel bus only - not supported yet ***************************************************************************************/ uint8_t TFT_eSPI::readByte(void) { uint8_t b = 0; /* busDir(0, INPUT); digitalWrite(TFT_RD, LOW); b |= digitalRead(TFT_D0) << 0; b |= digitalRead(TFT_D1) << 1; b |= digitalRead(TFT_D2) << 2; b |= digitalRead(TFT_D3) << 3; b |= digitalRead(TFT_D4) << 4; b |= digitalRead(TFT_D5) << 5; b |= digitalRead(TFT_D6) << 6; b |= digitalRead(TFT_D7) << 7; digitalWrite(TFT_RD, HIGH); busDir(0, OUTPUT); */ return b; } //////////////////////////////////////////////////////////////////////////////////////// #elif defined (RPI_WRITE_STROBE) // For RPi TFT with write strobe //////////////////////////////////////////////////////////////////////////////////////// /*************************************************************************************** ** Function name: pushBlock - for ESP32 or RP2040 RPi TFT ** Description: Write a block of pixels of the same colour ***************************************************************************************/ void TFT_eSPI::pushBlock(uint16_t color, uint32_t len){ if(len) { tft_Write_16(color); len--; } while(len--) {WR_L; WR_H;} } /*************************************************************************************** ** Function name: pushPixels - for ESP32 or RP2040 RPi TFT ** Description: Write a sequence of pixels ***************************************************************************************/ void TFT_eSPI::pushPixels(const void* data_in, uint32_t len) { uint16_t *data = (uint16_t*)data_in; if (_swapBytes) while ( len-- ) {tft_Write_16S(*data); data++;} else while ( len-- ) {tft_Write_16(*data); data++;} } //////////////////////////////////////////////////////////////////////////////////////// #elif defined (SPI_18BIT_DRIVER) // SPI 18 bit colour //////////////////////////////////////////////////////////////////////////////////////// /*************************************************************************************** ** Function name: pushBlock - for RP2040 and 3 byte RGB display ** Description: Write a block of pixels of the same colour ***************************************************************************************/ void TFT_eSPI::pushBlock(uint16_t color, uint32_t len) { uint16_t r = (color & 0xF800)>>8; uint16_t g = (color & 0x07E0)>>3; uint16_t b = (color & 0x001F)<<3; // If more than 32 pixels then change to 16 bit transfers with concatenated pixels if (len > 32) { uint32_t rg = r<<8 | g; uint32_t br = b<<8 | r; uint32_t gb = g<<8 | b; // Must wait before changing to 16 bit while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {}; hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS); while ( len > 1 ) { while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = rg; while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = br; while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = gb; len -= 2; } // Must wait before changing back to 8 bit while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {}; hw_write_masked(&spi_get_hw(SPI_X)->cr0, (8 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS); } // Mop up the remaining pixels while ( len-- ) {tft_Write_8N(r);tft_Write_8N(g);tft_Write_8N(b);} } /*************************************************************************************** ** Function name: pushPixels - for RP2040 and 3 byte RGB display ** Description: Write a sequence of pixels ***************************************************************************************/ void TFT_eSPI::pushPixels(const void* data_in, uint32_t len){ uint16_t *data = (uint16_t*)data_in; if (_swapBytes) { while ( len-- ) { uint32_t col = *data++; tft_Write_16(col); } } else { while ( len-- ) { uint32_t col = *data++; tft_Write_16S(col); } } } //////////////////////////////////////////////////////////////////////////////////////// #else // Standard SPI 16 bit colour TFT //////////////////////////////////////////////////////////////////////////////////////// /*************************************************************************************** ** Function name: pushBlock - for RP2040 ** Description: Write a block of pixels of the same colour ***************************************************************************************/ void TFT_eSPI::pushBlock(uint16_t color, uint32_t len){ while(len--) { while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = (uint32_t)color; } } /*************************************************************************************** ** Function name: pushPixels - for RP2040 ** Description: Write a sequence of pixels ***************************************************************************************/ void TFT_eSPI::pushPixels(const void* data_in, uint32_t len){ uint16_t *data = (uint16_t*)data_in; if (_swapBytes) { while(len--) { while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = (uint32_t)(*data++); } } else { while(len--) { uint16_t color = *data++; color = color >> 8 | color << 8; while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = (uint32_t)color; } } } //////////////////////////////////////////////////////////////////////////////////////// #endif // End of display interface specific functions //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// #ifdef RP2040_DMA // DMA functions for 16 bit SPI and 8/16 bit parallel displays //////////////////////////////////////////////////////////////////////////////////////// /* These are created in header file: uint32_t dma_tx_channel; dma_channel_config dma_tx_config; */ /*************************************************************************************** ** Function name: dmaBusy ** Description: Check if DMA is busy ***************************************************************************************/ bool TFT_eSPI::dmaBusy(void) { if (!DMA_Enabled) return false; if (dma_channel_is_busy(dma_tx_channel)) return true; #if !defined (RP2040_PIO_INTERFACE) // For SPI must also wait for FIFO to flush and reset format while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {}; hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS); #endif return false; } /*************************************************************************************** ** Function name: dmaWait ** Description: Wait until DMA is over (blocking!) ***************************************************************************************/ void TFT_eSPI::dmaWait(void) { while (dma_channel_is_busy(dma_tx_channel)); #if !defined (RP2040_PIO_INTERFACE) // For SPI must also wait for FIFO to flush and reset format while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {}; hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS); #endif } /*************************************************************************************** ** Function name: pushPixelsDMA ** Description: Push pixels to TFT ***************************************************************************************/ void TFT_eSPI::pushPixelsDMA(uint16_t* image, uint32_t len) { if ((len == 0) || (!DMA_Enabled)) return; dmaWait(); channel_config_set_bswap(&dma_tx_config, !_swapBytes); #if !defined (RP2040_PIO_INTERFACE) dma_channel_configure(dma_tx_channel, &dma_tx_config, &spi_get_hw(SPI_X)->dr, (uint16_t*)image, len, true); #else dma_channel_configure(dma_tx_channel, &dma_tx_config, &tft_pio->txf[pio_sm], (uint16_t*)image, len, true); #endif } /*************************************************************************************** ** Function name: pushImageDMA ** Description: Push image to a window ***************************************************************************************/ // This will clip to the viewport void TFT_eSPI::pushImageDMA(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t* image, uint16_t* buffer) { if ((x >= _vpW) || (y >= _vpH) || (!DMA_Enabled)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } if ((x + dw) > _vpW ) dw = _vpW - x; if ((y + dh) > _vpH ) dh = _vpH - y; if (dw < 1 || dh < 1) return; uint32_t len = dw*dh; if (buffer == nullptr) { buffer = image; dmaWait(); } // If image is clipped, copy pixels into a contiguous block if ( (dw != w) || (dh != h) ) { for (int32_t yb = 0; yb < dh; yb++) { memmove((uint8_t*) (buffer + yb * dw), (uint8_t*) (image + dx + w * (yb + dy)), dw << 1); } } // else, if a buffer pointer has been provided copy whole image to the buffer else if (buffer != image || _swapBytes) { memcpy(buffer, image, len*2); } dmaWait(); // In case we did not wait earlier setAddrWindow(x, y, dw, dh); channel_config_set_bswap(&dma_tx_config, !_swapBytes); #if !defined (RP2040_PIO_INTERFACE) dma_channel_configure(dma_tx_channel, &dma_tx_config, &spi_get_hw(SPI_X)->dr, (uint16_t*)buffer, len, true); #else dma_channel_configure(dma_tx_channel, &dma_tx_config, &tft_pio->txf[pio_sm], (uint16_t*)buffer, len, true); #endif } /*************************************************************************************** ** Function name: initDMA ** Description: Initialise the DMA engine - returns true if init OK ***************************************************************************************/ bool TFT_eSPI::initDMA(bool ctrl_cs) { if (DMA_Enabled) return false; ctrl_cs = ctrl_cs; // stop unused parameter warning dma_tx_channel = dma_claim_unused_channel(false); if (dma_tx_channel < 0) return false; dma_tx_config = dma_channel_get_default_config(dma_tx_channel); channel_config_set_transfer_data_size(&dma_tx_config, DMA_SIZE_16); #if !defined (RP2040_PIO_INTERFACE) channel_config_set_dreq(&dma_tx_config, spi_get_index(SPI_X) ? DREQ_SPI1_TX : DREQ_SPI0_TX); #else channel_config_set_dreq(&dma_tx_config, pio_get_dreq(tft_pio, pio_sm, true)); #endif DMA_Enabled = true; return true; } /*************************************************************************************** ** Function name: deInitDMA ** Description: Disconnect the DMA engine from SPI ***************************************************************************************/ void TFT_eSPI::deInitDMA(void) { if (!DMA_Enabled) return; dma_channel_unclaim(dma_tx_channel); DMA_Enabled = false; } //////////////////////////////////////////////////////////////////////////////////////// #endif // End of DMA FUNCTIONS ////////////////////////////////////////////////////////////////////////////////////////