本文将会分别介绍——使用软件I2C和硬件I2C在PSoC开发板上点亮OLED屏,并进行屏幕刷新率对比测试,最后还会在硬件I2C的基础上继续优化屏幕刷新率。本文实验使用的OLED屏尺寸为0.96寸,分辨率为128x64,驱动芯片为SSD1306。本文使用的开发环境为RT-Thread Studio,设备上运行的是RT-Thread实时系统。本文主旨在于,介绍如何在PSoC开发板上使用软件I2C和硬件硬件I2C驱动外设,以及对于屏幕刷新率优化的一些思路。
如需离线阅读,可以下载本文的完整pdf版本:
*附件:【英飞凌PSoC 6 RTT开发板试用】使用软件和硬件I2C点亮OLED屏,帧率从2FPS提升到51 53b89082947a409aaa80d410ab5527ca.pdf
开始之前,需要准备实验所需的硬件和软件,接下来分别介绍。
本次实验需要用到的硬件有:
本次实验需要使用的软件主要为:
假设你已经成功在电脑上安装了以上这些软件。
硬件连接分为两部分,一部分是PC和开发板,通过USB Type-C线连接;这个没啥难度,不做过多介绍;需要注意的是,开发板一端接DAP口;否则无法正常下载程序。
另外一部分是,开发板和OLED屏幕之间的连接,具体如下表所示:
OLED屏引脚 | 开发板引脚 |
---|---|
SDA | SDA |
SCL | SCL |
GND | GND |
VCC | 3V3 |
开发板和OLED屏幕之间的硬件连接,如下图所示:
这么连接之后,如果主控芯片使用软件I2C驱动OLED屏,那么什么限制,对应管脚只需要使用GPIO模拟I2C时序即可。如果想要让主控芯片使用硬件I2C驱动OLED屏,则需要检查一下主控芯片对应引脚可以设置为硬件I2C功能,接下来即为检查的过程。
首先,检查开发板原理图的Arduino接口部分:
这里只能看到标号,看不到主控芯片的引脚名称。
所以,还需要继续搜索这两个引脚的标号,找到主控芯片对应的引脚标号:
对照这两处可以知道——Arduino接口I2C引脚和主控芯片直接的连接关系为:
《PSoC 6 MCU: CY8C62x8, CY8C62xA Datasheet》文档的 Pinouts 章节,Table 8. Multiple Alternate Functions 引脚功能服用表,可以查到P8.0和P8.1的功能有:
可以看到,有scb[4].i2c_scl和scb[4].i2c_sda功能。
也就是说,P8.0和P8.1可以设置为硬件I2C功能。
接下来,将使用RT-Thread Studio创建项目,并通过添加软件包和修改配置的方式,实现使用软件I2C驱动OLED屏幕。
在RT-Thread Studio中,打开“文件”→“新建”→”RT-Thread项目”菜单,如下图所示:
在弹出的创建项目界面中,Project name中填入psoc6_oled,选中基于开发板的项目,如下图所示:
点击“完成”,即可创建名为psoc6_oled的项目。
创建项目后,双击项目资源管理器视图中,项目下方的“RT-Thread Settings”,主编辑区如下图所示:
点击其中的“添加软件包”,弹出的软件包搜索界面,如下图所示:
按照图中标注的操作顺序,即可将ssd1306软件包添加到当前项目。
添加完成后,主编辑区如下图所示:
此时,按Ctrl+S快捷键,保存对项目配置的修改。如果网络通常,则会在控制台窗口中看到ssd1306软件包正常下载的日志:
这样,ssd1306软件包就成功添加到项目中了,位于packages子目录下:
接下来,在RT-Thread Studio主编辑器,点击详细配置按钮,按钮位置如下图所示:
主编辑器将会显示详细配置:
切换到“硬件”标签页,找到“Enable Software I2C”选项,并打开该选项,如下图所示:
接着,打开“使能I2C1 BUS”,并将scl和sda中分别改为64和65,如下图所示:
然后,在搜索框输入ssd1306,弹出悬浮菜单后,单击该悬浮菜单,如下图所示:
勾选“Enable debug log output”和“Enable ssd1306 sample”,如下图所示:
最后,按Ctrl+S保存对所有配置项的修改。
首先,点击工具栏的锤子图标,或者按Ctrl+B快捷键,触发项目构建(全部编译):
项目构建完成后,可以在控制台窗口看到生成了elf文件,以及预计Flash和RAM占用情况:
接着,点击工具栏上的下载图标,或者Ctrl+Alt+D快捷键,触发下载程序二进制文件到开发板上,如下图:
下载过程中以及下载完成后,控制台窗都可以看到日志输出:
PS:开始下载之前,需要确认开发板以及和PC正确连接了(开发板要连在DAP口上,并能够正常识别)。
为了方便在串口中进行命令控制,运行之前,需要先打开MobaXterm(或者其他串口调试工具):
如上图所示,选中对应的COM号,串口参数设置为:
之后,点OK确认连接。
连接成功后,按开发板的复位键,可以看到串口连接中输出:
此时输入help命令并回车:
可以看到,有ssd1306_TestAll命令。
输入ssd1306_TestAll命令并回车,如无意外,将会看到OLED屏幕上已经有画面显示了:
但是此时的帧率较低,测试显示仅有2帧每秒:
方便起见,接下来将不再创建新项目,而是在刚刚创建的RT-Thread Studio项目上进行修改,通过修改配置的方式,实现使用硬件I2C驱动OLED屏幕。
RT-Thread Studio默认创建的项目不支持I2C4,不能实现硬件I2C驱动OLED。因此,需要先添加I2C4配置和代码,才能进行后续操作。
首先,修改 board/Kconfig 文件,在config BSP_USING_HW_I2C6之前添加如下代码行:
config BSP_USING_HW_I2C4
bool "Enable I2C4 Bus (Arduino I2C)"
default n
if BSP_USING_HW_I2C4
comment "Notice: P8_0 -- > 64; P8_1 -- > 65"
config BSP_I2C4_SCL_PIN
int "i2c4 SCL pin number"
range 1 113
default 64
config BSP_I2C4_SDA_PIN
int "i2c4 SDA pin number"
range 1 113
default 65
endif
接着,修改 libraries/HAL_Drivers/SConscript 文件,找到 src += ['drv_i2c.c'] 前一行,添加一个条件:
if GetDepend('BSP_USING_HW_I2C3') or GetDepend('BSP_USING_HW_I2C4') or GetDepend('BSP_USING_HW_I2C6'):
最后,修改 libraries/HAL_Drivers/drv_i2c.c 文件,具体修改内容为:
--- a/libraries/HAL_Drivers/drv_i2c.c
+++ b/libraries/HAL_Drivers/drv_i2c.c
@@ -11,7 +11,7 @@
#include "board.h"
#if defined(RT_USING_I2C)
-#if defined(BSP_USING_HW_I2C3) || defined(BSP_USING_HW_I2C6)
+#if defined(BSP_USING_HW_I2C3) || defined(BSP_USING_HW_I2C4) || defined(BSP_USING_HW_I2C6)
#include
#ifndef I2C3_CONFIG
@@ -22,7 +22,16 @@
.sda_pin = BSP_I2C3_SDA_PIN, \\\\\\\\
}
#endif /* I2C3_CONFIG */
-#endif
+
+#ifndef I2C4_CONFIG
+#define I2C4_CONFIG \\\\\\\\
+ { \\\\\\\\
+ .name = "i2c4", \\\\\\\\
+ .scl_pin = BSP_I2C4_SCL_PIN, \\\\\\\\
+ .sda_pin = BSP_I2C4_SDA_PIN, \\\\\\\\
+ }
+#endif /* I2C4_CONFIG */
+
#ifndef I2C6_CONFIG
#define I2C6_CONFIG \\\\\\\\
{ \\\\\\\\
@@ -32,11 +41,16 @@
}
#endif /* I2C6_CONFIG */
+#endif /* defined(BSP_USING_I2C1) || defined(BSP_USING_I2C2) */
+
enum
{
#ifdef BSP_USING_HW_I2C3
I2C3_INDEX,
#endif
+#ifdef BSP_USING_HW_I2C4
+ I2C4_INDEX,
+#endif
#ifdef BSP_USING_HW_I2C6
I2C6_INDEX,
#endif
@@ -63,6 +77,10 @@ static struct ifx_i2c_config i2c_config[] =
I2C3_CONFIG,
#endif
+#ifdef BSP_USING_HW_I2C4
+ I2C4_CONFIG,
+#endif
+
#ifdef BSP_USING_HW_I2C6
I2C6_CONFIG,
#endif
@@ -145,8 +163,7 @@ void HAL_I2C_Init(struct ifx_i2c *obj)
int rt_hw_i2c_init(void)
{
- rt_err_t result;
- cyhal_i2c_t mI2C;
+ rt_err_t result = RT_EOK;
for (int i = 0; i < sizeof(i2c_config) / sizeof(i2c_config[0]); i++)
{
@@ -157,8 +174,6 @@ int rt_hw_i2c_init(void)
i2c_objs[i].mI2C_cfg.address = 0;
i2c_objs[i].mI2C_cfg.frequencyhal_hz = (400000UL);
- i2c_objs[i].mI2C = mI2C;
-
i2c_objs[i].i2c_bus.ops = &i2c_ops;
HAL_I2C_Init(&i2c_objs[i]);
@@ -171,4 +186,4 @@ int rt_hw_i2c_init(void)
}
INIT_DEVICE_EXPORT(rt_hw_i2c_init);
-#endif /* defined(BSP_USING_I2C1) || defined(BSP_USING_I2C2) */
+#endif /* RT_USING_I2C */
首先,打开RT-Thread Settings的详细配置,切换到硬件标签页,关闭“Enable Software I2C Bus”配置项,如下图所示:
接着,打开“Enable Hardware I2C Bus”配置项,再打开其中的“Enable I2C4 Bus (Arduino I2C)”配置项,如下图所示:
最后,再次搜索ssd1306,点击悬浮菜单,跳回到ssd1306配置,修改I2C bus name为 i2c4 ,如下图所示:
完成修改后,按Ctrl+S保存配置。
重新Ctrl+B编译,Ctrl+Alt+D下载,按RESET键复位之后,重新运行 ssd1306_TestAll 命令,可以看到,OLED屏幕成功显示画面了。
并且,这一次测得的帧率为7fps,如下图所示:
这次显示画面快了很多,之前只有2fps,现在已经7fps了;但还是不够高,还可以继续优化。
接下来继续优化,提高帧率,提高帧率的思路:
接下来,分别从这两方面尝试提升帧率。
首先是提升I2C频率,默认的速度写在drv_i2c.c代码里面:
i2c_objs[i].mI2C_cfg.frequencyhal_hz = (400000UL); // 400K
查阅芯片手册,可以知道CY8C624ABZI芯片I2C最高支持1Mbps:
将drv_i2c.c里面的默认I2C频率改为1M之后,测试显示的帧率为12fps:
查看ssd1306.c文件,发现可以优化的代码:
// Send data
void ssd1306_WriteData(uint8_t* buffer, size_t buff_size)
{
#if PKG_USING_SSD1306_HW_I2C
HAL_I2C_Mem_Write(&SSD1306_I2C_PORT, SSD1306_I2C_ADDR, 0x40, 1, buffer, buff_size, HAL_MAX_DELAY);
#else
for (int i = 0; i < buff_size; i++)
{
uint8_t buf[2] = {SSD1306_CTRL_DATA, buffer[i]};
rt_i2c_master_send(i2c_bus, SSD1306_I2C_ADDR, RT_I2C_WR, buf, 2);
}
#endif
}
这段代码中,PKG_USING_SSD1306_HW_I2C 宏内部的分支是STM32的代码,不用关系,主要看下面的分支;
下面的分支,使用了一个for循环,调用rt_i2c_master_send接口,每次却只发送两个字节,存在重复的大量的SSD1306_CTRL_DATA字节。
查询SSD1306数据手册,可以知道,它是支持在一个数据标记字节之后,连续发送数据的:
相应的可以优化为:
// Send data
void ssd1306_WriteData(uint8_t* buffer, size_t buff_size)
{
uint32_t len = buff_size + 1;
uint8_t* data = rt_malloc(len); // 申请一个更大的缓冲
RT_ASSERT(data);
// 准备数据
data[0] = SSD1306_CTRL_DATA;
rt_memcpy(&data[1], buffer, buff_size);
// 发送
rt_i2c_master_send(i2c_bus, SSD1306_I2C_ADDR, RT_I2C_WR, data, len);
rt_free(data); // 释放
}
这段代码修改之后,再次运行,测得帧率为45fps:
更进一步检查,发现还有优化空间:
void ssd1306_UpdateScreen(void)
{
// Write data to each page of RAM. Number of pages
// depends on the screen height:
//
// * 32px == 4 pages
// * 64px == 8 pages
// * 128px == 16 pages
for(uint8_t i = 0; i < SSD1306_HEIGHT/8; i++)
{
ssd1306_WriteCommand(0xB0 + i); // Set the current RAM page address.
ssd1306_WriteCommand(0x00);
ssd1306_WriteCommand(0x10);
ssd1306_WriteData(&SSD1306_Buffer[SSD1306_WIDTH*i],SSD1306_WIDTH);
}
}
这个函数里面,从注释可以知道,对于128x64分辨率的屏,会分为8页进行发送;每个页面发送开始的时候,会调用三次ssd1306_WriteCommand函数。
实际上可以不需要重复调用ssd1306_WriteCommand函数:
void ssd1306_UpdateScreen(void)
{
uint8_t cmd[] = {
0X21, // 设置列起始和结束地址
0X00, // 列起始地址 0
0X7F, // 列终止地址 127
0X22, // 设置页起始和结束地址
0X00, // 页起始地址 0
0X07, // 页终止地址 7
};
uint32_t count = 0;
uint8_t data[2 * sizeof(cmd) + 1 + SSD1306_BUFFER_SIZE ] = {};
// copy cmd
for (uint32_t i = 0; i < sizeof(cmd)/sizeof(cmd[0]); i++) {
data[count++] = SSD1306_CTRL_CMD | SSD1306_MASK_CONT;
data[count++] = cmd[i];
}
// copy frame data
data[count++] = SSD1306_CTRL_DATA;
memcpy(&data[count], SSD1306_Buffer, sizeof(SSD1306_Buffer));
count += sizeof(SSD1306_Buffer);
// send to i2c bus
rt_i2c_master_send(i2c_bus, SSD1306_I2C_ADDR, RT_I2C_WR, data, count);
}
非常快了,已经是最初2fps的25倍。
最终达到的帧率并没有达到OLED屏幕的物理极限,可能还存在一些优化空间,感兴趣的读者还可以在此基础上继续探索。
文章就到这里,感谢阅读,下次再会~
全部0条评论
快来发表一下你的评论吧 !