TFT Library

This program was an implementation of the PIC32 TFT library for the Raspberry Pi Pico. The TFT display is interfaced using SPI. However, I used PIO to create my own SPI channel running at 31.25MHz to send data to the TFT display. The PIO driver code is pretty similar to the PIO DDS program except for a few modifications which have been addressed in the subsequent sections. The resources for the project include the C SDK User Guide, the RP2040 Datasheet, the ECE 4760 course website and Prof. Hunter's website.

Note: The fundamental difference between my program and the program for PIC32 is that instead of transmitting 16-bit words, I am transmitting 2 8-bit words whenever needed.


The complete code

The code uses a state machine to transmit 8 bits at a time using SPI protocol. At the end of each transaction, the state machine sets an interrupt flag that allows the CPU to register that the transaction is complete. Until the transaction is completed, the CPU is stalled by a while() loop.

TFT_PIO.c

This is the driver file which is used to test the created library.

#include <stdio.h> //The standard C library
#include <stdlib.h> //C stdlib
#include "pico/stdlib.h" //Standard library for Pico
#include <math.h> //The standard math library
#include "hardware/gpio.h" //The hardware GPIO library
#include "pico/time.h" //The pico time library
#include "hardware/irq.h" //The hardware interrupt library
#include "hardware/pwm.h" //The hardware PWM library
#include "hardware/pio.h" //The hardware PIO library
#include "TFTMaster.h" //The TFT Master library

int main(){ //The program running on core 0
    int i, j; //The insex variables
    unsigned short col, count = 0; //Variables to store current colour and count
    stdio_init_all(); //Initialize all of the present standard stdio types that are linked into the binary
    tft_init_hw(); //Initialize the hardware for the TFT
    tft_begin(); //Initialize the TFT
    tft_setRotation(0); //Set TFT rotation
    tft_fillScreen(ILI9340_BLACK); //Fill the entire screen with black colour

    while(1){ //Infinite while loop
        unsigned long begin_time = (unsigned long)(get_absolute_time() / 1000); //Get the start time
        switch(count){ //Based on the current count, switch different colours
            case 0: col = ILI9340_BLUE;
                    break;
            case 1: col = ILI9340_RED;
                    break;
            case 2: col = ILI9340_GREEN;
                    break;
            case 3: col = ILI9340_CYAN;
                    break;
            case 4: col = ILI9340_MAGENTA;
                    break;
            case 5: col = ILI9340_YELLOW;
                    break;
            case 6: col = ILI9340_WHITE;
                    break;
        }
        for(i = 0; i < ILI9340_TFTWIDTH / 4; i++){
            for(j = 0; j < ILI9340_TFTHEIGHT / 4; j++){
                //tft_drawRect(i << 2, j << 2, 4, 4, col); //Simply drawing a rectangle takes 222 ms
                tft_fillRect(i << 2, j << 2, 4, 4, col); //Filling the entire rectangle surprisingly takes 110 ms
            }
        }
        count = (count + 1) % 7; //Increment the count and keep it between 0-6
        unsigned char exTime = ((unsigned long)(get_absolute_time() / 1000) - begin_time); //Calculate the amount of time taken
        printf("%u\n", exTime); //Print the time out
    }
}


SPIPIO.pio

This is the driver code for the PIO state machine and can be downloaded here. The SPI transaction has been explained in detail here.

.program spi_cpha0_cs ;Program name
.side_set 1 ;Set 1 pin for sideset

; Drive SPI
; Pin assignments:
; - SCK is side-set bit 0
; - MOSI is OUT bit 0 (host-to-device)

.wrap_target ;Free 0 cycle unconditional jump
bitloop: ;Bitloop label
public entry_point: ;The entry point for the program
    out pins, 1        side 0x0 [1] ;Output the bit on pin, sideset the clock
    jmp x-- bitloop    side 0x1 [1] ;Jump to bitloop if bit counter still available

    out pins, 1        side 0x0 ;Output the bit on pin, sideset the clock
    mov x, y           side 0x0     ;Reload bit counter from Y
    jmp !osre bitloop  side 0x1 [1] ;Fall-through if TXF empties

    irq 0              side 0x0 [1] ;Set IRQ 0 flag
.wrap

;Helper function

