前言
单片机资源数据包_2023(点击下载)
一、通过onewire(单总线)驱动DS18B20
1.关于onewire单总线通信
2.关于onewire驱动
二、读取DS18B20
1.ROM检查
2.DS18B20命令设置
3.DS18B20的温度数据格式
三、代码实现
本系列文章意在帮助各位正在准备蓝桥杯单片机组的同学,提供一个参考与指南,但是所有指南的前提是,默认你已经有单片机基础,本系列文章会提供本人对蓝桥杯单片机组编程方面的一些源码实现。当然,或许与你写代码的style完全不想同,那咱们也可以彼此相互交流各自的优缺点,或许你可能只是一个小白,就是想来抄一些可供借鉴的代码,这些都不重要,重要的是能给正在准备单片机组的同学提供到一些帮助。
此外代码可能有写的不完善的地方,但是每一个代码都是经过测试可行之后才发出来的,不敢保证十全十美,但是跑起来应该没问题。
此外,比赛时会提供一个单片机资源数据包,里面的内容比较多,这里只放一个下载链接,是2023年省赛是提供的单片机资源数据包(今年才2024,已经是最新的资源数据包了),正文中会直接使用资源数据包内的资料:
关于此资源数据包还得补充两句,他的3-底层驱动代码参考与往些年不同(但其实差异也很小,这里不再赘述),而且还是快比赛时才公布的这个资源数据包。也就是说,在写底层驱动代码时,我都是以2023年的资源数据包为基础写的,如果你使用的不是2023年的资源数据包的话,可能会跟我使用的底层驱动不完全相同,不过影响也不大。
下图为单片机资源数据包_2023内的所有资源,其中最关键的就是3-底层代码驱动以及SCH_硬件原理图V30了,当然共阳数码管段码表也十分常用,不过一般也就新写一个项目时,会把里面的东西CV到main函数里,然后这个文件就用不着了。
这里只简单科普一下单总线通信,不深入介绍,嫌啰嗦的可以直接PASS。
OneWire是一种数字通信协议,用于在主机设备和多个从机设备之间进行数据传输。它允许通过单个数据线同时进行数据传输和供电。也就是说,如果只用单个数据线只传输数据的话,驱动一个设备并完成数据传输最少需要三根线就可以完成:VCC,GND和单总线DQ,如果使用单个数据线在传输数据的同时也完成供电操作的话(寄生电源),则只需要两根线就可以驱动一个设备并完成数据传输:GND和单总线DQ。
OneWire协议的主要功能包括以下几点:
单线传输:OneWire只需要一根数据线来传输数据,同时使用电地作为信号的返回路径。这样可以减少连线的复杂性,提高数据传输的可靠性。
寄生电源:OneWire协议支持从主机设备提供电源给从机设备。对于一些简单的从机设备,它们可以通过从主机设备接收的电能进行运行,而无需单独外部电源。
独特的设备地址:每个OneWire设备都有一个唯一的64位地址,主机设备可以通过该地址来识别和定位每个从机设备。这样可以实现多个从机设备的并行连接和通信。
数据传输和控制:OneWire协议支持在主机设备和从机设备之间进行双向数据传输。主机设备可以向从机设备发送命令和参数,从机设备可以通过数据线将其响应发送回主机设备。
总之,OneWire是一种数字通信协议,具有单线传输、寄生电源、设备地址识别、数据传输和控制等功能,适用于各种应用领域。它使得设备之间的通信更加简单、快速和可靠,并且可以减少系统的复杂性和成本。
在蓝桥杯比赛当中,我们的单总线上只挂载了一个设备,也就是DS18B20,所以我们在使用时可以直接跳过ROM检测。蓝桥杯板子上的DS18B20接的有VCC,所以我们也可以不用使用DS18B20的寄生电源功能(单总线在传数据的同时也负责供电)。
如果以后有幸从事相关领域的工作的话,我们在使用单总线通信时,ROM检测就未必能够直接skip了,甚至为了节省资源,我们还会使用寄生电源功能。
单片机资源数据包_2023里面提供了一些底层驱动,但仅包括基本的读写数据或者初始化和停止总线等。下面是单片机资源数据包_2023里面提供的onewire.c里面的代码,前边也已经提到,单片机资源数据包_2023里面提供的底层代码跟往些年的底层代码不太一样,这里不再比较2023年给的底层代码和往些年底层代码的差异,而是直接使用2023年的底层代码完成相关的任务。
/* # 单总线代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
//
void Delay_OneWire(unsigned int t)
{
unsigned char i;
while(t--){
for(i=0;i<12;i++);
}
}
//
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;
}
这串代码是不能直接使用的,再加入这个.c文件之后,我们只要点编译就会报错。在正常使用之前,我们需要定义DQ引脚,以及引用头文件。此外,为了使得main函数可以正常使用onewire.c文件,
通过原理图可知,DS18B20的DQ引脚是连接在P14上的,因此我们要顶一DQ为P14引脚。
综上,我们想使用onewire.c文件,需要在代码前几行加上如下代码:
#include "onewire.h"//2023年比赛的时候没有给.h文件需要自己新建
#includesbit DQ=P1^4;
onewire.h文件里只有一个空壳,代码如下:
#ifndef _ONWIRE_H_
#define _ONWIRE_H_#endif
做到这里,我们只完成了第一步——让我们点击编译时,不会报错。当然,此时点击编译会有许多警告,这些警告是说已经定义的函数未被调用,目前可以忽视(因为我们代码才只写了一半了)
DS18B20是一个温度传感器,挂载在onewire(单总线)上。
既然叫总线,那就说明一根总线就可以挂载多个支持onewire的设备,比如一根总线上可以挂载两个以上的DS18B20温度传感器,那我们启动某个特定的温度传感器,并与之通信之前,我们首先要“验证身份”,确定这个DS18B20就是我们要读取的那个温度传感器,然后才能发送控制指令。
这里的“验证身份”就是所谓的ROM检测。每个DS18B20都有一个64位的ROM,因此我们在启动总线之后,第一件事就是进行ROM检查。下面是常用的ROM检查命令:
Read ROM [33h]
这个命令允许总线控制器读到 DS1820 的 8 位系列编码、 唯一的序列号和 8 位 CRC 码。 只有在总线上存在单只 DS1820 的时候才能使用这个命令。 如果总上有不止一个从机, 当所有从机试图同时传送信号时就会发生数据冲突(漏极开路连在一起开成相与的效果)。
Match ROM [55h]
匹配 ROM 命令,后跟 64 位 ROM 序列,让总线控制器在多点总线上定位一只特定的 DS1820。只有和 64 位 ROM 序列完全匹配的 DS1820 才能响应随后的存储器操作命令。 所有和 64 位 ROM序列不匹配的从机都将等待复位脉冲。这条命令在总线上有单个或多个器件时都可以使用。
Skip ROM [CCh]
这条命令允许总线控制器不用提供 64 位 ROM 编码就使用存储器操作命令, 在单点总线情况下
右以节省时间。 如果总线上不止一个从机, 在 Skip ROM 命令之后跟着发一条读命令, 由于多
个从机同时传送信号,总线上就会发生数据冲突(漏极开路下拉效果相当于相与)。
当然,我们的蓝桥杯板子上的单总线只挂载了一个设备(DS18B20),因此我们可以直接跳过ROM检测,也就是说初始化总线之后直接发送0xCC跳过ROM检查,我们也能正常操作我们的DS18B20。
那么,我们读取DS18B20数据的函数,头两行代码肯定是:
init_ds18b20();//初始化ds18b20,底层驱动里面已经给出,可以直接调用
Write_DS18B20(0xCC);//跳过ROM检查
在进行ROM检测之后,我们就可以对DS18B20进行控制了,下表格列举了DS18B20的一些命令设置:
命令 | 说明 | 协 议 |
单线总线发出协议后 | 备 注 |
温度转换命令 | ||||
Convert T | 开始温度转换 | 44h | <读温度忙状态> | 1 |
存储器命令 | ||||
Read Scratchpad | 读取暂存器和 CRC 字节 | BEh | <读数据直到 9 字节> | |
Write Schratchpad |
把字节写入暂存器的地址 2 和 3(TH 和 TL 温度报警触发) | 4Eh | <写两个的字节到地址 2和 3> | |
Copy Scratchpad | 把暂存器内容拷贝到非易失性存储器中(仅指地址2 和 3) | 48h | <读拷贝状态> | 2 |
Recall E2 | 把非易失性存储器中的值召回暂存器(温度报警触 发) |
B8h | <读温度忙状态> | |
Read Power Supply |
标识 DS1820 的供电模式 | B4h | <读供电状态> |
这么多的命令,我们以后如果从事相关方面的工作的话,以后可能都会用到。当然对于我们蓝桥杯比赛的话,我们只需要用到其中两个,一个是Convert T开始温度转化命令(0x44),另一个是Read Scratchpad读取暂存器命令(0xBE)。
发送开始温度转化命令之后,DS18B20会将温度转化之后的数据存储到暂存器内。此外不得不提的一点是,DS18B20温度转化需要一定的时间,可能是
刚才已经说,发送开始温度转化命令之后,DS18B20会将温度转化之后的数据存储到暂存器内。这个暂存器是两个八位的数据总共十六位,其中高八位我们给它取名为TH,低八位我们取名为TL比如暂存器数据是:0x07D0,那么也就是说TH=0x07,TL=0xD0。TH和TL都是八位的数据。这里先放一个数据的表格,再详细解释温度数据
图片里的MS其实就是上文提到的TH,LS就是TL。
TEMPERATURE (°C) | DIGITAL OUTPUT (BINARY) |
DIGITAL OUTPUT (HEX) |
+125 | 0000 0111 1101 0000 | 07D0h |
+85* | 0000 0101 0101 0000 | 0550h |
+25.0625 | 0000 0001 1001 0001 | 0191h |
+10.125 | 0000 0000 1010 0010 | 00A2h |
+0.5 | 0000 0000 0000 1000 | 0008h |
0 | 0000 0000 0000 0000 | 0000h |
-0.5 | 1111 1111 1111 1000 | FFF8h |
-10.125 | 1111 1111 0101 1110 | FF5Eh |
-25.0625 | 1111 1110 0110 1111 | FE6Fh |
-55 | 1111 1100 1001 0000 | FC90h |
表格中+85°带了一个星号,再默认状态下(刚上电,没有有效数据时)DS18B20存储的温度数据时85°,这里了解即可。如果你写的代码有问题的话,有可能读到的数据就是固定的85。
TH的高四位只能是全0或者全1,是符号位,全0时表示正温度,全1时表示负温度,这样说不太恰当,这涉及到二进制表示负数的问题了,当然这里通过看TH的高四位来判断温度的正负值是没问题的,只是说负数再使用时需要转化为正数。
TH的第四位和TL的高四位表示温度的整数位,如第一行数据,TH的第四位和TL的高四位是0x7D,化为10进制的话就是125,也就是125°
TL的低四位是小数位,因为精确到了小数点后四位,所以可以理解为每一位是2的负4次方,也就是0.0625,如第五行数据的TL的低四位是0x8,化成10进制的话还是8,8*0.0625=0.5,也就是说这个数据的小数值是0.5。
对于负数转化为正数,我们常用的办法是:“取反后再+1”;
下面是一个具体的例子,假设我们要将二进制数 11111110(-2 的二进制表示)变为正数:
将绝对值转换为正数的二进制:00000010
对二进制数按位取反:11111101
将取反后的二进制数加1:11111110(此时得到的是正数 2)
再蓝桥杯比赛中,我们很少会用到负数,因为蓝桥杯比赛是在4月份,甚至连小数都很少用到,所以我在处理时会直接忽略符号位和小数位,只取中间四位,也就是TH的低四位和TL的高四位作为函数的返回值。
现在完成以下功能:
1.数码管前两位显示当前读取到的温度值(可以用收捏一下DS18B20,让他的温度提高,这样显示的数据也就提高了,应该捏一下就能提高到20+度)
onewire.c
/* # 单总线代码片段说明
1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include "onewire.h"
#include
sbit DQ=P1^4;
//
void Delay_OneWire(unsigned int t)
{
unsigned char i;
while(t--){
for(i=0;i<12;i++);
}
}
//
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;
}
//上边的代码都是在资源数据包里,比赛时会给
//下边这个函数是自己写的
unsigned int read_18b20()
{
unsigned int T=0;//定义温度
unsigned char low=0;//用于接受温度的低八位
unsigned char high=0;//用于接受温度的高八位
init_ds18b20();//初始化DS18B20
Write_DS18B20(0xCC);//跳过ROM检测
Write_DS18B20(0x44);//发送开始温度转换的命令
Delay_OneWire(200);//温度转化需要时间,这里直接延时一下。。注意应避免连续读取DS18B20
init_ds18b20();//重新初始化DS18B20
Write_DS18B20(0xCC);//跳过ROM检查
Write_DS18B20(0xBE);//发送读取温度数据的指令
low=Read_DS18B20();//接收低八位
high=Read_DS18B20();//接收高八位
T=high;
T&=0x0F;//第八位的高四位置0,也就是不考虑符号位
T<<=8;
T|=low;
T>>=4;//舍去低八位的低四位,也就是不考虑小数位
return T;
}
onewire.h
#ifndef _ONWIRE_H_
#define _ONWIRE_H_
unsigned int read_18b20();
#endif
main.c
#include
#include
#include "onewire.h"
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xFF
};//共阳极断码表,不用记,考试时会提供,直接CV过来即可
//关于数码管的两个函数,因为需要从数组内读取待显示的数据,还要进行位选,
//所以这两个函数只起到开关锁存器的功能,没有更新数据(会在其他地方实现数据更新和获取)
#define NIXIE_CHECK() P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON() P2|=0xE0;P2&=0xFF;P2&=0x1F;
void Timer0_Init(void); //1毫秒@11.0592MHz
void Delay100ms(void); //@11.0592MHz
unsigned char location=0;
unsigned char Nixie_num[]={10,10,10,10,10,10,10,10};
unsigned int Temp=0;
void main()
{
read_18b20();//上电先初始化一次DS18B20,方便后续读取(因为温度转化需要时间,相当于是把第一次温度转化的时机提前了)
Timer0_Init();//定时器0初始化
EA=1;//开总中断
while(1)
{
Temp=read_18b20();//读ds18b20。。切勿连续读取,比如连续两行代码都是read_18b20()
Nixie_num[6]=Temp/10%10;//显示十位
Nixie_num[7]=Temp/1%10;//显示个位
Delay100ms();
}
}
void Timer0_Isr(void) interrupt 1
{
P0=0x01<
后续会带大家逐步深入的分享蓝桥杯比赛的代码