STM32软件模拟IIC驱动的设计

概述

STM32的硬件IIC存在一些问题,由于IIC时序较为简单,多数情况下考虑使用GPIO模拟来实现IIC设备的驱动程序。这里给出一份在FreeRTOS操作系统下的使用软件模拟IIC的驱动的例程,如果在裸机环境下,还需要做点小改动。这里需要使用到一些知识,这些知识不在本片文章中展开:

1.IIC协议
2.FreeRTOS互斥锁;

定义一个IIC设备

在IIC协议中,可以在同一个总线上挂载多个IIC设备,这些设备通过不同的IIC设备地址进行区分。因此编写驱动的时候,需要设计一个IIC设备对象,该对象包含了设备的地址以及使用的IIC总线。

IIC总线的设计

熟悉51单片机的同学,在设计软件模拟IIC驱动的时候可能就直接将IIC驱动写死了。如果一个系统含有多条IIC总线,难道就要每条总线对实现一个对应的IIC驱动吗?这样就会导致代码膨胀、冗余,因为除了总线不一样,其他代码都是一样的。为了解决这个问题,就需要将总线做通用处理,即定义一个IIC总线。
在STM32中,每个GPIO引脚都是可以单独配置的,可以使用结构体来定义一个引脚:
结构体single_gpio_pin_t表示一个引脚:

typedef struct {
    GPIO_TypeDef *gpio;
    unsigned int  pin;
} single_gpio_pin_t;

在IIC总线中有两条信号线,分别为SCL和SDA,同样的可以使用两个引脚来定义IIC总线:
结构体i2c_bus_t:

/* 定义IIC总线 */
typedef struct {
    single_gpio_pin_t scl;      /* SCL引脚 */
    single_gpio_pin_t sda;      /* SDA引脚 */
    SemaphoreHandle_t mutex;    /* FreeRTOS互斥锁: 多线程环境下, 避免多个线程同时使用总线以至于IIC传输出现错误, 裸机环境下可以去掉该锁 */
} i2c_bus_t;

IIC设备的定义

在定义好总线之后,接下来就可以定义一个IIC设备的结构体了。IIC总线的设备拥有各自的地址,这些地址都必须不同,在协议中并不直接使用IIC的地址,通常需要将地址向左偏移一位后再进行操作,因此传递给IIC设备结构体的地址就是IIC设备地址向左偏移一位了。
IIC设备的结构体:

/* 定义IIC设备 */
typedef struct {
    const i2c_bus_t *bus;       /* IIC设备使用的总线 */
    uint8_t addr;               /* IIC设备的地址向左偏移一位 */
} i2c_device_t;

从该结构体可以看到,结构体成员bus被设计为了一个指针,指向一个IIC总线,可以实现多个IIC设备使用同一个IIC总线。

代码的实现

以下代码的实现参考IIC协议的时序图。
gpio.h:GPIO通用操作

#ifndef __GPIO_CONFIG_H_
#define __GPIO_CONFIG_H_

#include "stm32l4xx_hal.h"
#include "stm32l4xx_hal_gpio.h"

typedef struct {
    GPIO_TypeDef *gpio;
    unsigned int  pin;
} single_gpio_pin_t;

/* 使能GPIO的时钟 */
__STATIC_FORCEINLINE void gpio_clk_enable(GPIO_TypeDef *__RESTRICT GPIO)
{
    RCC->AHB2ENR |= 1 << (((uint32_t)GPIO - GPIOA_BASE) >> 10);
}

__STATIC_FORCEINLINE void gpio_set_pin(GPIO_TypeDef *__RESTRICT GPIO, const uint16_t pin)
{
    GPIO->BSRR = (uint32_t)pin;
}

__STATIC_FORCEINLINE void gpio_reset_pin(GPIO_TypeDef *__RESTRICT GPIO, const uint16_t pin)
{
    GPIO->BRR = (uint32_t)pin;
}

extern void gpio_switch_io_mode(GPIO_TypeDef *__RESTRICT GPIO, const uint16_t pin,
    const uint8_t mode);

#endif

gpio.c

#include "gpio.h"

