学习嵌入式,除了最基本的C语言学习以外,非常重要也是最最基本的知识要点就是弄透设备之间的通讯协议,其中主要讨论的有IIC总线、SPI总线、UART串口通讯和CAN总线四种通讯方式。
IIC总线协议(Inter-Integrated Circuit)是设备间最简单的通信协议之一,它是一种串行数据总线,只由两根线组成数据线(SDA)和时钟线(SCL),这两根线上可以连接多个设备,可实现一对一,一对多和多对多。由于只有一根数据线,所以是一种半双工通信方式,同一时间只能一端发送另一端接收,控制时钟线的作为主机。在数据传输的过程中采用的是高位(MSB)在前低位(LSB)在后的传输方式(小端模式)。
一.IIC协议时序
时序是IIC总线协议在编程上的敲门砖,弄懂了时序图才能进行下一步的工作,具体的图形可以在网上百度,这里就只做文字说明,时序信号分为起始信号、终止信号、应答信号、非应答信号。在开始数据传输之前,即总线处于闲置状态时,SDA和SCL的初始电平都被上拉电阻控制为高电平,而且不同状态的改变都是通过SDA信号线的改变实现的。
(1)起始信号:SCL保持高电平期间(保持一定的延时 1us即可),SDA由高电平变为低电平,编程为:SCL=1;SDA=1;delay();SDA=0;SCL=0;(SCL拉低可进行数据传输)
(2)终止信号:SCL保持高电平期间(保持一定的延时 1us即可),SDA由低电平变为高电平,编程为:SCL=1;SDA=0;delay();SDA=1,SCL=0;
注:数据传输时,SDA的电平改变只发生在SCL在低电平时期。
(3)应答信号:I2C总线的数据都是以字节(8位)的方式传送的,发送器件每发送一个字节之后,在时钟的第9个脉冲期间释放数据总线,由接收器发送一个ACK(在SCL信号线在高电平期间,把数据总线的电平拉低)来表示数据成功接收。编程实现为:SCL=1;SDA=0;SCL=0;
(4)非应答信号:与应答信号相反,在SCL信号线在高电平期间,把数据总线的电平拉低高,编程实现为SCL=1;SDA=1;SCL=0;
注:在一字节数据或者信号传输完成后将SCL拉低,使总线进入等待状态,等待后续操作,发送停止信号后结束数据传输。
二.数据传输
数据发送过程:发送设备地址——发送ROM地址——发送起始信号——发送数据——每一个字节后(判断接收设备是否应答)——数据发送完之后——停止信号
数据读取过程:发送设备地址——发送ROM地址——发送设备地址+1(进入接收模式)——发送起始信号——接收数据——每一个字节后(发送是否应答)——数据接收完之后——停止信号
编码实现过程为:(这里应用了位带操作,可以直接给SDA和SCL赋予0或1)
void IIC_SendByte(unsigned char dat) //写数据
{
unsigned char i;
SDA_OUT(); //将SDA设置为输出模式
for (i = 0; i < 8; i++)
{
if((dat<<i)&0x80)
{
SDA = 1;
}
else
{
SDA = 0;
}
SCL = 1; //开始让数据维持稳定
delay_us(1);
SCL = 0;
delay_us(1);
}
SDA = 1; //释放总线 , 发送完8位,主机置高电平
SCL = 1;
delay_us(1);
if (SDA) //SDA 低电平 从机回馈低电平
{
ack = 0; //0 == ack 代表无ack信号, 从机不应答,发送不成功
}
else
{
ack = 1; //从机应答,发送成功
}
SCL = 0;
delay_us(5);
}
unsigned char IIC_RecvByte(void) //读数据(接收数据)
{
unsigned char i, receive = 0;
SDA_IN(); //将SDA设置为输入模式
for (i = 0; i < 8; i++)
{
SCL = 0; // 告诉数据可以变化 SDA 脉冲线
//只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
delay_us(1);
SCL = 1; // 数据保持稳定, 开始读
delay_us(1);
receive<<=1;
if (SDA)
{
receive = receive + 1;
}
}
SCL = 0;
delay_us(10);
return receive;
}
void IIC_ACK(void) //应答信号
{
SDA = 0; //拉低SDA
SCL = 1;
delay_us(1);
SCL = 0;
}
void IIC_NOACK(void) // 非应答信号
{
SDA = 1;
SCL = 1; 拉高SDA
delay_us(1);
SCL = 0;
}
每发送和接收完成一位的数据,需要将SCL拉高一定延时,保持数据的稳定。
以AT24C02为例,设计IIC总线的数据传输程序:
unsigned char AT24CXX_WriteStr(unsigned char devaddr, unsigned char romaddr, unsigned char *s, unsigned char num)//写数据
{
unsigned char i;
IIC_Start(); //开始传输
IIC_SendByte(devaddr); //发送设备地址
if (0 == ack) //非应答
{
return 0;
}
IIC_SendByte(romaddr); //发送存储器地址
if (0 == ack)
{
return 0;
}
for (i = 0; i < num; i++) //发送数据,num为数据的字节数
{
IIC_SendByte(*s);
if (0 == ack)
{
return 0;
}
s++; //发送一个字节之后发送下一个字节
}
IIC_Stop();
return 1;
}
unsigned char AT24CXX_ReadStr(unsigned char devaddr, unsigned char romaddr, unsigned char *s, unsigned char num)
{
unsigned char i;
IIC_Start();
IIC_SendByte(devaddr);
if (0 == ack) //无应答 返回0 失败
{
return 0;
}
IIC_SendByte(romaddr);
if (0 == ack)
{
return 0;
}
IIC_Start();
IIC_SendByte(devaddr + 1); //最低位写1,进入接收模式
if (0 == ack)
{
return 0;
}
for (i = 0; i < num - 1; i++) //倒数第二个数据退出循环
{
*s = IIC_RecvByte();
IIC_ACK();
s++;
}
*s = IIC_RecvByte(); //将最后一字节数据赋值给指针
IIC_NOACK();
IIC_Stop();
return 1;
}
总结上述的过程在每发送一个数据,无论是地址信号还是所要传输的数据,都应该用应答信号进行简单的判断,数据传输开始前,要发送相应的设备地址和存储地址再进行数据的发送和读取,设备地址的最后一位为数据传输类型,0为发送模式,1为接收模式。
三.IIC多设备通信
虽然IIC总线只有两根线,但是在两根线上可以挂载多个设备。
IIC总线协议规定,在总线启动后,第一个字节的数据为设备地址,高7位为寻址地址,其中高四位为设备标识,接下来三位为片选,最低位为读写标志,0为写操作,1为读操作,按设备地址来看,最多可挂载2^7-1=127个设备。但由于嵌入式设备的专用性,理论上不会设计多设备挂载的情况。
当发生多个设备间的通信时,需要进行总线仲裁保证通信的有效进行。
主机只能在总线空闲的时候启动传输。两个或多个主机可能在起始条件的最小持续内产生一个起始条件,结果在总线上产生一个规定的起始条件。
当SCL线是高电平时,仲裁在SDA线发生:这样,在其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平和它自己的电平不同。仲裁可以持续多位。从地址位开始,同一个器件的话接着就是数据位(如果主机-发送器),或者比较相应位(如果主机-接收器)。IIC总线的地址和数据信息由赢得仲裁的主机决定,在这个过程中不会丢失信息。
仲裁不能在下面情况之间进行:
1)重复起始条件和数据位;
2)停止条件和数据位;
3)重复起始条件和停止条件。
四.IIC的主要应用——AT24C02芯片
在嵌入式产品中,IIC应用最广泛的当属EEPROM,用于小数据的存储,在简单的IO口编程中,需要根据对应的输入输出去设置SDA引脚的输入输出模式(如果搭载有系统,读取EEPROM中的数据时可以直接调用系统层的驱动即可实现,SDA始终保持输出模式即可),而SCL信号线都是有主机控制,所以设置为输出即可,由于有上拉电阻的原因,所以这里的输入模式应该为上拉输入(GPIO_Mode_IPU),输出模式为开漏输出(GPIO_Mode_AF_OD)。
EEPROM在读取传输过程中,可以设置为按字节或按页读写(根据不同的芯片,页的大小不同,具体参考芯片手册),要提一下的是EEPROM不能跨页写,可以跨页读。
五.实际应用中的时序测试
图为EE存储中的实际时序图,从起始信号到8个bit位的传输,再由从机回复一个低电平的应答信号之后再继续传输下一个字节,直到所有字节发送完之后以停止位结束该数据帧的传输,释放总线后SDA和SCL都处于高电平。