这里只是简单的读取温度数据的教程,只针对MCU的DQ引脚仅连接一片传感器
(不过看懂了这个,操作多个传感器也不难了,只是最后传输字节数据时的字节改一下)
这是芯片的样子,一共三个有效的引脚,电源、地、数据
只有一条数据线,那么他就是异步半双工的数据传输模式
写这个芯片的代码,最烦的地方在于时间把握不好,只要延时搞明白,基本就没问题了
传感器内部结果
首先简单了解一下传感器内部有什么东西,方便后面理解
有64位的ROM放器件的唯一序列号(当一个引脚控制多个ds18b20时使用)
有几个寄存器,分别放:温度REG(两个寄存器),报警REG(一个寄存器),配置REG(一个寄存器)
另外的我就没看懂了
此外,DQ是上拉的,这样可以设置开漏输出(又能控制引脚电平又能读取引脚电平)
看上图,我们操作DQ引脚就是让DQ引脚与这个64位ROM通讯
上图,(只看这一个图不太容易看出来,联系上下文才行) , 写了我们要做的操作,初始化,写,读
读取温度的流程
首先要把基础的复位,读,写操作完成(延时在后面写)
gpio的初始化与延时
gpio
因为只有一根数据线,又要读又要写,所以用开漏输出比较方便(外部要有上拉电阻)
初始电平设置高电平—这条数据线没有数据的时候是高电平的
数据传输都是几us一次电平改变了,所以低速就可以
另外写一个宏用来操作IO方便点
#define SET1 GPIOB->BSRR |= 1<<5;
#define SET0 GPIOB->BSRR |= 1<<5<<16;
//#define READ GPIOB->IDR&0x0010;
#define READ HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5);
延时
延时就用简单的_nop和for循环【如果不是72M的时钟,要自己去算一下】实现一个delay_us(uint32_t count)微秒级延时
一个_nop()会延时 1/72M秒,一个for会占用多少指令周期我忘了,不会算
具体计算方法和仿真延时时间可以去看查看延时时间
void Delay_us(uint32_t Delay)
{
for(;Delay>0;Delay--)
{
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
__nop();__nop();__nop();__nop();__nop();
}
}
如果不用这个的话,也可以用TIM实现延时
不要改systick来产生us延时,我试了,延时没问题,但是会把看门狗干废
复位
这里的复位就算是初始化了
具体操作就是低电平480us,拉高电平,等上15-60us,传感器会返回一个持续60~240us的低电平,读到这个电平就行,然后延时
发送还是容易的,但读取这个电平,要在什么时候读需要考虑一下
画出图看一下电平可能在什么时间被发过来
发现在60~75us时最有可能,于是我们就在68us的时候读
/*初始化,其实是reset。
操作:
1.先置1持续2us,(释放信号线的状态,可以不要。目的是保证开始时总线不是0)
2.置0,480us以上,设置500us就行
3.置1(释放信号线)等待传感器发送来的信号
在15-60us以后,传感器发送一个60~240us的低电平,时间取15~15+60最稳妥,取68
4.然后等待300us让传感器释放总线
*/
//返回1为初始化失败,返回0成功
uint8_t Gx18b20_Init(void)
{
uint8_t ret = 0;
uint16_t x;
// SET1;
// Delay_us(2);
SET0;
Delay_us(490);
SET1;
Delay_us(68);
x=READ;
if(x)ret=1;
else ret=0;
//if(ret == 1)while(1);
Delay_us(300);
return ret;
}
写
看图上说的很迷,直接看我代码
/*
写操作(从低位到高位)
1.写时序开始(1到0)置0,3us(拉低后需要在15us内释放总线)
2.置1(释放总线)
3.如果写0,拉低总线62us(至少拉低60us)
如果写1,不动总线,62us
4.拉高总线2us(两次写操作之间至少1us)
5.重复1-5直到写完
6.释放总线(下面写的延时没什么用,只是看示波器的时候时间差一点好看)
*/
void Gx18b20_Write_Byte(uint8_t data)
{
for(int i=0;i<8;i++)
{
SET0;
Delay_us(3);
if((data&0x01)==0)
{
SET0;
}
else
{
SET1;
}
Delay_us(62);
data >>=1;
SET1;
Delay_us(2);
}
SET1;
Delay_us(2);
}
读
这里注意一下读时序开始时,传感器就几乎同同时发送信息了,要在读时序开始的15us内读取
另外读时序是再给出ROM指令以后才会有的。
/*
读数据_位
1.读时序开始置0,1us(保持至少1us)
2.置1
3.等待8us,读取数据(在读时序开始后15us内有效)
4.延时60us(读时序最少要60us,这里就有了1+8+60us)
*/
//返回读取的电平,在uint8的最低位
uint8_t Gx18b20_Read_Bit(void)
{
uint8_t data=0;
SET0;
Delay_us(1);
SET1;
Delay_us(8);
data = READ;
//data = data>>5;
Delay_us(62);
return data;
}
/*
读字节
循环读数据8次
*/
uint8_t Gx18b20_Read_Byte(void)
{
uint8_t data=0;
data |= Gx18b20_Read_Bit();
for(int i=0;i<7;i++)
{
data |= (Gx18b20_Read_Bit()<<(i+1));
}
Delay_us(10);
return data;
}
读温度
首先根据流程图,复位然后写几个命令
然后就可以读取数据了
BE读寄存器指令发送完后,传感器会发送来9字节数据,我们要温度只需要前两个字节,读完重置或者不管了就好
读到数据需要处理一下
这里看到第一个字节的第四位是小数,我这里是直接不要的,如果你们需要可以自己处理一下
只要正数接收让LS右移四位,MS左移四位,合到一起
另外这个是可以传负数的,125°~-55度的测量范围,是不是负数,要看MS高五位,是1就是负数,是0就是正数
/*
读取温度
1.复位等待脉冲;
2.跳过ROM指令,写0xCC
3.产生一次温度转换,写0x44。此时流程结束
4.复位等待脉冲
5.跳过ROM指令,写0xCC
6.发送读寄存器指令。写0xBE【如果使用寄生电源总线控制器必须在发出这条命令的 10us 内启动强上拉并最少保持 10ms】
7.读两个数据,第一个是TL,第二个是TH
8.高五位为0则温度正,为1则温度负,如果是负,后面都是反码,需要取反
*/
int8_t Gx18b20_Temp(void)
{
uint8_t tl,th,symbol = 0;
uint8_t integer;
int8_t temp;
Gx18b20_Init();
Gx18b20_Write_Byte(0xCC);
Gx18b20_Write_Byte(0x44);
//HAL_Delay(94); //等待转换完成,没转换完成读到的是上一次转换完成的值,第一次的话就是85度
Gx18b20_Init();
Gx18b20_Write_Byte(0xCC);
Gx18b20_Write_Byte(0xBE);
tl=Gx18b20_Read_Byte();
th=Gx18b20_Read_Byte();
if((th&0xF8)==0XF8)
{
tl = ~tl;
th = ~th;
symbol = 1;
}
integer = ((th&0x07)<<4)|(tl>>4);
temp = integer;
if(symbol)temp = -temp;
return temp;
}
设置转换精度
至于为什么要设置他,转换精度高的时候,转换时间长,我们只要整数的话,可以把小数位都去掉
默认转换是12BITS的,也就是转换时间需要750ms
而9bits只需要90多ms
/*
更改配置寄存器为9位精度
12位精度需要转换时间为750ms,而9位精度只需要93.75ms转换时间
配置寄存器00011111是9位,01111111是12位
流程:reset,忽略rom指令(write(0xcc)),写暂存器指令(write(0x4e)),写两个报警值,不用就写0(write(0);write(0)),写配置寄存器9位(write(0x1f))
*/
void Gx18b20_Config(void)
{
Gx18b20_Init();
Gx18b20_Write_Byte(0xCC);
Gx18b20_Write_Byte(0x4E);
Gx18b20_Write_Byte(0);
Gx18b20_Write_Byte(0);
Gx18b20_Write_Byte(0x1F);
}
处理接收到的数据
如果有短暂时间内温度变化很大,很可能是因为传感器检测出问题了,忽略掉就好
通过上一次的温度数据和这一次的温度数据比较,相差20度就扔掉
温度太高太低也扔掉
如果读到85度,那很可能是没转换完成,直接不要了,万一真有85度,那么显示84度或86度也是没什么问题的。
//查看数据是否有问题
//高于120°的删掉
//低于-40°的删掉
//两次测量值相差20°的删掉
//设置previous_temp时初始值为-127
//没有考虑长时间出问题后怎么恢复
//输入之前该函数输出的温度数据
int8_t Gx18b20_Temp_2(int8_t previous_temp)
{
int8_t temp;
temp = Gx18b20_Temp();
if(previous_temp != -127){
if(temp == 85)return previous_temp;
if(temp-previous_temp>20 || temp - previous_temp < -20)return previous_temp;
if(temp > 120)return 120;
if(temp < (-40))return -40;
}
if(temp == 85)return previous_temp;
return temp;
}