DS18B20 单总线多器件的ROM 搜索, ALARM 检测, CRC 校验 源码实现, 基于 STM32F103

DS18B20使用5V供电, 数据线总线 DQ 经4.7k 上拉电阻到 5V, 连接到  B12 端口,  多个器件共用一条数据总线, 

(以前以为 ROM 搜索是穷举搜索, 但是 搜索1位需要 读两次写1次, 至少 180 us, 64位穷举完人类早已经不使用电子计算机了)

延时部分使用了 RTOS V2 里的API,  感觉还是非常准的, 如果需要移植请修改延时部分的代码

如果需要移植到其他处理器, 修改 GPIO 部分。 还有如果处理器不支持 64位整数, 可以用两个32位 或者用 8字节数组替代, 这个容易改

本文章主要是代码实现不讲原理性的东西, 请参考文章末尾的链接


头文件

#ifndef __DS18B20_H__
#define __DS18B20_H__


#include <stdint.h>


#define DS18B20_NO_ERR                  0       
#define DS18B20_ERR_NO_PRESENCE        -1
#define DS18B20_ERR_NO_DEVICES         -2
#define DS18B20_ERR_CRC_VERIFY_FAILED  -3
#define DS18B20_ERR_WAIT_TIMEOUT       -4

#define DS18B20_SUCCESSED(x)  ((x) >= 0)


#pragma push
#pragma anon_unions
typedef struct _DS18B20_BomCode
{
    union{
        struct{
            uint64_t familyCode   :8;        //所属系列
            uint64_t serialNumber :48;       //序列号
            uint64_t crcCode      :8;        //CRC 校验码
        };
        uint64_t romCode;
    };
} DS18B20_RomCode_t;
#pragma pop



typedef enum _DS18B20_Resolution_t
{
    resolution_9Bit    = 0x1f,              //9位精度  转换时间 93.75 ms
    resolution_10Bit   = 0x3f,              //10位精度 转换时间 187.5 ms
    resolution_11Bit   = 0x5f,              //10位精度 转换时间 375 ms
    resolution_12Bit   = 0x7f,              //10位精度 转换时间 750 ms
}DS18B20_Resolution_t;



typedef struct _DS18B20_ConfigInfo
{
    int8_t alarmTL;                         //警报上限温度, 单位摄氏度
    int8_t alarmTH;                         //警报下限温度, 单位摄氏度
    uint8_t resolution;                     //转换精度,  DS18B20_Resolution_t 值之一
} DS18B20_ConfigInfo_t;


/*****************************************************************************\
    所有器件初始化

返回值: 等于0成功, 小于0 失败
\*****************************************************************************/
uint32_t DS18B20_Init(void);


/*****************************************************************************\
    搜索总线上连接的器件的 ROM 

参数1: 保存 ROM 的 DS18B20_RomCode_t 数组缓冲区 
       如果函数执行成功, 这个参数保存搜索到的 ROM 
参数2: 指定 DS18B20_RomCode_t 数组缓冲区大小的指针,
       如果函数执行成功, 这个参数保存搜索到的 ROM 数量, 这是个输入输出参数

返回值: 等于0成功, 小于0失败
\*****************************************************************************/
int32_t DS18B20_SearchRom(DS18B20_RomCode_t* pBomList,uint32_t* numberOfRom);


/*****************************************************************************\
    搜索发出温度警报的器件的 ROM

参数1: 保存 ROM 的 DS18B20_RomCode_t 数组缓冲区 
       如果函数执行成功, 这个参数保存搜索到的 ROM 
参数2: 指定 DS18B20_RomCode_t 数组缓冲区大小的指针,
       如果函数执行成功, 这个参数保存搜索到的 ROM 数量, 这是个输入输出参数

返回值: 等于0成功, 小于0失败
\*****************************************************************************/
int32_t DS18B20_SearchAlarm(DS18B20_RomCode_t* pBomList,uint32_t* numberOfRom);

/*****************************************************************************\
    读指定器件的温度

参数1: 指定器件的 DS18B20_RomCode_t 指针
参数2: 指向温度值的指针

返回值: 等于0成功, 小于0 失败
\*****************************************************************************/
int32_t DS18B20_ReadTemperature(DS18B20_RomCode_t* pBomCode, float* pTemperature);


