概述
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;
}
...