% c-sdk {
#include "hardware/gpio.h" //The hardware GPIO library
static inline void pio_spi_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, int clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi){ //The PIO SPI initialize functions
    pio_sm_config c = spi_cpha0_cs_program_get_default_config(prog_offs); //Get default configurations for the PIO state machine
    sm_config_set_out_pins(&c, pin_mosi, 1); //Set the 'out' pins in a state machine configuration
    sm_config_set_sideset_pins(&c, pin_sck); //Set the 'sideset' pins in a state machine configuration
    sm_config_set_out_shift(&c, false, true, n_bits); //Setup 'out' shifting parameters in a state machine configuration
    sm_config_set_clkdiv(&c, clkdiv); //Set the state machine clock divider

    pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi)); //Use a state machine to set a value on multiple pins for the PIO instance
    pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi)); //Use a state machine to set the pin directions for multiple pins for the PIO instance
    pio_gpio_init(pio, pin_mosi); //Setup the function select for a GPIO to use output from the given PIO instance
    pio_gpio_init(pio, pin_sck); //Setup the function select for a GPIO to use output from the given PIO instance
    //pio_gpio_init(pio, pin_sck + 1); //Setup the function select for a GPIO to use output from the given PIO instance
    gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); //Set GPIO output override


    uint entry_point = prog_offs + spi_cpha0_cs_offset_entry_point; //The offset entry point
    pio_sm_init(pio, sm, entry_point, &c); //Resets the state machine to a consistent state, and configures it
    pio_sm_exec(pio, sm, pio_encode_set(pio_x, n_bits - 2)); //Put n_bits - 2 in pio_x
    pio_sm_exec(pio, sm, pio_encode_set(pio_y, n_bits - 2)); //Put n_bits - 2 in pio_y
    pio_sm_set_enabled(pio, sm, true); //Enable or disable a PIO state machine
}
%}


TFTMaster.h

This is the header file for the TFT and can be downloaded here. Its associated .c file can be downoaded here.

#define CS 18
#define MOSI 19
#define SCK 17
#define DC 16
#define RST 20

#define ILI9340_TFTWIDTH  240
#define ILI9340_TFTHEIGHT 320

#define ILI9340_NOP     0x00
#define ILI9340_SWRESET 0x01
#define ILI9340_RDDID   0x04
#define ILI9340_RDDST   0x09

#define ILI9340_SLPIN   0x10
#define ILI9340_SLPOUT  0x11
#define ILI9340_PTLON   0x12
#define ILI9340_NORON   0x13

#define ILI9340_RDMODE  0x0A
#define ILI9340_RDMADCTL  0x0B
#define ILI9340_RDPIXFMT  0x0C
#define ILI9340_RDIMGFMT  0x0A
#define ILI9340_RDSELFDIAG  0x0F

#define ILI9340_INVOFF  0x20
#define ILI9340_INVON   0x21
#define ILI9340_GAMMASET 0x26
#define ILI9340_DISPOFF 0x28
#define ILI9340_DISPON  0x29

#define ILI9340_CASET   0x2A
#define ILI9340_PASET   0x2B
#define ILI9340_RAMWR   0x2C
#define ILI9340_RAMRD   0x2E

#define ILI9340_PTLAR   0x30
#define ILI9340_MADCTL  0x36

#define ILI9340_MADCTL_MY  0x80
#define ILI9340_MADCTL_MX  0x40
#define ILI9340_MADCTL_MV  0x20
#define ILI9340_MADCTL_ML  0x10
#define ILI9340_MADCTL_RGB 0x00
#define ILI9340_MADCTL_BGR 0x08
#define ILI9340_MADCTL_MH  0x04

#define ILI9340_PIXFMT  0x3A

#define ILI9340_FRMCTR1 0xB1
#define ILI9340_FRMCTR2 0xB2
#define ILI9340_FRMCTR3 0xB3
#define ILI9340_INVCTR  0xB4
#define ILI9340_DFUNCTR 0xB6

#define ILI9340_PWCTR1  0xC0
#define ILI9340_PWCTR2  0xC1
#define ILI9340_PWCTR3  0xC2
#define ILI9340_PWCTR4  0xC3
#define ILI9340_PWCTR5  0xC4
#define ILI9340_VMCTR1  0xC5
#define ILI9340_VMCTR2  0xC7

#define ILI9340_RDID1   0xDA
#define ILI9340_RDID2   0xDB
#define ILI9340_RDID3   0xDC
#define ILI9340_RDID4   0xDD

#define ILI9340_GMCTRP1 0xE0
#define ILI9340_GMCTRN1 0xE1

#define ILI9340_PWCTR6  0xFC

