基于STM32_DS18B20单总线传感器驱动
前言
本文以一款典型的单总线传感器及其驱动——DS18B20为例,简单对1-Wire总线接口的传感器做个示例讲解,该项目基于硬件平台STM32F407,使用标准库本完成。
一、BS18B20?
DS18B20数字温度计提供9至12位(可配置)温度读数,指示设备的温度。信息通过1-Wire总线接口发送到/从DS18B20,因此只需要从中央微处理器连接到DS18B20的一根线(和接地)。读取、写入和执行温度转换的电源可以从数据线本身获得,而不需要外部电源。每个DS18B20都包含一个唯一的硅序列号,所以多个DS18B20可以存在于同一个1-Wire总线上。这允许在许多不同的地方放置温度传感器。此功能有用的应用包括暖通空调环境控制,感应建筑物,设备或机械内部的温度,以及过程监控和控制。
特点:
1.唯一的1线接口只需要一个端口引脚进行通信
2.多功能简化了分布式温度传感应用
3.不需要外部组件,可从数据线供电。
4.供电范围3.0V至5.5V 零待机电源
5.测量温度范围为-55°C至+125°C。相当于-67°F到+257°F ±0.5°C精度从-10°C到+85°C
6.温度计分辨率可编程从9到12位
二、原理
1.复位与检验
主机发送(TX)复位脉冲(低信号,至少480µs),然后主机释放线路并进入接收模式(RX),线总线通过5k上拉电阻拉到高状态。在检测到DQ引脚上的上升沿后,DS18B20等待15-60µs,然后发送存在脉冲(60-240µs的低电平信号),主机在该时段检测到DQ的低电平信号,表示DS18B20设备存在,否则设备不存在。参见驱动中:void DS18B20_Reset(void)和uint8_t DS18B20_Check(void)函数。
2.基本命令
DS18B20常见的命令及含义如表所示:
名称 | 命令码 | 功能 |
---|---|---|
Read ROM | 33h | 该命令允许总线主机读取DS18B20的8位族码、唯一的48位序列号和8位CRC。该命令只能在总线上有单个DS18B20时使用。如果总线上有多个从站,当所有从站试图同时传输时,就会发生数据冲突(open drain将产生有线AND结果)。 |
Match ROM | 55h | match ROM命令,后跟64位ROM序列,允许总线主机在多路总线上寻址特定的DS18B20。只有与64位ROM序列完全匹配的DS18B20才会响应以下内存功能命令。所有与64位ROM序列不匹配的从存储器将等待复位脉冲。该命令可用于总线上的单个或多个设备。 |
Skip ROM | CCh | 该命令允许总线主机在不提供64位ROM代码的情况下访问内存功能,从而在单丢总线系统中节省时间。如果总线上有一个以上的从站,并且在Skip ROM命令之后发出了Read命令,那么当多个从站同时传输时,总线上就会发生数据冲突(open drain下拉将产生一个有线and结果)。相当于跳过了识别码查验,直接读取温度 |
Search ROM | F0h | 当系统最初启动时,总线主机可能不知道1-Wire总线上的设备数量或它们的64位ROM代码。搜索ROM命令允许总线主人使用消除过程来识别总线上所有从设备的64位ROM代码。 |
Read Scratchpad | BEh | 该命令读取刮记本的内容。读取将从字节0开始,并将继续通过刮擦板,直到读取第九个(字节8,CRC)字节。如果不是所有的位置都要读取,主机可以在任何时候发出复位以终止读取。 |
Search ROM | 44h | 该命令开始温度转换。无需进一步收集数据。温度转换将被执行,然后DS18B20将保持空闲。如果总线主机按照该命令发出读时隙,只要DS18B20忙于进行温度转换,它就会在总线上输出0;当温度转换完成时,它将返回一个1。如果parasitepowered,总线主必须在发出该命令后立即启用一个大于tconv的强上拉。 |
基本操作过程参见官方手册给出的如下流程图
3.唯一ROM识别码
每个DS18B20包含一个64位长的唯一ROM代码。发送0x33命令后可读取改64位识别码,前8位是1-Wire族代码(DS18B20代码为28h)。接下来的48位是唯一的序列号。最后8位是前56位的CRC(CRC = X8 + X5 + X4 + 1),(参见图4)。当有多个DS18B20设备同时挂在1-Wire总线下时,可通过发送0x55匹配命令后发送识别码来匹配ROM识别码来确定控制或读取哪一个DS18B20设备数据。以前的方式是先准确无误得读取每个DS18B20设备唯一ROM识别码并提前写入代码中,如果某个传感器设备损坏,更换传感器设备的同时要更改代码或识别码配置文件。后来手册中给出一个Search ROM命令(暂时没试过)。
4.温度转换
DS18B20的核心功能是其直接数字温度传感器。DS18B20的分辨率是可配置的(9,10,11或12位),12位读数为出厂默认状态。这相当于0.5°C, 0.25°C, 0.125°C或0.0625°C的温度分辨率。在发出Convert T [44h]命令后,执行温度转换,热数据以16位扩展符号的二进制补码格式存储在刮板存储器中。一旦转换完成,可以通过发出Read Scratchpad [BEh]命令在1-Wire接口上检索温度信息。数据通过1-Wire总线传输,首先是LSB总线。温度寄存器的MSB包含“符号”(S)位,表示温度是正的还是负的。表2描述了输出数据与测量温度的确切关系。该表采用12位分辨率。转换方式参见驱动void DS18B20_GetTemp_Main(void)函数。
三、驱动代码
.h文件:
#ifndef __DS18B20_H
#define __DS18B20_H
#include "stdio.h"
#include "Config.h"
#include "SysTick.h"
/*******************************************
*DS18B20 devier
********************************************/
#define DS18B20_IS_READY (1u) //设备存在
#define DS18B20_NOT_READY (0u) //设备不存在
#define DS18B20_DQ_IN() {GPIOG->MODER&=~(3<<(9*2));GPIOG->MODER|=0<<9*2;} //PG9 输入模式
#define DS18B20_DQ_OUT() {GPIOG->MODER&=~(3<<(9*2));GPIOG->MODER|=1<<9*2;} //PG9 输出模式
#define DS18B20_DQ_PORT GPIOG
#define DS18B20_DQ_PIN GPIO_Pin_9
#define DS18B20_WAIT_TIMEOUT (uint8_t)240 //等待时间
#define DS18B20_DELAY (uint8_t)5 //延时时间
#define DS18B20_DQ_LOW GPIO_ResetBits(DS18B20_DQ_PORT,DS18B20_DQ_PIN) //为设置低电平
#define DS18B20_DQ_HIGH GPIO_SetBits(DS18B20_DQ_PORT,DS18B20_DQ_PIN) //设置为高电平
#define DS18B20_DQ_STATUS GPIO_ReadInputDataBit(DS18B20_DQ_PORT,DS18B20_DQ_PIN) //读取DQ状态
#define DS18B20_delay_us(a) SysCtlDelayus(a) //延时函数us
typedef enum
{
DS18B20_1 = 0,
DS18B20_2,
DS18B20_3,
DS18B20_4,
DS18B20_Num_Counter
}DS18B20_Num;
/*DS18B20_SerialNumber
------------------------------------------------------------------------------------
| 8-BIT CRC CODE | 48-BIT SERIAL NUMBER | 8-BIT FAMILY CODE(28h) |
------------------------------------------------------------------------------------
MSB LSB MSB LSB MSB LSB
*/
typedef struct
{
uint8_t DS18B20_IndexNumber; //设备编号
uint8_t DS18B20_SerialNumber[8]; //ROM唯一识别码
short DS18B20_Temperature; //温度数据
}DS18B20_ATTRIB_Type;
extern uint8_t DS18B20_Init(void);
extern void DS18B20_Reset(void);
extern short DS18B20_Get_Temperature(uint8_t Index_Num);
extern void DS18B20_GetTemp_Main(void);
#endif
.c文件:
#include "DS18B20_Dev.h"
//#define DS18B20_MORE_THAN_ONE
static DS18B20_ATTRIB_Type DS18B20_Temp[DS18B20_Num_Counter] =
{
{DS18B20_1,{0x08,0x22,0x70,0xB0,0x9C,0x87,0x28},0u},
{DS18B20_2,{0x00,0x00,0x00,0x00,0x00,0x00,0x00},0u},
{DS18B20_3,{0x00,0x00,0x00,0x00,0x00,0x00,0x00},0u},
{DS18B20_4,{0x00,0x00,0x00,0x00,0x00,0x00,0x00},0u},
};
static uint8_t DS18B20_Check(void);
static uint8_t DS18B20_Read_Bit(void);
static uint8_t DS18B20_Read_Byte(void);
static void DS18B20_Write_Byte(uint8_t aByte);
static uint8_t DS18B20_Get_RomID(uint8_t *ID_Buffer);
/************************************************************************************
*@fuction :DS18B20_Init
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
extern uint8_t DS18B20_Init(void)
{
DS18B20_Reset();
if(DS18B20_Check() == DS18B20_IS_READY)
{
#ifdef DS18B20_MORE_THAN_ONE
DS18B20_Get_RomID(DS18B20_Temp[0].DS18B20_SerialNumber);
#else
return DS18B20_IS_READY;
#endif
}
}
/************************************************************************************
*@fuction :DS18B20_Start
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
void DS18B20_Start(void)
{
DS18B20_Reset();
if(DS18B20_Check() == DS18B20_IS_READY)
{
DS18B20_Write_Byte(0xCC); //skip rom
DS18B20_Write_Byte(0x44); //convert
}
else
{
//error
}
}
/************************************************************************************
*@fuction :DS18B20_Reset
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
void DS18B20_Reset(void)
{
//DS18B20 复位时序:DQ输出模式 DQ = 0(750us), DQ = 0(20us.
DS18B20_DQ_OUT();
DS18B20_DQ_LOW;
DS18B20_delay_us(750);
DS18B20_DQ_HIGH;
DS18B20_delay_us(15);
}
/************************************************************************************
*@fuction :DS18B20_Check
*@brief :
*@param :--
*@return :1-device ok/0-device error
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
uint8_t DS18B20_Check(void)
{
uint8_t wait_time = 0;
uint8_t Ready_Dev = 0;
//DQ输入模式
DS18B20_DQ_IN();
//等待DQ脚被DS18B20拉低
while((DS18B20_DQ_STATUS) && (wait_time < DS18B20_WAIT_TIMEOUT))
{
wait_time++;
DS18B20_delay_us(1);
}
if(wait_time >= DS18B20_WAIT_TIMEOUT)
{
//如果等待时间超时,则退出等待
return (uint8_t)DS18B20_NOT_READY;
}
else
{
//等待DQ脚被DS18B20抬高
wait_time = 0;
while((!DS18B20_DQ_STATUS) && (wait_time < DS18B20_WAIT_TIMEOUT))
{
wait_time++;
DS18B20_delay_us(1);
}
if(wait_time >= DS18B20_WAIT_TIMEOUT)
{
//如果等待时间超时,则退出等待
return (uint8_t)DS18B20_NOT_READY;
}
else
{
//如果未超时,则说明设备存在
return (uint8_t)DS18B20_IS_READY;
}
}
}
/************************************************************************************
*@fuction :DS18B20_Write_Byte
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
static void DS18B20_Write_Byte(uint8_t aByte)
{
uint8_t i = 0;
uint8_t temp = 0;
//将DQ设置为输出模式
DS18B20_DQ_OUT();
for(i = 0;i < 8;i++)
{
temp = aByte&0x01;
aByte = aByte>>1;
if(temp)
{
//最低位为1
DS18B20_DQ_LOW;
DS18B20_delay_us(2);
DS18B20_DQ_HIGH;
DS18B20_delay_us(60);
}
else
{ //最低位为0
DS18B20_DQ_LOW;
DS18B20_delay_us(60);
DS18B20_DQ_HIGH;
DS18B20_delay_us(2);
}
}
}
/************************************************************************************
*@fuction :DS18B20_Read_Bit
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
static uint8_t DS18B20_Read_Bit(void) //read one bit
{
uint8_t Bit_Status;
DS18B20_DQ_OUT(); //SET PG9 OUTPUT
DS18B20_DQ_LOW;
DS18B20_delay_us(2);
DS18B20_DQ_HIGH;
DS18B20_DQ_IN(); //SET PG9 INPUT
DS18B20_delay_us(12);
if(DS18B20_DQ_STATUS)
{
Bit_Status = 1;
}
else
{
Bit_Status = 0;
}
DS18B20_delay_us(50);
return Bit_Status;
}
/************************************************************************************
*@fuction :DS18B20_Read_Byte
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
static uint8_t DS18B20_Read_Byte(void)
{
uint8_t i = 0 ,Bit_Status = 0,aByte = 0;
//DQ为输入模式
DS18B20_DQ_IN();
for (i = 0; i < 8; i++)
{
//低位先出
Bit_Status = DS18B20_Read_Bit();
aByte = (Bit_Status << 7) | (aByte >> 1);
}
return aByte;
}
/************************************************************************************
*@fuction :DS18B20_Read_Byte
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
static uint8_t DS18B20_Get_RomID(uint8_t *ID_Buffer)
{
//uint8_t RomID[8];
uint8_t i = 0;
//DS18B20_Start(); // ds1820 start convert
DS18B20_Reset();
DS18B20_Check();
DS18B20_Write_Byte(0x33); // skip rom
//SDA为输入模式
DS18B20_DQ_IN();
for (i = 0; i < 8; i++)
{
ID_Buffer[i] = DS18B20_Read_Byte();
}
//return aByte;
}
/************************************************************************************
*@fuction :DS18B20_GetTemp_Main
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
void DS18B20_GetTemp_Main(void)
{
uint8_t temp;
uint8_t TL,TH;
short Temperature;
DS18B20_Start(); // ds1820 start convert
DS18B20_Reset();
DS18B20_Check();
DS18B20_Write_Byte(0xCC); // skip rom
DS18B20_Write_Byte(0xBE); // read
TL = DS18B20_Read_Byte(); // LSB
TH = DS18B20_Read_Byte(); // MSB
if(TH > 7)
{
TH = ~TH;
TL = ~TL;
temp = 0; //温度为负
}
else
{
temp = 1; //温度为正
}
Temperature = TH; //获得高八位
Temperature <<= 8;
Temperature += TL; //获得低八位
Temperature = (double)Temperature * 0.625; //转换
if(temp)
{
DS18B20_Temp[0].DS18B20_Temperature = Temperature; //返回温度值
}
else
{
DS18B20_Temp[0].DS18B20_Temperature = -Temperature;
}
}
/************************************************************************************
*@fuction :DS18B20_Get_Temperature
*@brief :
*@param :--
*@return :void
*@author :_Awen
*@date :2022-12-04
************************************************************************************/
extern short DS18B20_Get_Temperature(uint8_t Index)
{
return DS18B20_Temp[0].DS18B20_Temperature;
}
四、注意事项
1.硬件电路中DQ脚外部会加上拉电阻,主机释放总线会被上拉电阻自动上拉,但为保险器件我们将主机释放中线写为主动上拉为高电平
2. .h文件中对延时函数DS18B20_delay_us(a)的定义,关于 SysCtlDelayus(a)实际是ARM汇编的一种延时函数的写法,参见另一篇关于延时函数的博客(汇编延时)https://blog.csdn.net/Yin_w/article/details/130036593?spm=1001.2014.3001.5501