STM32F4系列出来已有好几年,价格上也越来越亲人,在需求性能要求较高的场合上可以考虑使用。对于STM32F1的硬件i2c的诟病也是很多使用者吐槽,对于F4的硬件i2c,本人没有做详细测试,也不好判断,据一些使用者反馈有改善。在之前“i2c抽象/模拟i2c”文章中有使用到模拟i2c,现在将其移植到F4系列的MCU上。F1中用的是标准库,F4中用的是HAL库,函数API有差别,但使用思路并没有多少差异。
1.模拟i2c函数实体实现
“i2c_bitops.c”和”i2c_bitops.h”为模拟i2c的时序,与cpu无关部分,已经将其抽象出来,实现过程是函数指针的方式,这部分直接移植过来,无需修改。需要实现的则是函数指针的实体,即是跟cpu底层相关的部分,包括函数如下:
struct ops_i2c_dev
{
void (*set_sda)(int8_t state);
void (*set_scl)(int8_t state);
int8_t (*get_sda)(void);
int8_t (*get_scl)(void);
void (*delayus)(uint32_t us);
};
1.1数据线sda IO翻转函数实现
static void gpio_set_sda(int8_t state)
{
//sda 输出1bit
HAL_GPIO_WritePin(I2C1_SDA_PORT,I2C1_SDA_PIN,(GPIO_PinState)state);
}
static int8_t gpio_get_sda(void)
{
//从sda io获取1bit
return (uint8_t)HAL_GPIO_ReadPin(I2C1_SDA_PORT,I2C1_SDA_PIN);
}
1.2时钟线scl IO翻转函数实现
static void gpio_set_scl(int8_t state)
{
//scl io输出1bit
HAL_GPIO_WritePin(I2C1_SCL_PORT,I2C1_SCL_PIN,(GPIO_PinState)state);
}
static int8_t gpio_get_scl()
{
//从scl io获取1bit
return (uint8_t)HAL_GPIO_ReadPin(I2C1_SCL_PORT,I2C1_SCL_PIN);
}
1.3延时函数实现
延时函数根据MCU时钟计算,可以用心跳定时器或者纯软件计时。
这里写代码片static void gpio_delayus(uint32_t us)
{
#if 1
volatile int32_t i;
for (; us > 0; us--)
{
i = 30;
while(i--);
}
#else
delay_us(us);
#endif
}
2.模拟i2c总线
第一部分实现的是相关函数指针的实体,此部分主要是配置IO,及总线指针初始化。
2.1 i2c指针设备
首先定义模拟i2c函数相关和i2c总线相关指针,后面初始化过程即是实现这两个指针实体。
//i2c1 device
struct i2c_dev_device i2c1_dev;
struct ops_i2c_dev ops_i2c1_dev;
2.2 i2c函数实体
这部分主要实现i2c封装“struct i2c_dev_device”实体
struct i2c_dev_device
{
int (*xfer)(struct i2c_dev_device *dev,struct i2c_dev_message msgs[],unsigned int num);
void *i2c_phy;
};
其中“xfer”为i2c总线收发函数,实现功能是通过模拟i2c进行收发数据,实现代码:
int ops_i2c_bus_xfer(struct i2c_dev_device *i2c_dev,struct i2c_dev_message msgs[],unsigned int num)
{
return(i2c_bitops_bus_xfer((struct ops_i2c_dev*)(i2c_dev->i2c_phy),msgs,num));
}
指针“i2c_phy”是指向i2c总线的地址,可以为硬件i2c或者模拟i2c,这里用的是模拟i2c,因此初始化指向前面定义的“ops_i2c1_dev”。
2.3初始化
初始化部分,包括IO初始化、相关时钟初始化,及模拟i2c指针函数的实例化。
void stm32f4_i2c_init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_Initure.Pin = I2C1_SDA_PIN;
GPIO_Initure.Mode = GPIO_MODE_AF_OD; //开漏输出
GPIO_Initure.Pull = GPIO_NOPULL;
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
HAL_GPIO_Init(I2C1_SDA_PORT,&GPIO_Initure);
GPIO_Initure.Pin = I2C1_SCL_PIN;
GPIO_Initure.Mode = GPIO_MODE_AF_OD;
GPIO_Initure.Pull = GPIO_NOPULL;
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
HAL_GPIO_Init(I2C1_SCL_PORT,&GPIO_Initure);
HAL_GPIO_WritePin(I2C1_SDA_PORT,I2C1_SDA_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(I2C1_SCL_PORT,I2C1_SCL_PIN,GPIO_PIN_SET);
//device init
ops_i2c1_dev.set_sda = gpio_set_sda;
ops_i2c1_dev.get_sda = gpio_get_sda;
ops_i2c1_dev.set_scl = gpio_set_scl;
ops_i2c1_dev.get_scl = gpio_get_scl;
ops_i2c1_dev.delayus = gpio_delayus;
i2c1_dev.i2c_phy = &ops_i2c1_dev;
i2c1_dev.xfer = ops_i2c_bus_xfer;
}
初始化完毕,即可通过“i2c1_dev”地址,调用“i2c_core.h”中的函数接口“i2c_bus_xfer”使用该模拟i2c总线。可以使用之前文章中的EEPROM代码进行测试,此时EEPROM代码直接将文件拷贝到F4工作目录,加入编译路径,选择调试的宏,编译执行即可验证。
3.实例
相关例子可以参考之前文章或者GitHub上的EEPROM例子,可以进行测试。
[1] https://blog.csdn.net/qq_20553613/article/details/78878211