SSD13xx系列是一系列高度集成的单芯片IC,内部集成了OLED Common/Segment驱动器、显示RAM、控制器、DC/DC升压器和振荡器。这种高集成度设计使得OLED模组只需简单连接OLED面板和驱动IC即可实现,无需额外的背光或LED驱动电路,模组厚度可控制在1~1.5mm左右,非常适合空间受限的便携设备。SSD1306是最广为人知的单色驱动芯片,SSD1315在SSD1306的基础上加入了滚动功能外,其他与SSD1306保持一致。
yy3588的i2c资源基本都有各自的用途,本文使用gpio来模拟i2c驱动SSD13159的oled屏(oled屏使用SSD131x驱动,实际上可以使用SSD130x的驱动,如本文实际上是使用 SSD1307来进行驱动SSD13159,理论上也兼容SSD1306)
本文档用到的所有内容有
如果只是想要驱动ssd13xx oled屏而不关心使用哪个引脚的话也可以直接烧录我们构建的boot.img镜像
打开RKDevTool,右键之后选择导入配置

在弹出窗口中选择我们ssd13xx/images目录下的parameter.txt文件

点击打开之后RKDevTool会加载到我们的分区表,如下,然后点击后面的空白处选择我们的boot.img以及parameter.txt文件


最后确保设备处于loader模式或者maskrom模式之后点击执行即可,等待烧录完成之后即可支持ssd13xx
如果前面烧录了boot.img则跳过该章节,在执行构建之前需要修改或替换(替换和修改选择其中其中一个即可)yy3588 ubuntu源代码目录下的
kernel-6.1/arch/arm64/boot/dts/rockchip/rk3588-evb7-v11-yyt.dtsi
kernel-6.1/arch/arm64/configs/rockchip_linux_docker.config
直接使用ssd13xx/replace目录下的文件替换到yy3588 ubuntu源代码即可
这里直接添加以下的配置项到文件kernel-6.1/arch/arm64/configs/rockchip_linux_docker.config中
CONFIG_I2C_GPIO=y
CONFIG_FB=y
CONFIG_FB_SYS_FILLRECT=y
CONFIG_FB_SYS_COPYAREA=y
CONFIG_FB_SYS_IMAGEBLIT=y
CONFIG_FB_SYS_FOPS=y
CONFIG_FB_DEFERRED_IO=y
CONFIG_FB_SSD1307=y
完整的文件内容为
CONFIG_BRIDGE_NETFILTER=y
CONFIG_MEMCG=y
CONFIG_VETH=y
CONFIG_BRIDGE=y
CONFIG_BRIDGE_NETFILTER=y
CONFIG_IP_NF_FILTER=y
CONFIG_IP_NF_TARGET_MASQUERADE=y
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
CONFIG_NETFILTER_XT_MATCH_IPVS=y
CONFIG_NETFILTER_XT_MARK=y
CONFIG_IP_NF_NAT=y
CONFIG_NF_CONNTRACK=y
CONFIG_NF_NAT=y
CONFIG_POSIX_MQUEUE=y
CONFIG_CGROUP_BPF=y
CONFIG_I2C_GPIO=y
CONFIG_FB=y
CONFIG_FB_SYS_FILLRECT=y
CONFIG_FB_SYS_COPYAREA=y
CONFIG_FB_SYS_IMAGEBLIT=y
CONFIG_FB_SYS_FOPS=y
CONFIG_FB_DEFERRED_IO=y
CONFIG_FB_SSD1307=y
设备树配置
设备树选择复用GPIO2_B2作为SDA,GPIO2_B1作为SCL,在kernel-6.1/arch/arm64/boot/dts/rockchip/rk3588-evb7-v11-yyt.dtsi的根节点目录下添加以下的节点
i2c_gpio_ssd1306: i2c-gpio-ssd1306 {
compatible = "i2c-gpio";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c_gpio_pins>;
gpios = <&gpio2 RK_PB2 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>,
<&gpio2 RK_PB1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <5>;
i2c-gpio,timeout-ms = <1000>;
#address-cells = <1>;
#size-cells = <0>;
ssd1315: ssd1315@3c {
compatible = "solomon,ssd1306fb-i2c";
reg = <0x3c>;
solomon,height = <64>;
solomon,width = <128>;
solomon,page-offset = <0>;
solomon,com-offset = <0>;
solomon,prechargep1 = <1>;
solomon,prechargep2 = <15>;
reset-gpios = <&gpio2 RK_PC0 GPIO_ACTIVE_LOW>;
};
};

