【ST NUCLEO-C031C6开发板测评】移植SSD1306 OLED显示库及支持中文显示
<div class='showpostmsg'><p><strong>一、驱动SSD1306 OLED遇到的问题</strong></p><p>Zephyr提供的Display Interface,已经支持了SSD1306。</p>
<p>并且,在samples中,还提供了驱动演示:<a href="https://docs.zephyrproject.org/latest/samples/drivers/display/README.html">Display — Zephyr Project Documentation</a></p>
<p>也提供了LVGL演示,同样能够使用SSD1306:<a href="https://docs.zephyrproject.org/latest/samples/subsys/display/lvgl/README.html">LVGL basic sample — Zephyr Project Documentation</a></p>
<p>然后,再实际编译的过程中,会遇到:</p>
<p> </p>
<p> </p>
<p>核心在于,STM32C0资源实在有限:</p>
<p> </p>
<p> </p>
<p><strong>二、想办法解决问题</strong></p>
<p>经过一番研究,最终使用Zephyr自身设备驱动功能,驱动SSD1306,然后外挂一个SSD1306显示库,用于实际显示处理。</p>
<p>系统显示部分的功能,最小化调用,其他需要的自己代码中处理。</p>
<p> </p>
<p><strong>三、实际操作过程</strong></p>
<p>1. 硬件连接</p>
<p>我使用的是I2C接口的SSD1306,所以接线和 <a href="https://bbs.eeworld.com.cn/thread-1271959-1-1.html">【ST NUCLEO-C031C6开发板测评】I2C获取温湿度传感器SHT30数据 </a>中的一样I2C接口即可:</p>
<p><img src="https://bbs.eeworld.com.cn/data/attachment/forum/202402/18/173021rlxgdppvnwiqve5q.png.thumb.jpg" /></p>
<p> </p>
<p>2. dts配置修改</p>
<p>修改 zephyr/boards/arm/nucleo_c031c6/arduino_r3_connector.dtsi,启用arduino_i2c支持:</p>
<p> </p>
<p> </p>
<p>3. 测试工程</p>
<p>参考 samples/drivers/display 这个实例进行,但是需要大刀阔斧的修改,贴出所有文件吧:</p>
<p>prj.conf</p>
<pre>
<code class="language-json"># 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</code></pre>
<p> </p>
<p>CMakeLists.txt</p>
<pre>
<code class="language-bash"># 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)
</code></pre>
<p> </p>
<p>main_ssd1306.c:</p>
<pre>
<code class="language-cpp">#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;
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);
// 设置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;
}
</code></pre>
<p> </p>
<p>在上面的main代码中,调用了ssd1306_i2c库,可以从:<a href="https://github.com/HonestQiao/ssd1306_i2c/tree/zephyr">HonestQiao/ssd1306_i2c at zephyr (github.com)</a> 下载(zephyr分支),放到 samples/drivers/display/src/ssd1306_i2c。</p>
<p>该库来源于:<a href="https://github.com/iliapenev/ssd1306_i2c">iliapenev/ssd1306_i2c: SSD1306 i2c driver for Raspberry Pi (github.com)</a>,使用了buffer来处理来处理。</p>
<p>而zephry中的display,也使用了buffer进行处理,经过仔细研究,把两者的buffer关联到一起,就能够正常使用了。</p>
<p>另外:</p>
<ul>
<li>在 ssd1306_i2c/oled_fonts_cn.h 中,提供了仅包含“祝大家龙年吉利”的汉字字库,可以根据实际需要方便的定制。</li>
<li>在 ssd1306_i2c/ssd1306_addon.c/h 中,提供了显示汉字字符串的处理。</li>
<li>在 ssd1306_i2c/ssd1306_addon.c/h 中,还添加了 ssd1306_setCursor(),可以设置英文字符自动换行显示的起始坐标。</li>
</ul>
<p>oled_fonts_cn.h:</p>
<pre>
<code class="language-cpp">#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 = "祝大家龙年吉利";
static const unsigned char font_cn_16x16 PROGMEM = {
/* [字库]: [数据排列]:从左到右从上到下 [取模方式]:横向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
</code></pre>
<p> </p>
<p>ssd1306_addon.c:</p>
<pre>
<code class="language-cpp">#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 : 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;
}
}
}
}</code></pre>
<p> </p>
<p>为了最小限度的减少对原库的侵入,在main中定义了一个显示函数:</p>
<pre>
<code class="language-cpp">void ssd1306_displayShow()
{
display_write(display_dev, 0, 0, &buf_desc, buffer);
}</code></pre>
<p>以替代原有的ssd1306_display()。</p>
<p> </p>
<p>最终,编译出来的结果如下:</p>
<p> </p>
<p> </p>
<p>FLASH所剩无几,RAM还剩余不少。</p>
<p> </p>
<p><strong>四、实际效果</strong></p>
<p>编译下载到开发板,运行效果如下:</p>
<p>ea4a2e8a15742f34bbc9be70b6a39d97<br />
</p>
<p><strong>五、参考资料</strong></p>
<ul>
<li><a href="https://github.com/iliapenev/ssd1306_i2c">iliapenev/ssd1306_i2c: SSD1306 i2c driver for Raspberry Pi (github.com)</a></li>
<li><a href="https://github.com/HonestQiao/ssd1306_i2c/tree/zephyr">HonestQiao/ssd1306_i2c at zephyr (github.com)</a></li>
<li><a href="https://blog.csdn.net/innovvvvvation/article/details/123294490">0.96寸oled屏幕在任意区域画点_oled画点函数-CSDN博客</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/624251644">STM32的HAL第三方库 - SSD1306 OLED屏幕 - 知乎 (zhihu.com)</a></li>
<li><a href="https://www.zhihu.com/tardis/zm/art/624251644?source_id=1003">STM32的HAL第三方库 - SSD1306 OLED屏幕 (zhihu.com)</a></li>
<li><a href="http://www.yizu.org/archives/824/">51单片机(stc15f104w)驱动oled显示模块(ssd1306) - 爱易族 (yizu.org)</a></li>
<li><a href="https://blog.csdn.net/whqsz/article/details/79308526">Arduino SSD1306 OLED汉字显示例程_arduino ssd1306滚动显示中文-CSDN博客</a></li>
<li><a href="https://blog.csdn.net/qq_41954556/article/details/123761411">OpenHarmony学习笔记——I2C驱动0.96OLED屏幕_openharmony i2c-CSDN博客</a></li>
<li><a href="https://github.com/xusiwei/ohos-ssd1306/tree/master">xusiwei/ohos-ssd1306: SSD1306 OLED driver for OpenHarmony OS(OHOS) (github.com)</a></li>
<li><a href="https://www.23bei.com/tool/223.html">LCD/OLED汉字字模提取软件,(HZK12宋体)GB2312中文12*12点阵字库 (23bei.com)</a></li>
</ul>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script> 本帖最后由 lugl4313820 于 2024-2-19 20:19 编辑
<p>外挂128M的flash可以吗?帮主的再试一下DMA呗!体验飞一样的速度!</p>
页:
[1]