目录
前言
IIC为两线式串行总线,其用于主设备与从设备之间的通信,且任一时刻只能有一台主设备。网上关于IIC的详细说明很多,故本文不打算从介绍IIC入手,直接进行程序说明。
此外,由于不同的从设备的控制方法不同,鄙人不才,想不到一个比较完美的方法将不同的设备在程序上进行统一,因此,本文章仅介绍IIC的基本信号产生的函数,还望读者谅解。如对设备在程序上的统一,有好办法的读者,还请赐教!
文章中程序使用的单片机为STC15系列的单片机,所用的时钟频率为24MHz。但是它是适用51系列的单片机的,因其程序编写过程大同小异,故读者可在此基础上适当修改,以适用读者所用的芯片。
辅助函数
在IIC中,当SCL为高电平时,SDA上电平的变化将代表着相关的操作,为防止误触发,每个函数都以SCL为低电平收尾。
/************************************************************************
本文件为IIC的基本信号集合(作为主机),就目前测试,适合在各个频率下工作,
针对不同的频率,仅需适当调整IIC延时的循环次数即可
每个信号中都以SCL低电平为结尾,这样一来可防止信号发送之后在SDA上的电
平变化引起误触发
*************************************************************************/
sbit SDA = P0^0;
sbit SCL = P0^1;
在IIC适用过程中会涉及到相应信号的延时,在此吾建立了一个专用于IIC延时的函数,如下:
/*仅作为IIC延时,故使其仅作为该文件可以调用的函数,可根据不同的时钟频率调节相应的延时时间*/
static void IIC_Delay()
{
unsigned char i;
for(i = 0;i < 2;i++) //根据不同的时钟频率调整不同的循环次数
{
_nop_();
}
}
接下来的函数是配置所使用到的引脚的模式以及相关的初始化,如下:
/*IIC初始化,将端口设为准双向输入输出(不设置为推挽模式是因为还需要读取数据,推挽下输入的电平几乎是不变的)*/
void IIC_Init()
{
P0M0 &= 0xfc; //根据需要调整参数
P0M1 &= 0xfc; //根据需要调整参数
SCL = 0; //将时钟线拉低后,数据线上的变化不会产生影响,防止误触发
SDA = 1; //释放数据线
}
相关信号函数
起始信号
起始信号即在SCL为高电平情况下,SDA由高电平变为低电平
/****IIC起始信号(SCL高电平下SDA的一个下降沿,后以SCL变为低电平收尾)****/
/* ____
/ \4.7us
___/ \___ */
void IIC_Start()
{
SDA = 1;
SCL = 1;
SDA = 0;
SCL = 0;
}
停止信号
停止信号即在SCL为高电平情况下,SDA由低电平变为高电平
/****IIC停止信号(SCL高电平下SDA一个上升沿,后以SCL变为低电平收尾)****/
/* _____________
SCL ____/
4us_____
SDA ______/ \____ */
void IIC_Stop()
{
SDA = 0;
SCL = 1;
SDA = 1;
SCL = 0;
}
接收应答函数
主设备释放SDA总线,将SCL总线置为高电平,接收SDA上的应答信号
/*接收应答信号,若接收到非应答信号,则发送停止信号(SDA低电平表示应答,高电平表示非应答)*/
bit IIC_ReceiveACK()
{
bit state;
SDA = 1;
SCL = 1;
state = SDA;
SCL = 0;
if(state) //非应答
{
IIC_Stop();
}
return state;
}
发送应答信号
应答信号
SDA保持低电平,然后将SCL由低电平转为高电平
/*************************发送应答信号函数*************************/
/****应答(SDA保持为低电平)****/
/* ____
SCL _______/ \_____
_____
SDA \____________ */
void IIC_SendACK()
{
SDA = 0;
SCL = 1;
IIC_Delay(); //必须延时
SCL = 0;
}
非应答信号
SDA保持高电平,然后将SCL由低电平转为高电平
/****非应答(SDA保持为高电平)****/
/* ____
SCL _______/ \_____
_____________
SDA ____/ */
void IIC_SendNACK()
{
SDA = 1;
SCL = 1;
IIC_Delay();
SCL = 0;
}
发送/接收一个字节数据
发送一字节数据
/****IIC发送一个字节数据(每发送一个字节接收一次应答)****/
void IIC_SendByte(unsigned char dat)
{
unsigned char i;
for(i = 0;i < 8;i++)
{
SDA = (dat >> 7);
SCL = 1;
IIC_Delay();
SCL = 0;
dat <<= 1;
IIC_Delay(); //必须延时
}
}
接收一字节数据
/****IIC接收一个字节(每接收一个字节发送一次应答/非应答)****/
unsigned char IIC_ReadByte()
{
unsigned char i,receive;
SDA = 1; //释放数据线
for(i = 0;i < 8;i++)
{
SCL = 1;
receive <<= 1;
receive |= SDA;
SCL = 0;
IIC_Delay(); //必须延时
}
return receive;
}
应用
在使用IIC对设备进行读写操作时,需要先往IIC总线上发送7位从机地址和一位读写标志位,7位从机地址由模块厂商生产时确定,发送后与该地址一致的从机根据最后一位进入读或者写状态。当最后一位为0时,往从机写入数据;当最后一位为1时,往从机读出数据。以下假设从机的7位地址为0x68,为读者演示其读写操作:
写入数据:
IIC_SendByte((0x68 << 1) & 0xfe); //direction is write
读出数据:
IIC_SendByte((0x68 << 1) | 0x01); //direction is read
IIC级联
IIC级联即将不同模块的IIC时钟引脚和数据引脚分别连接在一起,然后连接到单片机的IO口上,可实现单片机的一对IIC的IO口控制多个模块。例如模块1的SCL引脚与模块2的SCL引脚连接在一起然后接到单片机定义的SCL引脚上;再将模块1的SDA引脚与模块2的SDA引脚连接在一起然后接到单片机的SDA引脚上,即级联。
在级联中如何区分不同的模块呢?此时前面提到的从机地址发挥了关键作用。由于每次对模块进行控制时都需要先发送从机地址和读写位,其中读写位说明了数据的传输方向,而从机地址则说明了此时所控制的模块。
在从机地址由7位数据组成的模块中,理论上可级联的IIC模块个数为127个。
在实际的级联操作中,由于每个模块多少会有漏电流,所以还要根据实际情况决定是否需要为单片机的IO口添加上拉电阻,增强其驱动能力。
假设此时有两个IIC模块进行级联,模块1的从机地址为0x68,模块2的从机地址为0x38。当对模块1进行读写时,操作如下:
IIC_SendByte((0x68 << 1) & 0xfe); //direction is write
IIC_SendByte((0x68 << 1) | 0x01); //direction is read
当对模块2进行读写时,操作如下:
IIC_SendByte((0x38 << 1) & 0xfe); //direction is write
IIC_SendByte((0x38 << 1) | 0x01); //direction is read
级联更多模块的操作与此类似,不作过多赘述。
最后
以上即IIC的基本函数,程序中有些地方必须有一定的延时,否则将造成通信异常,这些地方我在后面都有注释。这些延时仅适用于24MHz及一下的频率使用模拟IIC,其他频率下的其他地方是否需要延时鄙人未知;还有,在其他频率下,延时函数也需要进行一定的调整,这些问题本身难度不大,留给读者探索。