同时在文件rk3588-evb7-v11-yyt.dtsi的pinctrl节点中添加下面的节点
i2c-gpio {
i2c_gpio_pins: i2c-gpio-pins {
rockchip,pins =
/* SDA: GPIO2_B2 */
<2 RK_PB2 RK_FUNC_GPIO &pcfg_pull_up>,
/* SCL: GPIO2_B1 */
<2 RK_PB1 RK_FUNC_GPIO &pcfg_pull_up>;
};
};

保存之后运行下面的命令执行编译
./yy3588-build.sh
构建完整固件,最后将构建好的完整固件update.img烧录到板子上面即可
硬件按照下面的方式连接,详细的引脚位置参考yy3588的硬件原理图
| yy3588 | SSD13xx 0.96 OLED |
|---|---|
| 3.3V | VCC |
| GND | GND |
| GPIO2_B1(针脚的12号脚) | SCL |
| GPIO2_B2(针脚的10号脚) | SDA |
真实接线如下

烧录完成后并连接,屏幕会有类似终端的下划线在闪动,此时屏幕已经正常进入工作状态,可以通过内核日志或者使用i2ctools工具(位于ssd13xx/tools/i2c-tools/目录下)来进行验证,后续的命令都在adb中进行
可以通过以下方式来确定ssd13xx是否被系统正常识别
执行以下命令,查看内核日志
dmesg | grep i2c
输出结果如下
root@YY3588:/home/youyeetoo# dmesg | grep i2c
[ 1.920597] i2c-gpio i2c-gpio-ssd1306: using lines 74 (SDA) and 73 (SCL)
[ 2.894939] i2c_dev: i2c /dev entries driver
[ 2.915928] i2c 2-0037: Fixed dependency cycle(s) with /csi2-dcphy0/ports/port@0/endpoint@1
[ 3.835882] <<-GTP-ERROR->> GTP i2c test failed time 1.
[ 3.963880] <<-GTP-ERROR->> GTP i2c test failed time 2.
[ 4.091881] <<-GTP-ERROR->> GTP i2c test failed time 3.
[ 4.219882] <<-GTP-ERROR->> GTP i2c test failed time 4.
[ 4.347882] <<-GTP-ERROR->> GTP i2c test failed time 5.
[ 4.368096] i2c_device_probe+0x138/0x350
[ 4.368172] i2c_new_client_device+0x184/0x340
[ 4.368179] of_i2c_register_devices+0x15c/0x210
[ 4.368187] i2c_register_adapter+0x1e4/0x6b4
[ 4.368194] __i2c_add_numbered_adapter+0x60/0xac
[ 4.368201] i2c_add_numbered_adapter+0x20/0x34
[ 4.368208] rk3x_i2c_probe+0x354/0x514
[ 4.368280] rk3x_i2c_driver_init+0x20/0x28
[ 4.376727] i2c 4-0037: Fixed dependency cycle(s) with /csi2-dcphy1/ports/port@0/endpoint@1
[ 5.295883] <<-GTP-ERROR->> GTP i2c test failed time 1.
[ 5.423882] <<-GTP-ERROR->> GTP i2c test failed time 2.
[ 5.551879] <<-GTP-ERROR->> GTP i2c test failed time 3.
[ 5.679882] <<-GTP-ERROR->> GTP i2c test failed time 4.
[ 5.807882] <<-GTP-ERROR->> GTP i2c test failed time 5.
[ 5.828085] i2c_device_probe+0x138/0x350
[ 5.828159] i2c_new_client_device+0x184/0x340
[ 5.828166] of_i2c_register_devices+0x15c/0x210
[ 5.828173] i2c_register_adapter+0x1e4/0x6b4
[ 5.828180] __i2c_add_numbered_adapter+0x60/0xac
[ 5.828187] i2c_add_numbered_adapter+0x20/0x34
[ 5.828193] rk3x_i2c_probe+0x354/0x514
[ 5.828264] rk3x_i2c_driver_init+0x20/0x28
[ 5.836740] i2c 6-0022: Fixed dependency cycle(s) with /usbdrd3_0/usb@fc000000/port/endpoint@0
从
[ 1.920597] i2c-gpio i2c-gpio-ssd1306: using lines 74 (SDA) and 73 (SCL)
可以看出驱动已经被加载,并且以引脚编号为74的gpio作为SDA,引脚编号为73的gpio作为SCL
执行
dmesg | grep ssd
输出如下
root@YY3588:/home/youyeetoo# dmesg | grep ssd
[ 1.920597] i2c-gpio i2c-gpio-ssd1306: using lines 74 (SDA) and 73 (SCL)
[ 2.679150] ssd1307fb 9-003c: fb0: Solomon SSD1307 framebuffer device registered, using 1024 bytes of video memory
可以看出已经成功识别设备,如果输出
root@YY3588:/# dmesg | grep ssd
[ 1.918853] i2c-gpio i2c-gpio-ssd1306: using lines 74 (SDA) and 73 (SCL)
[ 2.257087] ssd1307fb 9-003c: Couldn't send I2C command.
则证明硬件没有正确连接,尤其是检查SCL和SDA是否反接
首先执行下面的命令列出板子所有的i2c设备
./i2cdetect -l
输出结果如下
root@YY3588:/home/youyeetoo/tools# ./i2cdetect -l
i2c-0 i2c rk3x-i2c I2C adapter
i2c-1 i2c rk3x-i2c I2C adapter
i2c-2 i2c rk3x-i2c I2C adapter
i2c-3 i2c rk3x-i2c I2C adapter
i2c-4 i2c rk3x-i2c I2C adapter
i2c-5 i2c rk3x-i2c I2C adapter
i2c-6 i2c rk3x-i2c I2C adapter
i2c-7 i2c rk3x-i2c I2C adapter
i2c-9 i2c i2c-gpio-ssd1306 I2C adapter
i2c-10 i2c DP-AUX I2C adapter
i2c-11 i2c ddc I2C adapter
i2c-12 i2c fde50000.dp I2C adapter
其中i2c9正是我们使用gpio模拟的i2c,接着可以执行下面的命令来查看i2c-9的状态,其中9表示要查看i2c9的信息
root@YY3588:/home/youyeetoo/tools# ./i2cdetect -y 9
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
先看横排为30,再看纵列的c,他们的交点存在一个UU的标志,UU标志表示探测到的通信地址,并且已经被内核占用,所以输出的意思为在地址0x30+0x0c的地址上,即0x3c存在被内核占用的通信地址,该地址就是SSD13159的I2C通信地址如果硬件连接错误会识别不到,输出如下
root@YY3588:/home/youyeetoo/tools# ./i2cdetect -y 9
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
如果硬件已经正常连接,SSD1315设备最终会被作为一个fb(framebuffer)设备,可以通过下面的命令查看设备上有什么fb设备
ls -l /dev/fb*
输出如下
root@YY3588:/home/youyeetoo/tools# ls -l /dev/fb*
crw------- 1 root root 29, 0 Jun 4 22:36 /dev/fb0
crw------- 1 root root 29, 1 Jun 4 22:17 /dev/fb1
接下来确定哪个fb设备对应的是SSD1315,执行
cat /sys/class/graphics/fb0/name
cat /sys/class/graphics/fb1/name
输出结果如下
root@YY3588:/home/youyeetoo/tools# cat /sys/class/graphics/fb0/name
Solomon SSD1307
root@YY3588:/home/youyeetoo/tools# cat /sys/class/graphics/fb1/name
rockchipdrmfb
可以看出fb0是我们要找的fb设备(使用的是SSD1307的驱动,实际上是SSD13159),接着可以通过下面的命令将数据写入到/dev/fb0来进行简单的测试
# 清空屏幕 全黑
dd if=/dev/zero of=/dev/fb0 bs=1024 count=1
# 全白
dd if=/dev/zero bs=1 count=1024 | tr '\000' '\377' > /dev/fb0
现在回到之前说的屏幕上会有类似于终端的下划线光标闪动的情况,这个闪动光标是Linux控制台(console)光标,当 SSD1315 被识别为/dev/fb0 时,内核会自动将它用作 控制台输出设备 ,就像一个终端屏幕。光标就是命令行提示符的光标,Linux内核在启动时会检测到framebuffer设备,并将控制台绑定到这个framebuffer,如果不想显示该光标按照如下的流程,首先查看有哪些虚拟控制台
ls /sys/class/vtconsole/
输出为
root@YY3588:/home/youyeetoo/tools# ls /sys/class/vtconsole/
vtcon0 vtcon1
得知存在两个虚拟控制台vtcon0和vtcon1,接下来查看这两个虚拟控制台的name属性
cat /sys/class/vtconsole/vtcon*/name
root@YY3588:/home/youyeetoo/tools# cat /sys/class/vtconsole/vtcon*/name
(S) dummy device
(M) frame buffer device
其中(S) dummy device是vtcon0的结果,(M) frame buffer device是vtcon1的结果,也可以单独执行来进行确定,(M)表示 主控制台(Main),(S)表示从控制台(Secondary)
cat /sys/class/vtconsole/vtcon0/name
cat /sys/class/vtconsole/vtcon1/name
输出如下
root@YY3588:/home/youyeetoo/tools# cat /sys/class/vtconsole/vtcon0/name
(S) dummy device
root@YY3588:/home/youyeetoo/tools# cat /sys/class/vtconsole/vtcon1/name
(M) frame buffer device
可以得出vtcon1即为我们要找的虚拟控制台,接着查看绑定状态
cat /sys/class/vtconsole/vtcon*/bind
其输出同前文的name属性,如下
root@YY3588:/home/youyeetoo/tools# cat /sys/class/vtconsole/vtcon*/bind
0
1
第一个输出对应的是vtcon0的绑定状态,值为0当前未绑定,第二个输出对应vtcon1的结果,值为1表示当前已经绑定,可以通过向该文件中写入0来取消绑定,此时便不会继续闪烁
echo 0 > /sys/class/vtconsole/vtcon1/bind
同时可以通过写入1来恢复绑定
echo 1 > /sys/class/vtconsole/vtcon1/bind
以上两个命令不会有输出结果,需要观察屏幕的状态来确定执行结果,或者执行
echo $?
当返回值为0时即为执行成功
Linux VT Console原理
┌─────────────────────────────────────┐
│ 应用程序输出到 /dev/console │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 内核 VT 子系统 │
│ ┌─────────┐ ┌──────────┐ │
│ │ vtcon0 │ │ vtcon1 │ │
│ │ (dummy) │ │ (fb) │ │
│ │ bind=0 │ │ bind=1 │◄────── 当前活动
│ └─────────┘ └────┬─────┘ │
└────────────────────────┼───────────┘
│
▼
┌──────────┐
│ /dev/fb0 │
│(SSD1315) │
└──────────┘
ssd13xx/
├── driver/ # 核心库源代码
│ ├── ssd13xx_driver.c/h # 显示驱动核心
│ ├── font_ascii.c # ASCII 字体数据
│ ├── font_chinese.c # 中文字体数据
│ ├── text_render.c # 文本渲染引擎
│ ├── font.h # 字体 API 接口
│ └── utf8_gb2312_table.h # UTF-8 到 GB2312 映射表
│
├── example/ # 示例程序
│ ├── ascii.c # ASCII 字符显示示例
│ ├── chinese.c # 中文字符显示示例
│ ├── mixed.c # 中英文混合显示示例
│ └── shape.c # 图形绘制示例
│
├── tools/ # 开发工具
│ ├── debug_chinese_font.c # 字体位图可视化工具
│ ├── font_ascii_converter.c # ASCII 字体转换工具
│ ├── font_chinese_converter.c # 中文字体转换工具
│ └── i2c-tools/ # i2c-tools
│
├── docs/ # 文档
│ ├── README.md # 文档索引
│ ├── zh-cn/ # 中文文档
│ │ ├── API.md # API 参考文档
│ │ ├── BUILD.md # 编译指南
│ │ ├── EXAMPLE.md # 示例程序说明
│ │ └── TOOLS.md # 开发工具说明
│ └── en/ # 英文文档
│ ├── API.md
│ ├── BUILD.md
│ ├── EXAMPLE.md
│ └── TOOLS.md
│
├── build/ # 编译中间文件 (.o)
├── output/ # 输出目录
│ ├── examples/ # 示例程序可执行文件
│ └── tools/ # 开发工具可执行文件
├── Makefile # 构建系统
└── README.md # 本文件
源码中已经实现了5x7,6x12,8x8,8x16的ASCII字库,以及少量测试使用的中文字符的字库,详情请参见example目录下的示例程序代码,快速验证可以执行bin目录下已经编译好的可执行文件
下面详细介绍如何添加中文字库
下面详细介绍如何添加中文字库
取模工具这里使用的是PCtoLCD2002,下载地址
先点开设置

