本文仅供参考
IIC是一个串行,半双工,同步的常用的总线协议,IIC总线有两根双向的信号线一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步。
IIC总线是一种多主机总线,连接在IIC总线上的器件分为主机和从机,主机有权发起和结束一次通信,从机只能被主机呼叫。当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误的产生;每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且IIC上的所有设备都可以做主机,但同一时刻只能有一个主机,总线上的器件增加和删除不影响其他期间的正常使用。
硬件IIC和软件IIC有什么区别?
硬件IIC是由开发板内部的硬件的内部模块来实现的,使用CPU的时钟信号来控制数据传输和时序。硬件IIC对于软件来说相对简单一点,不用编写复杂的代码,硬件IIC对应芯片上的IIC外设,有相应的IIC驱动电路,其所使用的IIC管脚也是专用的。
软件IIC是由CPU的GPIO模拟实现的,通过CPU的软件来控制时序和数据传输。可以实现多路IIC通信,硬件IIC一般只能实现单路通信,可以在普通GPIO上实现IIC通信,相对灵活,可以实现任意时序,更加灵活。
下面是软件IIC的代码实现示例:
#include "IIC.h"
#define IIC_SDA_0 GPIOB->ODR &= ((uint8_t)(~0x01))
#define IIC_SDA_1 GPIOB->ODR |= ((uint8_t)0x01)
#define IIC_SDA (GPIOB->IDR & (uint8_t)0x01)
#define IIC_SCL_0 GPIOB->ODR &= ((uint8_t)(~0x02))
#define IIC_SCL_1 GPIOB->ODR |= ((uint8_t)0x02)
#define Peripheral_Wr_Addr1 0x**
#define Peripheral_Rd_Addr1 0x**
extern Delay(u8 n);//精确延时到微秒
//模拟IIC引脚方向配置
void IIC_Pin_Init(void)
{
GPIOB->DDR |= ((uint8_t)0x01);//PC0 IIC_SDA 输出
GPIOB->CR1 |= ((uint8_t)0x01);//PC0 IIC_SDA 推挽
GPIOB->DDR |= ((uint8_t)0x02);//PC1 IIC_SCL 输出
GPIOB->CR1 |= ((uint8_t)0x02);//PC1 IIC_SCL 推挽
IIC_SCL_1; //拉高时钟线
IIC_SDA_1; //拉高数据线
}
//模拟IIC_SDA引脚方向配置 输入
void SDA_IN(void)
{
GPIOB->DDR &= ((uint8_t)~0x01);//PC0 IIC_SDA 输入
GPIOB->CR1 &= ((uint8_t)~0x01);//PC0 IIC_SDA 浮空
}
//模拟IIC_SDA引脚方向配置 输出
void SDA_OUT(void)
{
GPIOB->DDR |= ((uint8_t)0x01);//PC0 IIC_SDA 输出
GPIOB->CR1 |= ((uint8_t)0x01);//PC0 IIC_SDA 推挽
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT();//IIC_SDA线输出
IIC_SDA_1; //拉高数据线
IIC_SCL_1; //拉高时钟线
Delay(10);
IIC_SDA_0; //拉低数据线
Delay(10);
IIC_SCL_0; //拉低时钟线 发送IIC总线开始信号
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//IIC_SDA线输出
IIC_SCL_0; //拉低时钟线
IIC_SDA_0; //拉低数据线
Delay(10);
IIC_SCL_1; //拉高时钟线
Delay(10);
IIC_SDA_1; //拉高数据线 发送IIC总线停止信号
Delay(10);
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL_0; //拉低时钟线
SDA_OUT();//IIC_SDA线输出
IIC_SDA_0; //拉低数据线
Delay(10);
IIC_SCL_1; //拉高时钟线
Delay(10);
IIC_SCL_0; //拉低时钟线
}
//不产生ACK应答
void IIC_NotAck(void)
{
IIC_SCL_0; //拉低时钟线
SDA_OUT();//IIC_SDA线输出
IIC_SDA_1; //拉高数据线
Delay(10);
IIC_SCL_1; //拉高时钟线
Delay(10);
IIC_SCL_0; //拉低时钟线
}
//等待应答信号到来
//返回值:1 接收应答失败
// 0 接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 Wait_TOut_Cnt = 0;//设置等待应答信号超时计数
SDA_IN(); //IIC_SDA线输入
IIC_SDA_1; //拉高数据线
Delay(10);
IIC_SCL_1; //拉高时钟线 等待应答信号
Delay(10);
while(IIC_SDA)
{
Wait_TOut_Cnt++;
if(Wait_TOut_Cnt > 250)
{
IIC_Stop(); //等待应答信号超时 发送IIC总线停止信号
return 1;
}
}
IIC_SCL_0; //拉低时钟线 结束应答信号
return 0;
}
//IIC发送一个字节
void IIC_Write_Byte(u8 WByte)
{
u8 Wb_Cnt = 0; //写数据位计数
SDA_OUT();//IIC_SDA线输出
IIC_SCL_0; //拉低时钟线 开始数据传输
for(Wb_Cnt=0; Wb_Cnt<8; Wb_Cnt++)
{
if(WByte&0x80)
{
IIC_SDA_1;
}
else
{
IIC_SDA_0;
}
WByte <<= 1; //数据移位
Delay(10);
IIC_SCL_1; //拉高时钟线
Delay(10);
IIC_SCL_0; //拉低时钟线 准备开始传送数据位
Delay(10);
}
}
//IIC读取一个字节
//参数值:1 发送Ack
// 0 不发送Ack
u8 IIC_Read_Byte(u8 SF_Ack)
{
u8 Rb_Cnt = 0; //读数据位计数
u8 RByte = 0; //读字节
SDA_IN(); //SDA设置为输入
for(Rb_Cnt=0; Rb_Cnt<8; Rb_Cnt++)
{
IIC_SCL_0; //拉低时钟线 准备开始传送数据位
Delay(10);
IIC_SCL_1; //拉高时钟线
RByte <<= 1; //数据移位
if(IIC_SDA)
{
RByte++;
}
Delay(10);
}
if(!SF_Ack) //0 不发送Ack
{
IIC_NotAck(); //发送NAck
}
else //1 发送Ack
{
IIC_Ack(); //发送Ack
}
return RByte;
}
// 写一个字节
void Peripheral_Write_Byte(u8 i2c_addr, u8 *Wreg_data)
{
IIC_Start(); //发送IIC起始信号
IIC_Write_Byte(Peripheral_Wr_Addr1);//发送IIC写地址
IIC_Wait_Ack(); //等待IIC应答信号
IIC_Write_Byte(i2c_addr); //发送IIC寄存器地址
IIC_Wait_Ack(); //等待IIC应答信号
IIC_Write_Byte(*Wreg_data); //发送写入寄存器的数据
IIC_Wait_Ack(); //等待IIC应答信号
IIC_Stop(); //发送IIC停止信号
}
// 写N个字节
void Peripheral_Write_NByte(u8 i2c_addr, u8 *Wreg_data, u8 WLen)
{
u8 WB_Cnt = 0;
IIC_Start(); //发送IIC起始信号
IIC_Write_Byte(Peripheral_Wr_Addr1);//发送IIC写地址
IIC_Wait_Ack(); //等待IIC应答信号
IIC_Write_Byte(i2c_addr); //发送IIC寄存器地址
IIC_Wait_Ack(); //等待IIC应答信号
for(WB_Cnt=0; WB_Cnt<WLen; WB_Cnt++)
{
IIC_Write_Byte(Wreg_data[WB_Cnt]); //连续写入寄存器的数据 等待应答信号
IIC_Wait_Ack(); //等待IIC应答信号
}
IIC_Stop(); //发送IIC停止信号
}
// 读一个字节
void Peripheral_Read_Byte(u8 i2c_addr, u8 *reg_dataR)
{
IIC_Start(); //发送IIC起始信号
IIC_Write_Byte(Peripheral_Wr_Addr1);//发送IIC写地址
IIC_Wait_Ack(); //等待IIC应答信号
IIC_Write_Byte(i2c_addr); //发送IIC寄存器地址
IIC_Wait_Ack(); //等待IIC应答信号
IIC_Start(); //发送IIC起始信号
IIC_Write_Byte(Peripheral_Rd_Addr1);//发送IIC读地址
IIC_Wait_Ack(); //等待IIC应答信号
*reg_dataR = IIC_Read_Byte(0); //读取寄存器的数据
IIC_Stop(); //发送IIC停止信号
}
// 读N个字节
void Peripheral_Read_NByte(u8 i2c_addr, u8 *reg_dataR, u8 RLen)
{
u8 RB_Cnt = 0; //读字节计数
IIC_Start(); //发送IIC起始信号
IIC_Write_Byte(Peripheral_Wr_Addr1); //发送IIC写地址
IIC_Wait_Ack(); //等待IIC应答信号
IIC_Write_Byte(i2c_addr); //发送IIC寄存器地址
IIC_Wait_Ack(); //等待IIC应答信号
for(RB_Cnt=0; RB_Cnt<(RLen-1); RB_Cnt++)
{
reg_dataR[RB_Cnt] = IIC_Read_Byte(1); //连续读取寄存器的数据 等待应答信号
}
reg_dataR[RB_Cnt] = IIC_Read_Byte(0); //读取寄存器最后一个数据 不等待应答信号
IIC_Stop(); //发送IIC停止信号
}
实现软件IIC的代码需要通过GPIO模拟IIC时序,具体步骤如下:
配置GPIO用于IIC通信,将SCL和SDA引脚分别配置为推挽输出模式;
实现IIC起始信号、停止信号、产生ACK应答、不产生ACK应答、发送数据、接收数据等操作;
编写具体的IIC外设读写函数:1.主机发送起始信号启用总线
2.主机发送一个字节数据指明从机地址和后续字节的传送方向
3.被寻址的从机发送应答信号回应主机
4.发送器发送一个字节数据
5.接收器发送应答信号回应发送器
... ...(循环步骤4、5)
n.通信完成后主机发送停止信号释放总线
时序上
空闲状态:iic通信协议规定SDA、SCL两根线同时处于高电平;
起始信号:通信的起始点; 起始信号要求,SCL处于高电平时,将SDA从高拉低(跳变);
停止信号:通信的结束点; 停止信号要求在SCL处于高电平时,将SDA由低拉高(跳变);
应答信号:通信过程中接收器对发送器的反馈;
IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后
接收器必须发送1位应答位来回应发送器即一帧共有9位,和串口的先发低位不同,IIC是先发高位,然后从高到低。
9位=7位从机地址+1位传送方向标志位+1位对齐从机的应答位
9位=8位数据+1位对齐从机的应答位
数据有效性:数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定,要么保持高电平,要么保持低电平,这期间不能有电平跳变,只有在时钟线上的信号为低电平时,数据线上的高电平或低电平状态才允许变化。