【前言】
这次准备驱动IIC,首先采取模拟IIC的驱动,这里跟大家分享如何书写IIC的start、stop、ack、sendbyte,readbyte的时序。
【IIC时序】
【实现步骤】
1、新建iic.c/h并添加进HW分组中。
2、找到原理图,确定IIC的SCL、SDA的IO。在原图中SCL为PC00,SDA为PC01。
3、首先书写IIC两个IO的GPIO的初始化,分为时钟使能,由于开发板上有4.7K上拉电阻,这里SCL定义为推挽输出,SDA为开漏输出。初始化代码如下:
void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure = {0}; //开启GPIOA时钟 __RCC_GPIOC_CLK_ENABLE(); //GPIO配置 GPIO_InitStructure.Pins = GPIO_PIN_0; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; GPIO_Init(CW_GPIOC, &GPIO_InitStructure); GPIO_InitStructure.Pins = GPIO_PIN_1; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; GPIO_Init(CW_GPIOC, &GPIO_InitStructure); //初始化后,把空线设为空闲 IIC_SCL_H; IIC_SDA_H; }
4、为了方便阅读,在iic.h中宏义,高低电平的输出,以及GPIO电平的获取。
/* IIC IO高低电平的设置 */ #define IIC_SCL_H CW_GPIOC->BSRR = GPIO_PIN_0 #define IIC_SCL_L CW_GPIOC->BRR = GPIO_PIN_0 #define IIC_SDA_H CW_GPIOC->BSRR = GPIO_PIN_1 #define IIC_SDA_L CW_GPIOC->BRR = GPIO_PIN_1 /* SDA 电平获取 */ #define READ_SDA GPIO_ReadPin(CW_GPIOC, GPIO_PIN_1)
5、IIC起始信号,SCL保持高电平期间,数据线SDA上的电平被拉低
void IIC_Start(void) { IIC_SCL_H; IIC_SDA_H; Delay_US(2); IIC_SDA_L; Delay_US(2); IIC_SCL_L; }
6、IIC停止信号:SCL保持高电平期间,数据线SDA被释放,返回高电平
void IIC_Stop(void) { /* SCL保持高电平期间,数据线SDA被释放,返回高电平 */ IIC_SCL_H; IIC_SDA_L; Delay_US(2); IIC_SDA_H; Delay_US(2); IIC_SCL_L; }
7、获取ACK应答
首选读取SDA的电平,如果读取到低电平,说明从机有应答,然后再读取一次,确保稳定的低电平。
uint8_t IIC_Wait_Ack(int16_t timeout) { do{ timeout --; Delay_US(2); }while((READ_SDA) && (timeout>=0)); if(timeout<0) return 1; /* 判断SDA 稳定维持为低电平 */ IIC_SCL_H; Delay_US(2); if(0 != READ_SDA) return 2; IIC_SCL_L; Delay_US(2); return 0; }
8、发送一个字节
void IIC_Send_Byte(uint8_t txd) { int i; for(i = 7; i >= 0; i--) { IIC_SCL_L; if(txd & BIT(i)) IIC_SDA_H; else IIC_SDA_L; Delay_US(2); IIC_SCL_H; Delay_US(2); } IIC_SCL_L; Delay_US(2); IIC_SDA_H; }
8、读取一个字节,发送完成产生应该信号
ACK信号:发送者在ACK时钟脉冲期间释放SDA线,接收者可以将SDA拉低并在时钟信号为高时保持低电平。
NACK信号:当在第9个时钟脉冲的时候SDA线保持高电平,就被定义为NACK信号。
uint8_t IIC_Read_Byte(uint8_t ack) { int8_t i; uint8_t rxd; rxd = 0; for(i = 7; i >= 0; i--) { IIC_SCL_L; Delay_US(2); IIC_SCL_H; if(0 != READ_SDA) rxd |= BIT(i); Delay_US(2); } IIC_SCL_L; Delay_US(2); //开始应签 if(ack) { IIC_SDA_L; IIC_SCL_H; Delay_US(2); IIC_SCL_L; IIC_SDA_H; Delay_US(2); }else{ IIC_SDA_H; IIC_SCL_H; Delay_US(2); IIC_SCL_L; Delay_US(2); } return rxd; }
到此IIC时序、写、读函数全部写完。保存好后,就可以供其他的驱动来使用。此例程也可以通过修改Init、宏定义进方便的进行移植。