目录
1. 单总线驱动DS18B20
1.1 硬件结构
1.2 时序分析
1.2.1 初始化DS18B20
1.2.2 写数据指令
1.2.3 读数据指令
1.3 固件实现
1.3.1 设计流程图
1.3.2 接口实现
1.3.3 使用接口实现读取数据
1.4 测试
1.5 阻塞模式下运行时间
2 非阻塞模式操作DS18B20
2.1 背景
2.2 非阻塞模式实现原理
2.3 固件实现
2.4 函数性能测试
2.4.1 INIT_DQ步骤时间
2.4.2 触发温度转换命令时间
2.4.3 读温度数据命令时间
2.4.4 读数据指令
2.5 总结
3 详解DS18B20内部结构
3.1 DS18B20 概述
3.2 DS18B20 特性
3.3 DS18B20内部结构
3.4 温度测量
3.4.1 获取温度值
3.4.2 C语言实现温度值转换
DS18B20-PAR 1-Wire寄生供电数字温度计 | 亚德诺(ADI)半导体 (analog.com)
DS18B20的DQ和MCU的PB1引脚连接
首先看一下整个读写过程的数据流波形(1-wire 协议)
1) 初始化波形
2)配置指令波形
3)读取数据波形
分析:
Mater: 1->0, 持续时间 480us < time < 960 us, 然后IO配置为输入引进,master开始监测DQ电平
DS18B20: 在IO配置为输入后15us ~ 60 us,当DQ=0时,Initial成功。
使用逻辑分析仪捕捉波形:
1) MCU触发DS18B20在线信号,master波形:1----->0,该电平时间要求 480us < T < 960us, 本程序选择500us 左右时间 。
2) MCU IO切换为输入模式,此时主设备等待DS18B20发送在线信号,其时间范围 15us < T < 60us,本例选择时间为 34us 左右。
3) DS18B20响应波形:0 ------>1 , DS18B20发送响应在线信号。持续时间范围 60us < T < 240us,本例选择时间为124us左右。
4)DS18B20响应在线信号后,释放总线,此时IO电平:0-------->1, 该过程持续时间 T > 480 us, 本例选择时间为568us左右。
分析:
Master 首先发送一个写触发信号,Master IO: 1---->0, 该信号持续时间 T >= 15us, 然后DS18B20 IO开始采样master发送的信号。
1) Master发送0,则在master IO从1---->0后( 15us), 继续保持最大时间T< 45us
2) Master发送0,则在master IO从1---->0后(15us),IO值从0----->1,并保持此值,DS18B20采样到数据后,会自动释放该总线。
波形分析:
发送数据 0xcc,写数据顺序,高位在前,低位在后,其时序如下
时序分析
1)写 0 时序
写数据0波形,首先Master 从1---->0, 触发写响应,在低电平保持时间56us 左右。
低电平保持时间:
2)写1时序
写数据1波形,首先Master 从1---->0, 触发写响应,在低电平持续15us后,IO电平0----->1, 并保持60us 左右。由通过波形可以看出,触发信号:1----->0, 其中低电平保持时间11us左右
高电平保持时间:
分析:
读数据: Master电平1------>0, 该电平持续 T < 15us, 然后释放总线,等待DS18B20发送数据。DS18B20在接收到读触发信号后发送数据电平。 Master 在释放总线等待15us以后开始采样数据。采样数据如果为高电平,则该数据位为1;采样数据位低电平,则该数据位0。
波形分析:
接收数据0x01。 master首先发送接收数据触发信号1------->0, 然后释放总线,DS18B20向Master发送数据信号。
时序分析:
1) 读0时序
触发信号( 1------->0)从1------->0,持续时间T 13us左右,数据0时间持续时间T = 32 us左右
2)读1时序
触发信号( 1------->0)从1------->0,持续时间T 3us左右,数据0时间持续时间T = 42 us左右
通过上节分析,对操作ds18b20的时序完全了解了。下面根据时序图编写代码,实验平台基于STM32F407IGTx。
如下是一个最简单的流程图,实际软件实现的过程中,软件检测DS18B20应做超时判断,当出现检测超时时,应该跳出在线检测流程。说明ds18b20不在线,此时写命令和读数据已经没有意义了。
初始化DS18B20, 检测其是否在线
static uint8_t ds18b20Check( void )
{
uint16_t tempCnt = 0;
uint8_t status;
// Set PIN mode output
ds18b20SetIOMode( OUTPUT );
// Master pin is high
DQ_SET_HIGH;
timeDelayUS(10);
// Master pin is low
DQ_SET_LOW;
// wait for 600 us
timeDelayUS(600);
// Set PIN mode input
ds18b20SetIOMode( INPUT );
while(1)
{
status = DQ_RAD_PIN;
if( status == 0)
{
tempCnt = 0;
return TRUE;
}
else
{
timeDelayUS(1);
tempCnt++;
if( tempCnt > 480 ) //afer DQ bus is timeout, initial ds18b20 fail
return FALSE;
}
}
}
2. 写命令接口
void ds18b20WriteByte( uint8_t byte)
{
// Set PIN mode output
ds18b20SetIOMode( OUTPUT );
// write data bit
for ( uint8_t k = 0; k < 8; k++ )
{
DQ_SET_LOW;
timeDelayUS(10);
if( byte & 0x01 )
{
DQ_SET_HIGH;
}
else
{
DQ_SET_LOW;
}
timeDelayUS(50);
DQ_SET_HIGH;
timeDelayUS(20);
byte = byte>>1;
}
}
3 读数据接口
首先实现读取一个bit接口
static uint8_t readBit( void )
{
uint8_t readCnt = 2;
uint8_t bitVal = 1;
DQ_SET_LOW;
timeDelayUS(3);
DQ_SET_HIGH;
timeDelayUS(5);
while(readCnt-- )
{
//read DQ value
if( DQ_RAD_PIN == 0)
{
bitVal = 0;
}
timeDelayUS(2);
}
timeDelayUS(30);
return bitVal;
}
读完一个bit, 然后将其拼接成一个byte
static uint8_t ds18b20ReadByte( void )
{
uint8_t byteVal = 0;
for ( uint8_t i = 0; i < 8; i++ )
{
byteVal >>= 1;
uint8_t bitVal = readBit();
if( bitVal > 0)
{
byteVal |= 0x80;
}
}
return byteVal;
}
static uint16_t tempValue;
bool ds18b20Process( void )
{
uint8_t temp1, temp2;
if (ds18b20Check() == FALSE)
{
return false;
}
timeDelayUS(580);
ds18b20WriteByte(0xcc);
ds18b20WriteByte(0x44);
if (ds18b20Check() == FALSE)
{
return 0;
}
timeDelayUS(580);
ds18b20WriteByte(0xcc);
ds18b20WriteByte(0xbe);
temp1 = ds18b20ReadByte();
temp2 = ds18b20ReadByte();
tempValue = ((temp2 << 8) | temp1);
return true;
}
使用Keil开发软件,配合j-link工具,测试代码运行情况
函数体如下
uint8_t ds18b20Process( void )
{
uint8_t temp1, temp2;
if (ds18b20Init() == FALSE)
{
return FALSE;
}
// wait for 600 us
timeDelayUS(580);
ds18b20WriteByte(0xcc);
ds18b20WriteByte(0x44); // start convert temperature
if (ds18b20Init() == FALSE)
{
return FALSE;
}
// wait for 600 us
timeDelayUS(580);
ds18b20WriteByte(0xcc);
ds18b20WriteByte(0xbe); // read temperature data register
temp1 = ds18b20ReadByte();
temp2 = ds18b20ReadByte();
temperatureValue = ((temp2 << 8) | temp1);
return TRUE;
}
运行时间测试,该函数体的消耗时间为 3.1ms
在实际项目中,要求一个task中的函数运行尽量的快。以FreeRTOS为例,假如给每个task分配1ms的时间周期,那么要求该task中的函数必须在1ms内完成所有操作,否则就是出现阻塞情况,导致其他任务无法运行,或者数据丢失。
以操作DS18B20为例,如果把整个读写流程放在一个函数里面,使其顺序执行,那么整个读写流程所消耗的时间远远大于1ms。采取阻塞模式读取数据肯定是不可取。必须采用非阻塞模式才能实现该需求。
所谓非阻塞模式,就是把整个流程分成若干个不同的任务小段,然后在每个任务段里,实现其相应的功能。对应操作DS18B20,其整个读写流程可以分成如下步骤:
然后,分时操在各个功能模块。具体采用的方式是有限状态机。实现原理如下:
本例程基于STM32F407IGX,使用Free RTOS作为管理系统。task切换时间为1ms。操作ds18b20 采用状态机实现方式,其核心代码如下:
uint8_t ds18b20NoBlockingProcess( void )
{
static uint16_t waitCnt = 0;
uint8_t temp1, temp2;
static uint8_t runState = 0;
switch( runState )
{
default:
case INIT_DQ:
if (ds18b20Init() == FALSE)
{
return FALSE;
}
runState = WAIT_READY;
break;
case WAIT_READY:
timeDelayUS(1);
runState = SKIDROM_CMD;
break;
case SKIDROM_CMD:
ds18b20WriteByte(0xcc);
ds18b20WriteByte(0x44); // begin to convert temperature data
waitCnt = 0;
runState = WAIT_CONVERT;
break;
case WAIT_CONVERT:
waitCnt++;
if( waitCnt > WAIT_CNT_CONVERT)
{
waitCnt = 0;
runState = RESET_CMD;
}
break;
case RESET_CMD:
if (ds18b20Init() == FALSE)
{
return FALSE;
}
runState = WAIT_DATA_READY;
break;
case WAIT_DATA_READY:
timeDelayUS(1);
runState = READ_CMD;
break;
case READ_CMD:
ds18b20WriteByte(0xcc);
ds18b20WriteByte(0xbe); // read temperature data register
runState = GET_VALUE;
break;
case GET_VALUE:
temp1 = ds18b20ReadByte();
temp2 = ds18b20ReadByte();
temperatureValue = ((temp2 << 8) | temp1);
runState = INIT_DQ;
return TRUE;
}
return FALSE;
}
实验采用的逻辑分析仪,分析单总线协议。并通过MCU的一个IO作为时间监测电平
初始化DS18B20步骤消耗时间为610us
等待总线释放步骤耗时为550us
该步骤需要写 0xcc(跳过rom匹配)和 0x44(开始温度转换)两个命令,其耗时时间为887us
该步骤分别写 0xcc(跳过rom匹配)和 0xbe(读数据寄存器)两个命令,其消耗时间为886us
该步骤实现从ds18b20中两次读取16bit数据,其消耗时间为560US
通过以上测试,可以得出数据表如下:
执行步骤 | 功能描述 | 消耗时间( us) |
---|---|---|
INIT_DQ | 初始化ds18b20,并监测在线信息 | 610 |
WAIT_READY | 等待DS18B20释放总线 | 550 |
SKIDROM_CMD | 触发温度转换 | 887 |
READ_CMD | 发送读数据命令 | 886 |
GET_VALUE | 读取寄存器值,获取温度值 | 560 |
由以上列表可以看出,单个状态机最大消耗时间为887us,小于task切换时间 1ms的要求,此设计能满足系统高性能的设计要求。
DS18B20 数字温度传感器提供 9-Bit 至 12-Bit(可配置)温度读数和一个用户可编程的非易失性且具有高温和低温触发报警的报警功能。 DS18B20 采用 1-Wire 通信,即仅采用一根数据线与微控制器进行通信。该传感器的温度检测范围为-55℃至+125℃,在范围-10℃至+85℃之间具有± 0.5℃的精度。此外,读取、写入和执行温度转换的电源可以从数据线本身获得,而不需要外部电源,当然使用外部电源供电也行。每个 DS18B20 都有一个独一无二的 64 位序列号,所以,在一根总线上可以连接多个DS18B20 设备。
独特的 1-Wire 接口只需要一个端口引脚用于通信。 多路采集能力使得分布式温度采集应用更加简单。 无需外围器件。 可以采用数据线供电,供电范围为 3.0V 至 5.5V。 温度可测量范围为: -55℃到+125℃(-67 至+125 )。 内部温度采集精度可以由用户自定义为 9-Bit 至 12-Bit。 12Bit 的温度采集精度转换时间最大为 750ms。 用户可自定义非易失性的温度报警设置。 报警搜索命令识别并寻址温度超出编程限值的设备(温度报警条件) 。 应用于温度控制、工业系统、民用产品、温度传感器或者任何温度检测系统中。
DS18B20的内部有一个64位 ROM,其存储着序列号(56bit)和CRC ( 8bit),且该序列号是全球唯一的。高速缓存器总共包含9个byte,其具体内容如下:
1) TEMPERATURE SENSOR: 温度数据字节,包括2个字节,byte0为温度高字节位,byte1为温度低字节位
2) HIGH TEMPERATURE TRIGGER, TH : 一个字节,高温报警寄存器
3) LOW TEMPERATURE TRIGGER, TL :一个字节,低温报警寄存器
4) CONFIGURATION REGISTER : 一个字节,温度转换寄存器(配置寄存器)。配置寄存器允许用户自定义温 度转换精度 ( 9 位, 10 位 , 11 位 , 12 位)
5) RESERVED: 3个字节,保留字节,没有意义
6) CRC: 其主要计算56 bit 序列号,64位ROM数据中:0~57为序列号, 58~63 为CRC值
图一 DS18B20 BLOCK DIAGRAM
图二 DS18B20 MEMORY MAP
DS18B20 的核心功能是直接温度—数字测量。其温度转换可由用户自定义为 9、 10、11、 12 位,精度分别为 0.5℃、 0.25℃、 0.125℃、 0.0625℃分辨率,默认配置为12位的转换精度。
若要测量温度,主设备必须向 DS18B20 发送温度转换命令[44h]才能开始温度转换。温度转换后,转换的温度值将会保存在高速缓存器的温度寄存器中。通过命令[BEh]读取温度数据,数据通过 1-Wire 总线传输,传输顺序为低位到高位依次传输。温度数据中包含“符号”(S)位,表示温度的正负。DS18B20 的温度输出数据是在摄氏度下校准的;温度以一个 16 位标志扩展二进制补码的形式存储在温度寄存器中。
详细使用说明参考文章: 高性能非阻塞模式操作DS18B20 (qq.com) 和 单总线驱动DS18B20 (qq.com)
温度的正负极性:若 S=0,则为正数;若 S=1,则为负数。
分辨率:
1) 12位bit转换精度: 温度寄存器中所有位都将包含有效数据。
2) 11 位bit转换精度: 则 bit 0(最低位)为未定义的。
3) 10 位bit转换精度: 则 bit 0 和 bit 1 为未定义的。
4) 9 位bit转换精度: 则 bit 0、 bit 1 和 bit 2 为未定义的。
图三 Temperature/Data Relationships
根据图三 Temperature/Data Relationships可得,可通过符号位S判断温度的正负值。我们以默认12bit温度精度作为数据有效位来实现C语言算法。
typedef struct{
float temperatureVal; //温度数据
bool sign; //符号位,1: 负数, 0: 正数
}ds18b20Struc,
void GetTemperature( ds18b20Struc *pDs18b20Strc)
{
uint8_t tempH, tempL;
uint16_t tempValue;
tempL = ds18b20ReadByte();
tempH = ds18b20ReadByte();
tempValue = ((tempH << 8) | tempL);
pDs18b20Strc->sign = (tempValue & 0x8000) > 0 ? 1: 0;
if( !pDs18b20Strc->sign )
{
tempValue = ~tempValue; // 取补码
tempValue += 1; // 得到原码
}
pDs18b20Strc->temperatureVal = tempValue * 0.625;
}