概述
本章旨在通过 I²C 接口驱动 OLED 显示屏(常见型号如 SSD1306),实现图形和文本的显示功能。OLED 屏幕具有低功耗、自发光、对比度高等优点,适用于嵌入式人机界面显示。
最近在瑞萨RA的课程,需要样片的可以加qun申请:925643491。
视频教学
https://www.bilibili.com/video/BV15EMbzGEy7
RA4M2开发IOT(8)----IIC驱动OLED
样品申请
https://www.wjx.top/vm/rCrkUrz.aspx
硬件准备
首先需要准备一个开发板,这里我准备的是自己绘制的开发板,需要的可以进行申请。
主控为R7FA4M2AD3CFL#AA0
同时添加RA4M2_IOT扩展版。
参考程序
https://github.com/CoreMaker-lab/RA4M2_IOT
https://gitee.com/CoreMaker/RA4M2_IOT
修改IIC驱动
若同一个 I²C 总线上挂载多个不同地址的设备,只需在每次通信前调用 R_SCI_I2C_SlaveAddressSet() 设置目标设备地址,即可灵活访问多个设备,无需初始化多个 I²C 控制器实例。
由于LSM6DSV16X的地址是0x6B,所以在读写上添加如下函数。
R_SCI_I2C_SlaveAddressSet(&g_i2c2_ctrl, 0x6B, I2C_MASTER_ADDR_MODE_7BIT);
assert(FSP_SUCCESS == err);
OLED属性配置
查看手册,可以得知OLED的IIC地址为“b0111100” or “b0111101”,即0x3c或者0x3d。
在OLED的模块中,丝印上面写的地址是0x78(b0111 1000)或者0x7a(b0111 1010),是因为加了一位RW位,因为一般只要发送数据让屏幕进行显示。
这里接入的IIC和LSM6DSV16X是一个总线。
移植SSD1306
将用于驱动 OLED 显示屏的相关文件(oled.c, oled.h, oledfont.h, bmp.h)拷贝到当前 e2studio 项目 的 src 目录中。
OLED 驱动代码目前使用的是 基于 GPIO 模拟 I²C(Bit-Banging)方式 驱动 OLED。查看 oled.h 具体表现如下 。
删除 GPIO 宏定义与模拟 I²C 函数 。
通过 typedef 为无符号整型类型定义了简写别名,其中 u8 表示 8 位无符号整型(等同于 uint8_t),u32 表示 32 位无符号整型(等同于 uint32_t)。
typedef __uint8_t u8 ;
typedef __uint16_t u16 ;
typedef __uint32_t u32 ;
删除sys.h引用。
在 oled.c中删除模拟IIC的函数。
删除后如下所示。
在 oled.c最下面关闭IIC初始化。
删除delay.h引用。
添加E2STUDIO的部分头文件和定义。
#include "hal_data.h"
extern fsp_err_t err;
extern int timeout_ms;
extern i2c_master_event_t i2c_event ;
在 oled.c 中新增适用于 RA 平台的 I²C 写入函数, Send_Byte函数修改后如下所示。
//写入一个字节
void Send_Byte(u8 dat)
{
// u8 i;
// for(i=0;i<8;i++)
// {
// if(dat&0x80)//将dat的8位从最高位依次写入
// {
// OLED_SDA_Set();
// }
// else
// {
// OLED_SDA_Clr();
// }
// IIC_delay();
// OLED_SCL_Set();
// IIC_delay();
// OLED_SCL_Clr();//将时钟信号设置为低电平
// dat<<=1;
// }
R_SCI_I2C_SlaveAddressSet(&g_i2c2_ctrl, 0x3C, I2C_MASTER_ADDR_MODE_7BIT);
assert(FSP_SUCCESS == err);
err = R_SCI_I2C_Write(&g_i2c2_ctrl, &dat, 1, true);
assert(FSP_SUCCESS == err);
/* Since there is nothing else to do, block until Callback triggers*/
//while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms)
while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms>0)
{
R_BSP_SoftwareDelay(100U, BSP_DELAY_UNITS_MICROSECONDS);
timeout_ms--;
}
if (I2C_MASTER_EVENT_ABORTED == i2c_event)
{
__BKPT(0);
}
/* Read data back from the I2C slave */
i2c_event = I2C_MASTER_EVENT_ABORTED;
timeout_ms = 100000;
}
OLED_WR_Byte函数写入了地址0x78,然后是0x40或者0x00的数据/命令标志,最后是dat数据。
//发送一个字节
//mode:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 mode)
{
// I2C_Start();
// Send_Byte(0x78);
// I2C_WaitAck();
// if(mode){Send_Byte(0x40);}
// else{Send_Byte(0x00);}
// I2C_WaitAck();
// Send_Byte(dat);
// I2C_WaitAck();
// I2C_Stop();
R_SCI_I2C_SlaveAddressSet(&g_i2c2_ctrl, 0x3C, I2C_MASTER_ADDR_MODE_7BIT);
assert(FSP_SUCCESS == err);
uint8_t ii[2]={0x00,0x00};
if(mode)
ii[0] = 0x40;
else
ii[0] = 0x00;
ii[1] = dat;
err = R_SCI_I2C_Write(&g_i2c2_ctrl, ii, 0x02, true);
assert(FSP_SUCCESS == err);
/* Since there is nothing else to do, block until Callback triggers*/
//while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms)
while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms>0)
{
R_BSP_SoftwareDelay(100U, BSP_DELAY_UNITS_MICROSECONDS);
timeout_ms--;
}
if (I2C_MASTER_EVENT_ABORTED == i2c_event)
{
__BKPT(0);
}
/* Read data back from the I2C slave */
i2c_event = I2C_MASTER_EVENT_ABORTED;
timeout_ms = 100000;
}
OLED_Refresh() 中的模拟 I²C 方式批量写入 OLED 显存内容。
修改如下。
//更新显存到OLED
void OLED_Refresh(void)
{
// u8 i,n;
// for(i=0;i<8;i++)
// {
// OLED_WR_Byte(0xb0+i,OLED_CMD); //设置行起始地址
// OLED_WR_Byte(0x00,OLED_CMD); //设置低列起始地址
// OLED_WR_Byte(0x10,OLED_CMD); //设置高列起始地址
// I2C_Start();
// Send_Byte(0x78);
// I2C_WaitAck();
// Send_Byte(0x40);
// I2C_WaitAck();
// for(n=0;n<128;n++)
// {
// Send_Byte(OLED_GRAM[n][i]);
// I2C_WaitAck();
// }
// I2C_Stop();
// }
uint8_t i, n;
fsp_err_t err;
uint8_t buf[129]; // 控制字节 + 128 字节数据
buf[0] = 0x40; // 控制字节(0x40 表示后续为数据)
for(i = 0; i < 8; i++)
{
OLED_WR_Byte(0xB0 + i, OLED_CMD); // 设置页地址
OLED_WR_Byte(0x00, OLED_CMD); // 设置低列地址
OLED_WR_Byte(0x10, OLED_CMD); // 设置高列地址
for(n = 0; n < 128; n++)
{
buf[n + 1] = OLED_GRAM[n][i]; // 填充数据部分
}
// 设置 OLED I2C 地址(建议只设置一次,若已设置可略过)
R_SCI_I2C_SlaveAddressSet(&g_i2c2_ctrl, 0x3C, I2C_MASTER_ADDR_MODE_7BIT);
// 发送整帧显示数据(129字节)
err = R_SCI_I2C_Write(&g_i2c2_ctrl, buf, sizeof(buf), true);
assert(FSP_SUCCESS == err);
// 等待写入完成
while ((I2C_MASTER_EVENT_TX_COMPLETE != i2c_event) && timeout_ms > 0)
{
R_BSP_SoftwareDelay(100U, BSP_DELAY_UNITS_MICROSECONDS);
timeout_ms--;
}
if (I2C_MASTER_EVENT_ABORTED == i2c_event)
{
__BKPT(); // 出错断点
}
i2c_event = I2C_MASTER_EVENT_ABORTED;
timeout_ms = 100000;
}
}
在hal_entry.c中添加对应头文件。
#include "oled.h"
#include "bmp.h"
添加OLED_Start() 函数 ,初始化OLED。
// OLED 屏幕启动函数:完成初始化、清屏、颜色/方向配置
static void OLED_Start(void)
{
OLED_Init(); // 初始化 OLED 屏幕(发送初始化命令序列,设置工作模式)
OLED_Clear(); // 清空显存(OLED_GRAM),并刷新,使屏幕全黑
OLED_ColorTurn(0); // 设置显示颜色模式:0 为正常显示,1 为反色显示(黑白反转)
OLED_DisplayTurn(0); // 设置显示方向:0 为正常方向,1 为上下翻转显示
}
在主程序中添加。
// 初始化 OLED,保证屏幕点亮且处于默认显示状态
OLED_Start();
字符取模
由于单片机的flash比较吃紧,所以要显示汉字需要先进行取模。首先需要设置为字符模式。
取模软件设置。
设置完之后就可以取自己想要汉字的编码,输入完毕点击生成字模即可。
在oledfont.h中放入字库。
const unsigned char Hzk1[][32]={
{0x84,0x84,0xFC,0x84,0x84,0x40,0x5E,0x50,0x50,0x50,0xDF,0x50,0x50,0x50,0x5E,0x00,0x10,0x30,0x1F,0x08,0x08,0x00,0xFE,0x02,0x02,0x7F,0x02,0x7E,0x02,0x82,0xFE,0x00},/*"瑞",0*/
{0x04,0xE4,0x24,0x24,0xA4,0x6F,0x04,0x24,0x64,0xAF,0x34,0xA4,0x64,0x24,0x04,0x00,0x00,0xFF,0x00,0x11,0x22,0x9C,0x40,0x3F,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00},/*"萨",1*/
{0x00,0x00,0xF8,0x49,0x4A,0x4C,0x48,0xF8,0x48,0x4C,0x4A,0x49,0xF8,0x00,0x00,0x00,0x10,0x10,0x13,0x12,0x12,0x12,0x12,0xFF,0x12,0x12,0x12,0x12,0x13,0x10,0x10,0x00},/*"单",2*/
{0x00,0x00,0x00,0xFE,0x20,0x20,0x20,0x20,0x20,0x3F,0x20,0x20,0x20,0x20,0x00,0x00,0x00,0x80,0x60,0x1F,0x02,0x02,0x02,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x00},/*"片",3*/
{0x10,0x10,0xD0,0xFF,0x90,0x10,0x00,0xFE,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x04,0x03,0x00,0xFF,0x00,0x83,0x60,0x1F,0x00,0x00,0x00,0x3F,0x40,0x40,0x78,0x00},/*"机",4*/
};
OLED_ShowChinese() 函数,用于在 OLED 上显示汉字。代码逻辑已经实现了对不同大小字模的选择,主要依赖 Hzk1[]、Hzk2[]、Hzk3[]、Hzk4[] 这几个数组来获取字模数据。
在OLED_Start添加汉字显示驱动。
// OLED 屏幕启动函数:完成初始化、清屏、颜色/方向配置
static void OLED_Start(void)
{
OLED_Init(); // 初始化 OLED 屏幕(发送初始化命令序列,设置工作模式)
OLED_Clear(); // 清空显存(OLED_GRAM),并刷新,使屏幕全黑
OLED_ColorTurn(0); // 设置显示颜色模式:0 为正常显示,1 为反色显示(黑白反转)
OLED_DisplayTurn(0); // 设置显示方向:0 为正常方向,1 为上下翻转显示
OLED_Clear();//清空 OLED 显存
OLED_ShowChinese(0,0,0,16,1);//在坐标 (0,0) 显示 Hzk1[0],即汉字“瑞”,正显
OLED_ShowChinese(16,0,1,16,1);//在坐标 (16,0) 显示 Hzk1[1],即汉字“萨”,正显
OLED_ShowChinese(32,0,2,16,0);//在坐标 (32,0) 显示 Hzk1[2],即汉字“单”,反色
OLED_ShowChinese(48,0,3,16,0);//在坐标 (48,0) 显示 Hzk1[3],即汉字“片”,反色
OLED_ShowChinese(64,0,4,16,0);//在坐标 (64,0) 显示 Hzk1[4],即汉字“机”,反色
OLED_Refresh();//将上述通过 OLED_ShowChinese() 写入 OLED_GRAM[][] 的内容,批量发送到 OLED 屏幕显示。如果没有调用这个函数,屏幕上不会出现任何内容(即写入显存但未更新)。
}
ASCII显示
OLED_ShowNum 和 OLED_ShowString 的主要作用就是 在 OLED 上显示 ASCII 字符组成的数字或字符串,使用的是内置的 ASCII 字体库(如 oledfont.h 中定义的 6×8 或 8×16 等点阵字体)。它们都调用了 OLED_ShowChar 这个底层函数来逐个字符地显示。
在OLED_Start添加显示ASCII。
OLED_ShowNum(0, 16, 20250615, 8, 16, 1);//在坐标 (0,16) 显示20250615,长度8,字体16,正显
OLED_ShowString(84, 16, "RA4M2", 16, 1);//在坐标 (84,16) 显示RA4M2,字体16,正显
OLED_Refresh();// 将 OLED_GRAM 显存内容刷新到 OLED 屏幕上,显示数字和字符串
图片取模
设置为图形模式。
格式设置。
选择需要打开的文件。
打开的文件必须为bmp文件。
选择生成字模。
图片数据放在bmp.h中。
unsigned char BMP3[] =
{
0x1E,0x0E,0x07,0x07,0x07,0x07,0x03,0x03,0x83,0x83,0xC7,0xC7,0xFF,0xFF,0xFF,0xFF,
0xFF,0x7E,0x3C,0xE0,0xF0,0xF8,0xF8,0xF8,0xF8,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,0xBC,
0xBC,0xBC,0x1C,0xFC,0xFC,0xFC,0xFC,0xF8,0xF8,0xF8,0xF8,0xE0,0xC0,0x80,0x00,0x00,
0xFC,0xFC,0xFC,0xFC,0x00,0xE0,0xF0,0xF8,0xF8,0xF8,0xF8,0xBC,0xBC,0xBC,0xBC,0xBC,
0xBC,0xBC,0xBC,0x9C,0xE0,0xF0,0xF8,0xF8,0xF8,0xF8,0xFC,0xFC,0xFC,0xBC,0xBC,0xBC,
0x3C,0x3C,0x3C,0x3C,0x1C,0x00,0x00,0x80,0xC0,0xF0,0xF0,0xF8,0xF8,0xFC,0xFC,0xFC,
0xF8,0xF8,0xC0,0x80,0x00,0xF0,0xF0,0xF8,0xF8,0xF8,0xF8,0xFC,0xFC,0xBC,0xBC,0xBC,
0xBC,0x3C,0x3C,0x3C,0x1C,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x03,0x07,0x0F,
0x1F,0x3F,0x7F,0xFF,0xFD,0xF8,0xF0,0xE0,0xFF,0xBF,0x7F,0xFF,0xFF,0xFF,0xF7,0xE7,
0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE3,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0x03,0x07,0x0F,
0x3F,0x3F,0x7F,0xFF,0xFE,0xFF,0xFF,0xFF,0xFF,0x00,0x3F,0x7F,0xFF,0xFF,0xFF,0xFF,
0xF7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE7,0xE3,0xE3,0xE0,0xE0,0xE1,0xE3,0xE3,0xE7,0xE7,
0xEF,0xEF,0xEF,0xFF,0xFF,0xFF,0xFF,0xFE,0x7E,0xFC,0xFC,0xFE,0xFF,0xFF,0x7F,0x1F,
0xEF,0xE1,0xE1,0xE3,0xEF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFD,0xF1,0xE3,0xE3,0xE7,0xE7,
0xE7,0xEF,0xEF,0xEF,0xFF,0xFF,0xFF,0xFF,0xFE,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x03,0x03,0x07,0x07,
0x07,0x06,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*"RA4M2_IOT\OLED\renesas-logo.bmp",0*/
};
显示图片
查看属性可以知道分别率为19*117。
在 OLED 屏幕下半部分显示一张 117×19 的图片,并刷新屏幕显示。
OLED_ShowPicture(0, 32, 117, 19, BMP3, 1); // 在 OLED 屏幕的 (x=0, y=32) 位置显示一张宽 117、高 19 像素的图片 BMP3,正常模式显示
OLED_Refresh(); // 将图片内容从缓冲区刷新到 OLED 屏幕上,真正显示出来
R_BSP_SoftwareDelay(200, BSP_DELAY_UNITS_MILLISECONDS); // 延时 200 毫秒