DS18B20是只需要一根通讯线的温度传感器。
首先先看看它的通信时序,一共就仨,初始化时序,写时序,读时序。
第一个,初始化时序,我们(MCU)先拉低总线最少480us,然后释放总线(拉高)。
DS18B20收到上升沿之后会在15~60us之后把总线拉低,拉低60~240us之后再释放总线。
这样一套流程下来我们就算是初始化DS18B20了。
那我们要做的就是先把MCU用于和DS18B20通信的那个引脚设置为输出模式。然后拉低480us(可以稍微久一点,但是我试过了,480us是没问题的)后,再拉高。
接着切换引脚为输入模式(看情况,ESP32不需要,可以在一开始就配置为输入输出模式),因为DS18B20会在收到上升沿的15~60us后拉低总线,因此我们可以延时15us之后再尝试读取引脚的电平(while循环读取,直到引脚确实被拉低了),看看是否被DS18B20拉低了,也可以不延时,直接尝试读取,但是要记得while循环时间不要太久导致我们的程序卡死,可能是DS18B20那边出了问题,我们就循环读取个60us即可,60us之内没读到低电平就代表我们初始化失败。
DS18B20会在拉低电平的60~240us之后再把电平拉高,因此我们也和上一步一样,延时60us之后尝试读取引脚是否为高电平,也可以直接读取,然后while循环的时间不要超过240us。
一切顺利之后表示我们初始化成功。
接着是写时序,我们要么写‘1’要么写‘0’,我们看上图,‘0’和‘1’时序的区别就在于后半段拉高电平的时机,‘0’基本上是一直都是低电平,最后时序快结束了才拉高,而‘1’是一开始是拉低电平,后面拉高电平,拉高电平的时机大概是拉低电平后的1~15us之间。
光看时序图还有些细节我们把握不住,我们再看看文档的文字描述。
第一点,无论是写‘1’或是写‘0’,写时序都至少持续60us。
第二点,两个写周期之前需要有至少1us的恢复时间。
第三点DS18B20在初始化写时序后的15~60us这个窗口对数据线进行采样。
这边注意第三点的初始化写时序,这边的初始化并不是我们上面那个初始化时序,而是写时序的开头那个下拉总线的步骤,那个就是写时序的初始化。
因此我们进行写时序的步骤就是先把MCU的引脚改为输出模式,接着把引脚改成低电平,延时15us,然后再把要写的bit“放”在引脚上,也就是‘0’就改成低电平,‘1’就改成高电平。然后为了保证每个写时序至少是60us,因此我们延时至少45us。接着释放总线(拉高引脚),延时1us后结束写时序(因为两个写周期之间需要有至少1us的恢复时间)
写时序执行8次我们也就向DS18B20发送了一个字节了,这边需要注意的是低位先行,包括后面读取字节的时候也是先读低位。
最后是读时序,和写时序也是大差不差,我们一样是结合着文字描述看看。
读时序的持续时间和写一样至少为60us。
首先我们把引脚拉低,1us之后再释放(拉高),接着DS18B20在收到下降沿的15us之内会把传输的数据放到总线上,因此我们拉低释放总线之后就要把引脚改为输入模式。然后我们在15us之内读取引脚的电平即可。读完之后我们延时50us保证每个写时序都至少是有60us。
读时序循环8次我们也就读取了一个字节了,记得是低位先行。
接着我们就是需要给DS18B20发送指令获取到温度值了。
如果我们要跟DS18B20通信,那么必须要按照顺序遵循下面三个步骤:发送初始化时序,发送ROM操作指令,发送功能指令。两个指令轮流发完之后必须回到第一步初始化。
接下来先看看ROM操作指令有哪些。
一共是五个,但是我们最常用的就那一个。
0xCC,忽略指令。这个忽略是什么意思呢?
DS18B20通信只需要一根通信线挂载在总线上,而总线上可以挂载多个DS18B20。那么我们MCU只出一根线,怎么从多个DS18B20中指定一个为我们所用呢?
每只 DS1820 都有一个唯一的长达 64 位的编码。最前面 8 位是单线系列编码。下面 48 位是一个唯一的序列号。最后 8 位是以上 56 位的 CRC 码。
因此我们可以通过这个唯一序列号去指定DS18B20。
而忽略指令的忽略就是我们不指定DS18B20,这有两个用途,第一个就是我们总线上只挂了一个DS18B20,那么我们不指定DS18B20会更省事。第二个就是有个指令我们需要挂载在总线上的所有DS18B20去执行。
我们能忽略,那么就能匹配,0x55,匹配指令。这条匹配指令发完之后需要后接上64位的序列编码(这一步算在三个步骤中的第二步)。后续的功能指令就只会对匹配上序列编码的DS18B20生效了。
我们要匹配,首先就要先知道这64位序列编码是什么,0x33,读取指令。发完之后我们就可以接收到8byte的序列编码了。这边要注意,此时总线上只能有一个DS18B20,否则会导致多个DS18B20一起发送序列,导致数据混乱。
剩下俩命令一个是搜索,另一个是报警搜索,我没看懂咋用,就不介绍了,咱这篇文章主要还是说说怎么从DS18B20中得到温度值。
接下来看看功能指令,一共是六条,但是我们常用的就俩。
0x44,温度转换指令,发送指令后会让上一步指定的DS18B20进行温度转换,转换的结果会放在寄存器中。寄存器具体放了啥数据稍后再说。
0xBE,读寄存器,发完指令后DS18B20会把寄存器的值发过来。
0x4E,写寄存器,只能写3个byte。
0x48,拷贝寄存器,将寄存器中的3个byte(就是上面写寄存器中写的3byte)拷贝到EEPROM中,等等看下面的图就清楚了。
0xB8,召回EEPROM,也就是把EEPROM的值再整回寄存器里。
0xB4,读取电源模式,发送指令后我们再读一个bit,若是寄生电源模式,DS18B20
将拉低总线,若是外部电源模式,DS18B20 将会把总线拉高。
功能指令不少,但我们常用的就头俩,温度转换和读寄存器,寄存器是下面这样的。
一共是九个byte,如果我们只要获取温度,那么只需要接收前两个byte即可,后面七个可以不接收。
然后我们写寄存器指令能写的就是byte2~byte4一共三个byte。
其中TH和TL是掉电不丢失的,分别可以存储一个数(整型的),用作报警。如果测得的温度高于 TH 或低于 TL,报警条件成立,DS18B20 内部就会置位一个报警标识。每进行一次测温就对这个标识进行一次更新;因此,如果报警条件不成立了,在下一次温度转换后报警标识将被移去。
那么这个报警标志怎么用呢?是配合着操作指令的报警搜索指令的,总线控制器通过发出报警搜索命令(0xEC)检测总线上所有的 DS18B20 报警标识,报警标识被置位的 DS18B20 将响应这条命令。
但我们报警用的不多,重要的是byte4配置寄存器。
Byte4我们只用到了bit5和bit6,用它来设置DS18B20的精度的,默认是12bit,也就是我们通过寄存器读出的温度,每个数都代表着是0.0625℃,比如说我寄存器读出的温度数是100,那么实际表示的温度是100 * 0.0625 = 6.25℃。
9、10、11、12位的温度分辨率,分别对应着0.5℃,0.25℃,0.125℃,0.0625℃。
至此我们就可以使用DS18B20来获取采集到的温度值了。
首先先发个初始化时序,接着发送0xCC表示忽略(仅适用于总线上就一个DS18B20,换成匹配(0x55)也行),然后发送0x44让DS18B20开始温度转换,中间等待一会,最大转换时间在上面那个图里有,我们一般等个最大转换时间的2/3就行。
等待结束后我们再开始读取,三个步骤重头来一次,发送初始化时序,发送0xCC,最后发送0xBE读取寄存器,如果只要温度,那么我们接收前两个字节即可。
读取到的数据需要进一步处理,我们得到的数据实际上是一个补码,如果是正数的话那么无所谓补码原码都一样,如果是负数的话(最高位为1),那么我们需要将补码转成原码。
最后将温度数据的原码乘上0.0625(根据分辨率而定)就是我们要得到的温度(℃)了。
完整代码在下面,大家也不用跟私信跟我要工程文件,下面贴出来的基本上就是全部了,自己创个工程,把下面代码往main.c里一贴,最多因为环境不同有些小问题改改就可以了。
大家也可以关注我同名公众号“折途想要敲代码”,回复关键词“DS18B20”领取相关的文档资料(没有代码)
完整代码(ESP32(ESP-IDF)&STM32&ESP8266(Arduino))
这边附上ESP32的代码(ESP-IDF)。
#include <stdio.h>
#include <unistd.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define DS18B20_GPIO GPIO_NUM_9
// 初始化DS18B20的通信引脚
void DS18B20_init(void) {
gpio_config_t gpio_initer = {
.intr_type = GPIO_INTR_DISABLE, // 不中断
.mode = GPIO_MODE_INPUT_OUTPUT_OD, // 输入输出模式,要开漏模式
.pin_bit_mask = ((uint32_t)1 << DS18B20_GPIO), // 指定GPIO
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 不加下拉电阻
.pull_up_en = GPIO_PULLUP_ENABLE // 加上上拉电阻
};
gpio_config(&gpio_initer);
}
// 封装一下操作DS18B20的引脚
void DS18B20_SetDQ(uint8_t val) {
gpio_set_level(DS18B20_GPIO, val);
}
uint8_t DS18B20_GetDQ(void) {
return gpio_get_level(DS18B20_GPIO);
}
// 写一个Byte
void DS18B20_WriteByte(uint8_t data) {
for (uint8_t i = 0; i < 8; ++i) {
DS18B20_SetDQ(0); // 先拉低15us
usleep(15);
DS18B20_SetDQ(data & 0x01); // 将bit放置总线60us
usleep(60);
DS18B20_SetDQ(1); // 释放总线
usleep(1); // 每个bit发完等待1us
data >>= 1;
}
}
// 读取一个Byte
uint8_t DS18B20_ReadByte(void) {
uint8_t res = 0x00;
for (uint8_t i = 0; i < 8; ++i) {
DS18B20_SetDQ(0); // 拉低总线1us后释放表示开始接收
usleep(1);
DS18B20_SetDQ(1);
usleep(10); // 等待10us后读取(15us之内)
if (DS18B20_GetDQ() == 1)
res |= (0x01 << i);
usleep(50);
}
return res;
}
// 初始化时序
bool DS18B20_InitSlot(void) {
DS18B20_SetDQ(0); // 拉低总线最少480us
usleep(480);
DS18B20_SetDQ(1); // 释放
usleep(15);
uint8_t waitTime = 0;
while ((DS18B20_GetDQ() != 0) && (waitTime <= 45)) { // 等待DS18B20拉低总线
waitTime++;
usleep(1);
}
if (waitTime > 45)
return false;
waitTime = 0;
while ((DS18B20_GetDQ() != 1) &&
(waitTime <= 240)) { // 等待DS18B20释放总线
waitTime++;
usleep(1);
}
if (waitTime > 240 || waitTime < 60)
return false;
return true;
}
// 打印64位序列编码(要改为获取的话修改一下即可)
void DS18B20_PrintCode(void) {
if (!DS18B20_InitSlot())
return;
uint8_t code[8];
DS18B20_WriteByte(0x33); // 读取指令
for (uint i = 0; i < 8; ++i) {
code[i] = DS18B20_ReadByte();
}
printf("code is : ");
for (uint i = 0; i < 8; ++i) {
printf("%02x ", code[i]);
}
printf("\r\n");
}
// 获取温度
float DS18B20_GetTemperture(void) {
uint16_t temp;
uint8_t Hdata, Ldata;
if (!DS18B20_InitSlot())
return 0;
DS18B20_WriteByte(0xCC); // 忽视指令
DS18B20_WriteByte(0x44); // 温度转换指令
DS18B20_SetDQ(1); // 拉高总线后等待500ms
vTaskDelay(500 / portTICK_PERIOD_MS);
if (!DS18B20_InitSlot())
return 0;
DS18B20_WriteByte(0xCC);
DS18B20_WriteByte(0xBE); // 读取寄存器指令
Ldata = DS18B20_ReadByte();
Hdata = DS18B20_ReadByte();
temp = Hdata;
temp = (temp << 8) | Ldata;
return temp * 0.0625; // 12位分辨率,每个数表示0.0625.
}
void app_main(void) {
DS18B20_init();
DS18B20_PrintCode();
float tempertrue = 0.0;
while (1) {
tempertrue = DS18B20_GetTemperture();
printf("tempertrue is %f\r\n", tempertrue);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
STM32的代码。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#define DS18B20_DQ GPIO_Pin_0
void Z_DS18B20_Output(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef itd;
itd.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出
itd.GPIO_Pin=DS18B20_DQ;
itd.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&itd);
}
void Z_DS18B20_Input(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef itd;
itd.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
itd.GPIO_Pin=DS18B20_DQ;
itd.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&itd);
}
void Z_DS18B20_SetDQ(BitAction val){
GPIO_WriteBit(GPIOA,DS18B20_DQ,val);
}
uint8_t Z_DS18B20_GetDQ(void){
return GPIO_ReadInputDataBit(GPIOA,DS18B20_DQ);
}
void Z_DS18B20_WriteByte(uint8_t data){
Z_DS18B20_Output();
for(uint8_t i=0;i<8;++i){
Z_DS18B20_SetDQ(0); //拉低总线15us
Delay_us(15);
Z_DS18B20_SetDQ(data&0x01); //将数据从低位开始放到总线上60us
Delay_us(60);
Z_DS18B20_SetDQ(1); //释放总线后延时1us
Delay_us(1);
data>>=1;
}
}
uint8_t Z_DS18B20_ReadBit(void){
uint8_t res=0;
Z_DS18B20_Output();
Z_DS18B20_SetDQ(0); //拉低总线1us后释放表示开始读取字节
Delay_us(1);
Z_DS18B20_SetDQ(1);
Z_DS18B20_Input();
Delay_us(10); //等待10us(在15us之内)后读取总线
res=Z_DS18B20_GetDQ();
Delay_us(50); //延时50us以保证每个时序至少有60us.
return res;
}
uint8_t DS18B20_Read_Byte(void){
uint8_t res=0x00;
for(uint8_t i=0;i<8;++i){
if(Z_DS18B20_ReadBit()==1) res|=(0x01<<i);
}
return res;
}
uint8_t Z_DS18B20_Init(void){
Z_DS18B20_Output();
Z_DS18B20_SetDQ(0); //复位
Delay_us(480);
Z_DS18B20_SetDQ(1);
Delay_us(15);
uint8_t waitTime=0;
Z_DS18B20_Input();
while(Z_DS18B20_GetDQ() && waitTime<=45){ //等待从机拉低总线
waitTime++;
Delay_us(1);
}
if(waitTime>45) return 1;
waitTime=0;
while(!Z_DS18B20_GetDQ() && waitTime<=240){ //等待从机松开总线
waitTime++;
Delay_us(1);
}
if(waitTime>240||waitTime<60) return 1;
return 0;
}
float Z_DS18B20_GetTemperture(void){
uint16_t temp;
uint8_t Hdata,Ldata;
Z_DS18B20_Init();
Z_DS18B20_WriteByte(0xCC);
Z_DS18B20_WriteByte(0x44); //温度转换指令
Z_DS18B20_Init();
Z_DS18B20_WriteByte(0xCC);
Z_DS18B20_WriteByte(0xBE); //读取寄存器指令
Ldata=DS18B20_Read_Byte();
Hdata=DS18B20_Read_Byte();
temp=Hdata;
temp=(temp<<8)|Ldata;
return temp*0.0625;
}
int main(void){
OLED_Init();
while(1){
OLED_ShowNum(1,1,Z_DS18B20_GetTemperture(),3);
OLED_ShowNum(1,5,(int)(Z_DS18B20_GetTemperture()*100)%100,2);
Delay_ms(300);
}
}
ESP8266(Arduino)
#define DS18B20_DQ_GPIO D2
void DS18B20_Input_Init(void){
pinMode(DS18B20_DQ_GPIO, INPUT_PULLUP);
}
void DS18B20_Output_Init(void){
pinMode(DS18B20_DQ_GPIO, OUTPUT);
}
void DS18B20_Reset(void)
{
DS18B20_Output_Init();
digitalWrite(DS18B20_DQ_GPIO,LOW);
delayMicroseconds(750);
digitalWrite(DS18B20_DQ_GPIO, HIGH);
delayMicroseconds(15);
}
uint8_t DS18B20_Check(void){
uint8_t waitTime=0;
DS18B20_Input_Init();
while(digitalRead(DS18B20_DQ_GPIO) && waitTime<200){
waitTime++;
delayMicroseconds(1);
}
if(waitTime>=200) return 1;
else waitTime=0;
while((0==digitalRead(DS18B20_DQ_GPIO)) && waitTime<240){
waitTime++;
delayMicroseconds(1);
}
if(waitTime>=240) return 1;
return 0;
}
uint8_t DS18B20_Read_Bit(void){
uint8_t res=0;
DS18B20_Output_Init();
digitalWrite(DS18B20_DQ_GPIO, LOW);
delayMicroseconds(2);
digitalWrite(DS18B20_DQ_GPIO, HIGH);
DS18B20_Input_Init();
delayMicroseconds(12);
res=digitalRead(DS18B20_DQ_GPIO);
delayMicroseconds(50);
return res;
}
uint8_t DS18B20_Read_Byte(void){
uint8_t res=0x00;
for(uint8_t i=0;i<8;++i){
if(DS18B20_Read_Bit()==1) res|=(0x01<<i);
}
return res;
}
void DS18B20_Write_Byte(uint8_t data){
DS18B20_Output_Init();
for(uint8_t i=0;i<8;++i){
if(data&0x01==1){
digitalWrite(DS18B20_DQ_GPIO,LOW);
delayMicroseconds(2);
digitalWrite(DS18B20_DQ_GPIO, HIGH);
delayMicroseconds(60);
}else{
digitalWrite(DS18B20_DQ_GPIO,LOW);
delayMicroseconds(60);
digitalWrite(DS18B20_DQ_GPIO, HIGH);
delayMicroseconds(2);
}
data>>=1;
}
}
void DS18B20_Start(void){
DS18B20_Reset();
DS18B20_Check();
DS18B20_Write_Byte(0xCC);
DS18B20_Write_Byte(0x44);
}
uint8_t DS18B20_Init(void){
DS18B20_Output_Init();
DS18B20_Reset();
return DS18B20_Check();
}
float DS18B20_GetTemperture(void){
uint16_t temp;
uint8_t a,b;
float value;
DS18B20_Start();
DS18B20_Reset();
DS18B20_Check();
DS18B20_Write_Byte(0xCC);// skip rom
DS18B20_Write_Byte(0xBE);// convert
a=DS18B20_Read_Byte(); // LSB
b=DS18B20_Read_Byte(); // MSB
temp=b;
temp=(temp<<8)+a;
if((temp&0xF800)==0xF800){
temp=(~temp)+1;
value=temp*(-0.0625);
}else{
value=temp*0.0625;
}
return value;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
Serial.println("Hello, STM32!");
pinMode(D1, OUTPUT);
digitalWrite(D1, HIGH);
DS18B20_Init();
}
int count=0;
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(D1, !digitalRead(D1));
delay(1000); // this speeds up the simulation
Serial.println(DS18B20_GetTemperture());
}
标签:温度传感器,DS18B20,void,总线,uint8,时序,模块,GPIO
From: https://blog.csdn.net/m0_63235356/article/details/140664469