Esta documentação detalha o funcionamento e a aplicação de um módulo de software para controlar displays OLED baseados no driver SSD1306 com o Raspberry Pi Pico via comunicação I2C.
Datasheet de referência: https://www.mouser.com/datasheet/2/737/SSD1306-1159828.pdf
Esta seção é um guia direto para integrar a biblioteca do display SSD1306 em um novo projeto.
Componente | Pino no Pico | Observação |
---|---|---|
Display VCC | 3V3 (OUT) | Tensão de alimentação para o display. |
Display GND | GND | Essencial. O terra (GND) deve ser comum entre o Pico e o display. |
Display SDA | Qualquer pino I2C | Pino de dados. O padrão no seu módulo é o GPIO14. |
Display SCL | Qualquer pino I2C | Pino de clock. O padrão no seu módulo é o GPIO15. |
O display OLED SSD1306 utiliza o protocolo I2C. Certifique-se de usar pinos do Raspberry Pi Pico que sejam compatíveis com a função I2C e que o barramento (
i2c0
oui2c1
) seja inicializado corretamente no software.
Para que o projeto funcione, ele depende da biblioteca pico-ssd1306
. A maneira moderna e recomendada de gerenciar dependências como esta é usando o módulo FetchContent
do CMake. Ele automatiza o download e a integração da biblioteca durante a configuração do projeto, eliminando a necessidade de submódulos Git ou downloads manuais.
Ao usar FetchContent
, a estrutura de arquivos do seu projeto fica mais limpa. O CMake cuidará de baixar e gerenciar esses arquivos em um diretório temporário dentro da sua pasta de compilação (build/
).
Sua estrutura de projeto pode se concentrar apenas nos seus arquivos de aplicação:
meu_projeto/
|
├── inc/
│ ├── display.h
│ └── ...
├── src/
│ ├── display.c
│ ├── main.c
│ └── ...
└── CMakeLists.txt
Para que o SDK do Pico compile seu projeto e inclua a biblioteca pico-ssd1306
automaticamente, você deve adicionar o seguinte bloco de código ao seu CMakeLists.txt
, logo após a função pico_sdk_init()
.
Adicione o seguinte bloco de código ao seu CMakeLists.txt
, preferencialmente logo após a linha pico_sdk_init()
:
# ======================= FETCHCONTENT PARA SSD1306 =======================
# PASSO 1: Habilitar o módulo FetchContent
include(FetchContent)
# PASSO 2: Declarar os detalhes da dependência
FetchContent_Declare(
pico_ssd1306
GIT_REPOSITORY https://github.com/daschr/pico-ssd1306.git
GIT_TAG main # Ou um commit hash para estabilidade
)
# PASSO 3: Baixar e disponibilizar o conteúdo
FetchContent_MakeAvailable(pico_ssd1306)
# PASSO 4: Criar um alvo de biblioteca para a dependência
add_library(ssd1306_lib INTERFACE)
target_include_directories(ssd1306_lib INTERFACE ${pico_ssd1306_SOURCE_DIR})
target_sources(ssd1306_lib INTERFACE ${pico_ssd1306_SOURCE_DIR}/ssd1306.c)
# ===============================================================================
Após definir o bloco FetchContent
, o passo final é instruir o CMake a usar a nova biblioteca ssd1306_lib
ao compilar seu executável principal:
# Adiciona as bibliotecas necessárias para o Pico e o display
target_link_libraries(main
hardware_i2c
pico_stdlib
ssd1306_lib
)
O display SSD1306 é controlado via protocolo I2C. Ele funciona como um dispositivo "escravo" que responde às solicitações do "mestre" (o Raspberry Pi Pico).
- Endereçamento: O display possui um endereço I2C (no seu caso,
0x3C
) que o Pico usa para se comunicar. - Comandos vs. Dados: A comunicação I2C com o SSD1306 envia um byte de controle antes de cada pacote de dados. Esse byte informa ao display se a informação a seguir é um comando (para configurar o display, como ajustar contraste ou ligá-lo) ou dados (os pixels que serão exibidos na tela).
- Buffer de Memória (GDDRAM): O SSD1306 possui uma memória interna para armazenar o estado de cada pixel. A biblioteca
pico-ssd1306
cria um buffer na memória do Pico com o mesmo tamanho. As funções de desenho (ssd1306_draw_pixel
,ssd1306_draw_string
, etc.) modificam este buffer local. A funçãossd1306_show()
é responsável por transmitir o conteúdo desse buffer para a memória do display, atualizando a imagem na tela.
Este arquivo é a interface pública da biblioteca. Ele define:
ssd1306_command_t
: Uma enumeração com todos os códigos de comando I2C que o display entende, comoSET_CONTRAST
(0x81) eSET_DISP
(0xAE, para ligar/desligar).ssd1306_t
: Uma estrutura (struct
) que armazena todas as informações de configuração e estado de um display, incluindo:width
eheight
: As dimensões do display.address
: O endereço I2C do dispositivo.i2c_i
: A instância do barramento I2C (i2c0
oui2c1
).buffer
: O ponteiro para o buffer de memória no Pico que armazena a imagem a ser desenhada.
- Protótipos das Funções:
bool ssd1306_init(...)
: Inicializa o display, aloca o buffer e envia a sequência de comandos de configuração I2C.void ssd1306_clear(...)
: Preenche o buffer de memória com zeros, efetivamente "limpando" a imagem.void ssd1306_draw_pixel(...)
: Modifica um único bit no buffer para representar um pixel aceso.void ssd1306_draw_string(...)
: Desenha uma sequência de caracteres no buffer usando uma fonte pré-definida.void ssd1306_show(...)
: Envia todo o conteúdo do buffer local para a memória interna do display via I2C, atualizando a tela.
ssd1306_init()
: Esta função é crucial e realiza várias etapas:- Aloca memória para o buffer do display com base na largura e altura.
- Envia uma longa sequência de comandos I2C para o display. Esses comandos definem a taxa de multiplexação, o contraste, a orientação da tela e ativam a bomba de carga interna para gerar a tensão necessária para os pixels do OLED.
- Finalmente, limpa o display e o liga.
- Funções de Desenho: Funções como
ssd1306_draw_pixel
,ssd1306_draw_line
essd1306_draw_string
não se comunicam diretamente com o display. Elas apenas realizam cálculos para determinar quais bytes e bits no buffer de memória do Pico precisam ser alterados. Isso torna as operações de desenho muito rápidas. ssd1306_show()
: Esta é a única função (além dainit
) que realiza uma comunicação I2C intensiva. Ela envia comandos para definir os endereços de página e coluna e, em seguida, transmite todo o buffer de pixels para o display. É por isso que ela deve ser chamada sempre que você quiser que as alterações feitas pelas funções de desenho se tornem visíveis.
Esta seção descreve o projeto de exemplo completo que demonstra o uso do módulo de display OLED para fornecer feedback visual.
O projeto de exemplo inicializa o display OLED e, em seguida, executa uma contagem de 0 a 20. O texto é exibido no display com um efeito de "rolagem", onde a tela é limpa a cada 8 linhas, dando a impressão de que o texto está subindo. Ao final, uma mensagem de conclusão é exibida.
O projeto é organizado de forma modular para separar as responsabilidades e facilitar a manutenção:
Oled
├── inc
| ├── display.h # Interface pública para o módulo do display (camada de abstração)
| └── init.h # Módulo para inicializações do sistema
├── src
| ├── display.c # Implementação das funções para interagir com o display
| ├── init.c # Centraliza as inicializações de hardware (I2C, etc.)
| └── main.c # Lógica principal da aplicação
├── lib/pico-ssd1306 # Biblioteca de baixo nível para o SSD1306 (submódulo Git)
└──...
Para simplificar a interação com a biblioteca pico-ssd1306
, foi criada uma camada de abstração com os arquivos display.h
e display.c
. Isso permite que o main.c
chame funções mais simples e diretas, sem precisar lidar com os detalhes da estrutura ssd1306_t
a todo momento.
Este arquivo de cabeçalho define a interface pública do nosso módulo de display.
#ifndef DISPLAY_H
#define DISPLAY_H
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "ssd1306.h"
#include "hardware/i2c.h"
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_ADDRESS 0x3C // Endereço I2C do display
#define I2C_SDA_DISPLAY 14 // Pino SDA
#define I2C_SCL_DISPLAY 15 // Pino SCL
int initializeDisplay();
void showText(const char *texto, uint32_t x, uint32_t y, uint32_t scale);
void updateTextLine(const char* text, uint32_t x, uint32_t y, uint32_t scale, uint32_t clear_width);
void clearScreen();
extern ssd1306_t display;
#endif // DISPLAY_H
- Constantes:
SCREEN_WIDTH
,SCREEN_HEIGHT
,SCREEN_ADDRESS
,I2C_SDA_DISPLAY
, eI2C_SCL_DISPLAY
centralizam todas as configurações de hardware em um único local, facilitando futuras modificações. - Protótipos das Funções:
int initializeDisplay()
: Encapsula a chamada à funçãossd1306_init
da biblioteca, tornando a inicialização mais limpa.void showText(...)
: Uma função de alto nível que desenha um texto e imediatamente atualiza a tela. Isso simplifica o uso comum, combinandossd1306_draw_string
essd1306_show
em uma única chamada.void updateTextLine(...)
: Uma função especializada que primeiro limpa uma área retangular do display e depois desenha um novo texto no lugar. Ideal para atualizar informações que mudam com frequência, como leituras de sensores ou contadores.void clearScreen()
: Combinassd1306_clear
essd1306_show
para limpar a tela de forma imediata.
extern ssd1306_t display;
: Declara que uma variável global do tipossd1306_t
chamadadisplay
existe em algum outro arquivo (display.c
). Isso permite que omain.c
acesse diretamente a instância do display se necessário (por exemplo, para chamarssd1306_show
).
Este arquivo contém o código que implementa as funções prometidas em display.h
.
#include "display.h"
ssd1306_t display; // Declara uma instância do display
// Inicializa o display
int initializeDisplay() {
if (!ssd1306_init(&display, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_ADDRESS, i2c1)) {
printf("Falha ao inicializar o display SSD1306\n");
return 1;
}
ssd1306_poweron(&display); // Liga o display
printf("Display SSD1306 inicializado\n");
return 0;
}
void showText(const char *texto, uint32_t x, uint32_t y, uint32_t scale){
ssd1306_draw_string(&display, x, y, scale, texto); // Desenha o texto
ssd1306_show(&display); // Atualiza a tela
}
void clearScreen(){
ssd1306_clear(&display); // Limpa a tela
ssd1306_show(&display); // Atualiza a tela
}
void updateTextLine(const char* text, uint32_t x, uint32_t y, uint32_t scale, uint32_t clear_width) {
// A altura da fonte padrão é 8 pixels
uint32_t char_height = 8;
// Limpa a área retangular onde o texto ficará
ssd1306_clear_square(&display, x, y, clear_width, char_height * scale);
// Desenha a nova string
ssd1306_draw_string(&display, x, y, scale, text);
}
initializeDisplay()
: Esta função chamassd1306_init
com os parâmetros definidos emdisplay.h
, configurando o display para uso no barramentoi2c1
. Também liga o display comssd1306_poweron
.showText(...)
: Simplesmente chamassd1306_draw_string
para desenhar o texto no buffer e, em seguida, chamassd1306_show
para garantir que o texto apareça na tela.clearScreen()
: Da mesma forma, chamassd1306_clear
essd1306_show
.updateTextLine(...)
: Esta função é mais específica. Ela usassd1306_clear_square
para apagar a área onde o novo texto será escrito antes de usarssd1306_draw_string
. Importante: esta função não chamassd1306_show
, permitindo que o código principal (main.c
) faça várias atualizações no buffer antes de enviar tudo de uma vez para a tela, o que é mais eficiente e evita piscadas (flickering).
O laço principal implementa a lógica de contagem e atualização do display usando a camada de abstração.
#include <stdio.h>
#include "pico/stdlib.h"
#include "init.h"
int main() {
// Esta função única inicializa tudo!
initializeSystem();
sleep_ms(1000);
// Buffer para formatar o texto para o display
char buffer[40];
// Mensagem de boas-vindas criativa
showText("Iniciando...", 25, 28, 1);
sleep_ms(2000);
clearScreen();
// Loop principal para contar de 0 a 50
for (int i = 0; i <= 20; i++) {
// Formata a string que será exibida
sprintf(buffer, "Linha de teste N: %d", i);
// A cada 8 linhas, limpa a tela para criar um efeito de rolagem
if (i > 0 && i % 8 == 0) {
clearScreen();
}
// Calcula a posição Y para que o texto "suba" na tela.
// (i % 8) resulta em valores de 0 a 7, que são multiplicados pela altura da fonte (8 pixels).
uint32_t y_pos = (i % 8) * 8;
// A função updateTextLine não atualiza a tela sozinha,
// então chamamos ssd1306_show() manualmente depois.
updateTextLine(buffer, 0, y_pos, 1, SCREEN_WIDTH);
ssd1306_show(&display);
// Uma pequena pausa para que a contagem seja visível
sleep_ms(150);
}
// Mensagem de finalização
sleep_ms(1000);
clearScreen();
showText("Concluido!", 10, 20, 2);
while (1) {
// Loop infinito para manter a mensagem final no display
tight_loop_contents();
}
return 0;
}
Essa estrutura mostra claramente a vantagem da abstração: o main.c
se preocupa com a lógica da aplicação (o que exibir e quando), enquanto os detalhes de como limpar, desenhar e atualizar o hardware do display são tratados pelo módulo display
.