一、驱动SSD1306 OLED遇到的问题
Zephyr提供的Display Interface,已经支持了SSD1306。
并且,在samples中,还提供了驱动演示:Display — Zephyr Project Documentation
也提供了LVGL演示,同样能够使用SSD1306:LVGL basic sample — Zephyr Project Documentation
然后,再实际编译的过程中,会遇到:
核心在于,STM32C0资源实在有限:
二、想办法解决问题
经过一番研究,最终使用Zephyr自身设备驱动功能,驱动SSD1306,然后外挂一个SSD1306显示库,用于实际显示处理。
系统显示部分的功能,最小化调用,其他需要的自己代码中处理。
三、实际操作过程
1. 硬件连接
我使用的是I2C接口的SSD1306,所以接线和 【ST NUCLEO-C031C6开发板测评】I2C获取温湿度传感器SHT30数据 中的一样I2C接口即可:
2. dts配置修改
修改 zephyr/boards/arm/nucleo_c031c6/arduino_r3_connector.dtsi,启用arduino_i2c支持:
3. 测试工程
参考 samples/drivers/display 这个实例进行,但是需要大刀阔斧的修改,贴出所有文件吧:
prj.conf
# CONFIG_HEAP_MEM_POOL_SIZE=16384
CONFIG_HEAP_MEM_POOL_SIZE=2048
CONFIG_LOG=n
CONFIG_DISPLAY=y
CONFIG_BOOT_BANNER=n
CONFIG_DEBUG=y
CMakeLists.txt
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(display)
# set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -s")
target_sources(app PRIVATE src/main_ssd1306.c src/ssd1306_i2c/ssd1306_i2c.c src/ssd1306_i2c/ssd1306_addon.c)
main_ssd1306.c:
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sample, LOG_LEVEL_INF);
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/display.h>
#include "ssd1306_i2c/ssd1306_i2c.h"
#include "ssd1306_i2c/ssd1306_addon.h"
extern int buffer[SSD1306_LCDWIDTH * SSD1306_LCDHEIGHT / 8];
const struct device *display_dev;
struct display_capabilities capabilities;
struct display_buffer_descriptor buf_desc;
void ssd1306_displayShow()
{
display_write(display_dev, 0, 0, &buf_desc, buffer);
}
int main(void)
{
size_t x;
size_t y;
size_t buf_size = 0;
display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(display_dev))
{
LOG_ERR("Device %s not found. Aborting sample.",
display_dev->name);
return 0;
}
LOG_INF("Display sample for %s", display_dev->name);
display_get_capabilities(display_dev, &capabilities);
buf_size = sizeof(buffer) / sizeof(buffer[0]);
// 设置buf
buf_desc.buf_size = buf_size;
buf_desc.pitch = SSD1306_LCDWIDTH;
buf_desc.width = SSD1306_LCDWIDTH;
buf_desc.height = SSD1306_LCDHEIGHT;
display_blanking_off(display_dev);
x = 0;
y = 0;
ssd1306_displayShow();
k_msleep(2000);
ssd1306_clearDisplay();
ssd1306_setCursor(50,20);
char *text = "This is demo for SSD1306 i2c driver for Raspberry Pi";
ssd1306_drawString(text);
ssd1306_displayShow();
k_msleep(2000);
ssd1306_clearDisplay();
ssd1306_drawChinese(20, 20, "祝大家龙年");
ssd1306_drawChinese(30, 40, "大吉大利");
ssd1306_displayShow();
k_msleep(2000);
bool status = true;
ssd1306_clearDisplay();
while (1)
{
for (y = 0; y < 64; y+=2)
{
for (x = 0; x < 128; x += 2)
{
ssd1306_drawPixel(x, y, WHITE);
ssd1306_displayShow();
k_msleep(1);
}
}
status = !status;
}
return 0;
}
在上面的main代码中,调用了ssd1306_i2c库,可以从:HonestQiao/ssd1306_i2c at zephyr (github.com) 下载(zephyr分支),放到 samples/drivers/display/src/ssd1306_i2c。
该库来源于:iliapenev/ssd1306_i2c: SSD1306 i2c driver for Raspberry Pi (github.com),使用了buffer来处理来处理。
而zephry中的display,也使用了buffer进行处理,经过仔细研究,把两者的buffer关联到一起,就能够正常使用了。
另外:
- 在 ssd1306_i2c/oled_fonts_cn.h 中,提供了仅包含“祝大家龙年吉利”的汉字字库,可以根据实际需要方便的定制。
- 在 ssd1306_i2c/ssd1306_addon.c/h 中,提供了显示汉字字符串的处理。
- 在 ssd1306_i2c/ssd1306_addon.c/h 中,还添加了 ssd1306_setCursor(),可以设置英文字符自动换行显示的起始坐标。
oled_fonts_cn.h:
#ifndef OLED_FONTS_CN_H
#define OLED_FONTS_CN_H
#define PROGMEM
/**
* 汉字字模在线: https://www.23bei.com/tool-223.html
* 数据排列:从左到右从上到下
* 取模方式:横向8位左高位
**/
#define FONT_CN_CHAR_NUM 7
#define FONT_CN_CHAR_WIDTH 3
#define FONT_CN_CHAR_BIT_NUM 32
static const char font_cn_chars[FONT_CN_CHAR_NUM * FONT_CN_CHAR_WIDTH] = "祝大家龙年吉利";
static const unsigned char font_cn_16x16[FONT_CN_CHAR_NUM * FONT_CN_CHAR_BIT_NUM] PROGMEM = {
/* [字库]:[HZK1616宋体] [数据排列]:从左到右从上到下 [取模方式]:横向8点左高位 [正负反色]:否 [去掉重复后]共7个字符
[总字符库]:"祝大家龙年吉利"*/
/*-- ID:0,字符:"祝",ASCII编码:D7A3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x20, 0x08, 0x13, 0xFC, 0x12, 0x08, 0x02, 0x08, 0xFE, 0x08, 0x0A, 0x08, 0x12, 0x08, 0x3B, 0xF8,
0x56, 0xA8, 0x90, 0xA0, 0x10, 0xA0, 0x11, 0x20, 0x11, 0x22, 0x12, 0x22, 0x14, 0x1E, 0x18, 0x00,
/*-- ID:1,字符:"大",ASCII编码:B4F3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x04, 0xFF, 0xFE, 0x01, 0x00, 0x02, 0x80,
0x02, 0x80, 0x02, 0x40, 0x04, 0x40, 0x04, 0x20, 0x08, 0x10, 0x10, 0x0E, 0x60, 0x04, 0x00, 0x00,
/*-- ID:2,字符:"家",ASCII编码:BCD2,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x02, 0x00, 0x01, 0x00, 0x7F, 0xFE, 0x40, 0x02, 0x80, 0x04, 0x3F, 0xF8, 0x04, 0x00, 0x1A, 0x10,
0x63, 0x30, 0x05, 0x40, 0x19, 0x80, 0x63, 0x40, 0x05, 0x30, 0x19, 0x0E, 0x65, 0x04, 0x02, 0x00,
/*-- ID:3,字符:"龙",ASCII编码:C1FA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x02, 0x00, 0x02, 0x40, 0x02, 0x20, 0x02, 0x04, 0xFF, 0xFE, 0x02, 0x80, 0x02, 0x88, 0x04, 0x88,
0x04, 0x90, 0x04, 0xA0, 0x08, 0xC0, 0x08, 0x82, 0x11, 0x82, 0x16, 0x82, 0x20, 0x7E, 0x40, 0x00,
/*-- ID:4,字符:"年",ASCII编码:C4EA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x08, 0x00, 0x08, 0x08, 0x1F, 0xFC, 0x11, 0x00, 0x21, 0x00, 0x41, 0x10, 0x1F, 0xF8, 0x11, 0x00,
0x11, 0x00, 0x11, 0x04, 0xFF, 0xFE, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
/*-- ID:5,字符:"吉",ASCII编码:BCAA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x01, 0x00, 0x01, 0x00, 0x01, 0x04, 0xFF, 0xFE, 0x01, 0x00, 0x01, 0x00, 0x01, 0x10, 0x3F, 0xF8,
0x00, 0x00, 0x00, 0x10, 0x1F, 0xF8, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F, 0xF0, 0x10, 0x10,
/*-- ID:6,字符:"利",ASCII编码:C0FB,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
0x01, 0x04, 0x07, 0x84, 0x7C, 0x04, 0x04, 0x24, 0x04, 0x24, 0x05, 0x24, 0xFF, 0xA4, 0x0C, 0x24,
0x0E, 0x24, 0x15, 0xA4, 0x14, 0xA4, 0x24, 0x24, 0x44, 0x04, 0x04, 0x04, 0x04, 0x14, 0x04, 0x08
};
#endif
ssd1306_addon.c:
#include <stdio.h>
#include <string.h>
#include "ssd1306_i2c.h"
#include "oled_fonts_cn.h"
extern int cursor_y;
extern int cursor_x;
void ssd1306_setCursor(uint8_t x, uint8_t y)
{
cursor_x = x;
cursor_y = y;
}
void ssd1306_drawRegion(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *data, uint32_t size, uint32_t stride)
{
if (x + w > SSD1306_LCDWIDTH || y + h > SSD1306_LCDHEIGHT || w * h == 0)
{
fprintf(stderr, "%dx%d @ %d,%d out of range or invalid!\r\n", w, h, x, y);
return;
}
w = (w <= SSD1306_LCDWIDTH ? w : SSD1306_LCDWIDTH);
h = (h <= SSD1306_LCDHEIGHT ? h : SSD1306_LCDHEIGHT);
stride = (stride == 0 ? w : stride);
uint8_t rows = size * 8 / stride;
for (uint8_t i = 0; i < rows; i++)
{
uint32_t base = i * stride / 8;
for (uint8_t j = 0; j < w; j++)
{
uint32_t idx = base + (j / 8);
uint8_t byte = idx < size ? data[idx] : 0;
uint8_t bit = byte & (0x80 >> (j % 8));
ssd1306_drawPixel(x + j, y + i, bit ? WHITE : BLACK);
}
}
}
void ssd1306_drawChinese(uint8_t x, uint8_t y, char *str)
{
const uint32_t W = 16, H = 16, S = 16;
for (uint32_t i = 0, end=strlen(str); i < end; i += FONT_CN_CHAR_WIDTH)
{
for (uint32_t n = 0; n < FONT_CN_CHAR_NUM; n++)
{
if (strncmp(str+i, font_cn_chars+n*FONT_CN_CHAR_WIDTH, FONT_CN_CHAR_WIDTH)==0)
{
// 字符匹配成功,获取字体数据
ssd1306_drawRegion(x + i / FONT_CN_CHAR_WIDTH * W, y, W, H, font_cn_16x16 + n * FONT_CN_CHAR_BIT_NUM, FONT_CN_CHAR_BIT_NUM, S);
break;
}
}
}
}
为了最小限度的减少对原库的侵入,在main中定义了一个显示函数:
void ssd1306_displayShow()
{
display_write(display_dev, 0, 0, &buf_desc, buffer);
}
以替代原有的ssd1306_display()。
最终,编译出来的结果如下:
FLASH所剩无几,RAM还剩余不少。
四、实际效果
编译下载到开发板,运行效果如下:
4622_1708331393
五、参考资料