Manual

do

Maker

.

com

Como usar mais de um display SPI?

Como usar mais de um display SPI?

Se não viu o artigo "Como controlar N displays SPI no mesmo barramento", talvez (somente "talvez") você tenha pensando em dois barramentos SPI; um para cada display. Se leu o artigo citado, então pode pensar que é "mais do mesmo" - Respondo que não. No outro artigo os displays não tinham a habilidade de intercambiar o controle (há meios que envolvem mais recursos e tornam possível fazê-lo). No caso do artigo anterior, um display em cada SPI seria uma solução mais simples. Então, como usar mais de um display SPI para mostrar imagens diferentes?

Biblioteca TFT_eSPI

A primeira coisa é a biblioteca. A TFT_eSPI é uma biblioteca incrível e que facilita demais o controle de displays de praticamente todos os tipos. Nesse artigo vamos usar mais uma vez o display LCD GC9A01 que você encontra na loja da Curto. O display é relativamente barato, RGB565 e, especificamente esse modelo usado nos artigos aqui do blog, é 3v3. Não o ligue em 5V. Falaremos mais a respeito.

A biblioteca TFT_eSPI pode ser baixada pelo gerenciador de bibliotecas do Arduino ou, se estiver usando VS Code com PlatformIO, pelo gerenciador de bibliotecas do PlatformIO. Tenho muitos argumentos para usar VS Code com PlatformIO, mas fica a seu critério.

User_Setup.h

Ao instalar a biblioteca, o primeiro passo é definir os parâmetros que serão usados, conforme placa utilizada e modelo do display. Para poupar seu trabalho, apague o conteúdo do arquivo (ou faça um backup antes) e substitua por isso:

#define USER_SETUP_INFO "User_Setup"
#define GC9A01_DRIVER
#define TFT_HEIGHT 240
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS   -1 //32
#define TFT_DC   25
#define TFT_RST  27
//Se quiser fontes...
#define LOAD_GLCD
#define LOAD_FONT2 
#define LOAD_FONT4  
#define LOAD_FONT6 
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF
#define SMOOTH_FONT
#define SPI_FREQUENCY  27000000
#define SPI_READ_FREQUENCY  20000000

Esse setup é exclusivamente para o display desse artigo. Defina os pinos conforme a MCU em questão.

Aqui temos alguns pontos importantes; basta definir TFT_HEIGHT. Obviamente TFT_WIDTH é igual, senão o display seria "oval", invés de "redondo".

Qual GPIO usar ?

O SPI é um recurso de hardware. Para usar o SPI, é necessário sempre procurar o pinout da placa que for utilizar. Não sei por qual razão, nesse artigo usei um ESP32 Wemos. Então procurei pelo pinout dele e vi quais eram os GPIOs relacionados ao barramento SPI.

wemos-esp32-pinout.png
Tenha em mente que os pinos de SPI são MOSI, MISO, SCLK e CS. O que estamos fazendo aqui é ignorar o CS para fazer a seleção do dispositivo interativamente, por isso o pino CS no arquivo está como -1.

MISO não está definido, já que display não faz comunicação bi-direcional. Precisamos do CS? - Claro que sim. Mas vamos lá.

O barramento SPI comporta tantos dispositivos quando a MCU for capaz de gerenciar. Mas como podemos mandar um dado diferente para cada display, se estão todos no mesmo pino MOSI? - Simples - Pelo CS (Chip Select).

O pino CS é utilizado para dizer ao display que a mensagem é para ele. Então, se o CS estiver em LOW, ele recebe o dado no pino MOSI (já que isso é inevitável pelo wiring padrão), mas não faz nada. Se estiver em LOW, significa que o que está chegando é para ele. Com isso, podemos ter tantos displays quanto GPIOs disponíveis na placa. Deixar o CS com -1 não designa um CS para a configuração da biblioteca, daí basta fazer o controle do CS no seu código.

Para começar, vamos ver o código de controle de 1 display:

#include <Arduino.h>

#include "NotoSansBold36.h"

#include "heartbeat.h"
#include <TFT_eSPI.h>

#define AA_FONT_LARGE NotoSansBold36
#define CS_DSP_1 32

unsigned int timer      = millis();
unsigned int next_frame = 0;

uint8_t number_of_frames = 6;

TFT_eSPI tft = TFT_eSPI();

uint8_t show_frame    = 0;

int frame_interval = 100;
void setup() {
  pinMode(32,OUTPUT);
  digitalWrite(CS_DSP_1, LOW);

  Serial.begin(9600);
  tft.init();                       
  
  tft.setSwapBytes(true);           
  tft.setRotation(3);                

  tft.fillScreen(TFT_PURPLE);      

  tft.pushImage(0,0,EasyMaker_height,EasyMaker_width,Heartbeat[0]); 

  digitalWrite(CS_DSP_1,HIGH);
  vTaskDelay(pdMS_TO_TICKS(200));
}