/*****************************************************************************\
    转换温度,  总线上所有器件都会转换
    
返回值: 等于0成功, 小于0 失败
\*****************************************************************************/
int32_t DS18B20_ConvertTemperatureAll(void);


/*****************************************************************************\
    获取指定 ROM 器件的配置 

参数1: 要获取的器件的 DS18B20_RomCode_t 指针
参数2: 用来获取 DS18B20_ConfigInfo_t 的变量指针

返回值: 等于0成功, 小于0 失败   
\*****************************************************************************/
int32_t DS18B20_GetConfig(const DS18B20_RomCode_t* pBom, DS18B20_ConfigInfo_t* configInfo);

/*****************************************************************************\
    获取指定 ROM 器件的配置 

参数1: 要获取的器件的 DS18B20_RomCode_t 指针
参数2: 用来设置 DS18B20_ConfigInfo_t 的变量指针

返回值: 等于0成功, 小于0 失败
\*****************************************************************************/
int32_t DS18B20_SetConfig(const DS18B20_RomCode_t* pBom, const DS18B20_ConfigInfo_t* configInfo);


#endif
  




c 文件

#include "stm32f10x.h"                  // Device header
#include "cmsis_os2.h"                  // ::CMSIS:RTOS2

#include "DS18B20.h"

#define DS18B20_PERIPH      RCC_APB2Periph_GPIOB         //DS18B20 GPIO 外围
#define DS18B20_PORT        GPIOB                        //DS18B20 GPIO 端口   
#define DS18B20_PIN         GPIO_Pin_12                  //DS28B20 GPIO 引脚号

#include <stdio.h>     //调试用

typedef enum _DS18B20_Command
{
    
/* ROM Command */
    cmdReadRom         = 0x33,
    cmdMatchRom        = 0x55,
    cmdSkipRom         = 0xcc,
    cmdSearchRom       = 0xf0,
    cmdSearchAlarm     = 0xec,
    
/* Memory Command */
    cmdWriteScratchpad = 0x4e,
    cmdReadScratchpad  = 0xbe,
    cmdCopyScratchpad  = 0x48,
    cmdConvertT        = 0x44,
    cmdRecall          = 0xb8,
    cmdReadPowerSupply = 0xb4,
    
}_DS18B20_Command_t;

static const uint8_t CRC8_TAB[]= {
	0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
	157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
	35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
	190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
	70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
	219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
	101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
	248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
	140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
	17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
	175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
	50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
	202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
	87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
	233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
	116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 };

/*****************************************************************************\
    CRC 校验
参数1:  数据的指针
参数2:  数据长度
    
返回值: CRC 校验值
\*****************************************************************************/ 
uint8_t DS18B20_CRC(const void *pBuf, uint32_t len)
{
    uint8_t crc = 0;
    const uint8_t *pBuf_ = pBuf; 
    while(len--){
        crc = CRC8_TAB[*pBuf_++ ^ crc];
    }
    return crc;
}

/*****************************************************************************\
    延时函数, 基于 RTOS V2 的 API 实现

参数1: 延时时间, 单位 uS  , 范围  0 ~ 58,000,000 uS (0 ~ 58 秒)
\*****************************************************************************/
static void _delay_us(uint32_t us)          
{
    us *= 72;   
    uint32_t start = osKernelGetSysTimerCount();
    while((osKernelGetSysTimerCount() - start) < us){}      
}


static void DS18B20_GPIO_Init()
{
    GPIO_InitTypeDef  GPIO_InitStructure =  {
        DS18B20_PIN,
        GPIO_Speed_50MHz,
        GPIO_Mode_Out_OD ,
    };
    
    RCC_APB2PeriphClockCmd(DS18B20_PERIPH,ENABLE);
    GPIO_Init(DS18B20_PORT, &GPIO_InitStructure);
    GPIO_SetBits(DS18B20_PORT,DS18B20_PIN);    
}


int32_t  DS18B20_Reset()
{
    GPIO_ResetBits(DS18B20_PORT,DS18B20_PIN);  
    _delay_us(480);
    GPIO_SetBits(DS18B20_PORT,DS18B20_PIN);  
    _delay_us(65);     
    uint32_t state = GPIO_ReadInputDataBit(DS18B20_PORT,DS18B20_PIN);
    _delay_us(415);
    return state ? DS18B20_ERR_NO_PRESENCE : DS18B20_NO_ERR;
}