void gpio_switch_io_mode(GPIO_TypeDef *__RESTRICT GPIO,
    const uint16_t pin, const uint8_t mode)
{
    uint8_t mode_pos;

    mode_pos = POSITION_VAL(pin) << 1;
    GPIO->MODER &= (uint32_t)~(3UL << mode_pos);
    GPIO->MODER |= (uint32_t)(mode << mode_pos);
}

iic.h

#ifndef __IIC_H_
#define __IIC_H_

#include "gpio.h"
#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>

#define I2C_STATUS_OK       0
#define I2C_STATUS_FAILED   1

/* 定义IIC总线 */
typedef struct {
    single_gpio_pin_t scl;      /* SCL引脚 */
    single_gpio_pin_t sda;      /* SDA引脚 */
    SemaphoreHandle_t mutex;    /* FreeRTOS互斥锁: 多线程环境下, 避免多个线程同时使用总线以至于IIC传输出现错误, 裸机环境下可以去掉该锁 */
} i2c_bus_t;

/* 定义IIC设备 */
typedef struct {
    const i2c_bus_t *bus;       /* IIC设备使用的总线 */
    uint8_t addr;               /* IIC设备的地址向左偏移一位 */
} i2c_device_t;

/*
 * @brief: i2c_init(内联函数)
 *   初始化IIC设备, 设置IIC设备使用的总线和IIC设备地址
 * @param:
 *   i2c_dev: IIC设备
 *   bus: IIC设备使用的总线
 *   addr: IIC设备的地址向左偏移一位
 * @returns: void
 */
__STATIC_FORCEINLINE void i2c_init(i2c_device_t *__RESTRICT i2c_dev, const i2c_bus_t *__RESTRICT bus,
    const uint8_t addr)
{
    i2c_dev->bus = bus;
    i2c_dev->addr = addr;       /* addr为设备i2c_dev的地址向左偏移一位 */
}

/*
 * @brief: i2c_bus_init
 *   初始化IIC总线
 * @param:
 *   bus: IIC总线
 * @returns: void 
 */
extern void i2c_bus_init(i2c_bus_t *__RESTRICT bus);

/*
 * @brief: i2c_write_byte
 *   向IIC设备写入一个字节
 * @param:
 *   i2c_dev: IIC设备
 *   reg: IIC设备的寄存器
 *   data: 一个字节的数据
 * @returns: uint8_t
 *   返回一个状态:写入成功返回I2C_STATUS_OK, 失败返回I2C_STATUS_FAILED
 */
extern uint8_t i2c_write_byte(const i2c_device_t *__RESTRICT i2c_dev, const uint8_t reg, const uint8_t data);

/*
 * @brief: i2c_read_byte
 *    从IIC设备读出一个字节
 * @param:
 *   i2c_dev: IIC设备
 *   reg: IIC设备的寄存器
 * @returns: uint8_t
 *   返回读取到的一个字节
 */
extern uint8_t i2c_read_byte(const i2c_device_t *__RESTRICT i2c_dev, const uint8_t reg);

/*
 * @brief: i2c_write_bytes
 *   向IIC设备写入多个字节
 * @param:
 *   i2c_dev: IIC设备
 *   buf: 数据源
 *   len: 写入的数据长度, 单位字节
 *   reg: 写入的寄存器
 * @returns: uint8_t
 *   返回一个状态:写入成功返回I2C_STATUS_OK, 失败返回I2C_STATUS_FAILED
 */
extern uint8_t i2c_write_bytes(const i2c_device_t *__RESTRICT i2c_dev, const uint8_t *__RESTRICT buf,
    const uint8_t len, const uint8_t reg);

/*
 * @brief: i2c_read_bytes
 *   从IIC设备读出多个字节
 * @param:
 *   i2c_dev: IIC设备
 *   buf: 接受数据的缓冲区
 *   len: 读取的数据长度, 单位字节
 *   reg: 读取的寄存器
 * @returns:
 *   返回一个状态:读取成功返回I2C_STATUS_OK, 失败返回I2C_STATUS_FAILED
 */
