最近更新于 2024-05-05 12:31
1 环境
1.1 硬件
ESP32-WROOM-32
128×64 OLED(IIC)
1.2 软件
Arduino IDE 2.2.1
esp32 2.0.14(开发板)
Adafruit_SSD1306 2.5.7 :https://github.com/adafruit/Adafruit_SSD1306 (依赖库:
Adafruit_BusIO 1.14.5 :https://github.com/adafruit/Adafruit_BusIO
Adafruit-GFX-Library 1.11.9 :https://github.com/adafruit/Adafruit-GFX-Library
)
注:如果是在 Arduino IDE 中安装,会自动提示依赖库安装,如果是从 GitHub 下载离线包,就要手动分别安装。
2 接线
2.1 默认
使用 Wire 库时,ESP32 的默认引脚对应(下面探索就使用这个默认的)
- 21 – SDA
- 22 – SCL
2.2 自定义
方法一
Wire.setPins(int sda, int scl);
Wire.begin();
方法二
bool begin(int sda, int scl, uint32_t frequency=0);
3 探索
3.1 确定 IIC 地址
SSD1306 驱动的 OLED 的 IIC 地址要么是 0x3D,要么就是 0x3C,其实两选一试也行,这里提供了一份代码,会进行遍历测试可用的设备的地址。
#include <Wire.h>
void setup()
{
Serial.begin(115200);
delay(10);
Wire.begin();
Serial.println("开始扫描 IIC 设备");
}
void loop()
{
int devices = 0;
Serial.println("正在扫描......");
// 遍历地址,测试是否可以通信
for (byte error, address = 1; address < 127; ++address)
{
Wire.beginTransmission(address); // 指定通信地址(准备通信)
error = Wire.endTransmission(); // 结束通信
if (0 == error)
{
Serial.print("发现 IIC 设备,地址为 0x");
if (address < 16)
{
Serial.print(0);
}
Serial.println(address, HEX);
++devices;
}
else if (4 == error)
{
Serial.print("发生未知错误,地址为 0x");
if (address < 16)
{
Serial.print(0);
}
Serial.println(address, HEX);
}
}
if (0 == devices)
{
Serial.println("未发现 IIC 设备!\n");
}
else
{
Serial.println("完成!\n");
}
delay(5000);
}
3.2 显示像素点
Adafruit 这个库采用了缓存机制,内部维护了一个数组,数组每个元素映射到屏幕上的一个像素点,元素的值决定了这个点亮还是不亮,写操作其实就是修改数组元素的值,再调用 display 函数将缓存应用到屏幕上才会显示。
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// IIC 地址
#define OLED_ADDRESS 0x3C
// 复位引脚
#define OLED_RESET -1 // 不使用
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
void setup()
{
Serial.begin(115200);
delay(10);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS))
{
Serial.println("OLED 初始化失败!");
while (1);
}
display.clearDisplay(); // 清空缓存
for (int i = 0; i < 50; ++i) // 循环绘制像素点构成直线
{
display.drawPixel(i, i, SSD1306_WHITE); // 指定横纵坐标和颜色
}
display.display(); // 将写入缓存的内容显示到 OLED
}
void loop()
{
}
3.3 显示字符
字符线宽为 1 个像素点时,128×64 分辨率每行显示 21 个字符,8 行,平均每个字符像素尺寸为 6×8。
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// IIC 地址
#define OLED_ADDRESS 0x3C
// 复位引脚
#define OLED_RESET -1 // 不使用
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
void oled_print(int16_t start, int16_t end)
{
display.clearDisplay();
display.setCursor(0, 0); // 设置开始字符的左上角坐标
for (int16_t i = start; i < end; ++i)
{
if ('\n' == i)
{
display.write(' '); // 每次写一个字符,接着上一个字符后面
}
else
{
display.write(i);
}
}
display.display();
}
void setup()
{
Serial.begin(115200);
delay(10);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS))
{
Serial.println("OLED 初始化失败!");
while (1);
}
display.setTextSize(1); // 设置像素大小,不设置默认为 1
display.setTextColor(SSD1306_WHITE); // 设置亮色显示
display.cp437(true); // Code Page 437(IBM PC ASCII),有 256 个字符
}
void loop()
{
oled_print(0, 168); // 每行最多显示 21 个字符,共 8 行,共 168 个
delay(1000);
oled_print(168, 256);
delay(1000);
}
3.4 显示字符串
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// IIC 地址
#define OLED_ADDRESS 0x3C
// 复位引脚
#define OLED_RESET -1 // 不使用
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
void setup()
{
Serial.begin(115200);
delay(10);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS))
{
Serial.println("OLED 初始化失败!");
while (1);
}
display.setTextColor(SSD1306_WHITE);
display.clearDisplay();
display.setCursor(30, 30);
// 注:Arduino IDE 中 F 函数可以让字符串常量存储在 FLASH 中,而节省 RAM
display.println(F("hello world!")); // println 带有换行,后续显示不设置焦点会自动跳到下一行
display.print(F("h w"));
display.setCursor(30, 38); // 字符高度为 8 像素,纵坐标加 8 等于换行
display.print(F("ABCD1234!@#$")); // print 没有换行,后续显示不设置焦点会接着尾部
display.print(F(" & *"));
display.display();
}
void loop()
{
}
3.5 绘制图形
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// IIC 地址
#define OLED_ADDRESS 0x3C
// 复位引脚
#define OLED_RESET -1 // 不使用
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
void setup()
{
Serial.begin(115200);
delay(10);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS))
{
Serial.println("OLED 初始化失败!");
while (1);
}
display.clearDisplay();
// 直线(对角线)
display.drawLine(0, 0, // 起始点坐标
display.width() - 1, display.height() - 1, // 终点坐标
SSD1306_WHITE);
// 矩形
display.drawRect(0, 20, // 左上角坐标
20, 10, // 宽度和高度
SSD1306_WHITE
);
// 填充矩形
display.fillRect(0, 0,
20, 10,
SSD1306_WHITE
);
// 圆角矩形
display.drawRoundRect(0, 31, // 左上角坐标
20, 10, // 宽度和高度
3, // 圆角半径
SSD1306_WHITE);
// 填充圆角矩形
display.fillRoundRect(5, 42,
20, 10,
3,
SSD1306_WHITE);
// 三角形
display.drawTriangle(50, 0, // 指定三个点坐标
40, 30,
60, 15,
SSD1306_WHITE);
// 填充三角形
display.fillTriangle(100, 0,
90, 30,
110, 15,
SSD1306_WHITE);
// 圆
display.drawCircle(display.width() / 2, display.height() / 2, // 圆心,屏幕中心
10, // 半径
SSD1306_WHITE);
// 填充圆
display.fillCircle(display.width() / 2 + 20, display.height() / 2,
5,
SSD1306_WHITE);
display.display();
}
void loop()
{
}
3.5 滚动
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// IIC 地址
#define OLED_ADDRESS 0x3C
// 复位引脚
#define OLED_RESET -1 // 不使用
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
void setup()
{
Serial.begin(115200);
delay(10);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS))
{
Serial.println(F("OLED 初始化失败!"));
while (1);
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE); // 黑底亮字
display.setCursor(0, 0);
display.println(F("hello world!"));
display.setCursor(10, 10);
display.println(F("hello world!"));
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // 亮底黑字
display.println(F("1234ABCD"));
display.setTextSize(2); // 字符线宽
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 26);
display.println(F("hello!"));
display.setTextSize(1);
display.println(F("world"));
display.println(F("Good"));
display.display();
delay(1000);
}
void loop()
{
// 从左往右滚动
display.startscrollright(0, 0); // 参与滚动的行范围,每行高度为一个单像素字符高度,即 8 像素。这里是第一行滚动
delay(2000);
display.stopscroll(); // 停止滚动
delay(1000);
// 从右往左滚动
display.startscrollleft(1, 2); // 2、3 行滚动
delay(2000);
display.stopscroll();
delay(1000);
}
3.6 位图
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// IIC 地址
#define OLED_ADDRESS 0x3C
// 复位引脚
#define OLED_RESET -1 // 不使用
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
#define STAR_HEIGHT 16
#define STAR_WIDTH 16
// 星星图案
// PROGMEM 可以将数据存储在 flash
static const unsigned char PROGMEM star[] =
{ 0b00000000, 0b11000000,
0b00000001, 0b11000000,
0b00000001, 0b11000000,
0b00000011, 0b11100000,
0b11110011, 0b11100000,
0b11111110, 0b11111000,
0b01111110, 0b11111111,
0b00110011, 0b10011111,
0b00011111, 0b11111100,
0b00001101, 0b01110000,
0b00011011, 0b10100000,
0b00111111, 0b11100000,
0b00111111, 0b11110000,
0b01111100, 0b11110000,
0b01110000, 0b01110000,
0b00000000, 0b00110000 };
void setup()
{
Serial.begin(115200);
delay(10);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS))
{
Serial.println(F("OLED 初始化失败!"));
while (1);
}
}
void draw_star(int x, int y)
{
display.clearDisplay();
// 绘制位图
display.drawBitmap(x, y, // 左上角坐标
star, // 图像数据
STAR_WIDTH, STAR_HEIGHT, // 图像尺寸
SSD1306_WHITE);
display.display();
}
void loop()
{
for (int i = 0; i < 48; ++i)
{
draw_star(2 * i, i);
delay(100);
}
}
3.7 取模
取模工具下载:https://pan.baidu.com/s/1OI1mRI-rsP6qohK2DuV0dQ?pwd=r19e
3.7.1 图片取模
前面一个示例打开位图,而位图的数据就需要取模生成
首先可以选一张图片,用画图打开,准备调整大小
我这里 OLED 分辨率是 128×64,将 64 像素边作为高,那么这里图片的像素高度就不能超过 64,按这里的比例高度 64 时,宽度肯定超不了 128。最终图片大小就设置为 61×64,然后保存图片。(用画图调整的原因是支持按比例缩放,直接用下面的取模工具也可以设置生成图像大小,但是如果要长宽维持比例就得自己计算)
运行 Img2Lcd,打开刚才保存的图片
输出数据类型选C语言数组,扫描模式选水平扫描,输出灰度选单色,宽度和高度就设置为上面调整后的大小,这里就是 61×64,下面的都不勾选,然后保存
生成的数据就可以用于 OLED 绘制
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// IIC 地址
#define OLED_ADDRESS 0x3C
// 复位引脚
#define OLED_RESET -1 // 不使用
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
// 图片大小
#define IMG_WIDTH 61
#define IMG_HEIGHT 64
// 图片数据
static const unsigned char PROGMEM img[] =
{ 0XF9,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XEF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XF3,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X7F,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XE0,0X07,0XFF,0XFF,0XFF,0XF8,0XFF,0XFF,0X00,0X01,0X7F,0XFF,0XFF,0XFB,
0XFF,0XF8,0X07,0X00,0X1F,0XFF,0XFF,0XFF,0XFF,0XF0,0X03,0XC0,0X0F,0XFF,0XFF,0XFC,
0XFF,0XE7,0XFD,0XF0,0X0F,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XF0,0X0F,0XFF,0XFF,0XFF,
0XFE,0X07,0XFF,0XBE,0X07,0XFF,0XFF,0XFF,0XF0,0X07,0XFE,0XFF,0X83,0XFF,0XFF,0XFF,
0XF0,0X0F,0XFF,0X7F,0XC3,0XFF,0XFF,0XFB,0XF8,0X1F,0XFF,0X9F,0XE1,0XFF,0XFF,0XFF,
0XF8,0X1F,0XFF,0XEF,0XF0,0XFF,0XFF,0XFF,0XFC,0X3F,0XFF,0XF7,0XF0,0XFF,0XFF,0XFF,
0XFC,0X7F,0XFF,0XFB,0XF8,0X7F,0XFF,0XF9,0XF8,0XFF,0XFF,0XFF,0XFC,0XFF,0XFF,0XFF,
0XF9,0XFF,0XFF,0XFF,0XFF,0X7F,0XFF,0XFF,0XF3,0XFF,0XFF,0XFF,0XFF,0X7F,0XFF,0XFF,
0XF3,0XFF,0XFF,0XFF,0XFE,0X3F,0XFF,0XFF,0XF7,0XFF,0XFF,0XF3,0XFF,0X1F,0XFF,0XFE,
0XF7,0XFF,0XFF,0XFF,0XFF,0XCF,0XFF,0XFF,0XF7,0XFF,0XFF,0XFF,0XFF,0XC7,0XFF,0XFF,
0XF3,0XFF,0XFF,0XFF,0XFF,0XC3,0XFF,0XFC,0XFB,0XF3,0XDF,0XFF,0XFF,0XC0,0XFF,0XFE,
0XF9,0XFF,0XC3,0XFF,0XFF,0XF0,0XFF,0XFF,0XFD,0X7C,0X60,0XFF,0XFF,0XFC,0X7F,0XFF,
0XFE,0X38,0X60,0X0F,0XFF,0XFE,0X7F,0XFF,0XFF,0X99,0XE0,0X07,0XFF,0XFE,0X7F,0XFD,
0XFF,0XCC,0X60,0X03,0XFF,0XFF,0XFF,0XFF,0XFF,0XC6,0X40,0X01,0X7F,0XFF,0XBF,0XF8,
0XFF,0XE2,0X40,0X00,0X3F,0XFF,0X1F,0XF8,0XFF,0XF0,0X40,0X00,0X1F,0XFE,0X8F,0XFF,
0XFF,0XF8,0X40,0X00,0X07,0XF0,0X27,0XFF,0XFF,0XFF,0X00,0X00,0X01,0XE0,0X17,0XFF,
0XFF,0XFF,0XE0,0X00,0X00,0XC0,0X1B,0XF9,0XFF,0XFF,0XE0,0X18,0X01,0X00,0X09,0XFF,
0XFF,0XFF,0XE0,0X3C,0X04,0X00,0X01,0XF8,0XFF,0XFF,0XF0,0X3E,0X00,0X00,0X03,0XF8,
0XFF,0XFF,0XF8,0XFE,0X00,0X00,0X00,0XF8,0XFF,0XFF,0XFE,0XFC,0X00,0X00,0X00,0XFF,
0XFF,0XFF,0XFF,0X7C,0X00,0X00,0X00,0X3F,0XFF,0XFF,0XFF,0XF8,0X00,0X00,0X00,0X1F,
0XFF,0XFF,0XFF,0XF0,0X00,0X00,0X00,0X0F,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X06,
0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X01,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X18,
0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X1C,0XFF,0XFF,0XFF,0XE0,0X00,0X00,0X00,0X08,
0XFF,0XFF,0XFF,0XE0,0X01,0XC0,0X00,0X0F,0XFF,0XFF,0XFF,0XE0,0X03,0XC0,0X00,0X0F,
0XFF,0XFF,0XFF,0XE0,0X07,0XE0,0X00,0X07,0XFF,0XFF,0XFF,0XC0,0X0F,0XE0,0X00,0X07,
0XFF,0XFF,0XFF,0XD0,0X1F,0XF0,0X00,0X00,0XFF,0XFF,0XFF,0XC0,0X1F,0XF8,0X00,0X00,
0XFF,0XFF,0XFF,0XC0,0X3F,0XF8,0X00,0X00,0XFF,0XFF,0XFF,0XC0,0X3F,0XFC,0X00,0X07,
0XFF,0XFF,0XFF,0XC0,0X3F,0XFF,0X00,0X00,0XFF,0XFF,0XFF,0XC0,0X7F,0XFF,0X80,0X07,
0XFF,0XFF,0XFF,0XC0,0X7F,0XFF,0X80,0X07,0XFF,0XFF,0XFF,0XC6,0X7F,0XFF,0XC1,0X07,
0XFF,0XFF,0XFF,0XC0,0XFF,0XFF,0XFB,0XC7,0XFF,0XFF,0XFF,0XC7,0XFF,0XFF,0XFF,0X80,};
void setup()
{
Serial.begin(115200);
delay(10);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS))
{
Serial.println(F("OLED 初始化失败!"));
while (1);
}
display.clearDisplay();
display.drawBitmap(0, 0, // 左上角坐标
img, // 图像数据
IMG_WIDTH, IMG_HEIGHT, // 图像尺寸
SSD1306_WHITE);
display.display();
}
void loop()
{
}
3.7.2 字符取模(汉字)
运行 PCtoLCD2002,模式选择如图
然后选择字体和设置大小
选项设置如图
然后生成字模
然后就可以在 OLED 显示
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// IIC 地址
#define OLED_ADDRESS 0x3C
// 复位引脚
#define OLED_RESET -1 // 不使用
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
// 字模大小
#define FONT_WIDTH 16
#define FONT_HEIGHT 16
// 字模
static const unsigned char PROGMEM font[][32] =
{
0x08,0x80,0x08,0x80,0x08,0x80,0x11,0xFE,0x11,0x02,0x32,0x04,0x34,0x20,0x50,0x20,
0x91,0x28,0x11,0x24,0x12,0x24,0x12,0x22,0x14,0x22,0x10,0x20,0x10,0xA0,0x10,0x40,/*"你",0*/
0x10,0x00,0x10,0xFC,0x10,0x04,0x10,0x08,0xFC,0x10,0x24,0x20,0x24,0x20,0x25,0xFE,
0x24,0x20,0x48,0x20,0x28,0x20,0x10,0x20,0x28,0x20,0x44,0x20,0x84,0xA0,0x00,0x40,/*"好",1*/
0x02,0x20,0x12,0x20,0x12,0x20,0x12,0x20,0x12,0x20,0xFF,0xFE,0x12,0x20,0x12,0x20,
0x12,0x20,0x12,0x20,0x13,0xE0,0x10,0x00,0x10,0x00,0x10,0x00,0x1F,0xFC,0x00,0x00,/*"世",2*/
0x00,0x00,0x1F,0xF0,0x11,0x10,0x11,0x10,0x1F,0xF0,0x11,0x10,0x11,0x10,0x1F,0xF0,
0x02,0x80,0x0C,0x60,0x34,0x58,0xC4,0x46,0x04,0x40,0x08,0x40,0x08,0x40,0x10,0x40,/*"界",3*/
};
void setup()
{
Serial.begin(115200);
delay(10);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS))
{
Serial.println(F("OLED 初始化失败!"));
while (1);
}
display.clearDisplay();
display.drawBitmap(32, 24, font[0], FONT_WIDTH, FONT_HEIGHT, SSD1306_WHITE);
display.drawBitmap(48, 24, font[1], FONT_WIDTH, FONT_HEIGHT, SSD1306_WHITE);
display.drawBitmap(64, 24, font[2], FONT_WIDTH, FONT_HEIGHT, SSD1306_WHITE);
display.drawBitmap(80, 24, font[3], FONT_WIDTH, FONT_HEIGHT, SSD1306_WHITE);
display.display();
}
void loop()
{
}
3.8 局部刷新
有时候显示的东西只有局部变化,其它部分可能一直都显示相同的,那么刷新变化的内容的时候就没必要全屏重写,只需要重写变化的部分就行。但是也不能直接往变化的部分写,这样会和以前显示的内容重叠,要解决的关键问题就是如何只清除要更新的部分,然后再写。
这个就可以利用前面演示用过的填充矩形函数来实现,某部分要重写,就先用填充矩形指定范围,但是填充颜色选黑色就会抹掉这部分,再写到这个地方就可以实现局部刷新。
下面写的伪代码,大致理解逻辑
// 实例对象
Adafruit_SSD1306 oled(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
// 初始化
oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS);
// 清屏
oled.clearDisplay();
// 设置打印颜色为白色
oled.setTextColor(SSD1306_WHITE);
// 固定不变的显示内容
oled.print("counter:");
// 动态变化的显示内容(需要局部刷新的)
for (int i = 0; i < 10; ++i)
{
oled.fillRect(48, 0, 6, 8, SSD1306_BLACK); // 刷新部位局部绘制为黑色(清空)
oled.setCursor(48, 0); // 设置要显示部位坐标
oled.print(i); // 写入最新显示内容
oled.display(); // 显示
delay(1000);
}