inline static void _DS18B20_WriteBit(uint8_t bit) 
{
     GPIO_ResetBits(DS18B20_PORT,DS18B20_PIN); 
     _delay_us(1);    
     GPIO_WriteBit(DS18B20_PORT,DS18B20_PIN, (BitAction)bit);
     _delay_us(60);
     GPIO_SetBits(DS18B20_PORT,DS18B20_PIN);          
    _delay_us(5);   
}


inline static uint8_t _DS18B20_ReadBit()
{
    uint8_t bit = 0;
    
    GPIO_ResetBits(DS18B20_PORT, DS18B20_PIN);     
    _delay_us(1);
    GPIO_SetBits(DS18B20_PORT,DS18B20_PIN);     
    _delay_us(5);
    bit = GPIO_ReadInputDataBit(DS18B20_PORT,DS18B20_PIN);  
    _delay_us(60);    
    
    return bit != Bit_RESET;
}


static void _DS18B20_WriteByte(uint8_t byte_)
{
    for(uint8_t i =0;i<8;i++)
    {
        _DS18B20_WriteBit(byte_ & 0x01);
        byte_ >>= 1;
    }
}

static void _DS18B20_Write(const void* pBuf, uint8_t len)
{
    const char* rpBuf = pBuf;
    while(len--) {
        _DS18B20_WriteByte(*rpBuf++);
    }
    
}

static uint8_t _DS18B20_ReadByte()
{
    uint8_t byte_ = 0;

    for(uint8_t i=0; i<8; i++) {
        byte_ |=  _DS18B20_ReadBit() << i;
    }
    
    return byte_;
}


int32_t DS18B20_SetConfig(const DS18B20_RomCode_t* pBom, const DS18B20_ConfigInfo_t* configInfo)
{
    if(!DS18B20_SUCCESSED(DS18B20_Reset()))
        return DS18B20_ERR_NO_PRESENCE;
     _DS18B20_WriteByte(cmdMatchRom);
     _DS18B20_Write(pBom,sizeof(DS18B20_RomCode_t));
     _DS18B20_WriteByte(cmdWriteScratchpad);    
     _DS18B20_Write(configInfo,sizeof(DS18B20_ConfigInfo_t));
    
    return DS18B20_NO_ERR;
}


int32_t DS18B20_GetConfig(const DS18B20_RomCode_t* pBom, DS18B20_ConfigInfo_t* configInfo)
{
    if(!DS18B20_SUCCESSED(DS18B20_Reset()))
        return DS18B20_ERR_NO_PRESENCE;
     _DS18B20_WriteByte(cmdMatchRom);
     _DS18B20_Write(pBom,sizeof(DS18B20_RomCode_t));
     _DS18B20_WriteByte(cmdReadScratchpad);    

    uint8_t buf[9];
    for(int i=0;i<9;i++)
    {
        buf[i] = _DS18B20_ReadByte();
    }
    if(DS18B20_CRC(buf,8) != buf[8]){
        return DS18B20_ERR_CRC_VERIFY_FAILED;
    }
    configInfo->alarmTH = buf[2];
    configInfo->alarmTL = buf[3];
    configInfo->resolution = buf[4];
    return DS18B20_NO_ERR;
}

