这里将我编写的单片机控制单总线协议温度传感器DS18B20的程序共享一下,如有不足,敬请指出!
【实验视频】
单片机控制单总线协议DS18B20之综合实验【实验5·趣味检测实验】
目录
【说明】DS18B20为单总线协议通信,对时序要求较高,在不同单片机之间移植需要部分调整延时,本程序支持以下单片机(在DS18B20.h中修改TEST值即可)
实验一 总线悬挂一个DS18B20之实物实验
【实验图片】正常效果
【实验图片】拔掉DS18B20
【实验图片】插上另一个DS18B20,同时单片机复位一下(程序中只是上电执行程序时读取一次ROM编码)
代码结构图:
程序下载链接:实验1 总线悬挂一个DS18B20之实物实验
测试程序:DS18B20测试.c
#include <intrins.h>
#include "common.h"
#include "lcd2004.h"
#include "ds18b20.h"
//显示DS18B20的64Bit 的ROM编码、温度符号、温度值
void display(DS18B20_Struct temp) ;
void main(void)
{
DS18B20_Struct temp ;
LCD2004_Init() ;
DS18B20_Acquire64BitCode(&temp);//获取DS18B2064Bit的光刻ROM编号
while(1)
{
DS18B20_ReadTemperature(&temp) ;
display(temp) ;
}
}
//显示DS18B20的64Bit 的ROM编码、温度符号、温度值
void display(DS18B20_Struct temp)
{
unsigned char lsb,msb,i ;
if(temp.ExistFlag == DS18B20_EXISTENCE)/*器件存在*/
{
LCD2004_AddressWriteString(LCD2004_ROW0,0,"NO.") ;
LCD2004_AddressWriteString(LCD2004_ROW1,0,"Value:") ;
LCD2004_AddressWriteString(LCD2004_ROW2,0,"Resolution:") ;
LCD2004_AddressWriteString(LCD2004_ROW3,0,"QQ:279729201") ;
//显示DS18B2064Bit的光刻ROM编号
for(i=0 ; i<8 ; i++)
{
msb = temp.serial_number[i] >>4 ;
lsb = temp.serial_number[i]& 0x0f ;
if(msb <= 9)
LCD2004_AddressWriteByte(LCD2004_ROW0,2*i+3,msb+'0');
else
LCD2004_AddressWriteByte(LCD2004_ROW0,2*i+3,msb-10+'A');
if(lsb <= 9)
LCD2004_AddressWriteByte(LCD2004_ROW0,2*i+1+3,lsb+'0');
else
LCD2004_AddressWriteByte(LCD2004_ROW0,2*i+1+3,lsb-10+'A');
}
//显示温度符号、温度值
if(temp.Temperature_sign == DS18B20_TEMPERATURE_MINUS) //温度为负
LCD2004_AddressWriteByte(LCD2004_ROW1,6,'-');
else if((temp.Temperature_sign == DS18B20_TEMPERATURE_PLUS))//温度为正
LCD2004_AddressWriteByte(LCD2004_ROW1,6,'+');
LCD2004_AddressWriteByte(LCD2004_ROW1,7,temp.temperature_value/100 +'0') ;
LCD2004_AddressWriteByte(LCD2004_ROW1,8,(unsigned long)temp.temperature_value%100/10 +'0') ;
LCD2004_AddressWriteByte(LCD2004_ROW1,9,(unsigned long)temp.temperature_value%10 +'0') ;
LCD2004_AddressWriteByte(LCD2004_ROW1,10,'.') ;
LCD2004_AddressWriteByte(LCD2004_ROW1,11,(unsigned long)(temp.temperature_value*10+0.5)%10 +'0') ;//四舍五入
//显示 ℃
LCD2004_AddressWriteByte(LCD2004_ROW1,12,0xdf) ;
LCD2004_AddressWriteByte(LCD2004_ROW1,13,'C') ;
//显示温度值分辨率:默认12,如果用户修改,这里会显示最新值
LCD2004_AddressWriteByte(LCD2004_ROW2,11,temp.Configure_Resolution/10 +'0') ;
LCD2004_AddressWriteByte(LCD2004_ROW2,12,temp.Configure_Resolution%10 +'0') ;
}
else
{
LCD2004_AddressWriteString(LCD2004_ROW0,0," ") ;
LCD2004_AddressWriteString(LCD2004_ROW1,0," ") ;
LCD2004_AddressWriteString(LCD2004_ROW2,0," ") ;
LCD2004_AddressWriteString(LCD2004_ROW3,0,"Error ") ;
}
}
/*################DS18B20.h ################*/
#ifndef __DS18B20_H__
#define __DS18B20_H__
sbit DS18B20_IO_Bit = P3^7 ;/*根据硬件选择*/
#define TEST 1
/*当TEST等于1时候,适用于晶振11.0592MHZ 、8051指令集为STC-Y1硬件电路和Proteus8.9软件仿真
【备注】STC-Y1指令集包括:
STC89Cxx/STC89LExx
STC90Cxx/STC90LExx
当TEST等于2时候,适用于晶振11.0592MHZ 、8051指令集为STC-Y3硬件电路
【备注】STC-Y3指令集包括:
STC12Cxx/STC12LExx
STC11Fxx/STC11Lxx/STC10Fxx/STC10Lxx
STC15F104E/STC15L104E(A版)
STC15F204EA/STC15L204EA(A版)
*/
typedef struct {
unsigned char ExistFlag ; //DS18B20存在标志位,值为DS18B20_NOT_EXISTENCE(不存在)或DS18B20_EXISTENCE(存在)
unsigned char serial_number[8]; //激光ROM编号
unsigned char Temperature_H ; //温度值高位
unsigned char Temperature_L ; //温度值低位
unsigned char High_Trigger ; //触发警报的温度上限值
unsigned char Low_Trigger ; //触发警报的温度下限值
unsigned char Configure_Resolution ;//分辨率:9/10/11/12位
unsigned char Temperature_sign ; //温度符号,值为DS18B20_TEMPERATURE_PLUS(温度值为正)或DS18B20_TEMPERATURE_MINUS(温度值为负)
float temperature_value ; //温度值计算
}DS18B20_Struct;
//DS18B20存在标志位ExistFlag的值,两种可能性:
#define DS18B20_NOT_EXISTENCE 0//不存在DS18B20,DS18B20连接异常或已损坏
#define DS18B20_EXISTENCE 1//存在DS18B20
//DS18B20检测到温度的正负标志位Temperature_sign的值,两种可能性:
#define DS18B20_TEMPERATURE_PLUS 0 //温度为正
#define DS18B20_TEMPERATURE_MINUS 1 //温度为负
//DS18B20内部ROM命令集
#define DS18B20_SEARCH_ROM 0xF0 //搜索ROM命令
#define DS18B20_READ_ROM 0x33 //读取ROM命令
#define DS18B20_MATCH_ROM 0x55 //匹配ROM命令
#define DS18B20_SKIP_ROM 0xCC //跳过ROM命令
#define DS18B20_ALARM_SEARCH 0xEC
//DS18B20功能命令
#define DS18B20_CONVERT_TEMPERATURE 0x44 //转换温度命令
#define DS18B20_WRITE_SCRATCHPAD 0x4e //写暂存器命令
#define DS18B20_READ_SCRATCHPAD 0xBE //读暂存器命令
#define DS18B20_COPY_SCRATCHPAD 0x48 //拷贝暂存器命令,复制地址2、3、4地址数据TH、TL、configuration registers(配置寄存器)数据到DS18B20内部EEPROM中
#define DS18B20_RECALL_E2 0xB8 //召回EEPROM命令,复制DS18B20内部EEPROM的数据到地址2、3、4地址数据TH、TL、configuration registers(配置寄存器)处
#define DS18B20_READ_POWER_SUPPLY 0xB4 //读取供电方式命令
/*****************外部接口函数******************/
//读温度值(包含初始化,所以不需再调用初始化函数)
extern void DS18B20_ReadTemperature(DS18B20_Struct *temp);
//读取DS18B20的64bit光刻ROM编号
extern void DS18B20_Acquire64BitCode(DS18B20_Struct *temp) ;
/**********************************************/
#endif /*__DS18B20_H__*/
/*################ DS18B20.c################*/
/***************************************************************************
模 块:DS18B20.c
说 明:DS18B20驱动程序
版 本:Version3.0 2021/01/01 06:00
编译环境:Keil_C51 V9.55
主控芯片:STC12C5A60S2 @11.0592MHZ
STC89C52RC @11.0592MHZ
AT89C51 @11.0592MHZ @Proteus8.9仿真
作 者:杨瑞
联系方式:【 QQ 】279729201
【邮箱】279729201@qq.com
yangrui90s@163.com
【电话】13630279531
修改记录:
=================
2021/01/01 06:00
记录 :
1.完善延时函数delay720usForDs18b20()。
2.修改函数名称模式,由"模块名称"(大写)+"_"+功能函数名称构成,例如ds18b20WriteByte函数名称
修改为DS18B20_WriteByte函数。
3.用DS18B20_Reset()代替函数ds18b20Init()【修改原因:便于和其他模块的XXXX_Init()函数区分开,其他模块的
XXXX_Init()必须先调用,对其进行初步设置】。另外,将此函数修改为内部函数,取消之前的外部函数特性。
4.DS18B20复位函数内部,将:
if(!ds18b20_io_bit)
{
existenceFlag = DS18B20_EXISTENCE;
}
else
{
existenceFlag = DS18B20_NOT_EXISTENCE;
}
while((!ds18b20_io_bit) && (i<250))
{
i++;
}
修改为如下代码:
if(!DS18B20_IO_Bit)
{=
ExistenceFlag = DS18B20_EXISTENCE;
while((!DS18B20_IO_Bit) && (i++<70));
}
else
{
ExistenceFlag = DS18B20_NOT_EXISTENCE;
}
这里修改了while的位置,并完善辅助的i的值。(i是为了防止异常情况下程序”卡死”)。
5.将读、写一个Bit数据单独封装成函数DS18B20_ReadSlot()、DS18B20_WriteSlot(unsigned char DataValue),
这里是为了和datasheet的“READ/WRITE TIME SLOT TIMING ”相对应,便于理解。
6.删除函数delay750ms(),当单片机发送功能命令CONVERT T [44h] 后,DS18B20开始转换温度,同时将总线拉低,当
DS18B20转换温度完成后,DS18B20才释放总线为高电平,转换时间和DS18B20当前温度分辨率有关,最长时间为750ms
(即12Bit温度分辨率模式下),温度分辨率不同转换时间也不同,为了灵活,这里不通过延时固定的750ms,而是通过
判断总线电平,单片机一直等待,直到总线成高电平为止(注意这里“等待”是在DS18B20回复单片机“应答脉冲”的前
提下,所以程序不会因为DS18B20安装异常或器件损害而“卡死”)。
7.将函数名ds18b20Acquire(……)修改为DS18B20_Acquire64BitCode(……)。
8.对结构体进行完善,首先将结构体类型DS18B20_info改名为DS18B20_Struct,其次将其中元素进行完善,将ExistFlag放在第一项,
修改宏定义名称,DS18B20_TEMPERATURE_PLUS、DS18B20_TEMPERATURE_MINUS代替DS18B20_TEMPERATURE_POSITIVE、
DS18B20_TEMPERATURE_NEGATIVE。
=================
=================
2015/05/02 11:39
记录:
1.修改结构体
typedef struct {
UB8 Temperature_sign ;
UB8 Temperature_H ;
UB8 Temperature_L ;
float temperature_value ;
UB8 serial_number[8];
}DS18B20_info ;
为新的结构体:
typedef struct {
UB8 serial_number[8]; //激光ROM编号
UB8 Temperature_H ; //温度值高位
UB8 Temperature_L ; //温度值低位
UB8 High_Trigger ; //触发警报的温度上限值
UB8 Low_Trigger ; //触发警报的温度下限值
UB8 Configure_Resolution ;//分辨率:9/10/11/12位,默认精度为12位
UB8 Temperature_sign ; //温度符号,值为DS18B20_TEMPERATURE_POSITIVE(正)或者DS18B20_TEMPERATURE_NEGATIVE(负)
float temperature_value ;//温度值计算
UB8 ExistFlag ; //DS18B20存在标志位
}DS18B20_info ;
2.在函数ds18b20ReadTemperature(DS18B20_info *temp)中添加读取触发警报的温度上限值、
触发警报的温度下限值、读取分辨率的功能。
(这里对于触发警报的温度上限值、触发警报的温度下限值没有使用,暂时保留,以作后续开
发,对于分辨率的读取较为重要,虽然默认为12位分辨率,但是部分人员使用出现以下情况:
在液晶屏显示DS18B20的工作模式时,写一个固定的"12"上去,这样做不是很好,因为有特殊需
要,可能会设置DS18B20的分辨率为9/10/11位,这样就可能导致了DS18B20的真实分辨率和我
们在液晶屏"写死"的数据12不对应,较好的做法是直接从DS18B20的内部读取分辨率,这样即
使修改了DS18B20的分辨率,结构体DS18B20_info内部成员Configure_Resolution也可以及时
更新回来)。
3.修改
(温度为负)
temp->temperature_value = (~(((temp->Temperature_H )<<8 | temp->Temperature_L)-1))*0.0625 ;
(温度为正)
temp->temperature_value = ((temp->Temperature_H & (~(0xf8)))<<8 | temp->Temperature_L)*0.0625 ;
为:
(温度为负)
temp->temperature_value = (~(((temp->Temperature_H )<<8 | temp->Temperature_L)-1))*0.0625*(0x01<<(12-temp->Configure_Resolution)) ;
(温度为正)
temp->temperature_value = ((temp->Temperature_H & (~(0xf8)))<<8 | temp->Temperature_L)*0.0625*(0x01<<(12-temp->Configure_Resolution)) ;
变化主要为后面的乘数,也就是分辨率由0.0625修改为0.0625*(0x01<<(12-temp->Configure_Resolution)),意义在于最初乘以0.0625是因为DS18B20的默认
分辨率是12位,但是因为特殊需要可能会修改DS18B20的分辨率,然后用此代码运行,发现有所偏差,为了更加统一使用,做此修改。
=================
=================
2014/04/13 11:39
记录:
1.用结构体
typedef struct {
UB8 Temperature_sign ;
UB8 Temperature_H ;
UB8 Temperature_L ;
float temperature_value ;
UB8 serial_number[8];
}DS18B20_info ;
代替之前的结构,使得数据结构更加紧凑。
2.添加临时测试函数void ds18b20Acquire(DS18B20_info *temp),但要注意正确的
使用步骤。
=================
=================
2014/02/24 23:44
记录:
1.本程序还适用于proteus仿真
,但注意在仿真时修改delay15usForDs18b20()和delay720usForDs18b20()
和函数ds18b20ReadTemperature的预编译命令即可。
为了简化修改过程,只需要修改程序中的TEST值即可!!
2.该程序默认使用12位转换精度.(精确到0.0625摄氏度)
=================
***************************************************************************/
#include <intrins.h>
#include "common.h"
#include "ds18b20.h"
/*外部函数接口在ds18b20.h中声明*/
/*****************内部函数******************/
void delay15usForDs18b20(void) ;
void delay720usForDs18b20(void) ;
char DS18B20_Reset(void);
void DS18B20_WriteSlot(unsigned char DataValue);
void DS18B20_WriteByte(unsigned char DataValue);
unsigned char DS18B20_ReadSlot(void);
unsigned char DS18B20_ReadByte(void);
/**********************************************/
/******************************************************************
- 函数名称:delay15usForDs18b20
- 功能描述:延时15微秒
- 函数属性:内部函数
- 参数说明:无
- 返回说明:无
- 注 :1.此函数支持硬件电路、proteus软件仿真。
2.此函数由软件STC-ISP V6.88D自带的“软件延时计算器”产生。
当TEST等于1时候,适用于晶振11.0592MHZ 、8051指令集为STC-Y1硬件电路和Proteus8.9软件仿真
【备注】STC-Y1指令集包括:
STC89Cxx/STC89LExx
STC90Cxx/STC90LExx
当TEST等于2时候,适用于晶振11.0592MHZ 、8051指令集为STC-Y3硬件电路
【备注】STC-Y3指令集包括:
STC12Cxx/STC12LExx
STC11Fxx/STC11Lxx/STC10Fxx/STC10Lxx
STC15F104E/STC15L104E(A版)
STC15F204EA/STC15L204EA(A版)
3.单总线对时序要求较高,当移植至其他CPU时,需要适当修改。
******************************************************************/
void delay15usForDs18b20(void) //11.0592MZH
{
#if (TEST == 1) //适用于晶振11.0592MHZ 、8051指令集为STC-Y1硬件电路和Proteus8.9软件仿真
unsigned char i;
i = 4;
while (--i);
#elif (TEST == 2) //适用于晶振11.0592MHZ 、8051指令集为STC-Y3硬件电路
unsigned char i;
_nop_();_nop_();
i = 38;
while (--i);
#endif
}
/******************************************************************
- 函数名称:delay720usForDs18b20
- 功能描述:延时720微秒
- 函数属性:内部函数
- 参数说明:无
- 返回说明:无
- 注 :1.此函数支持硬件电路、proteus软件仿真。
2.此函数由软件STC-ISP V6.88D自带的“软件延时计算器”产生。
当TEST等于1时候,适用于晶振11.0592MHZ 、8051指令集为STC-Y1硬件电路和Proteus8.9软件仿真
【备注】STC-Y1指令集包括:
STC89Cxx/STC89LExx
STC90Cxx/STC90LExx
当TEST等于2时候,适用于晶振11.0592MHZ 、8051指令集为STC-Y3硬件电路
【备注】STC-Y3指令集包括:
STC12Cxx/STC12LExx
STC11Fxx/STC11Lxx/STC10Fxx/STC10Lxx
STC15F104E/STC15L104E(A版)
STC15F204EA/STC15L204EA(A版)
3.单总线对时序要求较高,当移植至其他CPU时,需要适当修改。
******************************************************************/
void delay720usForDs18b20(void) //11.0592MZH
{
#if (TEST == 1) //适用于晶振11.0592MHZ 、8051指令集为STC-Y1硬件电路和Proteus8.9软件仿真
unsigned char i, j;
_nop_();
i = 2;
j = 70;
do
{
while (--j);
} while (--i);
#elif (TEST == 2) //适用于晶振11.0592MHZ 、8051指令集为STC-Y3硬件电路
unsigned char i, j;
_nop_();
i = 8;
j = 187;
do
{
while (--j);
} while (--i);
#endif
}
/******************************************************************
- 函数名称:DS18B20_Reset
- 功能描述:DS18B20初始化序列操作
- 函数属性:内部函数
- 参数说明:无
- 返回说明:DS18B20_EXISTENCE,表示DS18B20复位成功,其产生“应答脉冲信号”
DS18B20_NOT_EXISTENCE,表示DS18B20复位不成功,其未产生“应答脉冲信号”,可能是连接异常或器件损坏
- 注 :1.初始化过程包括:复位和“应答脉冲”,主机(单片机)产生至少480us的低电平,
然后主机释放数据线,进入接收模式。当DS18B20检测到上升沿,它等待15us至60us
,然后发射一个60us至240us的“应答脉冲信号”。
2.DS18B20的所有通信,都是以初始化序列(包括单片机发出的复位脉冲和DS18B20的
“应答脉冲”)开始的。
3.初始化是正确操作DS18B20是重要一步,必须复位成功,只有这里返回值为DS18B20_EXISTENCE,
后续的操作才有意义,具体使用中可以通过检测返回值检测是否成功。
4.DS18B20对时间的要求较为严格,因此将此代码移植到其它CPU或场合时要注意延时函数。
5.经测试STC89C52RC @11.0592MHZ时i的值为2,STC12C5A60S2 @11.0592MHZ时i的值为7,这里
设置i的最大值为70,时间够长了。
******************************************************************/
char DS18B20_Reset(void)
{
char ExistenceFlag ;
unsigned long i=0;
DS18B20_IO_Bit = 1 ;
_nop_() ; _nop_() ;
DS18B20_IO_Bit = 0 ;
delay720usForDs18b20();//复位脉冲最少480us,这里采用1.5倍时间,即720us延时
DS18B20_IO_Bit = 1 ;
/*以上是单片机发送“复位”脉冲*/
//等待15us至60us,如果DS18B20存在,DS18B20会把数据线拉低,发送一个60us至240us的“应答脉冲”
delay15usForDs18b20();delay15usForDs18b20();
delay15usForDs18b20();delay15usForDs18b20();
if(!DS18B20_IO_Bit)
{//如果主机(单片机)检测到数据线被DS18B20拉低,表示DS18B20复位成功
ExistenceFlag = DS18B20_EXISTENCE;
while((!DS18B20_IO_Bit) && (i++<70));
//delay720usForDs18b20();
}
else
{//否则DS18B20连接异常(不存在或者已损坏)
ExistenceFlag = DS18B20_NOT_EXISTENCE;
}
return ExistenceFlag ;
}
/******************************************************************
- 函数名称:DS18B20_WriteSlot
- 功能描述:向DS18B20写入一个位(DS18B20的写时间片)
- 函数属性:内部函数
- 参数说明:要写入的位数据
- 返回说明:无
- 注 :1.DS18B20的写时间片对时间的要求较为严格。因此将此代码移植到其它
CPU或场合时要注意延时函数
******************************************************************/
void DS18B20_WriteSlot(unsigned char DataValue)
{
DS18B20_IO_Bit = 0 ;
_nop_();_nop_();_nop_();_nop_();
DS18B20_IO_Bit = DataValue;
delay15usForDs18b20();delay15usForDs18b20() ;delay15usForDs18b20() ;delay15usForDs18b20() ;
DS18B20_IO_Bit = 1 ;
_nop_();_nop_();_nop_();//注意:两个写时间片最低间隔1us
}
/******************************************************************
- 函数名称:DS18B20_WriteByte
- 功能描述:向DS18B20写入一个字节数据
- 函数属性:内部函数
- 参数说明:dataCode,需要写入的数据
- 返回说明:无
- 注 :1.此函数调用写时间片来实现字节的写入。
******************************************************************/
void DS18B20_WriteByte(unsigned char DataValue)
{
unsigned char i ;
for(i=0 ; i<8 ; i++)
{
DS18B20_WriteSlot(DataValue &(1<<i) );
}
}
/******************************************************************
- 函数名称:DS18B20_ReadSlot
- 功能描述:从DS18B20读取一个位(DS18B20的读时间片)
- 函数属性:内部函数
- 参数说明:无
- 返回说明:读取到的“位”数据
- 注 :1.DS18B20的读时间片对时间的要求较为严格。因此将此代码移植到其它
CPU或场合时要注意延时函数
******************************************************************/
unsigned char DS18B20_ReadSlot(void)
{
unsigned char dataValue;
DS18B20_IO_Bit = 0 ;
_nop_();_nop_();_nop_();_nop_();
DS18B20_IO_Bit = 1 ;
_nop_();_nop_();
_nop_();_nop_();
dataValue = DS18B20_IO_Bit;
delay15usForDs18b20() ;
delay15usForDs18b20() ;
delay15usForDs18b20() ;
delay15usForDs18b20() ;
return dataValue;
}
/******************************************************************
- 函数名称:DS18B20_ReadByte
- 功能描述:从DS18B20读取一个字节
- 函数属性:内部函数
- 参数说明:无
- 返回说明:读取到的字节数据
- 注 :1.注意此函数算法中对DataValue必须赋值为0,因为涉及到位或运算,必须注意。
******************************************************************/
unsigned char DS18B20_ReadByte(void)
{
unsigned char i,DataValue=0;//注意这里DataValue一定要赋初值为0,否则可能出现意外情况
for(i=0;i<8;i++)
{
DataValue |= (DS18B20_ReadSlot()<<i);
}
return(DataValue);
}
/******************************************************************
- 函数名称:DS18B20_ReadTemperature
- 功能描述:读取DS18B20温度值
- 函数属性:外部函数,供用户调用
- 参数说明:*temp为DS18B20_Information类型结构体指针
- 返回说明:temp指针对应的信息
- 注 :1.经测试STC89C52RC @11.0592MHZ时i的值为4245,STC12C5A60S2 @11.0592MHZ时i的值为7992,这里
设置i的最大值为83620,时间够长了。
******************************************************************/
void DS18B20_ReadTemperature(DS18B20_Struct *temp)
{
unsigned long i=0;
temp->ExistFlag = DS18B20_Reset() ;
if(temp->ExistFlag == DS18B20_EXISTENCE)
{//DS18B20存在
DS18B20_WriteByte(DS18B20_SKIP_ROM) ; //跳过ROM操作
DS18B20_WriteByte(DS18B20_CONVERT_TEMPERATURE) ;//转换温度命令
while((!DS18B20_ReadSlot()) && (i++<79920));//等待转换完成,注意如果无此等待,上电瞬间温度显示+85℃(详见datasheet)
//delay750ms();
DS18B20_Reset() ;
DS18B20_WriteByte(DS18B20_SKIP_ROM) ;
DS18B20_WriteByte(DS18B20_READ_SCRATCHPAD) ;
temp->Temperature_L= DS18B20_ReadByte() ;/*温度值低位*/
temp->Temperature_H= DS18B20_ReadByte() ;/*温度值高位*/
temp->High_Trigger = DS18B20_ReadByte() ;/*触发警报的温度上限值,代码中暂未实际使用*/
temp->Low_Trigger = DS18B20_ReadByte() ; /*触发警报的温度下限值,代码中暂未实际使用*/
temp->Configure_Resolution= (DS18B20_ReadByte()>>5) + 9 ;//分辨率:9/10/11/12位
if((temp->Temperature_H) & (0xf8))
{//温度为负数
temp->Temperature_sign = DS18B20_TEMPERATURE_MINUS ;
temp->temperature_value = (~(((temp->Temperature_H )<<8 | temp->Temperature_L)-1))*0.0625*(0x01<<(12-temp->Configure_Resolution)) ;
}
else
{//温度为正数
temp->Temperature_sign =DS18B20_TEMPERATURE_PLUS ;
temp->temperature_value = ((temp->Temperature_H & (~(0xf8)))<<8 | temp->Temperature_L)*0.0625*(0x01<<(12-temp->Configure_Resolution)) ;
}
}
}
/******************************************************************
- 函数名称:DS18B20_Acquire64BitCode
- 功能描述:读取DS18B20的64Bit光刻ROM编号
- 函数属性:外部函数,供用户调用
- 参数说明:DS18B20_Information类型指针
- 返回说明:无
- 注 :1.测试单个DS18B20的64位光刻ROM编号,8bit的CRC+48bit的串行编号+8bit的DS18B20家族码:0x28(固定)
******************************************************************/
void DS18B20_Acquire64BitCode(DS18B20_Struct *temp)
{
unsigned char i ;
temp->ExistFlag = DS18B20_Reset() ;
if(temp->ExistFlag == DS18B20_EXISTENCE)
{
DS18B20_WriteByte(DS18B20_READ_ROM);
for(i=0 ; i<8 ; i++)
{
temp->serial_number[7-i] = DS18B20_ReadByte();//先读出的是低字节,所以反着存储
}
}
}
对于LCD2004部分,请参考我的博客文章:单片机控制LCD2004液晶屏之模块化编程
【备注】DS18B20内部有64Bit的激光ROM编码,每一个DS18B20都有唯一的编码,编码的最低8Bit为DS18B20的family code,值为28H,即只要是DS18B20,它的最低8Bit值都是28H,具体参考datasheet,如下图:
【注意】经过上面的实验,得到两个DS18B20的64Bit激光ROM编码分别是8F000006F3A83328和F1000006F18C7628,这里备忘,后面实验用得到。
实验二 总线悬挂一个DS18B20之仿真实验
程序下载链接:实验2 总线悬挂一个DS18B20之仿真实验【程序】
Proteus仿真电路下载链接:实验2 总线悬挂一个DS18B20之仿真实验【Proteus仿真电路图】
步骤1:设置第一个DS18B20参数及硬件电路
步骤2: 第一个DS18B20实验效果截图
步骤3:设置第二个DS18B20参数及硬件设置
步骤4:第二个DS18B20实验效果截图
测试程序: 直接用“实验一”程序即可(通用)
实验三 总线悬挂两个DS18B20之实物实验
程序下载链接:实验3 总线悬挂两个DS18B20之实物实验
程序也可由实验一程序修改而来,具体步骤如下:
步骤1:修改DS18B20.C文件,在最前面增加两个变量的定义:
unsigned char Machine = 0;//器件编号
unsigned char Machine_Serial_Number[2][8]={
{0x8F,0x00,0x00,0x06,0xF3,0xA8,0x33,0x28},
{0xF1,0x00,0x00,0x06,0xF1,0x8C,0x76,0x28}
};//两个DS18B20的激光ROM编码,来源于实验一
(这里数组Machine_Serial_Number[2][8]里面保存的是两个DS18B20的光刻ROM编号,数据来自于实验一)
步骤2:此时因为一条总线悬挂了两个DS18B20,不能再通过跳过ROM搜索命令去操作特定的DS18B20,这时将函数DS18B20_ReadTemperature由
void DS18B20_ReadTemperature(DS18B20_Struct *temp)
{
unsigned long i=0;
temp->ExistFlag = DS18B20_Reset() ;
if(temp->ExistFlag == DS18B20_EXISTENCE)
{//DS18B20存在
DS18B20_WriteByte(DS18B20_SKIP_ROM) ; //跳过ROM操作
DS18B20_WriteByte(DS18B20_CONVERT_TEMPERATURE) ;//转换温度命令
while((!DS18B20_ReadSlot()) && (i++<79920));//等待转换完成,注意如果无此等待,上电瞬间温度显示+85℃(详见datasheet)
//delay750ms();
DS18B20_Reset() ;
DS18B20_WriteByte(DS18B20_SKIP_ROM) ;
DS18B20_WriteByte(DS18B20_READ_SCRATCHPAD) ;
temp->Temperature_L= DS18B20_ReadByte() ;/*温度值低位*/
temp->Temperature_H= DS18B20_ReadByte() ;/*温度值高位*/
temp->High_Trigger = DS18B20_ReadByte() ;/*触发警报的温度上限值,代码中暂未实际使用*/
temp->Low_Trigger = DS18B20_ReadByte() ; /*触发警报的温度下限值,代码中暂未实际使用*/
temp->Configure_Resolution= (DS18B20_ReadByte()>>5) + 9 ;//分辨率:9/10/11/12位
if((temp->Temperature_H) & (0xf8))
{//温度为负数
temp->Temperature_sign = DS18B20_TEMPERATURE_MINUS ;
temp->temperature_value = (~(((temp->Temperature_H )<<8 | temp->Temperature_L)-1))*0.0625*(0x01<<(12-temp->Configure_Resolution)) ;
}
else
{//温度为正数
temp->Temperature_sign =DS18B20_TEMPERATURE_PLUS ;
temp->temperature_value = ((temp->Temperature_H & (~(0xf8)))<<8 | temp->Temperature_L)*0.0625*(0x01<<(12-temp->Configure_Resolution)) ;
}
}
}
修改为:
步骤3:修改DS18B20.h文件,增加两个变量的声明
extern unsigned char Machine;/*DS18B20编号*/
extern unsigned char Machine_Serial_Number[2][8];/*两个DS18B20激光ROM编号*/
步骤4:修改主函数,原本只有一个DS18B20_Struct结构体的变量temp:
DS18B20_Struct temp ;
这里同一条单总线连接两个DS18B20,所以需要两个变量,修改为:
DS18B20_Struct temp ,temp2;
这里不再需要读取ROM编码,所以删除或屏蔽掉:
DS18B20_Acquire64BitCode(&temp);//获取DS18B2064Bit的光刻ROM编号
通过:
在while(1)大循环中,通过:
Machine = 0;
DS18B20_ReadTemperature(&temp) ;
Machine=1;
DS18B20_ReadTemperature(&temp2) ;
即可将1号DS18B20和2号DS18B20的温度数据保存到temp 和temp2中,此时mian函数如下:
现在就只需要显示出来具体数据即可,将显示函数:
void display(DS18B20_Struct temp)
{
……………………
……………………
}
修改为(注意同时修改对次函数的声明):
void display(DS18B20_Struct temp,unsigned char machine,unsigned char LCD2004_Row)
{
unsigned char lsb,msb,i ;
if(temp.ExistFlag == DS18B20_EXISTENCE)/*器件存在*/
{
LCD2004_AddressWriteString(LCD2004_Row,0,"NO.") ;
LCD2004_AddressWriteString(LCD2004_Row+1,0,"Value:") ;
//显示DS18B2064Bit的光刻ROM编号
for(i=0 ; i<8 ; i++)
{
msb = Machine_Serial_Number[machine][i] >>4 ;
lsb = Machine_Serial_Number[machine][i]& 0x0f ;
if(msb <= 9)
LCD2004_AddressWriteByte(LCD2004_Row,2*i+3,msb+'0');
else
LCD2004_AddressWriteByte(LCD2004_Row,2*i+3,msb-10+'A');
if(lsb <= 9)
LCD2004_AddressWriteByte(LCD2004_Row,2*i+1+3,lsb+'0');
else
LCD2004_AddressWriteByte(LCD2004_Row,2*i+1+3,lsb-10+'A');
}
//显示温度符号、温度值
if(temp.Temperature_sign == DS18B20_TEMPERATURE_MINUS) //温度为负
LCD2004_AddressWriteByte(LCD2004_Row+1,6,'-');
else if((temp.Temperature_sign == DS18B20_TEMPERATURE_PLUS))//温度为正
LCD2004_AddressWriteByte(LCD2004_Row+1,6,'+');
LCD2004_AddressWriteByte(LCD2004_Row+1,7,temp.temperature_value/100 +'0') ;
LCD2004_AddressWriteByte(LCD2004_Row+1,8,(unsigned long)temp.temperature_value%100/10 +'0') ;
LCD2004_AddressWriteByte(LCD2004_Row+1,9,(unsigned long)temp.temperature_value%10 +'0') ;
LCD2004_AddressWriteByte(LCD2004_Row+1,10,'.') ;
LCD2004_AddressWriteByte(LCD2004_Row+1,11,(unsigned long)(temp.temperature_value*10+0.5)%10 +'0') ;//四舍五入
//显示 ℃
LCD2004_AddressWriteByte(LCD2004_Row+1,12,0xdf) ;
LCD2004_AddressWriteByte(LCD2004_Row+1,13,'C') ;
}
else
{
LCD2004_AddressWriteString(LCD2004_Row,0," ") ;
LCD2004_AddressWriteString(LCD2004_Row+1,0,"Error ") ;
}
}
主函数为:
void main(void)
{
DS18B20_Struct temp ,temp2;
LCD2004_Init() ;
while(1)
{
Machine = 0;
DS18B20_ReadTemperature(&temp) ;//存储1号DS18B20信息
Machine=1;
DS18B20_ReadTemperature(&temp2) ;//存储2号DS18B20信息
display(temp,0,LCD2004_ROW0);//显示1号DS18B20信息
display(temp2,1,LCD2004_ROW2);//显示2号DS18B20信息
}
}
实验即可成功。
实验四 总线悬挂两个DS18B20之仿真实验
实验四和实验三代码几乎相同,只需要修改对ROM的定义部分,即将:
unsigned char Machine_Serial_Number[2][8]={
{0x8F,0x00,0x00,0x06,0xF3,0xA8,0x33,0x28},
{0xF1,0x00,0x00,0x06,0xF1,0x8C,0x76,0x28}
};//两个DS18B20的激光ROM编码,来源于实验一
修改软件仿真程序中DS18B20的ROM编码(具体参考实验二得到的显示结果)即看到效果,这里不再详述。
实验五 总线悬挂两个DS18B20之实物趣味检测实验
经过以上实验,DS18B20的基本操作已搞定了,这里增加一个思考,在试验一中,我分别连接了两个DS18B20,得到了他们的ROM编码是:8F000006F3A83328和F1000006F18C7628,这是分别连接两个器件得到的结果,那么如果我同时连接两个DS18B20会读取到ROM编码是多少呢?
经过试验,得到结果是81000006F1883228,注意这里有趣了,这里1号ROM编码和2号ROM编码 & 操作,得到的结果就是81000006F1883228,这实际上也是两个DS18B20对总线抢夺资源,“线与” 得到的结果。
所以这里可以利用这个特性,通过DS18B20_Acquire64BitCode函数,通过检测读取到的ROM编码,知道现在两个DS18B20的连接状况(连接状况包括:两个都连接正常、1号连接正常且2号连接异常、2号连接正常且1号连接异常、两个都连接异常),也就达到了测试视频中 “实时监测” 连接情况的效果,当然这在实际应用中不一定适用,但是可以增加趣味性,也可以增加我们对DS18B20的理解。
有兴趣的可以可下载程序(根据DS18B20ROM编码,代码中修改编码即可),不再详述,欢迎讨论。
程序下载链接: 实验5 总线悬挂两个DS18B20之实物实验【趣味检测器件运行】
启发思考
以上五个实验基本上可以掌握DS18B20的操作,这里共同讨论DS18B20的代码是如何一步一步完成的,在实际应用中DS18B20得程序还是不好调试的,很多人写了一堆代码,然后测试,结果显示的不是想要的结果就不知道如何处理了。这里作者把自己思考过程分享出来,也是以点概面讲述如何“一点一滴”写代码。【以下是个人理解,仅供参考】
【步骤1】书写延时函数。因为DS18B20是单总线协议,对时序要求严格,而DS18B20的操作中需要部分延时,例如:
发现这里有几种延时,首先有15us延时、60us延时、480us延时,这里复位信号至少480us,所以为了安全,复位信号维持1.5倍的480us即720us,所以至少需要15us延时、720us延时,以15us延时为例,作者的延时函数为:
/******************************************************************
- 函数名称:delay15usForDs18b20
- 功能描述:延时15微秒
- 函数属性:内部函数
- 参数说明:无
- 返回说明:无
- 注 :1.此函数支持硬件电路、proteus软件仿真。
2.此函数由软件STC-ISP V6.88D自带的“软件延时计算器”产生。
当TEST等于1时候,适用于晶振11.0592MHZ 、8051指令集为STC-Y1硬件电路和Proteus8.9软件仿真
【备注】STC-Y1指令集包括:
STC89Cxx/STC89LExx
STC90Cxx/STC90LExx
当TEST等于2时候,适用于晶振11.0592MHZ 、8051指令集为STC-Y3硬件电路
【备注】STC-Y3指令集包括:
STC12Cxx/STC12LExx
STC11Fxx/STC11Lxx/STC10Fxx/STC10Lxx
STC15F104E/STC15L104E(A版)
STC15F204EA/STC15L204EA(A版)
3.单总线对时序要求较高,当移植至其他CPU时,需要适当修改。
******************************************************************/
void delay15usForDs18b20(void) //11.0592MZH
{
#if (TEST == 1) //适用于晶振11.0592MHZ 、8051指令集为STC-Y1硬件电路和Proteus8.9软件仿真
unsigned char i;
i = 4;
while (--i);
#elif (TEST == 2) //适用于晶振11.0592MHZ 、8051指令集为STC-Y3硬件电路
unsigned char i;
_nop_();_nop_();
i = 38;
while (--i);
#endif
}
我这里用了预编码#if,是因为作者常用的单片机包括STC89C52RC和STC12C5A60S2,而STC12C5A60S2反应较快,所以它的延时和STC89C52RC不一样,如果用户平常只用一种单片机,那么就没有必要写这么复杂。然后介绍作者的延时程序是如何来的?如下图是STC-ISP下载软件,
所以作者的延时程序是从这里复制粘贴来的,DS18B20对时序要求较高,所以虽然我们使用的是不精准延时函数,但是应该尽量无限接近我们需要的结果,这里利用官方软件自带生成的代码得到结果,这里我通过逻辑分析仪测试(有兴趣买一个,对代码调试有帮助),延时还是很靠谱的,这时成功的第一步,相应的完成延时函数delay720usForDs18b20()。
【步骤2】书写复位函数。这里作者最初的复位函数是这样的(具体过程请参考datasheet,不再详述):
char DS18B20_Reset(void)
{
char ExistenceFlag ;
DS18B20_IO_Bit = 1 ;
_nop_() ; _nop_() ;
DS18B20_IO_Bit = 0 ;
delay720usForDs18b20();//复位脉冲最少480us,这里采用1.5倍时间,即720us延时
DS18B20_IO_Bit = 1 ;
/*以上是单片机发送“复位”脉冲*/
//等待15us至60us,如果DS18B20存在,DS18B20会把数据线拉低,发送一个60us至240us的“应答脉冲”
delay15usForDs18b20();delay15usForDs18b20();
delay15usForDs18b20();delay15usForDs18b20();
if(!DS18B20_IO_Bit)
{//如果主机(单片机)检测到数据线被DS18B20拉低,表示DS18B20复位成功
ExistenceFlag = DS18B20_EXISTENCE;
while(!DS18B20_IO_Bit) ;
//delay720usForDs18b20();
}
else
{//否则DS18B20连接异常(不存在或者已损坏)
ExistenceFlag = DS18B20_NOT_EXISTENCE;
}
return ExistenceFlag ;
}
【步骤3】验证复位函数成功与否。写一个测试程序,就是对DS18B20进行复位,看看返回值是否正常,这里的正常有两层含义:第一,正常连接DS18B20时,复位函数回复的值应该是DS18B20_EXISTENCE(在DS18B20.h中,表示DS18B20连接正常,值为1);第二,故意设置异常情况——拔掉DS18B20,这时候复位函数返回值应该是DS18B20_NOT_EXISTENCE(在DS18B20.h中,表示DS18B20连接异常(比如器件损坏或未连接),值为0),只有两个情况都得到验证,才表示复位函数书写成功。
这里,注意while(!DS18B20_IO_Bit);这一句代码,意思是单片机等待DS18B20的“存在脉冲”(低电平)发送完,这里实际上在一个复杂的工程中,是不安全的,例如有信号干扰导致数据线一直是低电平,那么一个复杂的工程中可能就“卡死”在这里了,不利于调试,不建议进行“卡死”等待,作者这里借用了一个辅助i:
/******************************************************************
- 函数名称:DS18B20_Reset
- 功能描述:DS18B20初始化序列操作
- 函数属性:内部函数
- 参数说明:无
- 返回说明:DS18B20_EXISTENCE,表示DS18B20复位成功,其产生“应答脉冲信号”
DS18B20_NOT_EXISTENCE,表示DS18B20复位不成功,其未产生“应答脉冲信号”,可能是连接异常或器件损坏
- 注 :1.初始化过程包括:复位和“应答脉冲”,主机(单片机)产生至少480us的低电平,
然后主机释放数据线,进入接收模式。当DS18B20检测到上升沿,它等待15us至60us
,然后发射一个60us至240us的“应答脉冲信号”。
2.DS18B20的所有通信,都是以初始化序列(包括单片机发出的复位脉冲和DS18B20的
“应答脉冲”)开始的。
3.初始化是正确操作DS18B20是重要一步,必须复位成功,只有这里返回值为DS18B20_EXISTENCE,
后续的操作才有意义,具体使用中可以通过检测返回值检测是否成功。
4.DS18B20对时间的要求较为严格,因此将此代码移植到其它CPU或场合时要注意延时函数。
5.经测试STC89C52RC @11.0592MHZ时i的值为2,STC12C5A60S2 @11.0592MHZ时i的值为7,这里
设置i的最大值为70,时间够长了。
******************************************************************/
char DS18B20_Reset(void)
{
char ExistenceFlag ;
unsigned long i=0;
DS18B20_IO_Bit = 1 ;
_nop_() ; _nop_() ;
DS18B20_IO_Bit = 0 ;
delay720usForDs18b20();//复位脉冲最少480us,这里采用1.5倍时间,即720us延时
DS18B20_IO_Bit = 1 ;
/*以上是单片机发送“复位”脉冲*/
//等待15us至60us,如果DS18B20存在,DS18B20会把数据线拉低,发送一个60us至240us的“应答脉冲”
delay15usForDs18b20();delay15usForDs18b20();
delay15usForDs18b20();delay15usForDs18b20();
if(!DS18B20_IO_Bit)
{//如果主机(单片机)检测到数据线被DS18B20拉低,表示DS18B20复位成功
ExistenceFlag = DS18B20_EXISTENCE;
while((!DS18B20_IO_Bit) && (i++<70));
//delay720usForDs18b20();
}
else
{//否则DS18B20连接异常(不存在或者已损坏)
ExistenceFlag = DS18B20_NOT_EXISTENCE;
}
return ExistenceFlag ;
}
这里的i++<70 ,70不是随便写的,而是经过测试的,具体看程序注释。
【步骤4】书写“写函数”和“读函数”,这里作者的函数如下:
/******************************************************************
- 函数名称:DS18B20_WriteSlot
- 功能描述:向DS18B20写入一个位(DS18B20的写时间片)
- 函数属性:内部函数
- 参数说明:要写入的位数据
- 返回说明:无
- 注 :1.DS18B20的写时间片对时间的要求较为严格。因此将此代码移植到其它
CPU或场合时要注意延时函数
******************************************************************/
void DS18B20_WriteSlot(unsigned char DataValue)
{
DS18B20_IO_Bit = 0 ;
_nop_();_nop_();_nop_();_nop_();
DS18B20_IO_Bit = DataValue;
delay15usForDs18b20();delay15usForDs18b20() ;delay15usForDs18b20() ;delay15usForDs18b20() ;
DS18B20_IO_Bit = 1 ;
_nop_();_nop_();_nop_();//注意:两个写时间片最低间隔1us
}
/******************************************************************
- 函数名称:DS18B20_WriteByte
- 功能描述:向DS18B20写入一个字节数据
- 函数属性:内部函数
- 参数说明:dataCode,需要写入的数据
- 返回说明:无
- 注 :1.此函数调用写时间片来实现字节的写入。
******************************************************************/
void DS18B20_WriteByte(unsigned char DataValue)
{
unsigned char i ;
for(i=0 ; i<8 ; i++)
{
DS18B20_WriteSlot(DataValue &(1<<i) );
}
}
/******************************************************************
- 函数名称:DS18B20_ReadSlot
- 功能描述:从DS18B20读取一个位(DS18B20的读时间片)
- 函数属性:内部函数
- 参数说明:无
- 返回说明:读取到的“位”数据
- 注 :1.DS18B20的读时间片对时间的要求较为严格。因此将此代码移植到其它
CPU或场合时要注意延时函数
******************************************************************/
unsigned char DS18B20_ReadSlot(void)
{
unsigned char dataValue;
DS18B20_IO_Bit = 0 ;
_nop_();_nop_();_nop_();_nop_();
DS18B20_IO_Bit = 1 ;
_nop_();_nop_();
_nop_();_nop_();
dataValue = DS18B20_IO_Bit;
delay15usForDs18b20() ;
delay15usForDs18b20() ;
delay15usForDs18b20() ;
delay15usForDs18b20() ;
return dataValue;
}
/******************************************************************
- 函数名称:DS18B20_ReadByte
- 功能描述:从DS18B20读取一个字节
- 函数属性:内部函数
- 参数说明:无
- 返回说明:读取到的字节数据
- 注 :1.注意此函数算法中对DataValue必须赋值为0,因为涉及到位或运算,必须注意。
******************************************************************/
unsigned char DS18B20_ReadByte(void)
{
unsigned char i,DataValue=0;//注意这里DataValue一定要赋初值为0,否则可能出现意外情况
for(i=0;i<8;i++)
{
DataValue |= (DS18B20_ReadSlot()<<i);
}
return(DataValue);
}
【步骤5】验证“写函数”和“读函数”。完成以上程序后,不要着急让DS18B20完成读温度等复杂的操作,先验证“读函数”和“写函数”是否有效,验证方法是,通过读取DS18B20的64Bit激光ROM编码,验证其是否正确,通过阅读DS18B20可知,64Bit的ROM编码中,最低8Bit为DS18B20的family code,即只要是DS18B20器件,最低8Bit的ROM编码都是28h,具体参考:
作者利用这个特性,读取编码,看看是否是28h,如果是就表示“读函数”和“写函数”是成功的。
/******************************************************************
- 函数名称:DS18B20_Acquire64BitCode
- 功能描述:读取DS18B20的64Bit光刻ROM编号
- 函数属性:外部函数,供用户调用
- 参数说明:DS18B20_Information类型指针
- 返回说明:无
- 注 :1.测试单个DS18B20的64位光刻ROM编号,8bit的CRC+48bit的串行编号+8bit的DS18B20家族码:0x28(固定)
******************************************************************/
void DS18B20_Acquire64BitCode(DS18B20_Struct *temp)
{
unsigned char i ;
temp->ExistFlag = DS18B20_Reset() ;
if(temp->ExistFlag == DS18B20_EXISTENCE)
{
DS18B20_WriteByte(DS18B20_READ_ROM);
for(i=0 ; i<8 ; i++)
{
temp->serial_number[7-i] = DS18B20_ReadByte();//先读出的是低字节,所以反着存储
}
}
}
然后写一个测试程序,显示出读取到的ROM值,如果最低8Bit数据是28H,就表示“写函数”“读函数”代码正确,再继续编写下一步代码,否则反复调试。
【6】书写其他功能函数(比如获取温度函数),这里就不再详述。
总之,写程序要写一点测试一点,这样一步步测试,结果一定是预期效果,方法对其他芯片同样适用。
预留思考
在一个复杂工程中,DS18B20也许只是一部分代码,如果在单片机与DS18B20通信关键时候,发生一个中断,待中断执行完后,再返回来可能通信中已经丢失了重要信息,所以建议大家可以充分运用CRC校验,对通信得到的数据进行校对,如果发现信有异常信息,就表示信息是不对的,就舍弃掉而不加以运用(显示或其他操作),datasheet中也对此部分进行了介绍,这里作者没有这部分操作,这里作为一个预留思考,大家可以评论留下想法。