extern uint8_t i2c_read_bytes(const i2c_device_t *__RESTRICT i2c_dev, uint8_t *__RESTRICT buf,
    const uint8_t len, const uint8_t reg);

/*
 * @brief: i2c_only_read_bytes
 *   从IIC设备读出多个字节, 一般来说这个函数要和i2c_write_byte配合使用
 * @param:
 *   i2c_dev: IIC设备
 *   buf: 接受数据的缓冲区
 *   len: 读取的数据长度, 单位字节
 * @returns:
 *   返回一个状态:读取成功返回I2C_STATUS_OK, 失败返回I2C_STATUS_FAILED
 */
extern uint8_t i2c_only_read_bytes(const i2c_device_t *__RESTRICT i2c_dev, uint8_t *__RESTRICT buf,
    const uint8_t len);

#endif /* End of __IIC_H_ */

iic.c

#include "sys.h"    /* 只提供delay_us */
#include "iic.h"

/* 互斥锁操作 */
#define I2C_MUTEX_LOCK(dev) \
    xSemaphoreTake((dev)->bus->mutex, portMAX_DELAY)
#define I2C_MUTEX_UNLOCK(dev) \
    xSemaphoreGive((dev)->bus->mutex)
/* 如果不需要互斥锁, 将以上两个宏的定义置为空 */
//#define I2C_MUTEX_LOCK(dev)
//#define I2C_MUTEX_UNLOCK(dev)

#define I2C_READ_SDA(dev) \
    HAL_GPIO_ReadPin((dev)->bus->sda.gpio, (dev)->bus->sda.pin)
#define I2C_WRITE_SDA(dev, bit) \
    HAL_GPIO_WritePin((dev)->bus->sda.gpio, (dev)->bus->sda.pin, (bit))
#define I2C_SET_SDA(dev) \
    gpio_set_pin((dev)->bus->sda.gpio, (dev)->bus->sda.pin)
#define I2C_RESET_SDA(dev) \
    gpio_reset_pin((dev)->bus->sda.gpio, (dev)->bus->sda.pin)
#define I2C_SET_SCL(dev) \
    gpio_set_pin((dev)->bus->scl.gpio, (dev)->bus->scl.pin)
#define I2C_RESET_SCL(dev) \
    gpio_reset_pin((dev)->bus->scl.gpio, (dev)->bus->scl.pin)

/* 切换SDA引脚的模式为输出 */
#define I2C_SDA_MODE_OUT(dev) \
    gpio_switch_io_mode((dev)->bus->sda.gpio, (dev)->bus->sda.pin, GPIO_MODE_OUTPUT_PP)
/* 切换SDA引脚的模式为输入 */
#define I2C_SDA_MODE_IN(dev) \
    gpio_switch_io_mode((dev)->bus->sda.gpio, (dev)->bus->sda.pin, GPIO_MODE_INPUT)

/* IIC设备的ACK信号的电平 */
#define I2C_SIGNAL_NOACK    GPIO_PIN_SET        /* 无ACK应答 */
#define I2C_SIGNAL_ACK      GPIO_PIN_RESET      /* 有ACK应答 */

void i2c_bus_init(i2c_bus_t *bus)
{
    GPIO_InitTypeDef GPIO_Initure;

    GPIO_Initure.Pull = GPIO_PULLUP;
    GPIO_Initure.Speed= GPIO_SPEED_FAST;
    GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_Initure.Alternate = 0;
    gpio_clk_enable(bus->scl.gpio);
    gpio_clk_enable(bus->sda.gpio);
    GPIO_Initure.Pin  = bus->scl.pin;
    HAL_GPIO_Init(bus->scl.gpio, &GPIO_Initure);
    GPIO_Initure.Pin = bus->sda.pin;
    HAL_GPIO_Init(bus->sda.gpio, &GPIO_Initure);
    gpio_set_pin(bus->scl.gpio, bus->scl.pin);
    gpio_set_pin(bus->sda.gpio, bus->sda.pin);

    /* 如果不需要互斥锁, 则删除该语句 */
    bus->mutex = xSemaphoreCreateMutex();
}