//Color definitions
#define ILI9340_BLACK   0x0000
#define ILI9340_BLUE    0x001F
#define ILI9340_RED     0xF800
#define ILI9340_GREEN   0x07E0
#define ILI9340_CYAN    0x07FF
#define ILI9340_MAGENTA 0xF81F
#define ILI9340_YELLOW  0xFFE0
#define ILI9340_WHITE   0xFFFF

#define tabspace 4

#define swap(a, b) {short t = a; a = b; b = t;}

void tft_init_hw(void);
void tft_spiwrite(unsigned char c);
void tft_spiwrite8(unsigned char c);
void tft_spiwrite16(unsigned short c);
void tft_writecommand(unsigned char c);
void tft_writecommand16(unsigned short c);
void tft_writedata(unsigned char c);
void tft_writedata16(unsigned short c);
void tft_commandList(unsigned char *addr);
void tft_begin(void);
void tft_setAddrWindow(unsigned short x0, unsigned short y0, unsigned short x1, unsigned short y1);
void tft_pushColor(unsigned short color);
void tft_drawPixel(short x, short y, unsigned short color);
void tft_drawFastVLine(short x, short y, short h, unsigned short color);
void tft_drawFastHLine(short x, short y, short w, unsigned short color);
void tft_fillScreen(unsigned short color);
void tft_fillRect(short x, short y, short w, short h, unsigned short color);
unsigned short tft_Color565(unsigned char r, unsigned char g, unsigned char b);
void tft_setRotation(unsigned char m);
void tft_drawLine(short x0, short y0, short x1, short y1, unsigned short color);
void tft_drawRect(short x, short y, short w, short h, unsigned short color);
void tft_drawCircle(short x0, short y0, short r, unsigned short color);
void tft_drawCircleHelper(short x0, short y0, short r, unsigned char cornername, unsigned short color);
void tft_fillCircle(short x0, short y0, short r, unsigned short color);
void tft_fillCircleHelper(short x0, short y0, short r, unsigned char cornername, short delta, unsigned short color);
void tft_drawTriangle(short x0, short y0, short x1, short y1, short x2, short y2, unsigned short color);
void tft_fillTriangle(short x0, short y0, short x1, short y1, short x2, short y2, unsigned short color);
void tft_drawRoundRect(short x0, short y0, short w, short h, short radius, unsigned short color);
void tft_fillRoundRect(short x0, short y0, short w, short h, short radius, unsigned short color);
void tft_drawBitmap(short x, short y, const unsigned char *bitmap, short w, short h, unsigned short color);
void tft_drawChar(short x, short y, unsigned char c, unsigned short color, unsigned short bg, unsigned char size);
void tft_setCursor(short x, short y);
void tft_setTextColor(unsigned short c);
void tft_setTextColor2(unsigned short c, unsigned short bg);
void tft_setTextSize(unsigned char s);
void tft_setTextWrap(char w);
void tft_gfx_setRotation(unsigned char r);
void tft_write(unsigned char c);
void tft_writeString(char* str);


The output

The output below shows output of the rectangle test program. As it is evident, the screen is constantly being redrawn with rectangles of different colours.

Testing the TFT Library with rectangles

Next, I created a program to test the dino game I created here for the TFT screen. The output of the program looks as follows.

Testing the TFT Library with Dino game

After the dino game, I tested the bouncing ball code from the ECE 4760 website and its output is shown below.

Testing the TFT Library with bouncing ball code

Finally, I implemented the boids algorithm from the ECE 4760 wesbite. For the purpose of preserving the integrity of the course assignment, I cannot reveal my code but the link has the algorithm associated with it. The program running on a single core can run support roughly 250 boids while the program running on 2 cores can support roughly 315 boids.

Testing the TFT Library with boids algorithm

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)

include(pico_sdk_import.cmake)

project(TFT_PIO-project)

pico_sdk_init()

add_executable(TFT_PIO)

pico_enable_stdio_usb(TFT_PIO 1)
pico_enable_stdio_uart(TFT_PIO 0)

pico_generate_pio_header(TFT_PIO ${CMAKE_CURRENT_LIST_DIR}/SPIPIO.pio)

target_sources(TFT_PIO PRIVATE TFT_PIO.c TFTMaster.c glcdfont.c)

target_link_libraries(TFT_PIO PRIVATE pico_stdlib hardware_gpio pico_time hardware_pio hardware_pwm hardware_irq)

pico_add_extra_outputs(TFT_PIO)