全国好的深圳网站设计,小企业网站建设5000块贵吗,赣州营销网站建设,wordpress发布文章禁用谷歌字体RT-Thread在STM32硬件I2C的踩坑记录 0.前言一、软硬件I2C区别二、RT Thread中的I2C驱动三、尝试适配硬件I2C四、i2c-bit-ops操作函数替换五、Attention Please!六、总结 参考文章#xff1a; 1.将硬件I2C巧妙地将“嫁接”到RTT原生的模拟I2C驱动框架 2.基于STM32F4平台的硬件I… RT-Thread在STM32硬件I2C的踩坑记录 0.前言一、软硬件I2C区别二、RT Thread中的I2C驱动三、尝试适配硬件I2C四、i2c-bit-ops操作函数替换五、Attention Please!六、总结 参考文章 1.将硬件I2C巧妙地将“嫁接”到RTT原生的模拟I2C驱动框架 2.基于STM32F4平台的硬件I2C驱动实现笔记 3.《rt-thread驱动框架分析》- i2c驱动
0.前言 最近打算用RT-Thread做一个小demo玩玩其中需要用I2C通信驱动一个oled屏幕但是找了一圈也没找到RTT中对硬件I2C的支持方式以及使用案例好像大家都心照不宣的用这个好用又不好用的软件I2C。这里还是忍不住吐槽两句连硬件SPI都已经支持了甚至支持SPI DMA模式了硬件I2C这么多年了也没适配。也希望有大佬能贡献一份力量做出一份能让DIY玩家凑合用的第三方硬件I2C驱动也行。
一、软硬件I2C区别 有关I2C通信协议的原理部分就不多介绍了这个算是很常见的通信协议了CSDN论坛一搜一大把RT Thread文档中心也有较详细的介绍。 软件I2C是使用GPIO的电平翻转模拟出I2C信号它的好处是方便移植下至51单片机上至linux平台只要有GPIO都能适用(当然linux下也不会有人用这个)。缺点则是速率很低软件操作GPIO电平翻转不可避免的有时延以及毛刺为了消除这种现象的影响模拟的I2C信号之间就需要稍微大点的时间间隔。软件I2C的信号频率一般在30KHz ~ 50KHz即便优化相当好的情况也差不多在这个量级。用来操作128x64的oled屏幕帧率基本在2帧左右。 硬件I2C则是通过操作芯片自带的寄存器进行I2C通信缺点就是不同芯片间驱动不通用优点则是速度更快并且可以适配DMA模式降低CPU负载。笔者使用的STM32RCT6硬件I2C标准模式信号频率为100KHz快速模式400KHz一些性能较好的芯片还有1MHz的极速模式。400kHz情况下操作128x64的oled帧率在25帧左右可以说是提升巨大了。
二、RT Thread中的I2C驱动 关于RT Thread中的I2C驱动框架的实现方式可以参考上述的第三篇参考文章个人觉得写的很详细也好懂。RT Thread为类Linux的实时操作系统所以I2C框架的实现方式和linux中的也比较相像I2C驱动提供一些操作相关的ops函数并注册到内核中I2C设备则可以通过probe函数挂载到总线上通过ops操作函数进行I2C通信。 并且在该篇文章中该作者跳过原本的bit_ops重新设计了一个硬件I2C的实现方式将驱动直接挂载到内核core中也实现了作为master设备的硬件I2C驱动。不过笔者认为这种方式对通用结构的兼容性不太好所以又找了一些其他方式。
三、尝试适配硬件I2C
参考文章1和2中通过修改I2C总线的实现函数“嫁接”一个硬件的I2C驱动实现方式。这里就先放上代码首先在原drv_soft_i2c.c和drv_soft_i2c.h的同级目录下分别创建drv_hard_i2c.c和drv_hard_i2c.h drv_hard_i2c.h
/** Copyright (c) 2006-2018, RT-Thread Development Team** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date Author Notes* 2018-11-08 balanceTWK first version*/#ifndef __DRV_I2C__
#define __DRV_I2C__#include rtthread.h
#include rthw.h
#include rtdevice.h#ifdef BSP_USING_HARD_I2C/* stm32 config class */
typedef void (*pI2cConfig)(void);
struct stm32_hard_i2c_config
{rt_uint8_t scl; /* scl pin */rt_uint8_t sda; /* sda pin */const pI2cConfig pFunc; /* i2c init function */const char* pName; /* i2c bus name */I2C_HandleTypeDef* pHi2c; /* i2c handle */struct rt_i2c_bus_device i2c_bus; /* i2c bus device */
};
/* stm32 i2c dirver class */
struct stm32_i2c
{struct rt_i2c_bit_ops ops;struct rt_i2c_bus_device i2c2_bus;
};#define HARD_I2C_CONFIG(x) \
{.scl BSP_I2C##x##_SCL_PIN, \.sda BSP_I2C##x##_SDA_PIN, \.pFunc MX_I2C##x##_Init, \.pHi2c hi2c##x, \.pName i2c#x, \.i2c_bus {.ops i2c_bus_ops,},
}int rt_hw_i2c_init(void);#endif#endif /* RT_USING_I2C */
其中stm32_hard_i2c_config可以理解为i2c实例对象属性包括scl和sda引脚、总线名称及初始化函数等。注在参考文章2中的总线速度、信号量及互斥锁则不需要因为使用CubeMx生成的初始化函数中已有总线速度HAL库中的I2C操作函数内部已有总线锁 stm32_i2c则封装了设备操作函数及总线用于与内核对接。 函数宏HARD_I2C_CONFIG(x)则用来后续创建I2C设备对象。
drv_hard_i2c.c
/** Copyright (c) 2006-2018, RT-Thread Development Team** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date Author Notes* 2018-11-08 balanceTWK first version*/#include board.h
#include drv_hard_i2c.h
#include drv_config.h
#includertthread.h
#includertdevice.h#ifdef BSP_USING_HARD_I2C//#define DRV_DEBUG
#define LOG_TAG drv.i2c
#include drv_log.hstatic const struct stm32_hard_i2c_config hard_i2c_config[]
{
#ifdef BSP_USING_HARD_I2C1HARD_I2C_CONFIG(1),
#endif
#ifdef BSP_USING_HARD_I2C2HARD_I2C_CONFIG(2),
#endif
#ifdef BSP_USING_HARD_I2C3HARD_I2C_CONFIG(3),
#endif
#ifdef BSP_USING_HARD_I2C4HARD_I2C_CONFIG(4),
#endif
};static struct stm32_i2c i2c_obj[sizeof(hard_i2c_config) / sizeof(hard_i2c_config[0])];/*** This function initializes the i2c pin.** param Stm32 i2c dirver class.*/
static void stm32_i2c_gpio_init(struct stm32_i2c *i2c)
{struct stm32_soft_i2c_config* cfg (struct stm32_soft_i2c_config*)i2c-ops.data;rt_pin_mode(cfg-scl, PIN_MODE_OUTPUT_OD);rt_pin_mode(cfg-sda, PIN_MODE_OUTPUT_OD);rt_pin_write(cfg-scl, PIN_HIGH);rt_pin_write(cfg-sda, PIN_HIGH);
}/*** The time delay function.** param microseconds.*/
static void stm32_udelay(rt_uint32_t us)
{rt_uint32_t ticks;rt_uint32_t told, tnow, tcnt 0;rt_uint32_t reload SysTick-LOAD;ticks us * reload / (1000000 / RT_TICK_PER_SECOND);told SysTick-VAL;while (1){tnow SysTick-VAL;if (tnow ! told){if (tnow told){tcnt told - tnow;}else{tcnt reload - tnow told;}told tnow;if (tcnt ticks){break;}}}
}/*** if i2c is locked, this function will unlock it** param stm32 config class** return RT_EOK indicates successful unlock.*/
static rt_err_t stm32_i2c_bus_unlock(const struct stm32_soft_i2c_config *cfg)
{rt_int32_t i 0;if (PIN_LOW rt_pin_read(cfg-sda)){while (i 9){rt_pin_write(cfg-scl, PIN_HIGH);stm32_udelay(100);rt_pin_write(cfg-scl, PIN_LOW);stm32_udelay(100);}}if (PIN_LOW rt_pin_read(cfg-sda)){return -RT_ERROR;}return RT_EOK;
}/* I2C initialization function */
int rt_hw_i2c_init(void)
{rt_int8_t ret RT_ERROR;rt_size_t obj_num NR(hard_i2c_config);rt_err_t result;for (int i 0; i obj_num; i){//GPIO初始化stm32_i2c_gpio_init(hard_i2c_config[i]);//检测SDA是否为低电平低电平则通过管脚模拟9个CLK解锁stm32_i2c_bus_unlock(hard_i2c_config[i]);//调用Hal库MX_I2Cx_Init()配置硬件I2Chard_i2c_config[i].pFunc();//向内核注册I2C Bus设备if(rt_i2c_bus_device_register((hard_i2c_config[i].i2c_bus), hard_i2c_config[i].pName) ! RT_EOK){LOG_E(%s bus init failed!\r\n, hard_i2c_config[i].pName);ret | RT_ERROR;}else{ret | RT_EOK;LOG_I(%s bus init success!\r\n, hard_i2c_config[i].pName);}}return ret;
}
//INIT_BOARD_EXPORT(rt_hw_i2c_init);#endif /* BSP_USING_HARD_I2C */
此文件中则主要根据宏定义开关创建I2C实例对象并对其进行初始化。主要函数为rt_hw_i2c_init()此函数中所需要的gpio init、delay函数等则保留软件i2c中的初始化操作。
user_i2c.h
/** Copyright (c) 2006-2021, RT-Thread Development Team** SPDX-License-Identifier: Apache-2.0** Change Logs:* Date Author Notes* 2023-08-27 14187 the first version*/
#ifndef DRIVERS_HARD_I2C_H_
#define DRIVERS_HARD_I2C_H_//硬件i2c宏开关
//#define BSP_USING_HARD_I2C
#ifdef BSP_USING_HARD_I2C
// #define BSP_USING_HARD_I2C1
// #define BSP_USING_HARD_I2C2
// #define BSP_USING_HARD_I2C3
// #define BSP_USING_HARD_I2C4#if defined(BSP_USING_HARD_I2C1 || BSP_USING_HARD_I2C2 || BSP_USING_HARD_I2C3 || BSP_USING_HARD_I2C4)//#define BSP_USING_DMA_I2C_TX//#define BSP_USING_DMA_I2C_RX#endif
#endif#endif /* DRIVERS_HARD_I2C_H_ */
为了不在每次保存RT Thread Settings时自己的配置被覆盖刷新所以额外定义了一个头文件用于保存自定义的I2C宏开关这样每次刷新后只需要重新在board.h中包含此头文件即可。
至此自定义的硬件I2C宏开关及设备对象创建已完成剩下的则只需要替换内核中的bit_ops操作函数即可。
四、i2c-bit-ops操作函数替换
在rt thread项目根目录下的 rt-thread/components/drivers/i2c/ 目录下有一个i2c-bit-ops.c文件其中则保存了i2c驱动框架中注册的ops操作函数
static rt_size_t i2c_bit_xfer(struct rt_i2c_bus_device *bus,struct rt_i2c_msg msgs[],rt_uint32_t num)
{struct rt_i2c_msg *msg;struct rt_i2c_bit_ops *ops (struct rt_i2c_bit_ops *)bus-priv;rt_int32_t i, ret;rt_uint16_t ignore_nack;if (num 0) return 0;for (i 0; i num; i){msg msgs[i];ignore_nack msg-flags RT_I2C_IGNORE_NACK;if (!(msg-flags RT_I2C_NO_START)){if (i){i2c_restart(ops);}else{LOG_D(send start condition);i2c_start(ops);}ret i2c_bit_send_address(bus, msg);if ((ret ! RT_EOK) !ignore_nack){LOG_D(receive NACK from device addr 0x%02x msg %d,msgs[i].addr, i);goto out;}}if (msg-flags RT_I2C_RD){ret i2c_recv_bytes(bus, msg);if (ret 1){LOG_D(read %d byte%s, ret, ret 1 ? : s);}if (ret msg-len){if (ret 0)ret -RT_EIO;goto out;}}else{ret i2c_send_bytes(bus, msg);if (ret 1){LOG_D(write %d byte%s, ret, ret 1 ? : s);}if (ret msg-len){if (ret 0)ret -RT_ERROR;goto out;}}}ret i;out:if (!(msg-flags RT_I2C_NO_STOP)){LOG_D(send stop condition);i2c_stop(ops);}return ret;
}
...
static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops
{i2c_bit_xfer,RT_NULL,RT_NULL
};这段代码中实现了对每个i2c设备发送对应的i2c msg流程将其修改为硬件i2c的发送方式
static rt_size_t i2c_xfer(struct rt_i2c_bus_device *bus,struct rt_i2c_msg msgs[],rt_uint32_t num)
{rt_uint32_t i;struct rt_i2c_msg *msg;struct stm32_hard_i2c_config *Pconfig rt_container_of(bus, struct stm32_hard_i2c_config, i2c_bus);fot(i 0;i num;i){msg msgs[i];if(msg-flags RT_I2C_RD){
#if defined(BSP_USING_DMA_I2C_RX)HAL_I2C_Master_Receive_DMA(Pconfig-pHi2c, (msg-addr)1, msg-buf, msg-len);rt_hw_us_delay(100);
#elseHAL_I2C_Master_Receive(Pconfig-pHi2c, (msg-addr)1, msg-buf, msg-len, 100);
#endif}else{
#if defined(BSP_USING_DMA_I2C_TX)HAL_I2C_Master_Transmit_DMA(Pconfig-pHi2c, (msg-addr)1, msg-buf, msg-len);rt_hw_us_delay(100);
#elseHAL_I2C_Master_Transmit(Pconfig-pHi2c, (msg-addr)1, msg-buf, msg-len, 100);
#endif}}return i;
}static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops
{i2c_xfer,RT_NULL,RT_NULL
};参考软件i2c的发送方式创建一个新的发送函数rt_size_t i2c_xfer()并将rt_i2c_bus_device_ops 中对应的发送方式修改为此方式。
至此硬件I2C的驱动则算是完成了一部分可以通过与软件i2c一样的声明及挂载方式将设备挂载到硬件I2C总线上。
五、Attention Please!
问题1在上述的实现方式中可以根据宏定义通过HAL_I2C_Master_Transmit()或HAL_I2C_Master_Transmit_DMA()方式发送I2C消息但并未对是否发送成功做出判断。 问题2:ST官方的HAL库中I2C发送消息共有三种方式polling模式(轮询)、中断模式、DMA模式HAL_I2C_Master_Transmit()则对应轮询模式此模式相对于软件I2C虽然速率有所提升但实际的提升效果其实不是特别大。而对于中断模式则需要移植并实现对应的中断处理函数可以按照参考文章2进行实现不过笔者认为该篇需要注意的地方很多比如在中断处理函数中释放信号量的操作可能会造成一些隐患(可以直接去除信号量)。对于DMA模式理论上也需要移植一些中断处理函数但笔者目前没有用这种方式所以也没有细究。所以理论上只能停留在polling模式。 问题3在drv_hard_i2c.c中INIT_BOARD_EXPORT(rt_hw_i2c_init);这个注册步骤需要根据实际情况而定如果想要使用DMA模式则在此注册步骤之前需要先注册MX_DMA_Init()此函数为CubeMX生成用来初始化DMA功能。中断模式同理。
六、总结 目前看来移植ST的硬件I2C驱动还是困难重重所以笔者选则了更换平台(我逃避。。。将oled的电路修改成了SPI模式并更换了芯片平台手头还有一个LPC54110和一个CH32的开发板这两个板子的RTT BSP支持包好像有适配硬件I2C驱动ST再见希望下次回来有大佬适配了硬件I2C。