DS18B20是单片机一个可以做到测量温度的模块。在CT107D型号(stc15系列)单片机中使用单总线协议通讯。这里总结针对于在代码实现的经验。
DS18B20的特点有以下几种:
1-采用单总线(1-Wire)协议.
2-拥有独立64位ID
3-多点测温应用
4-无需外部元件
5-可以由数据线供电
6-测量温度范围:-55℃~+125℃
7-在-10℃~+85℃区间内有±0.5℃的误差
8-可以选择9~12位温度分辨率(二进制数)
9-转换12位温度分辨率最多需要750ms
DS18B20电路图
- GND:电源地线
- DQ:数字信号输入/输出端。
- VDD:外接供电电源输入端。
- ROM:只读存储器。
- SCRATCHPAD:暂存器。
- MEMORY CONTROL LOGICl:存储器控制逻辑
这里着重看右半部分的工作,只读存储器(ROM)跟暂存器(SCRATCHPAD)依靠存储器控制逻辑(MEMORY CONTROL LOGICl)来控制双方的数据交换。只读存储器使用单总线协议用一个串口跟外界完成数据交换。暂存器则需要(从上到下)与温度传感器,警告触发寄存器,配置寄存器,2字节用户寄存器,8位循环冗余校验产生器交换数据。
DS18B20初始化
通过DS18B20来实现测温,一定要通过单总线协议实现对芯片的读写。首先使用之前我们要确定DS18B20的存在。这个过程也能叫做初始化DS18B20(一般而言单总线协议遇到同型号的多个单元要多确定地址,但是CT107D只有一个DS18B20,所以不考虑)。
线型注释
- 黑粗线:总线控制器拉低电平
- 灰粗线:DS18B20拉低电平
- 黑细线:上拉电阻拉高电平
根据以上时序图,我将整个初始化DS18B20的过程总括为几个步骤:总线(DQ)首先发送复位脉冲,将电平拉低。在至少480微秒之后,上拉电平释放总线,上拉电阻会在15到60微秒区间将电位拉高。在上拉电阻开始上拉电平后的60到240微秒区间内DS18B20会主动将电平拉低——也就是说,如果DS18B20不存在或者异常,那么电平在这个区间内保持高电平。度过这个区间之后就要重新上拉电平。
总结这个历程,我们就是要判断第一次上拉电平释放总线后的60到240微秒区间读取电平,读到低电平,则DS18B20存在,反之DS18B20异常或者不存在。
下面是DS18B20初始化的代码操作。首先补充这里的单总线协议由单片机的P14口完成,其次延时函数可以通过stc-isp来生成。
bit DQ = P1^4;
bit DS18B20_Init(void)
{
bit flag; //接收电平数据
DQ = 1; //先将电平置为高电平
Delay_10us(5);
DQ = 0; //从这里历程正式开始,将电平拉低
Delay_10us(55); //延时550us,至少要大于480us
DQ = 1; //第一次上拉电平
Delay_10us(10); //延时100us,电阻要最多60us才能上拉
flag = DQ; //读取DQ状态
Delay_10us(5); //防止短时间多次操作产生错误,结束历程要延时一会
return flag; //返回0则初始化成功,返回1则失败
}
DS18B20写操作
从上面的总括我们可以看到,DS18B20功能还是比较强大的,给我们有多个模式来选择(给我们添了又一道麻烦),这就需要我们通过写操作来配置。
电平的操作手段也是一样的,这里就不过多赘述。写操作的本质上就是向DS18B20一位一位写入“1”和“0“。那么写操作的核心就是如何向DS18B20写入“1”和“0”。看时序图可以知道:拉低电平之后历程开始,此后如果60到120微秒内保持低电平,那么DS18B20将会视作写入”0“。那么在1到无穷大时间内保持高电平就是会被视作写入1。看到下面的DS18B20的采样历程,实际上将高或低电平覆盖DS18B20的采样历程就是会被视作写入一个”1“或”0“。
总结就是拉低电平之后,要写入1就立马拉高电平,写入0就在60到120微秒区间内拉高电平就行了。由于我们通常是一个字节一个字节进行写操作,那将历程执行八次就行了。下面是代码操作。
bit DQ = P1^4;
void DS18B20_Write(unsigned char item)
{
unsigned char i = 0;
for(i;i<8;i++)
{
DQ = 0; //拉低电平
DQ = item&0x01; //读取输入数据的最低一位
Delay_10us(8); //延时80us,至少60us
DQ = 1; //拉高电平
item >>= 1; //输入数据右移一位
}
Delay_10us(5); //每次操作完成要间隔一会
}
DS18B20读操作
看上图的时序,读操作中,拉低电平之后1微秒以内就可以拉高电平释放总线。也就是拉低电平之后就可以直接执行拉高电平,这时候DS18B20就会视作执行读操作。当我们拉高电平释放总线是,如果DS18B20要输出“0”,那么会保持一段时间的低电平在60微秒以内,输出“1”就会直接拉高电平。手册推荐的采样器采样时间是拉高电平释放总线15微秒的区间,所以微秒释放总线之后就可以直接读取总线状态。
总结就是,拉低电平就直接拉高电平释放总线,并读取总线状态。下面是代码操作。
bit DQ = P1^4;
unsigned char DS18B20_Read(void)
{
unsigned char i = 0;
unsigned char dat = 0x00; //存储读取的数据
for(i;i<8;i++)
{
DQ = 0;
dat >>= 1; //读取的数据左移,最高位此时是0
DQ = 1;
if(DQ) //如果读出“1”,就将dat最高位置为1,是“0”就不操作
{
DQ |= 0x80;
}
Delay_10us(5); //延时一会
}
return dat;
}
DS18B20暂存器
从上往下看,暂存器分为9个区域。Byte0和1存储温度数据,这部分是与温度传感器交换数据的区域,Byte0存储低位数据,Byte1存储高位数据;Byte2和3存储警报触发值,这部分与警报触发寄存器交换数据,同样的Byte2存储低位数据,Byte3存储高位数据;Byte4存储配置寄存器数据,与配置寄存器交换数据,这里的数据决定温度精度; Byte4、5、6存储保留位与用户寄存器交换数据。Byte8则是与冗余校验发生器交换数据。
下面着重看到温度存储区域。
可以看出,LS BTYE是Btye0,MS BYTE是Byte1。数据存储方式如图所示,温度都是以2进制数,不同位数存储,从-4位一直到6位。它们的最大值就是大约85℃。另外高位的s是符号(sign),看到表格,全为0则位为正数,全为1则是负数。
DS18B20指令
DS18B20的操作指令已经设计了,只需要按照手册用写函数输入对应指令就行。
跳过寄存器指令(0xcc) :前文有提到操作DS18B20需要定位设备位置,但是由于单片机上只有一个DS18B20,那么我们就不需要定位,直接跳过这个流程。
写暂存器(0x4e) 读暂存器(0xbe):也就是读操作
启动温度转换(0x44):启动后DS18B20会将测出来的温度转换成数据(16进制)
DS18B20代码操作
综合上面的板块我们就可以完成读取温度的操作了,我们先看看这个读DS18B20温度参数的过程。
float fRead_Temperature()
{
unsigned char low,high;
float temp;
DS18B20_Init();
DS18B20_Write(0xCC); //跳过ROM
DS18B20_Write(0x44); //启动温度转换
DS18B20_Init();
DS18B20_Write(0xCC); //跳过ROM
DS18B20_Write(0xBE); //获取暂存器数据
low = DS18B20_Read(); //获取低8位数据
high = DS18B20_Read(); //获取高8位数据
temp = (high<<8|low)*0.0625;
return temp;
}
这里看到读和写是两次操作,写了就要重新初始化才能读。读取的数值也要注意,DS18B20测出来的数据是4位16进制数,需要两个u8类型的变量或者一个u32数据来存储(因为我们写的读数据函数一次读一个字节,所以这里读两次)。乘以0.0625是为了加上小数点,将2进制数转化位10进制数。
最后配合计时器数码管等加入主函数,将数值显示在数码管上。
#include <STC15F2K60S2.H>
#include "intrins.h"
#include "System.h"
#include "onewire.h"
#include "Device.h"
#include "Timer.h"
void vDevice_Ctrl(unsigned char p0,unsigned char p2)
{
P0 = p0;
P2 = P2&0x1f|p2; //与运算0x1f是为了保证iic通讯的正常
P2 &= 0x1f;
}
void vSystem_Init()
{
vDevice_Ctrl(0x00,0xa0);
vDevice_Ctrl(0xff,0x80);
vDevice_Ctrl(0xff,0xe0);
}
float temperature;
unsigned long temp;
unsigned char cnt;
unsigned char smg_ctrl[]={0x01,0x02,0x04,0x08,0x10,0x20,0x040,0x80};
unsigned char smg_code[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
unsigned char smg_buf[8];
void smg_process()
{
smg_buf[2] = smg_code[temp/100000];
smg_buf[3] = smg_code[temp/10000%10]|0x80;
smg_buf[4] = smg_code[temp/1000%10];
smg_buf[5] = smg_code[temp/100%10];
smg_buf[6] = smg_code[temp/10%10];
smg_buf[7] = smg_code[temp%10];
}
void smg_display()
{
static unsigned char i;
vDevice_Ctrl(~smg_buf[i],0xe0);
vDevice_Ctrl(smg_ctrl[i],0xc0);
i++;
if(i == 8) i = 0;
}
void vTimer2_Init()
{
AUXR |= 0x04; //定时器时钟1T模式
T2L = 0x20; //设置定时初始值
T2H = 0xD1; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
IE2 |= 0x04; //定时器2中断
EA = 1; //开启总中断
}
void main()
{
vSystem_Init();
vTimer2_Init();
while(1)
{
if(cnt >= 100)
{
temperature = fRead_Temperature();
temp = temperature*10000;
cnt = 0;
}
smg_process();
}
}
void Timer2_IRC() interrupt 12
{
cnt++;
smg_display();
}
下载到板子的现象就是如图
至此,本篇结束,如有纰漏,希望读者斧正。