STC12C5A60S2(11.0592MHz) + DS18B20测温系统 (仅适于小白参考,敬请大牛指点)
声明,本人小白一个,仅有点基础软硬件常识,想DIY一个热水器控制系统,在TB上买了个STC12C5A60S2最小系统(因为这东东这带AD,免得再去外置AD)和DS18B20封装好的传感器,当然还有其它东东不一一俱来。本天真的以为卖家提供学习代码,做一个代码模块组装即可,不料组装编译后不能用,于是上网找代码,发现网上代码很多,但没一个能用。不过折腾了一番大致搞明白两个重要的事实:
1、DS18B20是一个对时序控制极其严苛的系统,所以延时控制非常重要!!时序顺序非常重要!!
2、STC12C5A60S2机器周期是1T(STC公司1T单片机与其它公司1T单片机机器周期还不同!!!网上说的,具体不知道哪里不同!!),而网上代码大都是以89C52平台为基础的,并且要命的时许多代码不标注时钟周期,当然,也有很多存在明显时序错误的忽悠人的代码;
清楚认识的以上事实后,自己回到自己好好学习的道路上来,那么针对于上述问题,理清思路:
1. 要解决精确延时问题;
2. 要搞清楚DS18B20交互方法;
3. 要搞清楚DS18B20时序结构;
注:以下代码是在卖家代码基础上做时序调整的。
首先关于时精确延时问题,原本想上网找可用的延时代码,无一例外没一个能用,当然没有仿真能力,也没有示波器,也没法验证到底是代码有问题还是我有问题,最好发现一个叫“单片机小精灵”的神器(原谅我等小白的无知),于是充分信任了该神器,用它来做延时,事实证明它是神器!!!对于不同时钟与芯片的主板,各位可以自行重生成相应时延的代码即可。
步骤如下:
1、打开软件选延时功能模块;
2、下拉框选择自己单片机挂载时钟的频率;
3、选择单片机类型,注意前面提到STC12C5A机器周期是1T并且与其它1T单片机还有不同,所以一定要选最后一个;
4、选择输出代码类型,我选的是C代码,选取该代码时不能调参(因为很多指令不是一个机器周期的),所以得多写个时延时子程序(最后优化写了四个也就搞定了),汇编咱不会,如果会的话可以选它,方便很多;
5、输入需要取得的延时长度;
6、点击“计算”,输出代码;
7、复制代码到延时模块;
接下来就要学习DS18B20的信息交互流程:
1、执行DS18B20初始化(执行每个功能指令前均需要执行该指令(有两个指令例外,但是我等小白可以忽略))
2、执行ROM操作指令(指读取DS18B20编号、告警数据、ROM指令忽略指令(忽略指令也属于ROM操作指令,指不对ROM进行操作,单片机采用写指令进行输出,指令代码0xCC,我等小白不要妄想忽略这个步骤))
3、执行功能指令(指启动DS18B20温度转换、读取温度等指令)
再下来就要说一下时序及代码的实现了:
1. DS18B20初始化操作;
操作时序如下:
执行代码如下:
对于小白这里需要提醒一下,单片机在总线上输出高电平(即释放总线操作,单片机端口为接收状态),此时DS18B20在总线上输出低电平时即会将该总线电平拉低(可以理解为此时单片机输出端为一个电源+电阻,而DS18B20输出低电平可以认为其将该总线通过开关接至地线),所以不要以为单片机输出高电平,此总线就始终是高电平了!!!(自己反省一下,曾经掉这个坑了)
//****************************************************
//DS18B20初始化
//****************************************************
bit DS18B20_Init()
{
bit Flag_exist = 1; //初始化结果标志
DS18B20_DQ = 1; //单片机管脚电平拉高,以释放总线(图中步骤1)
delay2us(); //延时>1us,执行2us
DS18B20_DQ = 0; //单片机电平拉低以启动DS18B20初始化(图中步骤2)
delay500us(); //延时480~960us执行500us
DS18B20_DQ = 1; //释放总线(图中步骤3)
delay60us(); //延时15~60us,执行60us保险起见加个NOP
_nop_();
Flag_exist = DS18B20_DQ; //将DS18B20输出状态读到初始化结果状态标志中(图中步骤4)
delay500us(); //从单片机输出电平拉高时(步骤3)算起,总时长不得小于480us(执行500uS加上之前的60us及指令执行周期总执时时延大于560us)
return Flag_exist; //返回初始化结果标志,后面代码中用于判断初始化是否成功
}
2. DS18B20写操作;
操作时序(数据手册中都会说到低电平输出时隙与高电平输出时隙,其实处理上都是一样的):
执行代码:
//****************************************************
//DS18B20写1字节
//****************************************************
void DS18B20_Write_Byte( unsigned char dat)
{
unsigned char i;
for( i = 0 ; i < 8 ; i++ )
{
DS18B20_DQ = 0; //单片机拉低启动读/写时序(前面必须有初始化操作)(步骤2)
delay2us(); //延时>1us(执行2us)
DS18B20_DQ = dat&0x01; //DS18B20先写指令时要求先低位,该指令为取出写数据的最低bit送给单片机总线管脚输出 (步骤3)
dat >>= 1; //将写的数据进行右移一位,将原先的第二位放置到末位,为下个循环过程取值做准备
delay60us(); //单片机总线输出保持时间需要保证从总线拉低开始(步骤2)至写一个字节指令结束总时长大于60us,执行(2us+60us+指令时长大于62us)(步骤4)
_nop_(); //保险起见加个机器周期,可有可无
DS18B20_DQ = 1; //指令写完后要释放总线 (步骤5)
delay2us(); //写下一个BIT前必须间隔1us以上(执行2us)
}
}
3. DS18B20读操作;
操作时序:
执行代码:
//****************************************************
//DS18B20读1字节
//****************************************************
unsigned char DS18B20_Read_Byte( )
{
unsigned char dat,i;
for( i = 0 ; i < 8 ; i++ )
{
DS18B20_DQ = 0; //单片机拉低并保持1us以上(执行2us)启动DS18B20读/写时序(前面必须有初始化操作)(步骤2)
delay2us(); //延时2us
DS18B20_DQ = 1; //单片机输出高电平,拉高总线即释放总线 (步骤3)
delay10us(); //从步骤2开始,至释放数据线(即此处)不得超过15us,并且在快到15us时单片机进行总线数据读取,此处延时必须精确(到此处总延时2+10+两条指令略大于12us)
dat >>= 1; //将前面取到的bit左移,第一次为空转
if( DS18B20_DQ == 1) //总线取值操作 (步骤4)
{
dat |= 0X80;
}
else
{
dat &= 0x7f;
}
delay60us(); //读时隙总时长不得小于60us(执行12us+60us+指令周期大于72us,满足大于60us要求)(步骤5)
}
return dat;
}
其余代码比较简单,共享之:
#include <STC12C5A.H>
#include<intrins.h>
sbit DS18B20_DQ = P3^7; //DS18B20 IO设置
unsigned char flag_temper = 0; //定义变量
//****************************************************
//DS18B20延时函数
//****************************************************
void delay2us(void) //误差 -0.010706018519us
{
unsigned char a;
for(a=4;a>0;a--);
}
void delay10us(void) //误差 -0.053530092593us
{
unsigned char a;
for(a=26;a>0;a--);
}
void delay60us(void) //误差 -0.049913194444us
{
unsigned char a;
for(a=164;a>0;a--);
_nop_(); //if Keil,require use intrins.h
}
void delay500us(void) //误差 -0.054253472222us
{
unsigned char a,b;
for(b=251;b>0;b--)
for(a=4;a>0;a--);
_nop_(); //if Keil,require use intrins.h
}
//****************************************************
//DS18B20初始化
//****************************************************
bit DS18B20_Init()
{
bit Flag_exist = 1; //初始化结果标志
DS18B20_DQ = 1; //单片机管脚电平拉高,以释放总线(图中步骤1)
delay2us(); //延时>1us,执行2us
DS18B20_DQ = 0; //单片机电平拉低以启动DS18B20初始化(图中步骤2)
delay500us(); //延时480~960us执行500us
DS18B20_DQ = 1; //释放总线(图中步骤3)
delay60us(); //延时15~60us,执行60us保险起见加个NOP
_nop_();
Flag_exist = DS18B20_DQ; //将DS18B20输出状态读到初始化结果状态标志中(图中步骤4)
delay500us(); //从单片机输出电平拉高时(步骤3)算起,总时长不得小于480us(执行500uS加上之前的60us及指令执行周期总执时时延大于560us)
return Flag_exist; //返回初始化结果标志,后面代码中用于判断初始化是否成功
}
//****************************************************
//DS18B20写1字节
//****************************************************
void DS18B20_Write_Byte( unsigned char dat)
{
unsigned char i;
for( i = 0 ; i < 8 ; i++ )
{
DS18B20_DQ = 0; //单片机拉低启动DS18B20读/写时序(前面必须有初始化操作)(步骤2)
delay2us(); //延时>1us(执行2us)
DS18B20_DQ = dat&0x01; //DS18B20先写指令时要求先低位,该指令为取出写数据的最低bit送给单片机总线管脚输出 (步骤3)
dat >>= 1; //将写的数据进行右移一位,将原先的第二位放置到末位,为下个循环过程取值做准备
delay60us(); //单片机总线输出保持时间需要保证从总线拉低开始(步骤2)至写一个字节指令结束总时长大于60us,执行(2us+60us+指令时长大于62us)(步骤4)
_nop_(); //保险起见加个机器周期,可有可无
DS18B20_DQ = 1; //指令写完后要释放总线 (步骤5)
delay2us(); //写下一个BIT前必须间隔1us以上(执行2us)
}
}
//****************************************************
//DS18B20读1字节
//****************************************************
unsigned char DS18B20_Read_Byte( )
{
unsigned char dat,i;
for( i = 0 ; i < 8 ; i++ )
{
DS18B20_DQ = 0; //单片机拉低并保持1us以上(执行2us)启动DS18B20读/写时序(前面必须有初始化操作)(步骤2)
delay2us(); //延时2us
DS18B20_DQ = 1; //单片机输出高电平,拉高总线即释放总线 (步骤3)
delay10us(); //从步骤2开始,至释放数据线(即此处)不得超过15us,并且在快到15us时单片机进行总线数据读取,此处延时必须精确(到此处总延时2+10+两条指令略大于12us)
dat >>= 1; //将前面取到的bit左移,第一次为空转
if( DS18B20_DQ == 1) //总线取值操作 (步骤4)
{
dat |= 0X80;
}
else
{
dat &= 0x7f;
}
delay60us(); //读时隙总时长不得小于60us(执行12us+60us+指令周期大于72us,满足大于60us要求)(步骤5)
}
return dat;
}
//**********************************************************
//读取温度函数,返回温度的绝对值,并标注flag_temper,flag_temper=1表示负,flag_temper=0表示正
//**********************************************************
unsigned int Get_temp(void) //读取温度值
{
float tt;
unsigned char a,b;
unsigned int temp;
if( DS18B20_Init() == 0 ) //初始化
{
DS18B20_Write_Byte(0xcc); //忽略ROM指令
DS18B20_Write_Byte(0x44); //温度转换指令
if( DS18B20_Init() == 0 ) //初始化
{
DS18B20_Write_Byte(0xcc); //忽略ROM指令
DS18B20_Write_Byte(0xbe); //读暂存器指令
a = DS18B20_Read_Byte(); //读取到的第一个字节为温度LSB
b = DS18B20_Read_Byte(); //读取到的第一个字节为温度MSB
temp = b; //先把高八位有效数据赋于temp
temp <<= 8; //把以上8位数据从temp低八位移到高八位
temp = temp|a; //两字节合成一个整型变量
if(temp>0xfff)
{
flag_temper=1; //温度为负数
temp=(~temp)+1;
}
else
{
flag_temper=0; //温度为正或者0
}
tt = temp*0.0625; //得到真实十进制温度值
//因为DS18B20可以精确到0.0625度
//所以读回数据的最低位代表的是0.0625度
temp = tt*10+0.5; //放大十倍
//这样做的目的将小数点后第一位也转换为可显示数字
//同时进行一个四舍五入操作。
}
}
return temp; //注意返回值是实际值的十倍(如37.5度,返回值为375),目的是为在INT型中取小数据点后1位,
}
由于是子函数中抽出来的代码,可能存在变量未定义之类的问题,自行解决即可,不同类型的单片机或时钟只要用“单片机小精灵”重生成延时即可。
错误之处敬请各位大牛指点!