Raspberry Pi Pico & TFT ST7796

Come utilizzare un display St7796 da 3.95 pollici con Raspberry Pi Pico

In questo articolo parlerò di come gestire un display TFT da 3.95 pollici con un Raspberry Pi Pico.

Inoltre è anche il primo articolo che scrivo sul Raspberry Pi Pico e vorrei spendere due righe anche su questo.

Prime impressioni sul Raspberry Pi Pico

Nel momento in cui scrivo, il Raspberry Pi Pico (che in seguito chiamerò solo "Pico") è arrivato sul mercato da un paio di mesi e sono riuscito ad acquistarlo lo stesso giorno di uscita.

Abituato ad Arduino e ad altri microprocessori più diffusi e rilasciati molto tempo prima la fase di startup è stata davvero difficile!

Pochissima documentazione, zero librerie già pronte all'uso. Dei pochi articoli trovati, il 90% è ecommerce o copia&incolla degli esempi rilasciati insieme all' SDK e del 10% rimanente il 5% è di pessima qualità. Quando cerchi qualcosa su Google e hai 14 risultati di cui 10 fanno riferimento a siti compromessi la soluzione è solo una. Armarsi di santa pazienza, prendere il datasheet e studiarselo oltre che fare una marea di esperimenti.

L' IDE di sviluppo? Ripeto, non è Arduino!
Ho utilizzato Visual Studio Code prima da un Raspberry Pi4 poi su Windows. La configurazione sul RPi4 è banale. Su Windows assolutamente il contrario, ma credo che tutte le difficoltà che ho avuto siano state causate da precedenti installazioni di altri strumenti di sviluppo sul mio pc.

Di tutte le prove che ho fatto ho provveduto a caricare il codice C/C++ sul mio GitHub. Non ho provato tutta la parte in Python perchè, detto tra noi, è un linguaggio che non mi sta molto simpatico.

Quindi, per concludere questo paragrafo, ricorda: se stai iniziando ora con il Raspberry Pi Pico, sappi che la strada sarà tutta in salita!

Il driver ST7796

In giro di letteratura se ne trova tanta, ma la bibbia rimane sempre la stessa: il datasheet!

St7796S retro

L'alimentazione è a 5v, ma il Raspberry Pi Pico può fornirne solo 3.3v. Ho utilizzato un Arduino Uno per fornire i 5v.

Ho utilizzato i piedino DB0-DB7 come bus per i dati.

Sostanzialmente si può inviare comandi e dati. La sezione si può attuare mediante il pin LCD_CS che nel datasheet è indicato come "D/CX" (0 per command, 1 per data).

Altri piedini sono "Write" (LCD_WR), "Read" (LCD_RD), "Chip selection" (LCD_CS) e "Reset" (LCD_RST).

Per quanto riguarda il touch screen, quando lo schermo viene toccato, il pin TP_IRQ va a zero.

MISO, MOSI, TP_CS e EX_CLK consentono gestire e quindi rilevare le coordinate dello schermo in corrispondenza del "tocco".

Non mi dilungherò su come pilotarli  e sui comandi da inviare perchè il datasheet è abbastanza chiaro.

Ciò che conta è che il Raspberry Pi Pico dovrà pilotare i suoi piedini compatibilmente con il protocollo. Ho scoperto l'acqua calda?

No, ma ho dovuto studiarmi il protocollo e non è stata una passeggiata.

Step by step

Il primo step è capire come configurare e pilotare i piedini di uscita.

Il secondo step è pilotare il driver ST7796 per visualizzare delle figure geometriche.

Il terzo step è riordinare il codice mettendolo in una libreria in modo che sia possibile riutilizzarlo.

Il quarto step è pilotare i piedini tramite PIO in modo da velocizzare il ridisegno dello schermo.

Gestione dei piedini