/*****************************************************************************\
     搜索算法实现

参数1: 保存 ROM 的 DS18B20_RomCode_t 数组缓冲区 
       如果函数执行成功, 这个参数保存搜索到的 ROM 
参数2: 指定 DS18B20_RomCode_t 数组缓冲区大小的指针,
       如果函数执行成功, 这个参数保存搜索到的 ROM 数量, 这是个输入输出参数
参数3: cmdSearchRom,  值 0xf0  搜索总线上连接的器件的 ROM 
       cmdSearchAlarm, 值0xec  搜索发出温度警报的器件的 ROM
      只允许为以上两个值, 因为这两个命令的搜索流程一样, 所以在一个函数实现

返回值: 等于0成功, 小于0失败
\*****************************************************************************/
static int32_t _DS18B20_Search(DS18B20_RomCode_t* pBomList,uint32_t* numberOfRom, uint8_t cmd)
{
    uint32_t count = 0;                 //表示搜索到的数量
    uint64_t contentiousMask = 0;       //有差异的位标记

    if(!numberOfRom || !*numberOfRom )
    {
        *numberOfRom = count;
        return  DS18B20_NO_ERR;
    }
    do{
        uint64_t romCode = 0;

        //复位
        if(!DS18B20_SUCCESSED(DS18B20_Reset()))
        {            
            *numberOfRom = count;
            return DS18B20_ERR_NO_PRESENCE;                  
        }
        //发送搜索 ROM 命令
        _DS18B20_WriteByte(cmd);              

        for(uint64_t bitMask =1; bitMask; bitMask <<=1)
        {
            //读两次位
            switch(_DS18B20_ReadBit() | (_DS18B20_ReadBit() << 1))
            {
                case 0x00:     //两次读的都是 0, 表示当前的位是存在差异的位
                    {
                        //判断是不是标记过
                        if(contentiousMask & bitMask)       
                        {
                            //标记过的, 说明不是第一次检测到这个差异位
                            
                            //判断当前的位是不是有差异的位中最高的那位
                            if(contentiousMask & ~((bitMask << 1) - 1))     
                            {
                                //如果不是最高的差异位, 仍然先写入0, 仅跳过这个差异的位
                               _DS18B20_WriteBit(0);               
                            }
                            else                             
                            {
                                //如果是最高的差异位, 因为第一次的时候写0, 所以这次写1, 并清除标记
                                romCode |= bitMask;
                                contentiousMask ^=  bitMask;                               
                                _DS18B20_WriteBit(1);               
                            }
                        }
                        else{                              
                            //没有标记过, 第一次读到这个差异位
                            //进行标记,  并先写入0
                            contentiousMask |=  bitMask;    
                            _DS18B20_WriteBit(0);           
                        }
                    }
                    break;
                case 0x01: //第一次读1, 第二次读0,  所以逻辑1
                    romCode |= bitMask;
                    _DS18B20_WriteBit(1);
                    break;
                case 0x02: //第一次读0, 第二次读1,  所以逻辑0  
                    _DS18B20_WriteBit(0);
                    break;
                default:   // 0x03 这其实是通信异常
                   *numberOfRom = count;
                    return DS18B20_NO_ERR;
            }
        }
        //CRC 校验
        if(DS18B20_CRC(&romCode,7) ==  ((uint8_t*)&romCode)[7]){
            //校验通过, 保存结果, 计数加1
            pBomList[count].romCode = romCode;   
            count++;
        }
        else{
            //校验失败, 错误 ROM 不进行处理
            
            //这行是为了count不加1 也不会影响循环, *numberOfRom 最后肯定是要被 count 赋值
            (*numberOfRom)--;
        }
    } while(count < *numberOfRom && contentiousMask);

    *numberOfRom = count;
    return DS18B20_NO_ERR;
}


int32_t DS18B20_SearchRom(DS18B20_RomCode_t* pBomList,uint32_t* numberOfRom)
{
    return _DS18B20_Search(pBomList,numberOfRom, cmdSearchRom); 
}


int32_t DS18B20_SearchAlarm(DS18B20_RomCode_t* pBomList,uint32_t* numberOfRom)
{
    return _DS18B20_Search(pBomList,numberOfRom, cmdSearchAlarm); 
}

int32_t DS18B20_ConvertTemperatureAll()
{
    if(!DS18B20_SUCCESSED(DS18B20_Reset()))
        return DS18B20_ERR_NO_PRESENCE;
     _DS18B20_WriteByte(cmdSkipRom);
     _DS18B20_WriteByte(cmdConvertT);

    //等待转换操作完成,  转换的时间和分辨率有关, 并取决于需要时间最长的那个器件
    //对于寄生供电模式不可以使用这种轮循等待, 根据转换精确度进行适当延时
    while(!_DS18B20_ReadByte()){  
        osDelay(30);  //RTOS V2 的延时, 挂起线程30ms, 期间不占用 CPU 时间
    }
    return DS18B20_NO_ERR;
}


int32_t DS18B20_ReadTemperature(DS18B20_RomCode_t* pBomCode, float* pTemperature)
{
    if(!DS18B20_SUCCESSED(DS18B20_Reset()))
        return DS18B20_ERR_NO_PRESENCE;
    _DS18B20_WriteByte(cmdMatchRom);
    _DS18B20_Write(pBomCode, sizeof(DS18B20_RomCode_t));
    _DS18B20_WriteByte(cmdReadScratchpad);

    uint8_t buf[9];
    for(int i=0;i<9;i++)
    {
        buf[i] = _DS18B20_ReadByte();
    }
    if(DS18B20_CRC(buf,8) != buf[8]){
        return DS18B20_ERR_CRC_VERIFY_FAILED;
    }

    *pTemperature = *((int16_t*)buf) / 16.F;
    
    return  DS18B20_NO_ERR;
   
}

