目录
一、AT24C02存储器
1、AT24C02存储器介绍
2、存储器简化模型
3、AT24C02存储器原理图
二、I²C总线
1、I²C总线的介绍
2、I²C电路
3、I²C时序图
3.1I²C开始和结束时序图
3.2I²C主机发送和接收时序图
3.3I²C主机发送应答/接收应答
4、I²C数据帧发送/接收方式
4.1主机向从机发送数据
4.2主机从从机接收数据
4.3复合格式(主机先发送数据,再接收从机数据)
4.4两种变形——字节写和随机读
5、利用独立按键写入读取数字
6、利用定时器、数码管、独立按键、AT24C02制作秒表
6.1main.c
6.2Nixie.c
6.3key.c
三、DS18B20温度传感器
1、DS18B20温度传感器介绍
2、单总线电路规范
3、单总线时序结构
3.1初始化
3.2发送一位
3.3接收一位
3.4发送/接收一个字节
4、DS18B20操作流程
5、BS18B20读取环境温度
5.1main.c
5.2OneWire.c
5.3DS18B20.c
6、DS18B20温度报警器
6.1main.c
6.2OneWire.c
6.3Key.c
6.4AT24C02.c
6.5Buzzer.c
一、AT24C02存储器
1、AT24C02存储器介绍
AT24C02是一种可以实现掉电不丢失的存储器。易失性存储器RAM存储速度快,非易失性存储器ROM存储速度慢;这俩特性类似缓存和磁盘。
存储介质:E²PROM(可擦写的只读存储器);
通讯接口:I²C总线;
容量:256字节。
2、存储器简化模型
3、AT24C02存储器原理图
二、I²C总线
1、I²C总线的介绍
I²C总线是一种通用数据总线,有两根通信线SCL(串行时钟线)、SDA(串行数据线,只有一根线,双向),是一种同步、半双工带数据应答的通信协议。
2、I²C电路
1、所有I²C设备的中SCL、SDA各自连接在一起;
2、设备的SCL和SDA均要配置成开漏输出模式;
3、SCL和SDA各添加一个上拉电阻,阻值一般KΩ级别;
开漏输出是为了单一模块间通信的过程中,让其他模块对外呈现高阻态。如果在通信的过程中,其他模块是上拉电路,虽然上拉能力弱,但是多路并联会使上拉能力增强,可能会对正在通信的模块造成影响。
上拉电阻作用:如图所示,SCL和SDA各接一个外接电阻,电路就会由开漏输出变成弱上拉模式,那么CPU想输出1,内部开关闭合,反之开关断开即可。
那么问题来了,多机通讯时,如何确定接收端是谁?多机通讯时,通过时序确定谁和谁通信。
3、I²C时序图
3.1I²C开始和结束时序图
3.2I²C主机发送和接收时序图
主机发送一个字节
主机接收一个字节
1、当SCL为高电平时,SDA的跳变表示开始和结束状态;SDA电平不变表示正在读取和发送数据。
2、主机接收从机的数据时,需要解除对SDA的控制权,即从机发送数据前将主机SDA置1。
3.3I²C主机发送应答/接收应答
1、主机接收完从机1个字节数据后,主机需要向从机发送应答。0表示应答,1表示非应答。
2、主机发送至从机1个字节数据后,从机需要向主机接收应答。0表示应答,1表示非应答。因为是从机发送,所以主机要移交SDA控制权。
4、I²C数据帧发送/接收方式
4.1主机向从机发送数据
4.2主机从从机接收数据
4.3复合格式(主机先发送数据,再接收从机数据)
本质是上面两种方式的组合。可以理解为主机向从机提问,从机回复问题。
4.4两种变形——字节写和随机读
AT24C02的固定地址为1010,可配置地址本开发板上为000,所以从机地址+W为0XA0,从机地址+R为0xA1。
5、利用独立按键写入读取数字
unsigned char KeyNum;
unsigned int Num;
void main()
{
LCD_Init();
LCD_ShowNum(1,1,Num,5);
while(1)
{
KeyNum=Key();
if(KeyNum==1)
{
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2)
{
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3)
{
AT24C02_WriteByte(0,Num%256);//写入低8位
delay_ms(5);//每次写完延时5ms
AT24C02_WriteByte(1,Num/256);//写入高8位
delay_ms(5);//每次写完延时5ms
LCD_ShowString(2,1,"Write success");
delay_ms(1000);
LCD_ShowString(2,1," ");
}
if(KeyNum==4)
{
Num=AT24C02_ReadByte(0);//读出低8位
Num|=AT24C02_ReadByte(1)<<8;//读出高8位
LCD_ShowNum(1,1,Num,5);
LCD_ShowString(2,1,"Read success");
delay_ms(1000);
LCD_ShowString(2,1," ");
}
}
}
6、利用定时器、数码管、独立按键、AT24C02制作秒表
6.1main.c
#include <REGX52.H>
#include "key.h"
#include "Timer0.h"
#include "Nixie.h"
#include "AT24C02.h"
#include "delay.h"
unsigned char KeyNum;
unsigned char Min,Sec,MiniSec;
unsigned char RunFlag;
void main()
{
Timer0_Init();
while(1)
{
KeyNum=Key();
if(KeyNum==1)//暂停
{
RunFlag=!RunFlag;
}
else if(KeyNum==2)//清零
{
Min=Sec=MiniSec=0;
}
else if(KeyNum==3)//写入AT24C02
{
AT24C02_WriteByte(0,Min);
delay_ms(5);
AT24C02_WriteByte(1,Sec);
delay_ms(5);
AT24C02_WriteByte(2,MiniSec);
delay_ms(5);
}
else if(KeyNum==4)//读出AT24C02
{
Min=AT24C02_ReadByte(0);
Sec=AT24C02_ReadByte(1);
MiniSec=AT24C02_ReadByte(2);
}
Nixie_SetBuff(1,Min/10);
Nixie_SetBuff(2,Min%10);
Nixie_SetBuff(3,11);
Nixie_SetBuff(4,Sec/10);
Nixie_SetBuff(5,Sec%10);
Nixie_SetBuff(6,11);
Nixie_SetBuff(7,MiniSec/10);
Nixie_SetBuff(8,MiniSec%10);
}
}
void Sec_Loop()
{
if(RunFlag!=0)
{
MiniSec++;
if(MiniSec>=100)
{
MiniSec=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
}
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count1,T0Count2,T0Count3;//函数栈帧结束后保留T0Count
TH0=64535/256;//每次进中断函数后,让这两个寄存器重新回到初值
TL0=64535%256;
T0Count1++;//每中断一次T0Count++
if(T0Count1>=(20/(12/11)))//达到了定时20毫秒的作用(晶振11.0592)
{
T0Count1=0;//重置为0
Key_Loop();
}
T0Count2++;
if(T0Count2>=(2/(12/11)))//达到了定时2毫秒的作用(晶振11.0592)
{
T0Count2=0;//重置为0
Nixie_Loop();
}
T0Count3++;
if(T0Count3>=(10/(12/11)))//达到了定时10毫秒的作用(晶振11.0592)
{
T0Count3=0;//重置为0
Sec_Loop();
}
}
使用定时器中断控制数码管持续扫描、独立按键循环检测。(无需延时)
使用定时器中断控制秒表数字流动。
独立按键用于展示不同的效果,按键1表示秒表运行/暂停,按键2表示秒表数据清零,按键3存储秒表数据至AT24C02,秒表4读取AT24C02的数据至数码管,秒表显示存储的内容。
6.2Nixie.c
unsigned char Nixie_buff[9]={0,10,10,10,10,10,10,10,10};
//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0X00,0X40};
void Nixie_SetBuff(unsigned char Location,Number)
{
Nixie_buff[Location]=Number;
}
//数码管显示子函数
void Nixie_Scan(unsigned char Location,Number)
{
P0=0x00; //段码清0,消影
switch(Location) //位码输出
{
case 1:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=NixieTable[Number]; //段码输出
}
void Nixie_Loop()
{
static unsigned char i=1;//计数
Nixie_Scan(i,Nixie_buff[i]);
i++;
if(i>=9)
{
i=1;
}
}
6.3key.c
unsigned char Key_KeyNum;
unsigned char Key()
{
unsigned char Tmp=Key_KeyNum;
Key_KeyNum=0;
return Tmp;
}
unsigned char Key_GetStat()
{
unsigned char KeyNumber=0;
if(P3_1==0)
{
KeyNumber=1;
}
if(P3_0==0){KeyNumber=2;}
if(P3_2==0){KeyNumber=3;}
if(P3_3==0){KeyNumber=4;}
return KeyNumber;
}
void Key_Loop()
{
static unsigned char NowState,LastState;
LastState=NowState;
NowState=Key_GetStat();
if(LastState==1&&NowState==0)//松手状态(按键弹起)
{
Key_KeyNum=1;
}
if(LastState==2&&NowState==0)
{
Key_KeyNum=2;
}
if(LastState==3&&NowState==0)
{
Key_KeyNum=3;
}
if(LastState==4&&NowState==0)
{
Key_KeyNum=4;
}
}
三、DS18B20温度传感器
1、DS18B20温度传感器介绍
DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点。
测温范围:-55°C 到 +125°C
通信接口:1-Wire(单总线)异步、半双工
其它特征:可形成总线结构、内置温度报警功能、可寄生供电
2、单总线电路规范
为了多机通讯,设备的DQ均要配置成开漏输出模式并且DQ需要添加一个上拉电阻,阻值一般为4.7KΩ左右
若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路
3、单总线时序结构
3.1初始化
主机将总线拉低至少480us,然后释放总线,等待15~60us后,存在的从机会拉低总线60~240us以响应主机,之后从机将释放总线。
3.2发送一位
主机将总线拉低60~120us,然后释放总线,表示发送0;主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us
3.3接收一位
主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us
3.4发送/接收一个字节
发送一个字节:连续调用8次发送一位的时序,依次发送一个字节的8位(低位在前)
接收一个字节:连续调用8次接收一位的时序,依次接收一个字节的8位(低位在前)
4、DS18B20操作流程
初始化:从机复位,主机判断从机是否响应(从机是否存在)
ROM操作:ROM指令+本指令需要的读写操作
功能操作:功能指令+本指令需要的读写操作
5、BS18B20读取环境温度
5.1main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "delay.h"
#include "DS18B20.h"
float T;
void main()
{
LCD_Init();
DS18B20_ConvertTemperature();//DS18B20温度转换
delay_ms(1000);//延时1秒,提前读好,进while循环后读出的就是实际温度
while(1)
{
DS18B20_ConvertTemperature();//DS18B20温度转换
T=DS18B20_ReadTemperature();
if(T<0)
{
LCD_ShowChar(2,1,'-');
T=-T;
}
else
{
LCD_ShowChar(2,1,'+');
}
LCD_ShowNum(2,2,T,3);
LCD_ShowChar(2,5,'.');
LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);
}
}
5.2OneWire.c
#include <REGX52.H>
sbit OneWire_DQ=P3^7;
//初始化
unsigned char OneWire_Init()
{
unsigned char i;
unsigned char AckBit;
OneWire_DQ=1;
OneWire_DQ=0;//主机将总线拉低
i = 227;while (--i);//大概延时500μs>480μs
OneWire_DQ=1;//主机再拉高总线
i = 29;while (--i);//大概延时70μs
AckBit=OneWire_DQ;//读取从机应答
i = 227;while (--i);//再延时500μs走完初始化时序
return AckBit;
}
//发送一位
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
OneWire_DQ=0;//主机将总线拉低
i = 4;while (--i);//延时10μs
OneWire_DQ=Bit;//把数据给总线。发0,从机将在30μs后读到0;反之读到1
i = 23;while (--i);//延时50μs
OneWire_DQ=1;//主机恢复总线
}
//接收一位
unsigned char OneWire_ReceiveBit()
{
unsigned char Bit;
unsigned char i;
OneWire_DQ=0;//主机将总线拉低
i = 2;while (--i);//延时5μs
OneWire_DQ=1;//主机将总线释放
i = 2;while (--i);//延时5μs
Bit=OneWire_DQ;//主机取样
i = 23;while (--i);//延时50μs
return Bit;
}
//发送一个字节
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i=0;
for(i=0;i<8;++i)
{
OneWire_SendBit(Byte&(0X01<<i));
}
}
//接收一个字节
unsigned char OneWire_ReceiveByte()
{
unsigned char Byte=0X00;
unsigned char i=0;
for(i=0;i<8;++i)
{
Byte|=(OneWire_ReceiveBit()<<i);
}
return Byte;
}
5.3DS18B20.c
#include <REGX52.H>
#include "OneWire.h"
#define DS18B20_SKIP_ROM 0XCC
#define DS18B20_CONVERT_T 0X44
#define DS18B20_READ_SCRATCHPAD 0XBE
//DS18B20温度转换
void DS18B20_ConvertTemperature()
{
OneWire_Init();//初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//跳过ROM
OneWire_SendByte(DS18B20_CONVERT_T);//温度转变
}
//温度读取
float DS18B20_ReadTemperature()
{
unsigned char TLSB,TMSB;
int Temp;
float T;
OneWire_Init();//初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//跳过ROM
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//读出暂存器的数据
TLSB=OneWire_ReceiveByte();//先读出LSB
TMSB=OneWire_ReceiveByte();//再读出MSB
Temp=(TMSB<<8)|TLSB;//组合成int
T=Temp/16.0;//看图,最低位是2^-4而不是2^0,int要/16.0移回去
return T;
}
目前室温约为17℃。
6、DS18B20温度报警器
这里有一个单总线的缺点,单总线是严格依照时序电平的持续时间来确定当前执行的是什么操作。当我们使用定时器时,就会打断这个时序。所以单总线的时序一旦开始便不能中途打断,时序开始时必须先关闭定时器。所以单总线的模块和其他需要定时器操作的外设在一起时,为了单总线设备的运行,时序开始时必须关闭定时器,可能会影响其他外设(例如秒表的准确性)。
6.1main.c
#include <REGX52.H>
#include "DS18B20.h"
#include "delay.h"
#include "AT24C02.h"
#include "LCD1602.h"
#include "key.h"
#include "Timer0.h"
#include "Buzzer.h"
float T,TShow;//表示温度
char TLow,THigh;//表示温度最高和最低值
unsigned char KeyNum;
void main()
{
DS18B20_ConvertTemperature();//DS18B20温度转换
delay_ms(1000);//延时1秒,提前读好,进while循环后读出的就是实际温度
TLow=AT24C02_ReadByte(0);
THigh=AT24C02_ReadByte(1);//将0,1地址处TLow和THigh读出来
if(THigh>125||TLow<-55||THigh<=TLow)//判断AT24C02中存储的数据对不对
{
THigh=125;
TLow=-55;
}
LCD_Init();
LCD_ShowString(1,1,"T:");
LCD_ShowString(2,1,"TH:");
LCD_ShowString(2,9,"TL:");
Timer0_Init();
while(1)
{
//温度读取及显示
DS18B20_ConvertTemperature();//DS18B20温度转换
T=TShow=DS18B20_ReadTemperature();
if(T<0)
{
LCD_ShowChar(1,3,'-');
TShow=-T;
}
else
{
LCD_ShowChar(1,3,'+');
}
LCD_ShowNum(1,4,TShow,3);
LCD_ShowChar(1,7,'.');
LCD_ShowNum(1,8,(unsigned long)(TShow*10000)%10000,4);
//阈值判断及显示
LCD_ShowSignedNum(2,4,THigh,3);
LCD_ShowSignedNum(2,12,TLow,3);
KeyNum=key();
if(KeyNum!=0)
{
if(KeyNum==1)
{
++THigh;
if(THigh>125)
{
THigh=125;
}
}
if(KeyNum==2)
{
--THigh;
if(THigh<=TLow)
{
++THigh;
}
}
if(KeyNum==3)
{
++TLow;
if(TLow>=THigh)
{
--TLow;
}
}
if(KeyNum==4)
{
--TLow;
if(TLow<-55)
{
TLow==-55;
}
}
AT24C02_WriteByte(0,TLow);
delay_ms(5);
AT24C02_WriteByte(1,THigh);
delay_ms(5);
}
if(T>THigh)
{
LCD_ShowString(1,13,"OV:H");
Buzzer_Time(1000);
}
else if(T<TLow)
{
LCD_ShowString(1,13,"OV:L");
Buzzer_Time(1000);
}
else
{
LCD_ShowString(1,13," ");
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;//函数栈帧结束后保留T0Count
TH0=64535/256;//每次进中断函数后,让这两个寄存器重新回到初值
TL0=64535%256;
T0Count++;//每中断一次T0Count++
if(T0Count>=(20/(12/11)))//达到了定时20毫秒的作用(晶振11.0592)
{
T0Count=0;//重置为0
Key_Loop();
}
}
6.2OneWire.c
#include <REGX52.H>
sbit OneWire_DQ=P3^7;
//初始化
unsigned char OneWire_Init()
{
unsigned char i;
unsigned char AckBit;
EA=0;//关闭定时器
OneWire_DQ=1;
OneWire_DQ=0;//主机将总线拉低
i = 227;while (--i);//大概延时500μs>480μs
OneWire_DQ=1;//主机再拉高总线
i = 29;while (--i);//大概延时70μs
AckBit=OneWire_DQ;//读取从机应答
i = 227;while (--i);//再延时500μs走完初始化时序
EA=1;//重新打开定时器
return AckBit;
}
//发送一位
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
EA=0;
OneWire_DQ=0;//主机将总线拉低
i = 4;while (--i);//延时10μs
OneWire_DQ=Bit;//把数据给总线。发0,从机将在30μs后读到0;反之读到1
i = 23;while (--i);//延时50μs
OneWire_DQ=1;//主机恢复总线
EA=1;
}
//接收一位
unsigned char OneWire_ReceiveBit()
{
unsigned char Bit;
unsigned char i;
EA=0;
OneWire_DQ=0;//主机将总线拉低
i = 2;while (--i);//延时5μs
OneWire_DQ=1;//主机将总线释放
i = 2;while (--i);//延时5μs
Bit=OneWire_DQ;//主机取样
i = 23;while (--i);//延时50μs
EA=1;
return Bit;
}
//发送一个字节
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i=0;
for(i=0;i<8;++i)
{
OneWire_SendBit(Byte&(0X01<<i));
}
}
//接收一个字节
unsigned char OneWire_ReceiveByte()
{
unsigned char Byte=0X00;
unsigned char i=0;
for(i=0;i<8;++i)
{
Byte|=(OneWire_ReceiveBit()<<i);
}
return Byte;
}
6.3Key.c
#include "key.h"
#include "delay.h"
#include <REGX52.H>
unsigned char Key_KeyNum;
unsigned char Key()
{
unsigned char Tmp=Key_KeyNum;
Key_KeyNum=0;
return Tmp;
}
unsigned char Key_GetStat()
{
unsigned char KeyNumber=0;
if(P3_1==0)
{
KeyNumber=1;
}
if(P3_0==0){KeyNumber=2;}
if(P3_2==0){KeyNumber=3;}
if(P3_3==0){KeyNumber=4;}
return KeyNumber;
}
void Key_Loop()
{
static unsigned char NowState,LastState;
LastState=NowState;
NowState=Key_GetStat();
if(LastState==1&&NowState==0)//松手状态(按键弹起)
{
Key_KeyNum=1;
}
if(LastState==2&&NowState==0)
{
Key_KeyNum=2;
}
if(LastState==3&&NowState==0)
{
Key_KeyNum=3;
}
if(LastState==4&&NowState==0)
{
Key_KeyNum=4;
}
}
6.4AT24C02.c
#include <REGX52.H>
#include "IIC.h"
#define AT24C02_ADDRESS 0XA0
//字节写:在写入地址中写入Data
void AT24C02_WriteByte(unsigned char WordAddress,unsigned char Data)
{
IIC_Start();//开始函数
IIC_SendByte(AT24C02_ADDRESS);//发送从机地址+W
IIC_ReceiveAck();//接收应答
IIC_SendByte(WordAddress);//发送写入地址
IIC_ReceiveAck();//接收应答
IIC_SendByte(Data);//发送Data
IIC_ReceiveAck();//接收应答
IIC_Stop();//终止函数
}
//随机读:读出WordAddress地址中的Data
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
IIC_Start();//开始函数
IIC_SendByte(AT24C02_ADDRESS);//发送从机地址+W
IIC_ReceiveAck();//接收应答
IIC_SendByte(WordAddress);//发送写入地址
IIC_ReceiveAck();//接收应答
IIC_Start();//开始函数
IIC_SendByte(AT24C02_ADDRESS|0X01);//发送从机地址+R
IIC_ReceiveAck();//接收应答
Data=IIC_ReceiveByte();//接收一个字节
IIC_SendAck(0X01);//发送应答
IIC_Stop();//终止函数
return Data;
}
6.5Buzzer.c
#include "Buzzer.h"
#include <REGX52.H>
#include "delay.h"
#include <INTRINS.H>
sbit Buzzer=P2^5;
void Buzzer_Delay500us() //@11.0592MHz
{
unsigned char i;
_nop_();
i = 227;
while (--i);
}
//调用这个函数,蜂鸣器就响多少秒
void Buzzer_Time(unsigned int ms)
{
unsigned int i;
for(i=0;i<ms*2;++i)
{
Buzzer=!Buzzer;
Buzzer_Delay500us();//必须要有延时,保证高低电平维持0.5ms(几字波形)
}
}