设置点阵格式为阴码,取模方式为行列式,取模走向为正向(高位在前),点击左下角的确定回到主页

接下来设置字的宽高,对于中文字符若要保证字显示清晰,容易辨认字的大小应不小于12x12,本驱动实现了12x12和16x16的驱动,在需要时使用该两个尺寸的大小即可,这里使用16x16为例

生成的结果如下,但是生成的字模的原始数据还不能直接应用到代码中,需要经过数据格式的转换

打开ssd13xx的驱动源码目录,使用output/tools目录下的font_chinese_converter进行转换,具体使用方式参见docs目录下的TOOLS.md文档
将转换后的数据添加到driver/font_chinese.c的对应的数组中,这里是16x16的大小,所以添加到chinese_font_16x16数组中,如果是12x12就添加到chinese_font_12x12数组中
static const chinese_char_t chinese_font_16x16[] = {
/* 你 (ni3) - GB2312: C4E3 */
{0xC4E3, {
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}},
/* 好 (hao3) - GB2312: BAC3 */
{0xBAC3, {
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}},
/* ....... */
/* 风 - GB2312: B7E7 */
{0xB7E7, {
0x00,0x00, 0x3F,0xF0, 0x20,0x10, 0x20,0x10,
0x28,0x50, 0x24,0x50, 0x22,0x90, 0x22,0x90,
0x21,0x10, 0x21,0x10, 0x22,0x90, 0x22,0x92,
0x24,0x4A, 0x48,0x4A, 0x40,0x06, 0x80,0x02}},
/* 火 - GB2312: BBF0 */
{0xBBF0, {
0x01,0x00, 0x01,0x00, 0x01,0x00, 0x11,0x08,
0x11,0x08, 0x11,0x10, 0x21,0x20, 0x21,0x00,
0x42,0x80, 0x02,0x80, 0x04,0x40, 0x04,0x40,
0x08,0x20, 0x10,0x10, 0x20,0x08, 0xC0,0x06}},
/* 轮 - GB2312: C2D6 */
{0xC2D6, {
0x20,0x40, 0x20,0x40, 0x20,0xA0, 0xFC,0xA0,
0x41,0x10, 0x52,0x08, 0x94,0x06, 0xFD,0x10,
0x11,0x20, 0x11,0x40, 0x1D,0x80, 0xF1,0x00,
0x51,0x04, 0x11,0x04, 0x10,0xFC, 0x10,0x00}}
};
同时在driver/utf8_gb2312_table.h的映射表中添加utf8到gb2312的映射,如下
static const utf8_gb_map_t utf8_to_gb2312_table[] = {
/* 项目中已有的字符 */
{0x4E16, 0xCAC0, "世"},
{0x4F60, 0xC4E3, "你"},
{0x597D, 0xBAC3, "好"},
{0x5EA6, 0xB6C8, "度"},
{0x663E, 0xCFD4, "显"},
{0x6D4B, 0xB2E2, "测"},
{0x6E29, 0xCEC2, "温"},
{0x754C, 0xBDE7, "界"},
{0x793A, 0xCABE, "示"},
{0x8BD5, 0xCAD4, "试"},
{0x98CE, 0xB7E7, "风"},
{0x706B, 0xBBF0, "火"},
{0x8F6E, 0xC2D6, "轮"},
/* 可继续添加更多字符... */
/* 格式: {Unicode码点, GB2312编码, "字符"}, */
};
此时字库已可用,具体的显示,示例参考example下面的示例程序,同时可用使用tools目录下的get.py快速获取Unicode编码和GB2312编码,详细的使用请参考docs目录下的TOOLS.md文档