/**
 * @brief: i2c_start
 *  向IIC总线发送开始信号
 * @param:
 *  i2c_dev: IIC设备
 * @returns: void
 */
static void i2c_start(const i2c_device_t *__RESTRICT i2c_dev)
{
    I2C_SDA_MODE_OUT(i2c_dev);
    I2C_SET_SDA(i2c_dev);
    I2C_SET_SCL(i2c_dev);
    delay_us(4);
    I2C_RESET_SDA(i2c_dev);
	delay_us(4);
    I2C_RESET_SCL(i2c_dev);
}

/**
 * @brief: i2c_stop
 *  向IIC总线发送停止信号
 * @param:
 *  i2c_dev: IIC设备
 * @returns: void
 */
static void i2c_stop(const i2c_device_t *__RESTRICT i2c_dev)
{
    I2C_SDA_MODE_OUT(i2c_dev);
	I2C_RESET_SDA(i2c_dev);
    I2C_SET_SCL(i2c_dev);
    delay_us(4);
	I2C_SET_SDA(i2c_dev);
	delay_us(4);
	I2C_RESET_SCL(i2c_dev);
}

/**
 * @brief: i2c_wait_ack
 *  等待IIC设备的应答
 * @param:
 *  i2c_dev: IIC设备
 * @returns: uint8_t
 *  等待应答信号成功返回I2C_STATUS_OK, 否则返回I2C_STATUS_FAILED
 */
static uint8_t i2c_wait_ack(const i2c_device_t *__RESTRICT i2c_dev)
{
    uint8_t ucErrTime = 0;

    I2C_SET_SDA(i2c_dev);
    I2C_SDA_MODE_IN(i2c_dev);
    delay_us(2);
    I2C_SET_SCL(i2c_dev);
    delay_us(2);
    while (I2C_READ_SDA(i2c_dev)) {
        ucErrTime++;
        if (ucErrTime > 250) {
            i2c_stop(i2c_dev);
            return I2C_STATUS_FAILED;
        }
    }
    I2C_RESET_SCL(i2c_dev);

    return I2C_STATUS_OK;
}

/**
 * @brief: i2c_send_ack
 *  向IIC设备发送应答信号
 * @param:
 *  i2c_dev: IIC设备
 *  ack: 无需ACK应答: I2C_SIGNAL_NOACK, 需要ACK应答: I2C_SIGNAL_ACK
 * @returns: void
 */
static void i2c_send_ack(const i2c_device_t *__RESTRICT i2c_dev, const GPIO_PinState ack)
{
    I2C_RESET_SCL(i2c_dev);
    I2C_SDA_MODE_OUT(i2c_dev);
    I2C_WRITE_SDA(i2c_dev, ack);
    delay_us(2);
    I2C_SET_SCL(i2c_dev);
    delay_us(2);
    I2C_RESET_SCL(i2c_dev);
}

/**
 * @brief: i2c_base_send_byte
 *  底层接口, 循环右移向IIC设备写入一个字节
 * @param:
 *  i2c_dev: IIC设备
 *  data: 需要写入的数据
 * @returns: void
 */
static void i2c_base_send_byte(const i2c_device_t *__RESTRICT i2c_dev, uint8_t data)
{
    I2C_SDA_MODE_OUT(i2c_dev);
    I2C_RESET_SCL(i2c_dev);
    for(uint8_t t = 0; t < 8; t++) {
        I2C_WRITE_SDA(i2c_dev, data >> 7 ? GPIO_PIN_SET : GPIO_PIN_RESET);
        data <<= 1;
        I2C_SET_SCL(i2c_dev);
        delay_us(2);
        I2C_RESET_SCL(i2c_dev);
        delay_us(2);
    }
}

/**
 * @brief: i2c_base_read_byte
 *  底层接口, 循环左移从IIC设备读取一个字节
 * @param:
 *  i2c_dev: IIC设备
 *  ack: 读取操作是否需要ack信号应答, 无需ACK应答: I2C_SIGNAL_NOACK, 需要ACK应答: I2C_SIGNAL_ACK
 * @returns: uint8_t
 *  返回读取到的一个字节
 */
