感谢硬汉哥提供的方案H7-TOOL同时采样10路DS18B20实现,带CRC校验,很实用
我们知道,DS18B20 是单总线时序通讯,采用严格的信号时序,以保证数据的完整性。因此如果采用操作系统,逻辑读取DS18B20为了避免时序被操作系统的任务调度,或者其他中断打乱,我们都会采用临界区的形式保护时序。
正常情况下,DS18B20通信的时间并不长,也就几十us。时间长的是18b20的转换过程(750ms)。
那么我们可以再和18B20通讯时关闭中断,通讯完毕立马开启中断,转换的过程可以不用管(时序要求再通讯部分)。这样就可以既不影响18b20数据的读取,一起不影响任务的调度。
但是这样的话,我们读取到的温度值就是上一时刻的转换完成的值了。
并且还需要使用各种DelayMs,DelayUs。在RTOS中非常的不方便,因此我们可以使用定时器加状态图的方式实现数据读取,状态图如下:
该种思想我们可以应用到FPGA中,FPGA操作时也是采用状态转移图实现。
时序
- 写操作
-
读操作
代码:
#include "bsp.h"
#include "param.h"
#define DS18B20_IRQ_HANDLE TIM16_IRQHandler
#define TIM_HARD TIM16
#define TIM_HARD_IRQn TIM16_IRQn
#define DQ_DIR_OUTPUT(ch) EIO_ConfigPort(ch, ES_GPIO_OUT)
#define DQ_DIR_INPUT(ch) EIO_ConfigPort(ch, ES_GPIO_IN)
#define DQ_0(ch) EIO_SetOutLevel(ch, 0)
#define DQ_1(ch) EIO_SetOutLevel(ch, 1)
/* 判断DQ输入是否为低 */
#define DQ_IS_LOW(ch) (EIO_GetInputLevel(ch) == 0)
/*
D0 PD14 PA15 PI0 - DIR PH8 温度1
D1 PD15 PA8 PH19 - DIR PG8 温度2
D2 PE6 PD0 PB7 - DIR PD9 温度3
D3 PE5 PD1 PH11 - DIR PG10 温度4
D4 PE4 PE7 PH12 - DIR PG12 温度5 --SWD调试占用
D5 PE2 PE8 PI5 - DIR PG7 温度6
D6 PE9 PD3 PA0 - DIR PD10 温度7 --SWD调试占用
D7 PE10 PI6 - DIR PI1 温度8 (有上拉)
D8 PE11 PD4 PI3 - DIR PG9 温度9 --SWD调试占用
D9 PE12 PD5 - DIR PI12 温度10 (有上拉)
*/
#define DS_CHAN_NUM 10 /* 最多10个通道 */
static float s_DS18B20_TempReg[DS_CHAN_NUM];
static uint16_t s_err[DS_CHAN_NUM] = {0};
static void DS18B20_StartTimerIRQ(void);
static void DS18B20_StopTimerIRQ(void);
/* 必须添加 volatile, 中断和主任务同时访问时,部分等待语句会被优化掉 */
static volatile uint8_t s_ch = 0;
static volatile uint16_t s_status = 0;
/*
*********************************************************************************************************
* 函 数 名: DS18B20_ReadTempReg
* 功能说明: 读温度寄存器的值(原始数据). 循环读,返回上次结果.
* 形 参: 无
* 返 回 值: 0表示失败,1表示OK
*********************************************************************************************************
*/
uint8_t DS18B20_ReadTemp(uint8_t _ch, float *_result)
{
uint8_t re = 0;
if (_ch >= DS_CHAN_NUM)
{
return re;
}
EIO_ConfigPort(_ch, ES_GPIO_OUT); /* DQ方向输出 */
s_err[_ch] = 1;
s_ch = _ch;
s_status = 0;
/* 定时周期10us,频率100KHz */
DS18B20_StartTimerIRQ();
while (s_status != 10)
{
//bsp_Idle();
}
DS18B20_StopTimerIRQ();
if (s_err[s_ch] == 1)
{
re = 0; /* 返回异常温度值 */
}
else
{
*_result = (float)s_DS18B20_TempReg[s_ch] / 16;
re = 1;
}
return re;
}
/*
*********************************************************************************************************
* 函 数 名: DS18B20_CaculCRC
* 功能说明: CRC校验
* 形 参: _buf: 数据缓冲区
* _len : 数据长度
* 返 回 值: 校验值
*********************************************************************************************************
*/
uint8_t DS18B20_CaculCRC(uint8_t *_buf, uint8_t _len)
{
uint8_t crc = 0, i, j;
for (i = 0; i < _len; i++)
{
crc = crc ^ _buf[i];
for (j = 0; j < 8; j++)
{
if (crc & 0x01)
{
crc = (crc >> 1) ^ 0x8C;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
/*
*********************************************************************************************************
* 函 数 名: DS18B20_IRQ_HANDLE
* 功能说明: 10us定时中断服务程序,读取DS18B20温度值
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void DS18B20_IRQ_HANDLE(void)
{
static volatile uint16_t s_time = 0;
static volatile uint16_t s_call_ret = 0;
static volatile uint16_t s_write_value = 0;
static volatile uint8_t s_i;
static volatile uint8_t s_read;
static volatile uint8_t s_rx_buf[9];
static volatile uint8_t s_rx_len = 0;
if (TIM_HARD->SR & TIM_IT_UPDATE)
{
TIM_HARD->SR = (uint16_t)~TIM_IT_UPDATE;
}
/*
复位时序, 见DS18B20 page 15
首先主机拉低DQ,持续最少 480us
然后释放DQ,等待DQ被上拉电阻拉高,约 15-60us
DS18B20 将驱动DQ为低 60-240us, 这个信号叫 presence pulse (在位脉冲,表示DS18B20准备就绪 可以接受命令)
如果主机检测到这个低应答信号,表示DS18B20复位成功
*/
s_time++;
switch (s_status)
{
// DS18B20_Reset(_ch);
// DS18B20_WriteByte(_ch, 0xcc); /* 发命令 - 跳过ROM */
// DS18B20_WriteByte(_ch,0x44); /* 发转换命令 */
// DS18B20_Reset(_ch); /* 总线复位 */
// DS18B20_WriteByte(_ch, 0xcc); /* 发命令 */
// DS18B20_WriteByte(_ch,0xbe); /* 读温度 */
// temp1 = DS18B20_ReadByte(_ch); /* 读温度值低字节 */
// temp2 = DS18B20_ReadByte(_ch); /* 读温度值高字节 */
// return ((temp2 << 8) | temp1); /* 返回16位寄存器值 */
case 0:
s_status++;
break;
case 1:
s_call_ret = s_status + 1; /* 执行完毕返回状态 */
s_status = 100; /* 去执行DS18B20_Reset */
break;
case 2:
s_call_ret = s_status + 1;
s_write_value = 0xcc;
s_status = 110; /* DS18B20_WriteByte(_ch, 0xcc); */
break;
case 3:
s_call_ret = s_status + 1;
s_write_value = 0x44;
s_status = 110; /* DS18B20_WriteByte(_ch,0x44); */
break;
case 4:
s_call_ret = s_status + 1;
s_status = 100; /* DS18B20_Reset */
break;
case 5:
s_call_ret = s_status + 1;
s_write_value = 0xcc;
s_status = 110; /* DS18B20_WriteByte(_ch, 0xcc); */
break;
case 6:
s_call_ret = s_status + 1;
s_write_value = 0xBE;
s_status = 110; /* DS18B20_WriteByte(_ch,0xbe); */
break;
case 7:
s_call_ret = s_status + 1;
s_status = 120; /* DS18B20_ReadByte(_ch); 读温度值 */
break;
case 8:
if (DS18B20_CaculCRC((uint8_t *)s_rx_buf, 8) == s_rx_buf[8] && s_rx_buf[4] == 0x7F)
{
int16_t reg16;
reg16 = (s_rx_buf[1] << 8) + s_rx_buf[0];
s_DS18B20_TempReg[s_ch] = reg16;
s_err[s_ch] = 0; /* 读取成功清0 */
}
else
{
s_err[s_ch] = 1;
}
s_status++;
break;
case 9:
/* 任务结束,关闭定时中断 */
DS18B20_StopTimerIRQ();
s_status++;
break;
case 10: /* 执行一次结束,再此处等待 */
;
break;
case 99: /* 异常处理 */
s_err[s_ch] = 1; /* 错误 */
s_status = 9; /* 结束任务 */
break;
/************ DS18B20_Reset 总线复位子程序 ************/
case 100:
DQ_DIR_OUTPUT(s_ch); /* DQ方向输出 */
DQ_0(s_ch); /* 拉低DQ */
s_time = 0;
s_status++;
break;
case 101: /* 延迟 520uS, 要求这个延迟大于 480us */
if (s_time >= 52)
{
DQ_DIR_INPUT(s_ch); /* DQ方向输入, 外部上拉会拉到高 */
//DQ_1(s_ch); /* 释放DQ */
s_time = 0;
s_status++;
}
break;
case 102: /* 延时60us,等待总线电平归位 */
if (s_time >= 6)
{
s_time = 0;
s_status++;
}
if (!DQ_IS_LOW(s_ch)) /* 总线已经拉高 */
{
s_time = 0;
s_status++;
}
break;
case 103: /* 等待DS18B20拉低总线 */
if (DQ_IS_LOW(s_ch))
{
s_time = 0;
s_status++;
}
if (s_time > 8) /* 80us 没检测到应答,则认为DS18B20异常 */
{
s_status = 99; /* 错误处理 */
}
break;
case 104: /* 等待DS18B20释放总线 */
if (!DQ_IS_LOW(s_ch))
{
s_time = 0;
s_status++;
}
if (s_time > 30) /* 300us 没释放,则认为DS18B20异常 */
{
s_status = 99; /* 错误处理 */
}
break;
case 105: /* 复位成功,下面可以开始读数据了 */
s_status = s_call_ret;
break;
/************ DS18B20_WriteByte ************/
// for (i = 0; i < 8; i++)
// {
// DQ_0(_ch);
// bsp_DelayUS(2);
// if (_val & 0x01)
// {
// DQ_1(_ch);
// }
// else
// {
// DQ_0(_ch);
// }
// bsp_DelayUS(60);
// DQ_1(_ch);
// bsp_DelayUS(2);
// _val >>= 1;
// }
case 110:
s_i = 0;
s_status++;
break;
case 110 + 1:
DQ_DIR_OUTPUT(s_ch); /* DQ方向输出 */
DQ_0(s_ch); /* 拉低DQ */
bsp_DelayUS(2);
if (s_write_value & 0x01)
{
DQ_1(s_ch);
}
else
{
DQ_0(s_ch);
}
s_time = 0;
s_status++;
break;
case 110 + 2:
if (s_time >= 6)
{
DQ_1(s_ch);
s_write_value >>= 1;
if (++s_i >= 8)
{
s_status++;
}
else
{
s_status--;
}
}
break;
case 110 + 3:
s_status = s_call_ret;
break;
/************ DS18B20_ReadByte ************/
// uint8_t i;
// uint8_t read = 0;
// for (i = 0; i < 8; i++)
// {
// read >>= 1;
// DQ_0(_ch);
// bsp_DelayUS(3);
// DQ_1(_ch);
// bsp_DelayUS(3);
// if (DQ_IS_LOW(_ch))
// {
// ;
// }
// else
// {
// read |= 0x80;
// }
// bsp_DelayUS(60);
// }
// return read;
case 120:
s_rx_len = 0;
s_status++;
break;
case 120 + 1:
s_i = 0;
s_read = 0;
s_status++;
break;
case 120 + 2:
s_read >>= 1;
DQ_DIR_OUTPUT(s_ch); /* DQ方向输出 */
DQ_0(s_ch);
bsp_DelayUS(3);
//DQ_1(s_ch);
DQ_DIR_INPUT(s_ch); /* DQ方向输入 */
bsp_DelayUS(3);
if (DQ_IS_LOW(s_ch))
{
;
}
else
{
s_read |= 0x80;
}
s_time = 0;
s_status++;
break;
case 120 + 3:
if (s_time >= 6) /* 延迟60us */
{
if (++s_i >= 8)
{
s_status++;
}
else
{
s_status--;
}
}
break;
case 120 + 4:
s_rx_buf[s_rx_len] = s_read;
if (++s_rx_len >= 9)
{
s_status++;
}
else
{
s_status = 120 + 1;
}
break;
case 120 + 5:
//s_err[s_ch] = 0; /* 读取成功清0 */
s_status = s_call_ret;
break;
}
}
/*
*********************************************************************************************************
* 函 数 名: DS18B20_StartTimerIRQ
* 功能说明: 启动TIM定时中断
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void DS18B20_StartTimerIRQ(void)
{
bsp_SetTIMforInt(TIM_HARD, 100*1000, 0, 0);
}
/*
*********************************************************************************************************
* 函 数 名: DS18B20_StopTimerIRQ
* 功能说明: 关闭TIM定时中断
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void DS18B20_StopTimerIRQ(void)
{
bsp_SetTIMforInt(TIM_HARD, 0, 0, 0);
}