简述
IIC(Inter-Integrated Circuit)其实是IICBus简称,它是一种串行通信总线,使用多主从架构,在STM32开发中经常见到。
关于IIC可以参考之前发的一篇文章:《通信协议 IIC 与 SPI 最全对比》来了解。
使用面向对象的编程思想封装IIC驱动,将IIC的属性和操作封装成一个库,在需要创建一个IIC设备时只需要实例化一个IIC对象即可,本文是基于STM32和HAL库做进一步封装的。
底层驱动方法不重要,封装的思想很重要。在完成对IIC驱动的封装之后借助继承特性实现AT24C64存储器的驱动开发,仍使用面向对象的思想封装AT24C64驱动。
IIC驱动面向对象封装
iic.h头文件主要是类模板的定义,具体如下:
//定义IIC类
typedef struct IIC_Type
{
//属性
GPIO_TypeDef *GPIOx_SCL; //GPIO_SCL所属的GPIO组(如:GPIOA)
GPIO_TypeDef *GPIOx_SDA; //GPIO_SDA所属的GPIO组(如:GPIOA)
uint32_t GPIO_SCL; //GPIO_SCL的IO引脚(如:GPIO_PIN_0)
uint32_t GPIO_SDA; //GPIO_SDA的IO引脚(如:GPIO_PIN_0)
//操作
void (*IIC_Init)(const struct IIC_Type*); //IIC_Init
void (*IIC_Start)(const struct IIC_Type*); //IIC_Start
void (*IIC_Stop)(const struct IIC_Type*); //IIC_Stop
uint8_t (*IIC_Wait_Ack)(const struct IIC_Type*); //IIC_Wait_ack,返回wait失败或是成功
void (*IIC_Ack)(const struct IIC_Type*); //IIC_Ack,IIC发送ACK信号
void (*IIC_NAck)(const struct IIC_Type*); //IIC_NAck,IIC发送NACK信号
void (*IIC_Send_Byte)(const struct IIC_Type*,uint8_t); //IIC_Send_Byte,入口参数为要发送的字节
uint8_t (*IIC_Read_Byte)(const struct IIC_Type*,uint8_t); //IIC_Send_Byte,入口参数为是否要发送ACK信号
void (*delay_us)(uint32_t); //us延时
}IIC_TypeDef;
iic.c源文件主要是类模板具体操作函数的实现,具体如下:
//设置SDA为输入模式
static void SDA_IN(const struct IIC_Type* IIC_Type_t)
{
uint8_t io_num = 0; //定义io Num号
switch(IIC_Type_t->GPIO_SDA)
{
case GPIO_PIN_0:
io_num = 0;
break;
case GPIO_PIN_1:
io_num = 1;
break;
case GPIO_PIN_2:
io_num = 2;
break;
case GPIO_PIN_3:
io_num = 3;
break;
case GPIO_PIN_4:
io_num = 4;
break;
case GPIO_PIN_5:
io_num = 5;
break;
case GPIO_PIN_6:
io_num = 6;
break;
case GPIO_PIN_7:
io_num = 7;
break;
case GPIO_PIN_8:
io_num = 8;
break;
case GPIO_PIN_9:
io_num = 9;
break;
case GPIO_PIN_10:
io_num = 10;
break;
case GPIO_PIN_11:
io_num = 11;
break;
case GPIO_PIN_12:
io_num = 12;
break;
case GPIO_PIN_13:
io_num = 13;
break;
case GPIO_PIN_14:
io_num = 14;
break;
case GPIO_PIN_15:
io_num = 15;
break;
}
IIC_Type_t->GPIOx_SDA->MODER&=~(3<GPIO_SDA清零
IIC_Type_t->GPIOx_SDA->MODER|=0<GPIO_SDA设置为输入模式
}
//设置SDA为输出模式
static void SDA_OUT(const struct IIC_Type* IIC_Type_t)
{
uint8_t io_num = 0; //定义io Num号
switch(IIC_Type_t->GPIO_SDA)
{
case GPIO_PIN_0:
io_num = 0;
break;
case GPIO_PIN_1:
io_num = 1;
break;
case GPIO_PIN_2:
io_num = 2;
break;
case GPIO_PIN_3:
io_num = 3;
break;
case GPIO_PIN_4:
io_num = 4;
break;
case GPIO_PIN_5:
io_num = 5;
break;
case GPIO_PIN_6:
io_num = 6;
break;
case GPIO_PIN_7:
io_num = 7;
break;
case GPIO_PIN_8:
io_num = 8;
break;
case GPIO_PIN_9:
io_num = 9;
break;
case GPIO_PIN_10:
io_num = 10;
break;
case GPIO_PIN_11:
io_num = 11;
break;
case GPIO_PIN_12:
io_num = 12;
break;
case GPIO_PIN_13:
io_num = 13;
break;
case GPIO_PIN_14:
io_num = 14;
break;
case GPIO_PIN_15:
io_num = 15;
break;
}
IIC_Type_t->GPIOx_SDA->MODER&=~(3<GPIO_SDA清零
IIC_Type_t->GPIOx_SDA->MODER|=1<GPIO_SDA设置为输出模式
}
//设置SCL电平
static void IIC_SCL(const struct IIC_Type* IIC_Type_t,int n)
{
if(n == 1)
{
HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SCL,IIC_Type_t->GPIO_SCL,GPIO_PIN_SET); //设置SCL为高电平
}
else{
HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SCL,IIC_Type_t->GPIO_SCL,GPIO_PIN_RESET); //设置SCL为低电平
}
}
//设置SDA电平
static void IIC_SDA(const struct IIC_Type* IIC_Type_t,int n)
{
if(n == 1)
{
HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA,GPIO_PIN_SET); //设置SDA为高电平
}
else{
HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA,GPIO_PIN_RESET); //设置SDA为低电平
}
}
//读取SDA电平
static uint8_t READ_SDA(const struct IIC_Type* IIC_Type_t)
{
return HAL_GPIO_ReadPin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA); //读取SDA电平
}
//IIC初始化
static void IIC_Init_t(const struct IIC_Type* IIC_Type_t)
{
GPIO_InitTypeDef GPIO_Initure;
//根据GPIO组初始化GPIO时钟
if(IIC_Type_t->GPIOx_SCL == GPIOA || IIC_Type_t->GPIOx_SDA == GPIOA)
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟
}
if(IIC_Type_t->GPIOx_SCL == GPIOB || IIC_Type_t->GPIOx_SDA == GPIOB)
{
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能GPIOB时钟
}
if(IIC_Type_t->GPIOx_SCL == GPIOC || IIC_Type_t->GPIOx_SDA == GPIOC)
{
__HAL_RCC_GPIOC_CLK_ENABLE(); //使能GPIOC时钟
}
if(IIC_Type_t->GPIOx_SCL == GPIOD || IIC_Type_t->GPIOx_SDA == GPIOD)
{
__HAL_RCC_GPIOD_CLK_ENABLE(); //使能GPIOD时钟
}
if(IIC_Type_t->GPIOx_SCL == GPIOE || IIC_Type_t->GPIOx_SDA == GPIOE)
{
__HAL_RCC_GPIOE_CLK_ENABLE(); //使能GPIOE时钟
}
if(IIC_Type_t->GPIOx_SCL == GPIOH || IIC_Type_t->GPIOx_SDA == GPIOH)
{
__HAL_RCC_GPIOH_CLK_ENABLE(); //使能GPIOH时钟
}
//GPIO_SCL初始化设置
GPIO_Initure.Pin=IIC_Type_t->GPIO_SCL;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //快速
HAL_GPIO_Init(IIC_Type_t->GPIOx_SCL,&GPIO_Initure);
//GPIO_SDA初始化设置
GPIO_Initure.Pin=IIC_Type_t->GPIO_SDA;
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH; //快速
HAL_GPIO_Init(IIC_Type_t->GPIOx_SDA,&GPIO_Initure);
//SCL与SDA的初始化均为高电平
IIC_SCL(IIC_Type_t,1);
IIC_SDA(IIC_Type_t,1);
}
//IIC Start
static void IIC_Start_t(const struct IIC_Type* IIC_Type_t)
{
SDA_OUT(IIC_Type_t); //sda线输出
IIC_SDA(IIC_Type_t,1);
IIC_SCL(IIC_Type_t,1);
IIC_Type_t->delay_us(4);
IIC_SDA(IIC_Type_t,0); //START:when CLK is high,DATA change form high to low
IIC_Type_t->delay_us(4);
IIC_SCL(IIC_Type_t,0); //钳住I2C总线,准备发送或接收数据
}
//IIC Stop
static void IIC_Stop_t(const struct IIC_Type* IIC_Type_t)
{
SDA_OUT(IIC_Type_t); //sda线输出
IIC_SCL(IIC_Type_t,0);
IIC_SDA(IIC_Type_t,0); //STOP:when CLK is high DATA change form low to high
IIC_Type_t->delay_us(4);
IIC_SCL(IIC_Type_t,1);
IIC_SDA(IIC_Type_t,1); //发送I2C总线结束信号
IIC_Type_t->delay_us(4);
}
//IIC_Wait_ack 返回HAL_OK表示wait成功,返回HAL_ERROR表示wait失败
static uint8_t IIC_Wait_Ack_t(const struct IIC_Type* IIC_Type_t) //IIC_Wait_ack,返回wait失败或是成功
{
uint8_t ucErrTime = 0;
SDA_IN(IIC_Type_t); //SDA设置为输入
IIC_SDA(IIC_Type_t,1);IIC_Type_t->delay_us(1);
IIC_SCL(IIC_Type_t,1);IIC_Type_t->delay_us(1);
while(READ_SDA(IIC_Type_t))
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Type_t->IIC_Stop(IIC_Type_t);
return HAL_ERROR;
}
}
IIC_SCL(IIC_Type_t,0);//时钟输出0
return HAL_OK;
}
//产生ACK应答
static void IIC_Ack_t(const struct IIC_Type* IIC_Type_t)
{
IIC_SCL(IIC_Type_t,0);
SDA_OUT(IIC_Type_t);
IIC_SDA(IIC_Type_t,0);
IIC_Type_t->delay_us(2);
IIC_SCL(IIC_Type_t,1);
IIC_Type_t->delay_us(2);
IIC_SCL(IIC_Type_t,0);
}
//产生NACK应答
static void IIC_NAck_t(const struct IIC_Type* IIC_Type_t)
{
IIC_SCL(IIC_Type_t,0);
SDA_OUT(IIC_Type_t);
IIC_SDA(IIC_Type_t,1);
IIC_Type_t->delay_us(2);
IIC_SCL(IIC_Type_t,1);
IIC_Type_t->delay_us(2);
IIC_SCL(IIC_Type_t,0);
}
//IIC_Send_Byte,入口参数为要发送的字节
static void IIC_Send_Byte_t(const struct IIC_Type* IIC_Type_t,uint8_t txd)
{
uint8_t t = 0;
SDA_OUT(IIC_Type_t);
IIC_SCL(IIC_Type_t,0);//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA(IIC_Type_t,(txd&0x80)>>7);
txd <<= 1;
IIC_Type_t->delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL(IIC_Type_t,1);
IIC_Type_t->delay_us(2);
IIC_SCL(IIC_Type_t,0);
IIC_Type_t->delay_us(2);
}
}
//IIC_Send_Byte,入口参数为是否要发送ACK信号
static uint8_t IIC_Read_Byte_t(const struct IIC_Type* IIC_Type_t,uint8_t ack)