本次编程实验以IAP15F2K61S2为单片机主控芯片,头文件为STC15F2K60S2.H
。若用于51系列单片机,以reg52.h
为头文件,则读者需将程序中可能涉及的定时器初始化程序和LED亮灭程序和数码管显示程序,根据自身所用单片机原理图和手册进行修改。
IIC通信为串行通信,总线结构为一主多从,即总线上的每个设备都有一个特定的设备地址,用来区分同一IIC总线上的其他设备。IIC总线有两根双向线,串行时钟线(SCL)和串行数据线(SDA)组成,可用于发送和接收数据,但是通信都是由主设备发起,从设备只能被动响应,实现数据的传输。
1.时钟及数据传输:SDA 引脚的数据应在 SCL 为低时变化,否则当数据在 SCL 为高时变化时,将被视为开始或停止信号(开始或停止信号见下文所述)。
2.开始信号:当 SCL 为高,SDA 由高到低的变化被视为开始信号,用于启动IIC通信,必须以开始信号作为任何一次读/写操作命令
的开始(读/写操作命令见下文)。
3.停止信号:当 SCL 为高,SDA 由低到高的变化被视为停止信号,在数据传输完成后,停止信号用于结束IIC通信。
4.应答位:所有的从设备地址和数据字节都是以 8 位为一组串行输入和输出的。当从设备每收到一组 8 位的数据后,会向主设备发送一个应答位,用于告知主设备是否接收成功(0成功,1非成功);当主设备每收到一组 8 位的数据后,会向从设备发送一个应答位,用于告知从设备是否需要继续发送数据(0需要,1不需要)。
IIC通信基本时序图如下:
具体步骤如下:
1.首先,主设备需要发送一个开始(START)信号,用于“唤醒”挂在IIC总线上的从设备;
2.之后发送8位数据,位0-位7用于声明从设备的地址(ADDRESS),建立与特定从设备的通信,位8用于声明此次是主—从还是从—主(数据流向,0主—从,1从—主);
3.在从设备接收到主设备发来的通信请求时,会返回一个应答位,用于主设备确认从设备是否准备好接收数据;
4.在主设备确认从设备准备好后,如果数据流向是主—从,那么主设备将向从设备发送8位数据,从设备接收完成后发回一个应答位,告诉主设备是否接收成功;如果数据流向是从—主,那么从设备将向主设备发送8位数据,主设备接收完成后发回一个应答位,告诉从设备是否需要继续发送数据;
5.在数据完成收发后,主设备需要发送一个停止(STOP)信号,结束通信。
注意:有时我们需要让主从设备之间收发多个数据,则每完成一个数据字节的收发时,需要进行一次应答位的收发和判断,直至数据收发完成后,再让主设备发送停止信号。SDA所有数据的发均在SCL的上升沿完成,SCL每迎来一次上升沿,SDA发送一位数据,高位在前,低位在后。
1. 主设备发送开始信号,“唤醒”挂在IIC总线上的从设备;
2. 主设备发送从设备地址,选择与哪个从设备通信;
3. 等待从设备响应;
4. 主设备发送数据到从设备,每发送一个数据字节,接收一次从设备的应答;
5. 数据发送完毕,主设备发送停止信号,结束通信。
1. 主设备发送开始信号;
2. 主设备发送从设备地址,选择与哪个从设备通信;
3. 等待从设备响应;
4. 主设备接收来自从设备的数据,每接收一个数据字节,向从设备发送一个应答;
5. 主设备接收到最后一个数据后,发送一个无效的应答,然后主设备发送停止信号,结束通信。
#ifndef _IIC_H
#define _IIC_H
#include
#include "intrins.h"
void IIC_Start(void); //开始信号
void IIC_Stop(void); //停止信号
bit IIC_WaitAck(void); //主设备等待从设备的应答
void IIC_SendAck(bit ackbit); //主设备向从设备发送应答
void IIC_SendByte(unsigned char byt); //主设备发送一个字节数据
unsigned char IIC_RecByte(void); //主设备接收一个字节数据
#endif
#include "iic.h"
#define DELAY_TIME 5
//总线引脚定义
sbit SDA = P2^1; /* 数据线 */
sbit SCL = P2^0; /* 时钟线 */
//IIC专用延时函数
void IIC_Delay(unsigned char i)
{
do{_nop_();}
while(i--);
}
//总线启动条件
void IIC_Start(void)
{
SDA = 1; //将SDA和SCL拉高
SCL = 1;
IIC_Delay(DELAY_TIME); //防止单片机运行速度过快,总线和从设备反应不及时
SDA = 0; //SCL为高时,SDA迎来下降沿,视为开始信号
IIC_Delay(DELAY_TIME);
SCL = 0; //SCL拉低,准备发送字节
}
//总线停止条件
void IIC_Stop(void)
{
SDA = 0; //将SDA拉低
SCL = 1; //将SCL拉高
IIC_Delay(DELAY_TIME);
SDA = 1; //SCL为高时,SDA迎来下降沿,视为停止信号
IIC_Delay(DELAY_TIME);
}
//主设备发送应答
void IIC_SendAck(bit ackbit)
{
SCL = 0; //SCL拉低,使SDA上的数据可以改变
SDA = ackbit; // 0:应答,1:非应答
IIC_Delay(DELAY_TIME);
SCL = 1; //SCL拉高,迎来上升沿,使SDA上的数据发送
IIC_Delay(DELAY_TIME);
SCL = 0; //拉低SCL
SDA = 1; //拉高SDA,这两句都是为了发数据做准备
IIC_Delay(DELAY_TIME);
}
//主设备等待从设备应答
bit IIC_WaitAck(void)
{
bit ackbit;
SCL = 1; //由于开始信号和主设备发送完一个字节数据后,SCL最后均置0,所以此时SCL迎来上升沿
IIC_Delay(DELAY_TIME);
ackbit = SDA; //取出从设备的应答
SCL = 0; //拉低SCL
IIC_Delay(DELAY_TIME);
return ackbit; //返回从设备的应答,可用于进行扩展判断
}
//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt) //byt为要发送的字节数据
{
unsigned char i;
for(i=0; i<8; i++)
{
SCL = 0; //在SCL低电平时,改变SDA上的数据
IIC_Delay(DELAY_TIME);
if(byt & 0x80) SDA = 1; //取出byt最高位的数据
else SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 1; //SCL上升沿,SDA发送一位数据
byt <<= 1; //byt左移一位,使位6变成位7,位5变成位6...
IIC_Delay(DELAY_TIME);
}
SCL = 0; //拉低SCL
}
//从I2C总线上接收数据,各语句作用请读者自行思考
unsigned char IIC_RecByte(void)
{
unsigned char i, byt;
for(i=0; i<8; i++)
{
SCL = 1;
IIC_Delay(DELAY_TIME);
byt <<= 1;
if(SDA) byt |= 1;
SCL = 0;
IIC_Delay(DELAY_TIME);
}
return byt;
}
AT24C02是一个2KBit的串行EEPROM存储器(掉电不丢失),内部含有256个字节,每个8字节为一页,通过IIC总线与单片机进行通信。
其中,A2-A0为器件寻址:24C02 在一个IIC总线上最多可寻址八个,A2-A0的用于选择哪一个24C02;WP:写保护,置高电平时无法向24C02写入数据。
由原理图可知,A2-A0均接地,即此单片机只有一块24C02;WP接地,即写保护一直关闭。
1.MCU发送一个开始信号;
2.发送24C02写操作地址0xA0,等待应答信号;
3.发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想要存在哪就写入哪,等待应答信号;
4.发送要写入的数据。数据发送可以有多个,数据存储地址会自动加一,但不能跨页写入,且写入多个字节时,每写入一个字节后需要等待应答信号,再写入下一个字节;
5.MCU发送结束信号,停止通信。
1.MCU发送一个开始信号;
2.发送器件写操作地址0xA0,等待应答信号;
注意:这里写操作是为了把要读出数据的存储地址先写进去,告诉24C02要读取哪个地址的数据。
3.发送要读取内存的地址,等待应答信号;
4.MCU重新发送开始信号;
5.发送设备读操作地址0xA1,等待应答信号;
6.24C02会自动向主机发送数据,主机读取数据。在读一个字节后,如果MCU回应一个应答信号0,24C02会继续传输下一个地址的数据,直到MCU发送一个非应答信号1。
7.MCU发送结束信号,结束通信。
#ifndef __AT24C02_H
#define __AT24C02_H
#include
#include "iic.h"
void at24c02_write(unsigned char addr,byte); //写数据
void at24c02_delay5ms(void); //@11.0592MHz
unsigned char at24c02_read(unsigned char addr); //读数据
#endif
#include "at24c02.h"
/*
* @brief 写入一个字节数据
* @param addr:数据存储地址;byt:要存储的数据
* @reval
* @note:
*/
void at24c02_write(unsigned char addr,byte)
{
IIC_Start(); //开始信号
IIC_SendByte(0xa0); //建立与24C02的通信,写操作
IIC_WaitAck(); //等待应答
IIC_SendByte(addr); //发送数据的存储地址
IIC_WaitAck(); //等待应答
IIC_SendByte(byte); //发送存储数据
IIC_WaitAck(); //等待应答
IIC_Stop(); //停止信号
}
/*
* @brief 24C02专用5ms延时
* @param
* @reval
* @note: 当连续调用at24c02_write()写入数据时,每调用一次需要延时5ms
*/
void at24c02_delay5ms(void) //@11.0592MHz
{
unsigned char data i, j;
i = 54;
j = 199;
do
{
while (--j);
} while (--i);
}
/*
* @brief 读出一个字节数据
* @param addr:要读出数据的存储地址
* @reval temp:读出的数据
* @note:
*/
unsigned char at24c02_read(unsigned char addr)
{
unsigned char temp=0;
IIC_Start(); //开始信号
IIC_SendByte(0xa0); //建立与24C02的通信,写操作
IIC_WaitAck(); //等待应答
IIC_SendByte(addr); //发送要读出数据的存储地址
IIC_WaitAck(); //等待应答
IIC_Start(); //重新发送开始信号
IIC_SendByte(0xa1); //建立与24C02的通信,读操作
IIC_WaitAck(); //等待应答
temp=IIC_RecByte(); //读出数据
IIC_SendAck(1); //发送非应答位
IIC_Stop(); //停止信号
return temp; //返回读出的数据
}
注意:上述程序只适应一个字节数据的收发,如何一次收发多个数据,请读者结合上述内容自行思考。
在进行测试前,我们首先需要一个可视化工具,为此加入数码管显示模块。
#ifndef __SMG_H
#define __SMG_H
#include
#define outputp0(y,x) P0=x,P2&=0x1f,P2|=y,P2&=0x1f;
void showbit(unsigned char pos,dat,dot);
#endif
#include "smg.h"
unsigned char code Seg_Table[]={
0xc0, //共阳数码管
0xf9,
0xa4,
0xb0,
0x99,
0x92,
0x82,
0xf8,
0x80,
0x90,
0xff
};
/*
* @brief 1位数码管显示函数
* @param pos:位选;dat:显示数字;dot:小数点选择位,1有0无
* @reval
* @note:
*/
void showbit(unsigned char pos,dat,dot)
{
outputp0(0xc0,0x01<<pos);
outputp0(0xe0,Seg_Table[10]); //这两行用于消影
outputp0(0xc0,0x01<<pos); //位选
outputp0(0xe0,Seg_Table[dat]+0x80*dot); //段选
}
创建一个新工程,将上述所有代码分模块添加入工程中,主程序如下:
#include
#include "at24c02.h"
#include "smg.h"
unsigned char showbyte=0;
void Delay2ms(void) //@11.0592MHz
{
unsigned char data i, j;
_nop_();
_nop_();
i = 22;
j = 128;
do
{
while (--j);
} while (--i);
}
void test()
{
showbit(0,showbyte,0);
Delay2ms();
}
void main()
{
at24c02_write(0x00,0x03); //第二次上电删除
at24c02_delay5ms(); //第二次上电删除
showbyte=at24c02_read(0x00);
while(1)
{
test();
}
}
下载程序后,数码管显示3;之后将标记的代码删除,编译再次下载,数码管显示3,说明测试成功。
IIC作为极其重要的通信方式,掌握它的原理和编程是十分有必要。在此次编程实验中,讲述了24C02一次收发多字节的原理和时序,未给出编程实现,读者可自行思考。
与IIC较为接近的通信方式还有SPI通信,具体见DS1302原理和代码或51单片机DS1302可调时钟。
有任何问题和补充,欢迎私信或评论区交流。