void showImage(){ 
  next_frame = millis()-timer;
  if (next_frame > frame_interval){
    
    show_frame = show_frame > number_of_frames-2 ? 0 : show_frame+1;
    
    timer = millis();
  }
  
  digitalWrite(CS_DSP_1,LOW);      
  tft.pushImage(0,0,EasyMaker_height,EasyMaker_width,Heartbeat[show_frame]); 
  digitalWrite(CS_DSP_1,HIGH); 
}

void loop(){
  showImage();
}

Defini o CS como pino 32 do ESP32. Repare na função showImage() que o pino é posto em LOW, o dado é escrito e o pino é posto em HIGH. Precisa fazer isso para "1" display? - Respondo novamente - Não, não precisa. Basta que o pino esteja em LOW ou que seja definido no header User_setup.h da biblioteca TFT_eSPI. Porém essa "é" a prova de conceito. Então minha sugestão de aprendizado é a seguinte:

Coloque o pino simplesmente em LOW e suba o código. A animação deverá funcionar. Coloque o pino em HIGH e suba o código. A animação "não" deve funcionar.

Tendo provado o conceito, hora de definir o CS para o segundo display.

Wiring

Todos os displays recebem os mesmos pinos de GPIO, exceto o pino de CS. Alguns displays vem com o pino MOSI marcado como SDA. É o caso desse display redondo.

E a animação????

Ah, verdade. O código é a parte mais fácil, fazer a animação é terrível. Bem; era, antigamente. Hoje você pode usar o EasyMaker Animation Studio para extrair frames de um gif animado, png animado ou webp animado - e gerar o código de sketch e animação em 1 clique! Eu tenho um imenso orgulho de ter criado esse programa e o distribuo gratuitamente. Hoje, apenas versões para Windows, mas prometo compilar também para Linux. Sugiro que dê uma pausa e assista o vídeo de apresentação dos recursos do EasyMaker Animation Studio. Se preferir um vídeo tutorial mostrando como usar em um caso real, assista o vídeo Gif animado no ESP32.

Aproveitando, se quiser apenas exibir imagens em displays, seja OLED, LCD, RGB ou e-paper, recomendo o EasyMaker Image Suite, que é outro software que fiz com muito orgulho e nele incluí o recurso de esteganografia, para você brincar com ocultação de imagens. Tem vídeo também.

Aqui descreverei o passo a passo, mas também disporei em vídeo o processo completo, desde o download de uma animação até a inserção no display. Bora começar!

Onde baixar animações para display?

Não existe um site de "animações para display". Pelo menos, "acho" que não. Mas o que você precisa é de uma animação qualquer. Qualquer uma! As imagens usadas nesse display são gratuitas e baixei em um site especializado em gif. Não é o único, tem esse outro site de gifs que também gosto bastante. Escolheu seu gif? Lembre-se de que o tamanho ideal é 240x240, mas se for maior, podemos ajustar na edição para tirar bordas ou simplesmente dimensionar a imagem no EasyMaker-AS.

Instalar o EasyMaker Animation Studio para gerar o código

Hora de baixar o EasyMaker Animation Studio. Esse link é do meu repositório git. Clique em Releases do lado direito, mais ou menos no centro vertical da tela. Baixe o instalador EasyMaker-AS.zip. Se preferir um link direto para o programa, aqui está.

Extraia o arquivo zip e rode o instalador. É um instalador típico, não precisa de explicação. Ao final, execute o programa.

Carregue a animação

Clique em File > Open e selecione o gif que escolheu para a animação. A janela deverá ficar semelhante a essa:

EMAS-plasma.png

A deformação da esquerda é apenas ajuste da imagem para o máximo do viewer. É proposital; você pode dar uma "deformada" na animação para ocupar todo o espaço da dimensão pretendida. Selecione o mínimo de frames possível para sua animação. Se for usar apenas 1 display, até umas 8 deve caber bem, mas para um gif é demais. Nessa animação eu escolhi apenas 3 frames.

Depois de selecionar os frames, clique em Send. Sua seleção será enviada para o player da direita. Clique em Play e veja se ficou bom. Vários gifs tem muita borda. Nesse caso, é necessário uma edição externa. Se for o caso:

Recorte de bordas - Edite no programa preferido

Clique em Timeline > Save Frames > Checked Frames as Images. Escolha um diretório para salvar as imagens. Como a borda está bem próxima, eu preferi não aproximar mais. Se preferir, faça o corte das bordas. Para redimensionar os frames na dimensão de 240x240, editei as imagens no Photopea, que é um programa gratuito online muito parecido com o Photoshop CS2. Redimensionados? Agora vá novamente ao EasyMaker Animation Studio e clique em FIle > Load Sequence e carregue seus frames. Selecione-os na timeline, clique em Send para que sejam enviados para o player da direita, então clique em Apply e Code. Escolha um diretório para salvar os arquivos. O trabalho de preparação acabou!

Redimensionar no EasyMaker

O plasma está com borda satisfatória para mim. Nesse caso, usei o próprio EasyMaker para redimensionar.

Após enviar os frames para o player da direita, clique em Wrap e dimensione em 240x240. Clique em Apply e clique em Code. Salve o código.

Como usar o código gerado pelo EasyMaker-AS

