2024.1.29更新
2024.3.2
基本概念
所用外设:
DS18B20温度传感器(OneWire协议)
前置概念
一、OneWire(单总线)协议
•单总线(1-Wire BUS)是由Dallas公司开发的一种通用数据总线
•一根通信线:DQ
•异步、半双工
•单总线只需要一根通信线即可实现数据的双向传输,当采用寄生供电时,还可以省去设备的VDD线路,此时,供电加通信只需要DQ和GND两根线
二、单总线电路规范
•设备的DQ均要配置成开漏输出模式
•DQ添加一个上拉电阻,阻值一般为4.7KΩ左右
•若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路

三、单总线的时序结构

DS18B20介绍
•DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点
•测温范围:-55°C 到 +125°C
•通信接口:1-Wire(单总线)
•其它特征:可形成总线结构、内置温度报警功能、可寄生供电
一、DS18B20温度存储格式

MS BYTE和LS BYTE两个字节中BIT的解释:
BIT16BIT11作为符号位,表示温度的正负BIT4存储温度的整数部分
BIT10
BIT3~BIT0存储温度的小数部分(精度),BIT0 ‘1’对应 “0.0625”
二、DS18B20时序

三、程序编写
首先写第一个函数,用于初始化,其中添加Askbit,在LCD1602上显示一个数值,观察初始化函数是否有效,即从机是否响应。
首先编写好单总线的初始化程序,如果代码正确,根据单总线的通信协议,从机会发送一个应答信号(拉低总线)。
进行应答测试,通过LCD1602观察应答位,检测代码是否有效
```bash
#include <REGX52.H>
sbit OneWire_DQ=P3^7;//通信口
unsigned char OneWire_Init(void) { unsigned char i,Askbit; OneWire_DQ=1; OneWire_DQ=0; i = 227;while (—i);//500um OneWire_DQ=1; i = 29;while (—i);//70um Askbit = OneWire_DQ; return Askbit; }
## 一、DS18B20温度读取
将程序下载到单片机上,运行如下为main.c文件
```bash#include <REGX52.H>#include "LCD1602.h"#include "DS18B20.h"#include "OneWire.h"
void main(){ LCD_Init(); LCD_ShowString(1,1,"Temperture:"); DS18B20_Start();
while(1) { unsigned int temp=DS18B20_Read()*10000; if(temp>0) { LCD_ShowString(2,1,"+"); } else if(temp<0) { LCD_ShowString(2,1,"-"); } LCD_ShowNum(2,2,temp/10000,4); LCD_ShowNum(2,6,temp%10000,4); }}结果异常:LCD1602第二行显示不正确数据/显示-000.0625
分析:
main.c中存在语法错误
OneWire.c DS18B20.c中的函数时序定义有误
对main.c进行更改
#include <REGX52.H>#include "Delay.h"#include "LCD1602.h"#include "DS18B20.h"
float T;void main(){ DS18B20_Convert(); Delay(1000); LCD_Init(); LCD_ShowString(1,1,"Temperature:"); while(1) { DS18B20_Convert(); T=DS18B20_Read(); 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);//强制类型转换 }}OneWire.c 错误分析:
#include <REGX52.H>
sbit OneWire_DQ=P3^7;//通信口
void OneWire_Init(void){ unsigned char i,AskBit; OneWire_DQ=1; OneWire_DQ=0; i = 227;while (--i);//500um OneWire_DQ=1;//释放总线 i = 29;while (--i);//70um AskBit=OneWire_DQ;//主机判断从机是否发送应答 i = 227;while (--i);//500um,要根据时序图构造代码}
void OneWire_SendBit(unsigned char Bit)//发送一位{ unsigned char i; OneWire_DQ=1; i = 2;while (--i);//10um OneWire_DQ=Bit; i = 22;while (--i);//54um}
unsigned char OneWire_ReadBit(void)//接收一位{ unsigned char i,Bit; OneWire_DQ=0; i = 2;while (--i);//10um OneWire_DQ=1;//释放总线 i = 2;while (--i);//10um Bit=OneWire_DQ; i = 22;while (--i);//54um return Bit;}
void OneWire_WriteByte(int Byte){ unsigned int i; for(i=0;i<8;i++) { OneWire_SendBit(Byte&(0x01<<i));//低位在前 }}
unsigned char OneWire_ReadByte(){ unsigned int i; unsigned int Byte; for(i=0;i<8;i++) { Byte=OneWire_ReadBit()|(0x01<<i);//低位在前 } return Byte;}对OneWire.c进行更改
#include <REGX52.H>
sbit OneWire_DQ=P3^7;//通信口
unsigned char OneWire_Init(void){ unsigned char i,AskBit; OneWire_DQ=1; OneWire_DQ=0; i = 247;while (--i);//500us OneWire_DQ=1;//释放总线 i = 29;while (--i);//70us AskBit=OneWire_DQ;//主机判断从机是否发送应答 i = 247;while (--i);//500um,要根据时序图构造代码 return AskBit;}
void OneWire_SendBit(unsigned char Bit)//发送一位{ unsigned char i; OneWire_DQ=0;//错误,应为0 i = 2;while (--i);//10us OneWire_DQ=Bit; i = 20;while (--i);//50us OneWire_DQ=1;//遗漏}
unsigned char OneWire_ReadBit(void)//接收一位{ unsigned char i,Bit; OneWire_DQ=0; i = 1;while (--i);//8us OneWire_DQ=1;//释放总线 i = 1;while (--i);//8us Bit=OneWire_DQ; i = 20;while (--i);//50us return Bit;}
void OneWire_WriteByte(unsigned char Byte){ unsigned char i; for(i=0;i<8;i++) { OneWire_SendBit(Byte&(0x01<<i));//低位在前 }}
unsigned char OneWire_ReadByte(void){ unsigned char i; unsigned char Byte=0x00; for(i=0;i<8;i++) { if(OneWire_ReadBit()){Byte|=(0x01<<i);}//低位在前 } return Byte;}结论:OneWire_SendBit函数对总线的电平处理不正确
void OneWire_SendBit(unsigned char Bit)//发送一位
{
unsigned char i;
OneWire_DQ=1;//此处错误,应为0
i = 2;while (–i);//10us
OneWire_DQ=Bit;
i = 20;while (–i);//50us
OneWire_DQ=1;//遗漏
}
二、DS18B20温度报警器
#include <REGX52.H>#include "Delay.h"#include "LCD1602.h"#include "DS18B20.h"#include "AT24C02.h"#include "I2C.h"#include "Key.h"#include "Timer0.h"
//mark//在编写单片机程序时,要考虑到运行的高效性,定义变量的数据类型,优先选择较低内存占用的类型float T;float Tshow;char Thigh,Tlow;unsigned char KeyNum;
void main(){ Thigh=AT24C02_ReadByte(0);//每次开机,读取存储器中的数据 Tlow=AT24C02_ReadByte(1);
DS18B20_Convert();//温度装载,防止T第一次读取到的是默认值20,消除多余现象 Delay(1000);
LCD_Init(); Timer0_Init(); LCD_ShowString(1,1,"T:");
while(1) { DS18B20_Convert();//温度装载 T=DS18B20_Read(); if(T<0) { LCD_ShowChar(1,3,'-'); //T=-T,这里T=-T的话,进行温度预置判断有问题,所以再定义一个变量Tshow Tshow=-T; } else { LCD_ShowChar(1,3,'+'); Tshow=T; } LCD_ShowNum(1,4,Tshow,3); LCD_ShowChar(1,7,'.'); LCD_ShowNum(1,8,(unsigned char)(Tshow*100)%100,2);
//温度阈值控制 KeyNum=Key(); if(KeyNum) { if(KeyNum实验现象1:加入定时器扫描按键后,按下按键不影响温度的读取和显示,但是LCD1602上显示的温度数值会出现闪烁的状态
分析:每隔20ms进入中断程序,对按键进行扫描检测,但是,单总线上对数据的发送和接受,部分过程时间在几十us,远远小于20ms,中断程序打断了温度的正常读取
解决方案:每个OneWire的读写操作加入定时器的关闭动作,这样不会对OneWire的读写操作产生影响
如下:
#include <REGX52.H>
sbit OneWire_DQ=P3^7;//通信口
unsigned char OneWire_Init(void){ unsigned char i,AskBit; EA=0; OneWire_DQ=1; OneWire_DQ=0; i = 247;while (--i);//500us OneWire_DQ=1;//释放总线 i = 29;while (--i);//70us AskBit=OneWire_DQ;//主机判断从机是否发送应答 i = 247;while (--i);//500um,要根据时序图构造代码 EA=1; return AskBit;}
void OneWire_SendBit(unsigned char Bit)//发送一位{ unsigned char i; EA=0; OneWire_DQ=0;//错误,应为0 i = 2;while (--i);//10us OneWire_DQ=Bit; i = 20;while (--i);//50us OneWire_DQ=1;//遗漏 EA=1;}
unsigned char OneWire_ReadBit(void)//接收一位{ unsigned char i,Bit; EA=0; OneWire_DQ=0; i = 1;while (--i);//8us OneWire_DQ=1;//释放总线 i = 1;while (--i);//8us Bit=OneWire_DQ; i = 20;while (--i);//50us EA=1; return Bit;}
void OneWire_WriteByte(unsigned char Byte){ unsigned char i; EA=0; for(i=0;i<8;i++) { OneWire_SendBit(Byte&(0x01<<i));//低位在前 } EA=1;}
unsigned char OneWire_ReadByte(void){ unsigned char i; unsigned char Byte=0x00; EA=0; for(i=0;i<8;i++) { if(OneWire_ReadBit()){Byte|=(0x01<<i);}//低位在前 } EA=1; return Byte;}缺点:对定时器有较大影响,需要区分任务的优先级,这里我们只有对温度进行处理的任务,一但存在多个任务,比如这里同时在数码管上显示一个时钟,使用单总线的局限性会非常大
IIC通信方式一般比单总线广泛地多
思考:IIC和单总线地区别
蓝桥杯(STC15)拓展
OneWire底层
/* # 单总线代码片段说明 1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。 2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题 中对单片机时钟频率的要求,进行代码调试和修改。*/
//#include <onewire.h>sbit DQ= P1^4;void Delay_OneWire(unsigned int t){ t *= 12; while(t--);}
//void Write_DS18B20(unsigned char dat){ unsigned char i; for(i=0;i<8;i++) { DQ = 0; DQ = dat&0x01; Delay_OneWire(5); DQ = 1; dat >>= 1; } Delay_OneWire(5);}
//unsigned char Read_DS18B20(void){ unsigned char i; unsigned char dat;
for(i=0;i<8;i++) { DQ = 0; dat >>= 1; DQ = 1; if(DQ) { dat |= 0x80; } Delay_OneWire(5); } return dat;}
//bit init_ds18b20(void){ bit initflag = 0;
DQ = 1; Delay_OneWire(12); DQ = 0; Delay_OneWire(80); DQ = 1; Delay_OneWire(10); initflag = DQ; Delay_OneWire(5);
return initflag;}
/*基于底层编写*/float ds18b20_read(void){ unsigned char low,high;//高八位,低八位,DS18B20温度数据是十六位二进制 init_ds18b20();//ds18b20初始化时序 Write_DS18B20(0xcc);//ds18b20跳过rom指令 Write_DS18B20(0x44);//ds18b20开始温度转换
init_ds18b20();//ds18b20初始化时序 Write_DS18B20(0xcc);//ds18b20跳过rom指令 Write_DS18B20(0xbe);//ds18b20开始温度读取 low=Read_DS18B20(); high=Read_DS18B20(); return((high << 8 )| low) /16.0;}主模块(DS18B20模拟题)
//头文件引用#include <Key.h>#include <Nixie.h>#include <LED.h>#include <Timer0.h>#include <Init.h>#include <onewire.h>#include <iic.h>
unsigned char Nixie_Buf[8]={10,10,10,10,10,10,10,10};//数码管显示数据unsigned char Nixie_Pos;//数码管数据数组位下标unsigned char Nixie_Point[8]={0,0,0,0,0,0,0,0};//数码管每一段的“点”数据unsigned char Nixie_Disp_Mode;
unsigned char Key;//键值unsigned char Key_Up,Key_Down,Key_Val,Key_Old;//按键扫描变量
unsigned char ucLED[8]={0,0,0,0,0,0,0,0};//LED显示数据
unsigned int Nixie_Timer;//数码管定时更新unsigned int Key_Timer;//按键定时扫描
float Tempture=0;//实时温度unsigned char Tempture_Disp=25;//温度暂存设置参数unsigned char Tempture_Control=25;//温度阈值参数
float DAC_Output=3.25;//DAC输出电压bit DAC_Output_MODE;//DAC输出模式
/*按键处理函数*/void Key_Proc(void){ if(Key_Timer) return;//Key_Timer=0时执行下面的语句 Key_Timer=1; //按键扫描部分// Key_Val=Key_Read();//读取键值 Key_Down=Key_Val & (Key_Old ^ Key_Val);//读取上升沿键值 Key_Up=~Key_Val & (Key_Old ^ Key_Val);//读取下降沿键值 Key_Old=Key_Val;//保存键值(旧键值)
switch(Key_Down) { case 4: if(Nixie_Disp_Mode
