HonestQiao 发表于 2024-2-19 16:34

【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 &mdash; Zephyr Project Documentation</a></p>

<p>也提供了LVGL演示,同样能够使用SSD1306:<a href="https://docs.zephyrproject.org/latest/samples/subsys/display/lvgl/README.html">LVGL basic sample &mdash; Zephyr Project Documentation</a></p>

<p>然后,再实际编译的过程中,会遇到:</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>核心在于,STM32C0资源实在有限:</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p><strong>二、想办法解决问题</strong></p>

<p>经过一番研究,最终使用Zephyr自身设备驱动功能,驱动SSD1306,然后外挂一个SSD1306显示库,用于实际显示处理。</p>

<p>系统显示部分的功能,最小化调用,其他需要的自己代码中处理。</p>

<p>&nbsp;</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>&nbsp;</p>

<p>2. dts配置修改</p>

<p>修改 zephyr/boards/arm/nucleo_c031c6/arduino_r3_connector.dtsi,启用arduino_i2c支持:</p>

<p> &nbsp;</p>

<p>&nbsp;</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>&nbsp;</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>&nbsp;</p>

<p>main_ssd1306.c:</p>

<pre>
<code class="language-cpp">#include &lt;zephyr/logging/log.h&gt;
LOG_MODULE_REGISTER(sample, LOG_LEVEL_INF);

#include &lt;zephyr/kernel.h&gt;
#include &lt;zephyr/device.h&gt;
#include &lt;zephyr/drivers/display.h&gt;

#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, &amp;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-&gt;name);

                return 0;
        }

        LOG_INF("Display sample for %s", display_dev-&gt;name);
        display_get_capabilities(display_dev, &amp;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 &lt; 64; y+=2)
                {
                        for (x = 0; x &lt; 128; x += 2)
                        {
                                ssd1306_drawPixel(x, y, WHITE);
                                ssd1306_displayShow();
                                k_msleep(1);
                        }
                }
                status = !status;
        }

        return 0;
}
</code></pre>

<p>&nbsp;</p>

<p>在上面的main代码中,调用了ssd1306_i2c库,可以从:<a href="https://github.com/HonestQiao/ssd1306_i2c/tree/zephyr">HonestQiao/ssd1306_i2c at zephyr (github.com)</a>&nbsp;下载(zephyr分支),放到&nbsp;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>在&nbsp;ssd1306_i2c/oled_fonts_cn.h 中,提供了仅包含&ldquo;祝大家龙年吉利&rdquo;的汉字字库,可以根据实际需要方便的定制。</li>
        <li>在&nbsp;ssd1306_i2c/ssd1306_addon.c/h&nbsp;中,提供了显示汉字字符串的处理。</li>
        <li>在&nbsp;ssd1306_i2c/ssd1306_addon.c/h&nbsp;中,还添加了 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>&nbsp;</p>

<p>ssd1306_addon.c:</p>

<pre>
<code class="language-cpp">#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

#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 &gt; SSD1306_LCDWIDTH || y + h &gt; 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 &lt;= SSD1306_LCDWIDTH ? w : SSD1306_LCDWIDTH);
        h = (h &lt;= SSD1306_LCDHEIGHT ? h : SSD1306_LCDHEIGHT);
        stride = (stride == 0 ? w : stride);

        uint8_t rows = size * 8 / stride;
        for (uint8_t i = 0; i &lt; rows; i++)
        {
                uint32_t base = i * stride / 8;
                for (uint8_t j = 0; j &lt; w; j++)
                {
                        uint32_t idx = base + (j / 8);
                        uint8_t byte = idx &lt; size ? data : 0;
                        uint8_t bit = byte &amp; (0x80 &gt;&gt; (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 &lt; end; i += FONT_CN_CHAR_WIDTH)
        {
                for (uint32_t n = 0; n &lt; 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>&nbsp;</p>

<p>为了最小限度的减少对原库的侵入,在main中定义了一个显示函数:</p>

<pre>
<code class="language-cpp">void ssd1306_displayShow()
{
        display_write(display_dev, 0, 0, &amp;buf_desc, buffer);
}</code></pre>

<p>以替代原有的ssd1306_display()。</p>

<p>&nbsp;</p>

<p>最终,编译出来的结果如下:</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>FLASH所剩无几,RAM还剩余不少。</p>

<p>&nbsp;</p>

<p><strong>四、实际效果</strong></p>

<p>编译下载到开发板,运行效果如下:</p>

<p>ea4a2e8a15742f34bbc9be70b6a39d97<br />
&nbsp;</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学习笔记&mdash;&mdash;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:17

本帖最后由 lugl4313820 于 2024-2-19 20:19 编辑

<p>外挂128M的flash可以吗?帮主的再试一下DMA呗!体验飞一样的速度!</p>
页: [1]
查看完整版本: 【ST NUCLEO-C031C6开发板测评】移植SSD1306 OLED显示库及支持中文显示