STM32 硬件I2C 移植U8G2
Background: U8g2是Arduino上一个广泛使用的屏幕显示库,有着丰富的库函数实现功能,之前做的OLED桌面天气小摆件就是基于U8g2库的,最近在使用STM32 想着移植U8g2 发现大多数是基于软件I2C的,于是准备使用硬件I2C驱动OLED显示屏 0.96寸OLED SSD1306
参考链接: u8g2stm32 移植记录
视频链接: Bilibili STM32 HAL库硬件IIC移植u8g2库
u8g2配置
U8g2源码下载
U8g2源码配置
u8g2-master\csrc 中
-
留下**u8x8_d_ssd1306_128x64_noname.c **为了减少编译文件体积以及速度 删除其他的后缀中带_d的配置文件
-
在u8g2_d_setup.c中修改文件
我们使用的驱动时ssd1306 s所以只关注和1306 相关的 主要有以下函数
其中第一个代码块主要是关于i2c的驱动,第二个代码块主要是关于spi的驱动,
/*i2c*/
u8g2_Setup_ssd1306_i2c_128x64_noname_1
u8g2_Setup_ssd1306_i2c_128x64_noname_2
u8g2_Setup_ssd1306_i2c_128x64_noname_f
/*spi*/
u8g2_Setup_ssd1306_128x64_noname_1
u8g2_Setup_ssd1306_128x64_noname_2
u8g2_Setup_ssd1306_128x64_noname_f
函数不同的后缀表示的含义是其代表的缓冲区大小不同,
1代表128字节、2代表256字节、f代表1024字节 ,主要区别在于缓冲区小的刷新OLED就比较耗时,这个根据自己单片机实际的需求选取。我们使用的是STM32F103C8T6选则的函数是u8g2_Setup_ssd1306_i2c_128x64_noname_f
- 修改u8g2_d_memory.c
在上面的的u8g2_Setup_ssd1306_i2c_128x64_noname_f函数中,初始化只用到的u8g2_m_16_8_f函数,所以我们在menmoey.c文件中只留下这个函数 其余的均删除(如果不删除,那么编译的时候会编译进去,增大文件体积),效果如下图所示
- 修改u8g2.h
在第3步,我们删除了大量的函数,这些函数是声明在u8g2.h 中所以我们在头文件中将这些声明删除
u8g2修改完成,现在我们生成STM32的工程,进行下一步测试
STM32CubeMX代码生成
生成32工程
- 配置时钟
-
配置调试接口
-
配置I2C 选择快速模式 400khz 其他默认设置
- 生成文件 编译测试 没有错误的话就进行下一步
导入KEIL
上面我的修改是在csrc文件下进行的,在移植的时候我们只要这个文件夹。
- 拷贝csrc文件夹到生成的32工程的Drivers目录下 并重命名为U8g2
- 点击keil中的"品"的图标 新建工程目录 将上一步U8g2中的文件全部加进来
- 导入头文件路径
点击魔术棒,进入c/c++设置 加入u8g2头文件的路径
- 尝试编译
在main函数中加入#include “u8g2.h” 然后尝试编译
编译成功,没有错误 只有一些关于最后一行后面没有回车的警告,暂时忽略
自此,将代码导入到KEIL工程已经结束,接下来我们需要完成几个函数 以实现硬件I2C的适配
适配驱动
在这一步我们主要完成两个操作,实现stm32的延时函数 一个是使用硬件I2C的数据传输函数适配官方的
- 实现延时回调函数回调函数
这一步主要是适配STM32 HAL库中的HAL_Delay,函数如下,函数名称无所谓,主要是函数中的参数一致即可
uint8_t u8g2_stm32_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr)
{
switch(msg){
case U8X8_MSG_GPIO_AND_DELAY_INIT:
break;
case U8X8_MSG_DELAY_MILLI:
HAL_Delay(arg_int);//change it!
break;
default:
return 0;
}
return 1; // command processed successfully.
}
- 实现u8x8_byte_i2c的函数
这里我们主要修改U8X8_MSG_BYTE_END_TRANSFER下的使用硬件IIC发送数据的库函数,在官方的库中使用sw(software)的方式适配如下 由于我们是硬件 i2c所以只关注数据传输方面
uint8_t u8x8_byte_sw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
uint8_t *data;
switch(msg)
{
case U8X8_MSG_BYTE_SEND:
data = (uint8_t *)arg_ptr;
while( arg_int > 0 )
{
i2c_write_byte(u8x8, *data);
data++;
arg_int--;
}
break;
case U8X8_MSG_BYTE_INIT:
i2c_init(u8x8);
break;
case U8X8_MSG_BYTE_SET_DC:
break;
case U8X8_MSG_BYTE_START_TRANSFER:
i2c_start(u8x8);
i2c_write_byte(u8x8, u8x8_GetI2CAddress(u8x8));
//i2c_write_byte(u8x8, 0x078);
break;
case U8X8_MSG_BYTE_END_TRANSFER:
i2c_stop(u8x8);
break;
default:
return 0;
}
return 1;
}
在STM32 HAL库中这个函数为
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
/*
hi2c:i2c实例化对象
DevAddress:设备地址 这里我们使用 u8x8_GetI2CAddress(u8x8) 函数得到
pData:要传输的数据
Size:数据大小
Timeout:超时时间
*/
适配如下
uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
static uint8_t buffer[32]; /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
static uint8_t buf_idx;
uint8_t *data;
switch(msg)
{
case U8X8_MSG_BYTE_SEND:
data = (uint8_t *)arg_ptr;
while( arg_int > 0 )
{
buffer[buf_idx++] = *data;
data++;
arg_int--;
}
break;
case U8X8_MSG_BYTE_INIT:
/* add your custom code to init i2c subsystem */
break;
case U8X8_MSG_BYTE_SET_DC:
/* ignored for i2c */
break;
case U8X8_MSG_BYTE_START_TRANSFER:
buf_idx = 0;
break;
case U8X8_MSG_BYTE_END_TRANSFER:
HAL_I2C_Master_Transmit(&hi2c1,u8x8_GetI2CAddress(u8x8), buffer, buf_idx,0x100);//change it
break;
default:
return 0;
}
return 1;
}
这个函数是我们新写的,可以定义在任意位置,如main.c中,但是我为了统一,将其放在了u8x8_byte.c 中 大概在577行 这几行附近主要是关于驱动方式的初始化 软件i2c spi等 ,配置完成我们将这个函数在u8x8.h的头文件中声明
上面初始化用到了 hi2c1 这个函数包含在i2c.h 中 所以我们在u8x8_byte.c中加入对i2c.h的引用 防止报错
初始化
经过上面的移植,我们已经完成了驱动部分的适配,接下来就要对他进行初始化,主要使用到了以下函数
void u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
/*
*u8g2 实例化的u8g2 对象的地址
*rotation:旋转方向 对应参数如下 具体效果可以自己测试
* U8G2_R0
U8G2_R1
U8G2_R2
U8G2_R3
U8G2_MIRROR
U8G2_MIRROR_VERTICAL
*byte_cb:字节传输函数 是上面我们适配的u8x8_byte_hw_i2c
*gpio_and_delay_cb:延时函数 是上面我们适配的u8g2_stm32_delay
*/
主要适配代码如下
#include "u8g2.h"
u8g2_t u8g2;
u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2,U8G2_R0,u8x8_byte_hw_i2c ,u8g2_gpio_and_delay_stm32);
u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
u8g2_SetPowerSave(&u8g2, 0); // wake up display
u8g2_ClearDisplay(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese1);
u8g2_DrawBox(&u8g2,60,10,20,20);
u8g2_DrawUTF8(&u8g2,10,50,"hi,world");
u8g2_SendBuffer(&u8g2);
效果展示
优化
在显示汉字时,汉字部分显示不了,经过搜索是keil 默认使用GB2312 将其切换成UTF-8编码
在edit->configuration->Editor->Encoding
修改后的代码
u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this,
u8g2_SetPowerSave(&u8g2, 0); // wake up display
u8g2_ClearDisplay(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_chinese1);
u8g2_DrawBox(&u8g2,60,10,20,20);
u8g2_DrawUTF8(&u8g2,14,50,"你好,world");
u8g2_SendBuffer(&u8g2);
效果