static uint8_t i2c_base_read_byte(const i2c_device_t *__RESTRICT i2c_dev, const GPIO_PinState ack)
{
    uint8_t i, receive = 0;

    I2C_SDA_MODE_IN(i2c_dev);
    for(i = 0; i < 8; i++) {
        I2C_RESET_SCL(i2c_dev);
        delay_us(2);
        I2C_SET_SCL(i2c_dev);
        receive <<= 1;
        if(I2C_READ_SDA(i2c_dev))
            receive++;
        delay_us(2);
    }
    i2c_send_ack(i2c_dev, ack);

    return receive;
}

uint8_t i2c_write_byte(const i2c_device_t *__RESTRICT i2c_dev, const uint8_t reg, const uint8_t data)
{
    uint8_t ret;

    I2C_MUTEX_LOCK(i2c_dev);
    ret = I2C_STATUS_OK;
    i2c_start(i2c_dev);
    i2c_base_send_byte(i2c_dev, i2c_dev->addr);
    if (i2c_wait_ack(i2c_dev)) {
        ret = I2C_STATUS_FAILED;
        goto stop_i2c_write_byte;
    }
    i2c_base_send_byte(i2c_dev, reg);
    i2c_wait_ack(i2c_dev);
    i2c_base_send_byte(i2c_dev, data);
    if (i2c_wait_ack(i2c_dev))
        ret = I2C_STATUS_FAILED;

stop_i2c_write_byte:
    i2c_stop(i2c_dev);
    I2C_MUTEX_UNLOCK(i2c_dev);
    return ret;
}

uint8_t i2c_read_byte(const i2c_device_t *__RESTRICT i2c_dev, const uint8_t reg)
{
    uint8_t data;

    I2C_MUTEX_LOCK(i2c_dev);
    i2c_start(i2c_dev);
    i2c_base_send_byte(i2c_dev, i2c_dev->addr);
    i2c_wait_ack(i2c_dev);
    i2c_base_send_byte(i2c_dev, reg);
    i2c_wait_ack(i2c_dev);
    i2c_start(i2c_dev);
    i2c_base_send_byte(i2c_dev, i2c_dev->addr | 0x01);
    i2c_wait_ack(i2c_dev);
    data = i2c_base_read_byte(i2c_dev, I2C_SIGNAL_NOACK);
    i2c_stop(i2c_dev);
    I2C_MUTEX_UNLOCK(i2c_dev);

    return data;
}

uint8_t i2c_read_bytes(const i2c_device_t *__RESTRICT i2c_dev, uint8_t *__RESTRICT buf,
    const uint8_t len, const uint8_t reg)
{
    uint8_t i, ret;
    GPIO_PinState ack_signal;

    I2C_MUTEX_LOCK(i2c_dev);
    ret = I2C_STATUS_OK;
    i2c_start(i2c_dev);
    i2c_base_send_byte(i2c_dev, i2c_dev->addr);
    if (i2c_wait_ack(i2c_dev)) {
       ret = I2C_STATUS_FAILED;
       goto stop_i2c_read_bytes;
    }
    i2c_base_send_byte(i2c_dev, reg);
    i2c_wait_ack(i2c_dev);
    i2c_start(i2c_dev);
    i2c_base_send_byte(i2c_dev, i2c_dev->addr | 1);
    i2c_wait_ack(i2c_dev);
    ack_signal = I2C_SIGNAL_ACK;
    for (i = 0; i < len; ++i) {
        if (i == len - 1)
            ack_signal = I2C_SIGNAL_NOACK;
        buf[i] = i2c_base_read_byte(i2c_dev, ack_signal);
    }

stop_i2c_read_bytes:
    i2c_stop(i2c_dev);
    I2C_MUTEX_UNLOCK(i2c_dev);
    return ret;
}

