STM32F4模拟i2c

  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

    原文作者:Acuity.
    原文地址: https://blog.csdn.net/qq_20553613/article/details/80822885
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