Il display sarà collegato nella seguente configurazione:

  • GPIO0 a GPIO7 dati
  • GPIO8 -> CS -> command 0 data 1 selection D/CX
  • GPIO9 -> WR -> write
  • GPIO10 -> RST -> reset
  • GPIO11 -> RS -> chip selection CSX
  • GPIO12 -> RD -> read

Per gestire gli output ci sono diversi modi.

Il primo è gestire ogni GPIO singolarmente:

//inizializzazione
gpio_init(PIN_NUMBER);

//configurazione come output
gpio_set_dir(PIN_NUMBER, GPIO_OUT);

//set del piedino a 1
gpio_put(PIN_NUMBER, 1);

Questa modalità può andar bene nei casi più semplici, ma potrebbe essere poco efficiente nel caso in cui dovessi modificare simultaneamente più GPIO. Per esempio, se dovessi scrivere un byte dovrei chiamare 8 volte la funzione "gpio_put".

La seconda modalità invece consiste nell'utilizzo della funzione gpio_put_masked un semplice caso d'uso potrebbe essere il seguente:

//inizializzazione delle GPIO che dovrò utilizzare
gpio_init(PIN_NUMBER_0);
...
gpio_init(PIN_NUMBER_N);

//configurazione come output di tutte le GPIO che dovrò utilizzare
gpio_set_dir(PIN_NUMBER_0, GPIO_OUT);
...
gpio_set_dir(PIN_NUMBER_N, GPIO_OUT);

//Utilizzo la seguente funzione per impostare la machera solo sugli 8 bit meno significativi.
//Setto a 1 GPIO0 e GPIO7
gpio_put_masked (0b00000000000000000000011111111,0b00000000000000000000010000001);

Ho caricato su GitHub un semplice esempio RapsberryPiPico/multiblink at main · giemma/RapsberryPiPico (github.com)

Il risultato è il seguente:

 

La terza modalità è utilizzare una delle particolarità di Raspberry Pi Pico ovvero PIO.

Per adesso riporto due link molto utili ma prevedo di parlarne dettagliatamente in seguito:

What is PIO | Programmable I/O on Raspberry Pi Pico - YouTube

In-depth: Raspberry Pi Pico's PIO - programmable I/O! - YouTube

L'utilizzo di PIO è molto efficiente perché consente l'accesso diretto alla memoria

Primi passi con il display ST7796

Il primo esperimento che ho fatto con il display ST7796 è il disegno di linee e quadrati.

Per l'alimentazione del display ho utilizzato un Arduino Uno poiché il display richiede 5 Volt ma come è risaputo il Raspberry Pi Pico può fornirne solo 3.3.

Ho caricato il codice su GitHub: RapsberryPiPico/simpleST7796 at main · giemma/RapsberryPiPico (github.com)

Cosa fa questo primo esempio?

Prima di tutto è necessario capire come funziona il driver ST7796.

Il datasheet è disponibile su: ST7796S_SPEC_V1.0 (orientdisplay.com)

Ho preferito pilotarlo a 8bit perché non avevo nessuna voglia di dissaldare la resistenza SMD dietro al display  e saldarla un po più in basso per poter pilotare il display a 16 bit. Naturalmente sarebbe stato meglio farlo per aumentare la velocità di ridisegno del display, ma probabilmente otterrò delle prestazioni soddisfacenti anche così.

Come accennato all'inizio di questo articolo, il driver ST7796 accetta "Command" e "Data". A tal proposito ho scritto due funzioni ("Lcd_Write_Com" e "Lcd_Write_Data") in cui l'unica differenza è settare il pin RS a 0 per command e 1 per data. Entrambe le funzioni chiamano la funzione Lcd_Writ_Bus che provvede a settare a 1 il pin WR e a zero il pin CS, poi setta i piendini relativi al "bus" data e invine riporta WR a 0 e CS a 1.

Le funzioni "Lcd_Write_Com" e "Lcd_Write_Data" vengono chiamate più volte dalla funzione "Lcd_Init" che provvede a inizializzare il driver.

