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 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.
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
}
}
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
}
%}
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 below shows output of the rectangle test program. As it is evident, the screen is constantly being redrawn with rectangles of different colours.
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.
After the dino game, I tested the bouncing ball code from the ECE 4760 website and its output is shown below.
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.
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)