前言
在项目中遇到要驱动10个IIC设备其中有8个地址相同并且无法更改的情况,使用宏定义形式的IO口操作不能很好的满足需求,于是想到了面向对象的写法,在C语言中虽然没有对象这一概念,但对象也只是相关的数据结构和一组方法的封装,在C程序中可以提过将相关数据结构封装到结构体中,函数传入应该结构体指针的方式来实现类似的面向对象写法。
一、Software_IIC.h
将IIC协议信息抽象到IIC_TypeDef结构体中,包括一个SDA、SCL引脚以及一个地址信息,这样在使用软件IIC时只需要定义IIC_TypeDef的结构体就可以了,并且可以很好的复用IIC操作的代码。
如我这种8个地址相同的情况就可以定义如下IIC设备:
IIC_Typedef IIC1 = {
{RCC_AHB1Periph_GPIOH, GPIOH, 4},
{RCC_AHB1Periph_GPIOH, GPIOH, 5},
0xA0,
};
IIC_Typedef IIC2 = {
{RCC_AHB1Periph_GPIOH, GPIOH, 4},
{RCC_AHB1Periph_GPIOH, GPIOH, 6},
0xA0,
};
IIC_Typedef IIC3 = {
{RCC_AHB1Periph_GPIOH, GPIOH, 4},
{RCC_AHB1Periph_GPIOH, GPIOH, 7},
0xA0,
};
IIC_Typedef IIC4 = {
{RCC_AHB1Periph_GPIOH, GPIOH, 4},
{RCC_AHB1Periph_GPIOH, GPIOH, 5},
0xA8,
};
/**
* @Author: 时间世纪
* @Date: 2021-08-06 13:35:00
* @Description: 软件IIC驱动程序
*/
#ifndef _SOFTWARE_IIC_H_
#define _SOFTWARE_IIC_H_
#include "HAL_Driver.h"
#define IIC_DELAY_TIME 1 //延时时间
//一个软件IIC设备
typedef struct
{
HAL_GPIO_TypeDef SCL;//包含操作IO口的信息
HAL_GPIO_TypeDef SDA;
uint8_t ADD;//设备地址,内部使用时没有移位,只设置了读写位
} IIC_TypeDef;
/**
* @brief 初始化一个IIC设备
* @param IIC_TypeDef *pIIC
* @retval:
*/
extern void IIC_Init(IIC_TypeDef * const pIIC);
/**
* @brief 开始一次IIC通信
* @param IIC_TypeDef *pIIC
* @retval
*/
extern void IIC_Start(IIC_TypeDef *const pIIC);
/**
* @brief 停止一次IIC通信
* @param IIC_TypeDef *pIIC
* @retval
*/
extern void IIC_Stop(IIC_TypeDef *const pIIC);
/**
* @brief 等待从机应答
* @param IIC_TypeDef *pIIC
* @retval 0:有应答, 1:无应答
*/
extern uint8_t IIC_Sack(IIC_TypeDef *const pIIC);
/**
* @brief 发送一字节数据
* @param IIC_TypeDef *pIIC
* @param uint8_t dat
* @retval
*/
extern void IIC_SendByte(IIC_TypeDef *const pIIC, uint8_t dat);
/**
* @brief 发送多个数据
* @param IIC_TypeDef *pIIC
* @param uint8_t * dat
* @param uint8_t len 长度
* @retval 0:发送成功,1: 失败
*/
extern uint8_t IIC_Send(IIC_TypeDef *const pIIC, uint8_t *dat, uint8_t len);
/**
* @brief 读取一字节数据
* @param IIC_TypeDef *pIIC
* @retval
*/
extern uint8_t IIC_ReadByte(IIC_TypeDef *const pIIC);
/**
* @brief 读取多个数据
* @param IIC_TypeDef *pIIC
* @param uint8_t * dat
* @param uint8_t len 长度
*/
extern void IIC_Read(IIC_TypeDef *const pIIC, uint8_t *dat, uint8_t len);
/**
* @brief 发送写地址
* @param IIC_TypeDef *pIIC
* @retval 0:有应答,1:无应答
*/
__STATIC_FORCEINLINE uint8_t IIC_SendWriteAddress(IIC_TypeDef *const pIIC)
{
IIC_SendByte(pIIC, pIIC->ADD);
return IIC_Sack(pIIC);
}
/**
* @brief 发送读地址
* @param IIC_TypeDef *pIIC
* @retval 0:有应答,1:无应答
*/
__STATIC_FORCEINLINE uint8_t IIC_SendReadAddress(IIC_TypeDef *const pIIC)
{
IIC_SendByte(pIIC, pIIC->ADD | 0x01);
return IIC_Sack(pIIC);
}
/**
* @brief 发送一个字节并检查从机应答
* @param IIC_TypeDef *pIIC
* @param uint8_t dat
* @retval 0:有应答,1:无应答
*/
__STATIC_FORCEINLINE uint8_t IIC_SendAndSack(IIC_TypeDef *const pIIC, uint8_t dat)
{
IIC_SendByte(pIIC, dat);
return IIC_Sack(pIIC);
}
/**
* @brief 读取一字节数据并进行应答操作
* @param IIC_TypeDef *pIIC
* @param uint8_t ack 是否对从机进行应答继续读取,0:否停止读取,1:继续读取
* @retval
*/
__STATIC_FORCEINLINE uint8_t IIC_ReadAndAck(IIC_TypeDef *const pIIC, uint8_t ack)
{
uint8_t dat = 0;
dat = IIC_ReadByte(pIIC);
IIC_Ack(IIC, ack);
return dat;
}
#endif
二、起始时序
注意:程序未采用STM32的HAL库,程序中HAL_GPIO_High,HAL_GPIO_Low函数仅代表当前这款单片机IO口的操作的封装,并向其传递控制IO口的必要参数。
起始时序为在SCL为高时SDA拉低启动一次发送。
/**
* @brief 启动一次传输,在SCL高电平时SDA高变低开始传输
* @param IIC_TypeDef *pIIC
* @retval
*/
void IIC_Start(IIC_TypeDef * const pIIC)
{
HAL_GPIO_SetOutput(&pIIC->SDA);
HAL_GPIO_High(&pIIC->SDA);
HAL_GPIO_High(&pIIC->SCL);//拉高SCL
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_Low(&pIIC->SDA);//拉低SDA启动一次传输
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_Low(&pIIC->SCL);//拉低SCL准备传输
Delay_us(IIC_DELAY_TIME);
}
三、停止时序
停止时序为SCL为高时SDA由低变高结束一次发送。
/**
* @brief 停止本次传输,在SCL高电平时SDA低变高停止传输
* @param IIC_TypeDef *pIIC
* @retval
*/
void IIC_Stop(IIC_TypeDef *const pIIC)
{
HAL_GPIO_SetOutput(&pIIC->SDA);
HAL_GPIO_Low(&pIIC->SDA);//拉低SDA
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_High(&pIIC->SCL);//拉高SCL
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_High(&pIIC->SDA);//拉高SDA结束一次传输
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_Low(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
}
四、发送数据时序
发送时序为在SCL为低时SDA可变换,SCL拉高从机读取SDA。
/**
* @brief 发送一字节数据
* @param IIC_TypeDef *pIIC
* @param uint8_t dat
*/
void IIC_SendByte(IIC_TypeDef *const pIIC, uint8_t dat)
{
HAL_GPIO_SetOutput(&pIIC->SDA);
for (uint8_t i = 0; i < 8; i++)
{
HAL_GPIO_Low(&pIIC->SCL);//拉低SCL
Delay_us(IIC_DELAY_TIME);
if (dat & (0x80 >> i))//传输数据
HAL_GPIO_High(&pIIC->SDA);
else
HAL_GPIO_Low(&pIIC->SDA);
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_High(&pIIC->SCL);//拉高SCL从机读取数据
Delay_us(IIC_DELAY_TIME);
}
HAL_GPIO_Low(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
}
五、读取时序
在SCL为低时从机将数据放在SDA上。
/**
* @brief 接收一字节数据
* @param IIC_TypeDef *pIIC
* @retval
*/
uint8_t IIC_ReadByte(IIC_TypeDef *const pIIC)
{
uint8_t dat = 0;
HAL_GPIO_SetInput(&pIIC->SDA);
for (uint8_t i = 0; i < 8; i++)
{
HAL_GPIO_Low(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_High(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
dat = (dat << 1) | HAL_GPIO_Read(&pIIC->SDA);
}
HAL_GPIO_Low(&pIIC->SCL);
return dat;
}
六、应答时序
/**
* @brief 等待从机应答,在SCL高时从机会拉低SDA进行应答
* @param IIC_TypeDef *pIIC
* @retval 0:有应答,1:无应答
*/
uint8_t IIC_Sack(IIC_TypeDef *const pIIC)
{
uint8_t result;
HAL_GPIO_SetInput(&pIIC->SDA);//设置SDA为输入
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_High(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
for (result = 0; result <= 10; result++)
{
if (!HAL_GPIO_Read(&pIIC->SDA))
break;
Delay_us(IIC_DELAY_TIME);
}
HAL_GPIO_Low(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
return result >= 10 ? 1 : 0;
}
/**
* @brief 产生一个应答信号对从机应答
* @param IIC_TypeDef *pIIC
* @param uint8_t ack 0: 结束不应答,1:应答
* @retval:
*/
static void IIC_Ack(IIC_TypeDef *const pIIC, uint8_t ack)
{
HAL_GPIO_Low(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_SetOutput(&pIIC->SDA);
if (ack)
HAL_GPIO_Low(&pIIC->SDA);
else
HAL_GPIO_High(&pIIC->SDA);
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_High(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
HAL_GPIO_Low(&pIIC->SCL);
Delay_us(IIC_DELAY_TIME);
}