参考
orangepizero3 硬件配置
bash
orangepi@orangepizero3:~$ sudo orangepi-config
# System->Hardware
# 使用 /dev/i2c-3
orangepi@orangepizero3:~$ ls /dev/i2c-*
/dev/i2c-1 /dev/i2c-2 /dev/i2c-3 /dev/i2c-4 /dev/i2c-5
# 扫描并探测 I2C 总线 3 上的所有设备
# oled的地址是3c
orangepi@orangepizero3:~$ sudo i2cdetect -y 3
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --

硬件连线
I2C3 连接 SSD1306 128*32 (0.91寸)

源码目录
bash
orangepi@orangepizero3:~$ ls
build
font.cpp
font.h
main.cpp
Makefile
oled.cpp
oled.h
源码
Makefile
bash
CXX = g++
CXXFLAGS = -std=c++17 -Wall
TARGET = build/oled_demo
SRCS = main.cpp \
oled.cpp \
font.cpp
OBJS = $(patsubst %.cpp,build/%.o,$(SRCS))
all: $(TARGET)
# 创建 build 目录
build:
mkdir -p build
# 链接
$(TARGET): build $(OBJS)
$(CXX) -o $@ $(OBJS)
# 编译
build/%.o: %.cpp | build
$(CXX) $(CXXFLAGS) -c $< -o $@
# 清理
clean:
rm -rf build
.PHONY: all clean build
font.cpp和font.h
main.cpp
c
#include <arpa/inet.h>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <ifaddrs.h>
#include <net/if.h>
#include <unistd.h>
#include "oled.h"
extern const ASCIIFont afont24x12;
extern const ASCIIFont afont16x8;
static void GetLocalIP(char *ip, size_t len)
{
strncpy(ip, "0.0.0.0", len);
ip[len - 1] = '\0';
struct ifaddrs *ifaddr = nullptr;
if (getifaddrs(&ifaddr) == -1)
return;
for (struct ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next)
{
if (!ifa->ifa_addr)
continue;
if (ifa->ifa_addr->sa_family != AF_INET)
continue;
if (ifa->ifa_flags & IFF_LOOPBACK)
continue;
inet_ntop(AF_INET,
&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr,
ip,
len);
break;
}
freeifaddrs(ifaddr);
}
static void MakeShortIP(const char *ip, char *shortIp, size_t len)
{
const char *last = strrchr(ip, '.');
if (!last)
{
strncpy(shortIp, ip, len);
shortIp[len - 1] = '\0';
return;
}
const char *prev = last - 1;
while (prev > ip && *prev != '.')
prev--;
if (*prev == '.')
prev++;
strncpy(shortIp, prev, len);
shortIp[len - 1] = '\0';
}
int main()
{
char ip[64] = "0.0.0.0";
char oldIp[64] = "";
char ipShort[16] = "0.0.0.0";
char msg[16];
int refresh = 0;
time_t lastUpdate = 0;
OLED_Init();
while (1)
{
// 每秒检查一次 IP 是否变化
time_t now = time(nullptr);
if (now != lastUpdate)
{
lastUpdate = now;
GetLocalIP(ip, sizeof(ip));
if (strcmp(ip, oldIp) != 0)
{
strcpy(oldIp, ip);
MakeShortIP(ip, ipShort, sizeof(ipShort));
printf("IP Changed: %s -> %s\n", ip, ipShort);
}
}
OLED_NewFrame();
// 左侧:大字显示最后两段 IP,例如 3.11
OLED_PrintASCIIString(
0,
0,
ipShort,
&afont24x12,
OLED_COLOR_NORMAL);
// 右侧:小字刷新计数
snprintf(msg, sizeof(msg), "%05d", refresh++);
OLED_PrintASCIIString(
68,
8,
msg,
&afont16x8,
OLED_COLOR_NORMAL);
OLED_ShowFrame();
usleep(100000); // OLED 刷新 10Hz
}
return 0;
}
oled.h
c
#ifndef __OLED_H__
#define __OLED_H__
#include "font.h"
#include "string.h"
typedef enum {
OLED_COLOR_NORMAL = 0, // 正常模式 黑底白字
OLED_COLOR_REVERSED // 反色模式 白底黑字
} OLED_ColorMode;
void OLED_Init();
void OLED_DisPlay_On();
void OLED_DisPlay_Off();
void OLED_NewFrame();
void OLED_ShowFrame();
void OLED_SetPixel(uint8_t x, uint8_t y, OLED_ColorMode color);
void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, OLED_ColorMode color);
void OLED_DrawRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color);
void OLED_DrawFilledRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color);
void OLED_DrawTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, OLED_ColorMode color);
void OLED_DrawFilledTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, OLED_ColorMode color);
void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r, OLED_ColorMode color);
void OLED_DrawFilledCircle(uint8_t x, uint8_t y, uint8_t r, OLED_ColorMode color);
void OLED_DrawEllipse(uint8_t x, uint8_t y, uint8_t a, uint8_t b, OLED_ColorMode color);
void OLED_DrawImage(uint8_t x, uint8_t y, const Image *img, OLED_ColorMode color);
void OLED_PrintASCIIChar(uint8_t x, uint8_t y, char ch, const ASCIIFont *font, OLED_ColorMode color);
void OLED_PrintASCIIString(uint8_t x, uint8_t y, char *str, const ASCIIFont *font, OLED_ColorMode color);
void OLED_PrintString(uint8_t x, uint8_t y, char *str, const Font *font, OLED_ColorMode color);
#endif // __OLED_H__
oled.cpp
c
#include "oled.h"
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cstring>
#define OLED_ADDR 0x3C
#define OLED_WIDTH 128
#define OLED_HEIGHT 32
#define OLED_PAGE 4
#define OLED_COLUMN 128 // OLED列数
static int g_fd = -1;
static uint8_t OLED_GRAM[OLED_PAGE][OLED_WIDTH];
static void sendCmd(uint8_t cmd)
{
uint8_t buf[2] = {0x00, cmd};
write(g_fd, buf, 2);
}
static void sendData(const uint8_t *data, int len)
{
uint8_t buf[129];
buf[0] = 0x40;
while (len > 0)
{
int n = len > 128 ? 128 : len;
memcpy(buf + 1, data, n);
write(g_fd, buf, n + 1);
data += n;
len -= n;
}
}
uint8_t _OLED_GetUTF8Len(char* string) {
if ((string[0] & 0x80) == 0x00) {
return 1;
}
else if ((string[0] & 0xE0) == 0xC0) {
return 2;
}
else if ((string[0] & 0xF0) == 0xE0) {
return 3;
}
else if ((string[0] & 0xF8) == 0xF0) {
return 4;
}
return 0;
}
void OLED_Init()
{
printf("OLED_Init\n");
g_fd = open("/dev/i2c-3", O_RDWR);
if (g_fd < 0)
{
perror("open");
return;
}
printf("open ok\n");
if (ioctl(g_fd, I2C_SLAVE, OLED_ADDR) < 0)
{
perror("ioctl");
return;
}
printf("ioctl ok\n");
ioctl(g_fd, I2C_SLAVE, OLED_ADDR);
sendCmd(0xAE);
sendCmd(0x20);
sendCmd(0x00);
sendCmd(0xB0);
sendCmd(0xC8);
sendCmd(0x00);
sendCmd(0x10);
sendCmd(0x40);
sendCmd(0x81);
sendCmd(0x7F);
sendCmd(0xA1);
sendCmd(0xA6);
sendCmd(0xA8);
sendCmd(0x1F);
sendCmd(0xD3);
sendCmd(0x00);
sendCmd(0xD5);
sendCmd(0x80);
sendCmd(0xD9);
sendCmd(0xF1);
sendCmd(0xDA);
sendCmd(0x02);
sendCmd(0xDB);
sendCmd(0x40);
sendCmd(0x8D);
sendCmd(0x14);
sendCmd(0xAF);
OLED_NewFrame();
OLED_ShowFrame();
}
void OLED_NewFrame()
{
memset(OLED_GRAM, 0, sizeof(OLED_GRAM));
}
void OLED_ShowFrame()
{
for (int page = 0; page < OLED_PAGE; page++)
{
sendCmd(0xB0 + page);
sendCmd(0x00);
sendCmd(0x10);
sendData(OLED_GRAM[page], OLED_WIDTH);
}
}
void OLED_SetByte_Fine(uint8_t page, uint8_t column, uint8_t data, uint8_t start, uint8_t end, OLED_ColorMode color) {
static uint8_t temp;
if (page >= OLED_PAGE || column >= OLED_COLUMN)
return;
if (color)
data = ~data;
temp = data | (0xff << (end + 1)) | (0xff >> (8 - start));
OLED_GRAM[page][column] &= temp;
temp = data & ~(0xff << (end + 1)) & ~(0xff >> (8 - start));
OLED_GRAM[page][column] |= temp;
}
void OLED_SetBits(uint8_t x, uint8_t y, uint8_t data, OLED_ColorMode color) {
uint8_t page = y / 8;
uint8_t bit = y % 8;
OLED_SetByte_Fine(page, x, data << bit, bit, 7, color);
if (bit) {
OLED_SetByte_Fine(page + 1, x, data >> (8 - bit), 0, bit - 1, color);
}
}
/**
@brief 设置显存中的一字节数据的某几位
**/
void OLED_SetBits_Fine(uint8_t x, uint8_t y, uint8_t data, uint8_t len, OLED_ColorMode color) {
uint8_t page = y / 8;
uint8_t bit = y % 8;
if (bit + len > 8) {
OLED_SetByte_Fine(page, x, data << bit, bit, 7, color);
OLED_SetByte_Fine(page + 1, x, data >> (8 - bit), 0, len + bit - 1 - 8, color);
}
else {
OLED_SetByte_Fine(page, x, data << bit, bit, bit + len - 1, color);
}
}
/**
@brief 设置显存中一字节长度的数据
**/
void OLED_SetBlock(uint8_t x, uint8_t y, const uint8_t* data, uint8_t w, uint8_t h, OLED_ColorMode color) {
uint8_t fullRow = h / 8; // 完整的行数
uint8_t partBit = h % 8; // 不完整的字节中的有效位数
for (uint8_t i = 0; i < w; i++) {
for (uint8_t j = 0; j < fullRow; j++) {
OLED_SetBits(x + i, y + j * 8, data[i + j * w], color);
}
}
if (partBit) {
uint16_t fullNum = w * fullRow; // 完整的字节数
for (uint8_t i = 0; i < w; i++) {
OLED_SetBits_Fine(x + i, y + (fullRow * 8), data[fullNum + i], partBit, color);
}
}
}
void OLED_PrintString(uint8_t x, uint8_t y, char* str, const Font* font, OLED_ColorMode color) {
uint16_t i = 0; // 字符串索引
uint8_t oneLen = (((font->h + 7) / 8) * font->w) + 4; // 一个字模占多少字节
while (str[i]) {
uint8_t found = 0; // 是否找到字模
uint8_t utf8Len = _OLED_GetUTF8Len(str + i); // UTF-8编码长度
if (utf8Len == 0)
break; // 有问题的UTF-8编码
// 寻找字符 TODO 优化查找算法, 二分查找或者hash
for (uint8_t j = 0; j < font->len; j++) {
uint8_t* head = (uint8_t*)(font->chars) + (j * oneLen); // 字模头指针
if (memcmp(str + i, head, utf8Len) == 0) {
OLED_SetBlock(x, y, head + 4, font->w, font->h, color);
// 移动光标
x += font->w;
i += utf8Len;
found = 1;
break;
}
}
// 若未找到字模,且为ASCII字符, 则缺省显示ASCII字符
if (found == 0) {
if (utf8Len == 1) {
OLED_PrintASCIIChar(x, y, str[i], font->ascii, color);
// 移动光标
x += font->ascii->w;
i += utf8Len;
}
else {
OLED_PrintASCIIChar(x, y, ' ', font->ascii, color);
x += font->ascii->w;
i += utf8Len;
}
}
}
}
void OLED_PrintASCIIChar(uint8_t x, uint8_t y, char ch, const ASCIIFont* font, OLED_ColorMode color) {
OLED_SetBlock(x, y, font->chars + (ch - ' ') * (((font->h + 7) / 8) * font->w), font->w, font->h, color);
}
void OLED_PrintASCIIString(uint8_t x, uint8_t y, char* str, const ASCIIFont* font, OLED_ColorMode color) {
uint8_t x0 = x;
while (*str) {
OLED_PrintASCIIChar(x0, y, *str, font, color);
x0 += font->w;
str++;
}
}
开机启动配置
bash
orangepi@orangepizero3:~$ sudo vim /etc/systemd/system/oled_demo.service
bash
[Unit]
Description=OLED Demo
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/home/orangepi/oled_demo
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
oled_demo相关脚本
bash
# 添加执行权限
chmod +x /home/orangepi/oled_demo
# 修改 service 文件后,重新加载 systemd 配置
sudo systemctl daemon-reload
# 设置 oled_demo 为开机自动启动
sudo systemctl enable oled_demo
# 查看 oled_demo 当前运行状态
sudo systemctl status oled_demo
# 查看 oled_demo 的实时运行日志
journalctl -u oled_demo -f
# 查看是否已设置为开机自启动
systemctl is-enabled oled_demo
# 停止服务
sudo systemctl stop oled_demo
# 立即重启(重新启动)oled_demo 服务
sudo systemctl restart oled_demo