Creazione di una libreria generica per Raspberry Pi Pico

Vogli spendere un po di tempo anche per questo argomento poiché in rete non ho trovato nulla e il risultato è frutto di diverse ore di tentativi ed errori di compilazione.

Quando si crea un nuovo un nuovo progetto il file principale ha estensione ".c" naturalmente se vogliamo includere codice c++ è necessario rinominarlo in *.cpp.

Ho creato una libreria e l'ho caricata su GitHub: RapsberryPiPico/ST7796Library at main · giemma/RapsberryPiPico (github.com)

Di fondamentale importanza è il file CMakeLists.txt in particolare il comando "add_library".

La libreria per il display ST7796 per Raspberry Pi Pico 

Dopo queste prove ho scritto una libreria per gestire il display.

Le funzioni sono:

LCD_Init()

Inizializza il display

LCD_TP_Init()

Inizializza il touch pad

LCD_Clear(uint16_t color)

Cancella tutto il contenuto del display e imposta lo sfondo al colore passato come parametro

Draw_Pixel(uint16_t x, uint16_t y, uint16_t color)

Disegna un singolo pixel

Draw_LineH(uint16_t x0, uint16_t y0, uint16_t lenght, uint16_t color)
Draw_LineV(uint16_t x0, uint16_t y0, uint16_t lenght, uint16_t color)
Draw_Line(uint16_t x0,uint16_t y0,uint16_t x1,uint16_t y1,uint16_t color)

Disegna una linea di un determinato colore dal punto (x0,y0) al punto (x1,y1)

Draw_Rect(uint16_t x0,uint16_t y0,uint16_t width,uint16_t height,uint16_t color)

Disegna un rettangolo vuoto a partire dal punto (x0,y0) con una certa larghezza e una certa altezza

Draw_RectF(uint16_t x0,uint16_t y0,uint16_t width,uint16_t height,uint16_t color)

Disegna un rettangolo pieno a partire dal punto (x0,y0) con una certa larghezza e una certa altezza

Draw_Circle(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t color)

Disegna un cerchio vuoto alle coordinate (x0,y0) di raggio "radius" e colore "color".

Draw_CircleF(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t color)

Disegna un cerchio pieno alle coordinate (x0,y0) di raggio "radius" e colore "color".

Draw_Triangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)

Disegna un triangolo vuoto dati i tre punti corrispondenti ai vertici

Draw_TriangleF(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)

Disegna un triangolo pieno dati i tre punti corrispondenti ai vertici

Draw_Char(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size_x, uint8_t size_y)

Disegna un carattere

Draw_Text(int16_t x, int16_t y, const char *text, uint16_t color, uint16_t bg, uint8_t size_x, uint8_t size_y)

Disegna una linea di testo

Draw_Bitmap(int16_t x, int16_t y, const uint8_t bitmap[],int16_t w, int16_t h)

Disegna una bitmap. L'array bitmap contiene tutti i byte da scrivere. Ho creato e messo su GitHub un piccolo applicativo che ridimensiona se necessario e ottiene l'array di byte da disegnare. E' molto semplice per ogni pixel estrae i due byte relativi al colore e scrive l'array in una casella di testo.

bool LCD_TD_IsTouched()

Ritorna un booleano che identifica se il touchpad è "toccato".

uint8_t LCD_TP_Read_Coordinate(uint16_t *x,uint16_t *y)

Legge le coordinate x,y dal touchpad. Ritorna 1 se la lettura è andata a buon fine.

LCD_AddButton(uint16_t x,uint16_t y, uint16_t width, uint16_t height, const char *label, void (*f)())

Aggiunge un tasto e chiama la funzione "f" quando viene premuto

RedrawButtons()

Ridisegna tutti i tasti che sono stati aggiunti

WaitForClick()

Attende fino a quando un tasto non viene premuto

 

Nel momento i cui scrivo il risultato è il seguente, ma l'ultima versione su GitHub potrebbe avere qualche funzionalità in più