uint8_t i2c_write_bytes(const i2c_device_t *__RESTRICT i2c_dev, const uint8_t *__RESTRICT buf,
    const uint8_t len, const uint8_t reg)
{
    uint8_t i, ret;

    I2C_MUTEX_LOCK(i2c_dev);
    ret = I2C_STATUS_OK;
    i2c_start(i2c_dev);
    i2c_base_send_byte(i2c_dev, i2c_dev->addr);
    if (i2c_wait_ack(i2c_dev)) {
        ret = I2C_STATUS_FAILED;
        goto stop_i2c_write_bytes;
    }
    i2c_base_send_byte(i2c_dev, reg);
    i2c_wait_ack(i2c_dev);
    for (i = 0; i < len; ++i) {
        i2c_base_send_byte(i2c_dev, buf[i]);
        if (i2c_wait_ack(i2c_dev)) {
            ret = I2C_STATUS_FAILED;
            goto stop_i2c_write_bytes;
        }
    }

stop_i2c_write_bytes:
    i2c_stop(i2c_dev);
    I2C_MUTEX_UNLOCK(i2c_dev);
    return ret;
}

uint8_t i2c_only_read_bytes(const i2c_device_t *__RESTRICT i2c_dev, uint8_t *__RESTRICT buf,
    const uint8_t len)
{
    uint8_t ret, i;
    GPIO_PinState ack_signal;

    I2C_MUTEX_LOCK(i2c_dev);
    ret = I2C_STATUS_OK;
    i2c_start(i2c_dev);
    i2c_base_send_byte(i2c_dev, i2c_dev->addr | 1);
    if (i2c_wait_ack(i2c_dev)) {
        ret = I2C_STATUS_FAILED;
        goto stop_i2c_only_read_bytes;
    }
    ack_signal = I2C_SIGNAL_ACK;
    for (i = 0; i < len; i++) {
        if (i == len - 1)
            ack_signal = I2C_SIGNAL_NOACK;
        buf[i] = i2c_base_read_byte(i2c_dev, ack_signal);
    }

stop_i2c_only_read_bytes:
    i2c_stop(i2c_dev);
    I2C_MUTEX_UNLOCK(i2c_dev);
    return ret;
}

使用示例

1.IIC设备定义

static i2c_bus_t i2c_device_bus[] = {
    [0] = {.sda.gpio = GPIOC, .sda.pin = GPIO_PIN_1,
           .scl.gpio = GPIOC, .scl.pin = GPIO_PIN_0},
    [1] = {.sda.gpio = GPIOC, .sda.pin = GPIO_PIN_1,
           .scl.gpio = GPIOD, .scl.pin = GPIO_PIN_6},
    [2] = {.sda.gpio = GPIOA, .sda.pin = GPIO_PIN_8,
           .scl.gpio = GPIOC, .scl.pin = GPIO_PIN_7},
};
static i2c_device_t ap3216c_i2c_dev = {.bus = &i2c_device_bus[0], .addr = AP3216C_I2C_ADDR};
static i2c_device_t icm20608_i2c_dev= {.bus = &i2c_device_bus[0], .addr = ICM20608_I2C_ADDR};
static i2c_device_t ath10_i2c_dev   = {.bus = &i2c_device_bus[1], .addr = AHT10_IIC_ADDR};
static i2c_device_t mpu9250_i2c_dev = {.bus = &i2c_device_bus[2], .addr = MPU925X_I2C_AD1_ADDR};
static i2c_device_t ak8963_i2c_dev = {.bus = &i2c_device_bus[2], .addr = AK8963_ADDR};

2.IIC设备初始化

int main(void)
{
	...
	i2c_bus_init(&i2c_device_bus[0]);
    i2c_bus_init(&i2c_device_bus[1]);
    vSemaphoreDelete(i2c_device_bus[1].mutex);	/* i2c_device_bus[0]和i2c_device_bus[1]共用了scl, 要用同一个锁 */
    i2c_device_bus[1].mutex = i2c_device_bus[0].mutex;
	i2c_bus_init(&i2c_device_bus[2]);
	...
}

3.接下来就可以使用iic.h提供的write/read函数了

    ...
    /* 示例 */
    if (i2c_write_bytes(aht10_dev, temp, 2, AHT10_NORMAL_CMD) == I2C_STATUS_FAILED) {
        ret = 1;
        goto stop_aht10_init;
	}
	...
  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值