Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
stm32F4
基于STM32F4平台的硬件I2C驱动实现笔记
发布于 2022-11-28 16:31:19 浏览:4790
订阅该版
[tocm] ## 1. 了解rtthread中模拟I2C驱动框架 模拟I2C驱动框架是将I2C的START、STOP、READ、WRITE通过操作GPIO拉高拉低实现的,时钟周期控制则是通过IO翻转附加延时的方式实现,模拟的方式最简单粗暴,但是相对于硬件的方式比较浪费CPU资源。 #### 关于模拟I2C框架中需要实现的操作函数: ```c static const struct rt_i2c_bit_ops stm32_bit_ops_default = { .data = RT_NULL, .set_sda = stm32_set_sda, //SDA数据线操作 .set_scl = stm32_set_scl, //SCL时钟线操作 .get_sda = stm32_get_sda, //SDA数据线电平读取 .get_scl = stm32_get_scl, //SCL时钟线电平读取 .udelay = stm32_udelay, //延时函数实现 .delay_us = 1, .timeout = 100 }; ``` #### 在驱动初始化中通过结构体注册的方式进行总线注册: ```c i2c_obj[i].ops = stm32_bit_ops_default; i2c_obj[i].ops.data = (void*)&soft_i2c_config[i]; i2c_obj[i].i2c2_bus.priv = &i2c_obj[i].ops; stm32_i2c_gpio_init(&i2c_obj[i]); /* IO操作函数结构体注册到总线驱动框架 */ result = rt_i2c_bit_add_bus(&i2c_obj[i].i2c2_bus, soft_i2c_config[i].bus_name); RT_ASSERT(result == RT_EOK); stm32_i2c_bus_unlock(&soft_i2c_config[i]); ``` 这里的核心还是这个函数:rt_i2c_bit_add_bus,将IO操作的函数注册到设备驱动框架里面。 这边的操作代码需要看下 \components\drivers\i2c\i2c-bit-ops.c 里面的 i2c_bit_xfer 函数。 ## 2. 开始适配硬件I2C驱动 了解清楚rtthread提供的软件模拟I2C驱动框架后,就知道后面该怎样去实现硬件I2C驱动接口了。实现硬件I2C驱动接口本质是去替换 i2c-bit-ops.c 这个文件,将里面的 i2c_bit_xfer 通过硬件I2C驱动的 Transmit 及 Receive 去实现。 ### 硬件I2C配置结构体: 先看配置结构体部分,这部分主要是总线管脚、通信速率及一些HAL库句柄的赋值定义。 这部分也参考了 @07lhluo 大佬的结构体,在此基础上加入了总线速率设置、信号量及互斥锁。 ```c typedef void (*pI2CInit)(rt_uint32_t speed); struct stm32_hard_i2c_config { const char *bus_name; //总线名称 const char *scl_pin_name; //scl管脚定义字符串 const char *sda_pin_name; //sda管脚定义字符串 rt_uint32_t speed; //总线时钟速率 pI2CInit pInitFunc; //硬件I2C外设初始化函数指针 I2C_HandleTypeDef *pHi2c; //HAL库硬件I2C句柄 struct rt_i2c_bus_device i2c_bus;//总线操作函数结构体指针 /* notice and lock define */ rt_sem_t tx_notice; //总线发送完成信号量 rt_sem_t rx_notice; //总线接收完成信号量 rt_mutex_t lock; //总线操作互斥锁 }; ``` 定义好配置结构体后对于任意总线的配置就能统一使用一个宏定义去实现了: ```c #define HAED_I2C_CONFIG(x) \ { \ .bus_name = "i2c"#x, \ .scl_pin_name = BSP_I2C##x##_SCL_PIN, \ .sda_pin_name = BSP_I2C##x##_SDA_PIN, \ .speed = BSP_I2C##x##_CLOCK, \ .pInitFunc = MX_I2C##x##_Init, \ .pHi2c = &hi2c##x, \ .i2c_bus = { \ .ops = &i2c_bus_ops, \ }, \ } ``` 其中的pin脚和通信速率的定义,我把它们放到board.h中: ```c #define BSP_USING_I2C1 #ifdef BSP_USING_I2C1 #define BSP_I2C1_CLOCK 400000 #define BSP_I2C1_SCL_PIN "PB6" #define BSP_I2C1_SDA_PIN "PB7" #endif ``` 所以最后我们定义的结构体就是这样的形式: ```c static struct stm32_hard_i2c_config hard_i2c_config[] = { #ifdef BSP_USING_I2C1 HAED_I2C_CONFIG(1) #endif /* BSP_USING_I2C1 */ #ifdef BSP_USING_I2C2 HAED_I2C_CONFIG(2) #endif /* BSP_USING_I2C2 */ #ifdef BSP_USING_I2C3 HAED_I2C_CONFIG(3) #endif /* BSP_USING_I2C3 */ }; ``` 至此,关于任意总线配置部分结构体就是这样。 ### 硬件I2C外设初始化函数: 这部分初始化来源与CubeMX生成的代码,唯一不同的就是增加了总线时钟形参的传入,这里以I2C1的初始化为例: ```c static void MX_I2C1_Init(rt_uint32_t speed) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = speed; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_16_9; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } /*## Configure the NVIC for I2C1 ########################################*/ /* NVIC for I2Cx */ HAL_NVIC_SetPriority(I2C1_EV_IRQn, 10, 0); HAL_NVIC_EnableIRQ(I2C1_EV_IRQn); HAL_NVIC_SetPriority(I2C1_ER_IRQn, 11, 0); HAL_NVIC_EnableIRQ(I2C1_ER_IRQn); } ``` 其中对I2C的事件中断及总线错误中断进行使能。 ### 硬件I2C总线GPIO初始化函数: 讲到这里,是不是发现GPIO初始化还没有添加进来,于是乎,我们参考一下串口部分的GPIO初始化,以字符串的形式进行解析。外设时钟的初始化也是参考串口驱动里面的形式。 ```c static rt_err_t hard_i2c_gpio_configure(struct stm32_hard_i2c_config *config) { GPIO_InitTypeDef GPIO_InitStruct = { 0 }; GPIO_TypeDef *scl_port; GPIO_TypeDef *sda_port; uint16_t scl_pin; uint16_t sda_pin; uint8_t i2c_num = config->bus_name[3] - '0'; /* get gpio port and pin address */ get_pin_by_name(config->scl_pin_name, &scl_port, &scl_pin); get_pin_by_name(config->sda_pin_name, &sda_port, &sda_pin); /* gpio ports clock enable */ hard_i2c_gpio_clk_enable(scl_port); if (scl_port != sda_port) { hard_i2c_gpio_clk_enable(sda_port); } /* scl pin initialize */ GPIO_InitStruct.Pin = scl_pin; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; HAL_GPIO_Init(scl_port, &GPIO_InitStruct); HAL_GPIO_WritePin(scl_port, scl_pin, GPIO_PIN_SET); /* sda pin initialize */ GPIO_InitStruct.Pin = sda_pin; HAL_GPIO_Init(sda_port, &GPIO_InitStruct); HAL_GPIO_WritePin(sda_port, sda_pin, GPIO_PIN_SET); /* i2c periphal clock enable */ hard_i2c_clk_enable(i2c_num); return RT_EOK; } ``` 到这里,所有底层的初始化已经完成了,接下来可以进行数据传输接口及中断服务函数的操作了。 ## 3. 硬件I2C驱动数据传输接口: 与软件模拟不同,硬件I2C只有数据发送与接收操作,HAL库提供三种操作方式供开发者选择使用:polling方式、中断方式及DMA方式。这三种方式在HAL库中函数名上有体现,这里我选用中断的方式,因为我个人觉得I2C通信速率不高没必要占用宝贵的DMA资源,如果使用polling的方式则和模拟I2C比起来体现不出来优势。 ![新建 BMP 图像.bmp](https://oss-club.rt-thread.org/uploads/20221128/93e44083c949c2a23ce6e9ae1c9e7c3e.bmp.webp "新建 BMP 图像.bmp") 了解完HAL层的API函数后,开始适配中断接口: ```c oid I2C1_EV_IRQHandler(void) { /* enter interrupt */ rt_interrupt_enter(); i2cx_event_isr(&hard_i2c_config[I2C1_INDEX]); /* leave interrupt */ rt_interrupt_leave(); } void I2C1_ER_IRQHandler(void) { /* enter interrupt */ rt_interrupt_enter(); i2cx_error_isr(&hard_i2c_config[I2C1_INDEX]); /* leave interrupt */ rt_interrupt_leave(); } ``` 这里我把总线的事件中断和错误中断统一管理起来: ```c static inline void i2cx_event_isr(struct stm32_hard_i2c_config *config) { HAL_I2C_EV_IRQHandler(config->pHi2c); } static inline void i2cx_error_isr(struct stm32_hard_i2c_config *config) { HAL_I2C_ER_IRQHandler(config->pHi2c); } ``` 以及HAL库里面的总线发送接收完成的回调函数: ```c void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) { /* release sem to notice function */ rt_sem_release(hard_i2c_config[I2C1_INDEX].tx_notice); } void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) { /* release sem to notice function */ rt_sem_release(hard_i2c_config[I2C1_INDEX].rx_notice); } ``` 到这里中断及回调部分完成。 结合中断及回调函数,核心数据传输接口函数我们就比较容易实现了: ```c static rt_size_t i2c_xfer( struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num) { HAL_StatusTypeDef status; rt_err_t ret = 0; rt_size_t xfer_len = 0; struct rt_i2c_msg *msg; struct stm32_hard_i2c_config *pCfg = rt_container_of(bus, struct stm32_hard_i2c_config, i2c_bus); for (int i = 0; i < num; i++) { msg = &msgs[i]; if (msg->flags & RT_I2C_RD) { /***** I2C master to receive *****/ rt_mutex_take(pCfg->lock, RT_WAITING_FOREVER); /* master receive data by interrupt */ status = HAL_I2C_Master_Receive_IT(pCfg->pHi2c, msg->addr << 1, msg->buf, msg->len); if (status != HAL_OK) { HAL_I2C_Master_Abort_IT(pCfg->pHi2c, msg->addr << 1); rt_mutex_release(pCfg->lock); ret = RT_ERROR; goto __exit; } /* wait receive complete */ ret = rt_sem_take(pCfg->rx_notice, (10 * msg->len)); if (ret != RT_EOK) { HAL_I2C_Master_Abort_IT(pCfg->pHi2c, msg->addr << 1); rt_mutex_release(pCfg->lock); goto __exit; } rt_mutex_release(pCfg->lock); xfer_len++; } else { /***** I2C master to transmit *****/ rt_mutex_take(pCfg->lock, RT_WAITING_FOREVER); /* master transmit data by interrupt */ status = HAL_I2C_Master_Transmit_IT(pCfg->pHi2c, msg->addr << 1, msg->buf, msg->len); if (status != HAL_OK) { HAL_I2C_Master_Abort_IT(pCfg->pHi2c, msg->addr << 1); rt_mutex_release(pCfg->lock); ret = RT_ERROR; goto __exit; } /* wait transmit complete */ ret = rt_sem_take(pCfg->tx_notice, (10 * msg->len)); if (ret != RT_EOK) { HAL_I2C_Master_Abort_IT(pCfg->pHi2c, msg->addr << 1); rt_mutex_release(pCfg->lock); goto __exit; } rt_mutex_release(pCfg->lock); xfer_len++; } } __exit: if (xfer_len == num) { ret = xfer_len; } return ret; } ``` 到这里,基本的硬件I2C驱动适配完成,驱动初始化及注册部分就是通用的路子了,大差不差了。
24
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
cheung
实事求是
文章
1
回答
23
被采纳
1
关注TA
发私信
相关文章
1
编译stm32F407动态模块报错
2
RTT-STUDIO STM32F4 如何配置 开启FPU功能
3
STM32F405RG CAN1发送波特率设置
4
STM32F401RC 用 RT-Thread Studio 烧写不了程序
5
为什么单片机STM32F405RG只能成功烧录一次?
6
rt-thread studio生成的程序写入stm32f405rg无法运行
7
MCU探索版+RT_Thread+Webnet的疑问
8
STM32F407VG启动硬件定时器系统忙类死机
9
STM32F4的虚拟串口 的USB时钟如何配置
10
stm32客户端TCP Client连接不上pc端的TCP Server
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
MicroPython
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
19
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
6
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
5
次点赞
RTT_逍遥
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部