uint32_t DS18B20_Init()
{
    DS18B20_GPIO_Init();
    return DS18B20_Reset();
}


简单测试代码

// 初始化器件
DS18B20_Init();

// 搜索器件  rom
DS18B20_RomCode_t romCode[4];
uint32_t count = 4;
if(!DS18B20_SUCCESSED(DS18B20_SearchRom(romCode,&count)))
{
    for(;;){}   //...
}

//配置器件
DS18B20_ConfigInfo_t configInfo = {100, -100, resolution_12Bit};
for(int i=0;i<count;i++)
{
    configInfo.alarmTH = 20 + i;
    configInfo.alarmTL = 33 + i*2;
    DS18B20_SetConfig(romCode+i, &configInfo);
}


while(1)
{
    printf("---------------------------------------------------------\n");

    //温度转换
    DS18B20_ConvertTemperatureAll();
    
    
    //获取所有器件的温度值, 打印结果
    printf("ROM List [%d]:\n",count);
    for(int i=0;i<count;i++)
    {
        float temperature = 0;
        if(DS18B20_SUCCESSED(DS18B20_ReadTemperature(romCode+i, &temperature)))
        {
            printf("\t[Family=%02x, SN=%012llx, CRC=%02x]   ",
                    romCode[i].familyCode,
                    romCode[i].serialNumber, 
                    romCode[i].crcCode); 

            printf("%f\n", temperature);
        }
    }

    //检查所有发出警报的器件, 打印结果
    DS18B20_RomCode_t alarmRomCode[4];
    uint32_t alarmCount = 4;  
    if(DS18B20_SUCCESSED( DS18B20_SearchAlarm(alarmRomCode,&alarmCount)))
    {
        if(alarmCount)
        {
            printf("Alarm [%d]\n",alarmCount);
            for(int i=0;i<alarmCount;i++)
            {
                printf("\t[Family=%02x, SN=%012llx, CRC=%02x]   ",
                        alarmRomCode[i].familyCode,
                        alarmRomCode[i].serialNumber, 
                        alarmRomCode[i].crcCode);
                
                float temperature = 0;
                if(DS18B20_SUCCESSED(DS18B20_ReadTemperature(romCode+i, &temperature)))
                {
                    printf("%f ", temperature);
                }
                if(DS18B20_SUCCESSED(DS18B20_GetConfig(alarmRomCode+i, &configInfo)))
                {
                    printf("@ (TH %d, TL %d)",configInfo.alarmTH, configInfo.alarmTL);
                }
                printf("\n");
            }
        }
    }
}


执行结果

连接了三个器件 , 打印了三个器件的 ROM 和 温度
并且SN为 041731b5c8ff 的这个器件正在发出警报, 它的报警上限是 33 , 下限是 20 单位摄氏度 , (这三个器件的警报参数都不同) 

---------------------------------------------------------
ROM List [3]:
	[Family=28, SN=041731b5c8ff, CRC=d2]   33.562500
	[Family=28, SN=031731ffe4ff, CRC=73]   33.562500
	[Family=28, SN=0317321981ff, CRC=14]   33.437500
Alarm [1]
	[Family=28, SN=041731b5c8ff, CRC=d2]   33.562500 @ (TH 33, TL 20)
---------------------------------------------------------
ROM List [3]:
	[Family=28, SN=041731b5c8ff, CRC=d2]   33.562500
	[Family=28, SN=031731ffe4ff, CRC=73]   33.625000
	[Family=28, SN=0317321981ff, CRC=14]   33.437500
Alarm [1]
	[Family=28, SN=041731b5c8ff, CRC=d2]   33.562500 @ (TH 33, TL 20)
---------------------------------------------------------






其他引用:

DS18B20 数据手册, 必备

DS18B20 数据手册  


可以去了解下搜索流程, 流程在手册里有讲

DS18B20 1-WIRE ROM搜索算法详解系列 


上个链接提到的MAXIM公司对单总线搜索的描述, 代码比较复杂, 没看明白

MAXIM - 1-Wire搜索算法


如果没看明白可以去学习 TA 整理的代码

CSDN Blog - DS18B20多ROM搜索并读取温度



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值