Após salvar, você terá no diretório de destino os arquivos EasyMaker.h, EasyMakerSketch e um frame com as dimensões escolhidas. Esse frame é para ter uma referência visual da sua escolha, para não ter que perder tempo fazendo depuração em seu código porque esqueceu de acertar as dimensões!

EasyMaker.h

Para um display só, basta usar o sketch, mas se for usar múltiplos displays, renomeie o arquivo de animação de EasyMaker.h para o nome desejado. No caso do plasma, vou chamar de plasma.h.

Edite o arquivo (agora "plasma.h") e renomeie apenas a variável do array bi-dimensional. Chame o array de plasma também. As dimensões podem continuar como EasyMaker_width e EasyMaker_height. Como todas as animações serão para displays da mesma dimensão, a referência pode ser sempre a mesma, mas nas demais animações, substitua o nome da variável de dimensão para não haver duplicidade; ou simplesmente remova as variáveis de dimensão dos demais arquivos de animação - mas mantenha as dimensões em uma das animações.

EasyMakerSketch.ino

Se estiver utilizando VS Code com PlatformIO, basta copiar o código para main.cpp. Não tem mistério.

Se estiver utilizando múltiplos displays, precisaremos manipular o código. É o propósito desse artigo.

Porque apenas 2 displays?

Tenho mais desses displays, mas o ESP32 Wemos tem 1.3MB de flash. É pouquíssimo, e só consegui armazenar 2 animações de 3 frames. Talvez coubesse 2 animações de 4 frames, mas não mais que isso.

No próximo artigo já inicio a série com ESP32S3, onde temos recursos assustadores! A série inicia com conceitos sobre os periféricos da placa, seguindo pelo controle de mais displays, de uma forma mais agradável ainda! Espero que acompanhe o blog para ver as novidades, certamente serão conceitos interessantes e aplicáveis por um longo tempo, assim como a série com o "gigante" ESP32 ante o Arduino, que perdurou por alguns anos, mas agora ele é "pequeno" perto do ESP32S3!

Sketch para controlar 2 displays

Vamos ao resumo do projeto.

  • Instalar a biblioteca TFT_eSPI
  • Configurar os pinos no arquivo User_setup.h da biblioteca
  • Baixar os gifs que deseja animar no display
  • Usar o EasyMaker Animation Studio para converter em código
  • Adicionar as animações ao projeto, criando o respectivo arquivo.h
  • Subir o sketch a seguir
#include <Arduino.h>

#include "NotoSansBold36.h"

#include "heartbeat.h"
#include "plasma.h"

#include <TFT_eSPI.h>

#define AA_FONT_LARGE NotoSansBold36
#define CS_DSP_1 32
#define CS_DSP_2 17

unsigned int timer      = millis();
unsigned int next_frame = 0;

uint8_t number_of_frames = 3;

TFT_eSPI tft = TFT_eSPI();

uint8_t show_frame    = 0;

int frame_interval = 100;
void setup() {
  pinMode(32,OUTPUT);
  pinMode(17,OUTPUT);
  digitalWrite(CS_DSP_1, LOW);
  digitalWrite(CS_DSP_2, LOW);

  Serial.begin(9600);
  tft.init();                       
  
  tft.setSwapBytes(true);           
  tft.setRotation(3);                

  tft.fillScreen(TFT_PURPLE);     
  
  digitalWrite(CS_DSP_2, HIGH);
  tft.pushImage(0,0,EasyMaker_height,EasyMaker_width,Heartbeat[0]); 
  digitalWrite(CS_DSP_1,HIGH);

  digitalWrite(CS_DSP_2, LOW);
  tft.pushImage(0,0,EasyMaker_height,EasyMaker_width,plasma[0]);
  digitalWrite(CS_DSP_2, HIGH);

  vTaskDelay(pdMS_TO_TICKS(200));

}

void showImage(){ 
  next_frame = millis()-timer;
  if (next_frame > frame_interval){
    
    show_frame = show_frame > number_of_frames-2 ? 0 : show_frame+1;
    
    timer = millis();
  }
  
  digitalWrite(CS_DSP_1,LOW);      
  tft.pushImage(0,0,EasyMaker_height,EasyMaker_width,Heartbeat[show_frame]);  //(pos x,y | size w,h | img to show)
  digitalWrite(CS_DSP_1,HIGH); 

  //dsp 1 = 6 frames; dsp 2 = 3 frames
  digitalWrite(CS_DSP_2, LOW);
  tft.pushImage(0,0,EasyMaker_height,EasyMaker_width,plasma[show_frame >2 ? show_frame-3 : show_frame]);
  digitalWrite(CS_DSP_2, HIGH);
}

void loop(){
  showImage();
}

Salvo erro, esse é o último código válido. E digo isso porque enquanto estou escrevendo esse artigo, já migrei para o ESP32S3 e inclusive já estou indo para a próxima fase, adicionando mais displays.

No próximo artigo o conteúdo será mais "seco", focando exclusivamente na implementação do código, considerando que as animações já foram preparadas a partir desse artigo.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.