作者:卢老师,华清远见嵌入式学院讲师。
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感器技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。它具有成本低,性能稳定,抗干扰能力强等优点。
其中对应引脚2int是输出引脚,连接SAM3S4B芯片PA6引脚。
DHT11器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。设备(主机或从机)通过一个漏极开路或三态端口连至该数据线,以允许设备在不发送数据时能够释放总线,而让其它设备使用总线;单总线通常要求外接一个约 4.7kΩ的上拉电阻,这样,当总线闲置时,其状态为高电平。由于它们是主从结构,只有主机呼叫从机时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主机。
电气连接图
TH_DATA 接SAM3S4B的PA6引脚:
实现程序
主程序:
int main(void)
{
unsigned char acc[3]={0,0,0};
unsigned char temp[2]={0,0}; //data comes from the tem-hum sensor
unsigned char hum[2]={0,0}; //data comes from the tem-hum sensor
/* Initialize the SAM system */
sysclk_init();
sysTick_Config(sysclk_get_cpu_hz() / 1000);//interrupt in every ms
WDT->WDT_MR = WDT_MR_WDDIS;
/* Configure UART pins */
gpio_configure_group(PINS_UART0_PIO,PINS_UART0,PINS_UART0_FLAGS);//add luyj 2013.5.8
configure_console();
pmc_enable_periph_clk(ID_PIOA);//add by luyj 2013.5.8
while(1)
{
Read_Temp_Hum(temp,hum);
printf(" temp[0]=%d temp[1]=%d hum[0]=%d,hum[1]=%d\r" ,temp[0],temp[1],hum[0],hum[1]);
mdelay(1000) ;
}
}
上面的是主程序main,主要是系统时钟选为外部晶振,pll倍频cpu_hz为64Mhz,关闭看门狗,配置串口用于打印温湿度值,使能PA口的能源管理器(PMC),接下来开始每隔一秒读取温湿度。
void SetTemIntType(uint8_t type)
{
switch (type)
{
case 0:
NVIC_EnableIRQ((IRQn_Type) ID_PIOA);
pio_enable_interrupt(PIOA, PIO_PA6 );
pio_handler_set_priority(PIOA, (IRQn_Type) ID_PIOA, IRQ_PRIOR_PIO);
pio_handler_set(PIOA, ID_PIOA, PIO_PA6, (PIO_PULLUP | PIO_DEBOUNCE | IRQ_FALLING_EDGE),TUM_Handler);//Interrupt on falling edge
break;
case 1:
NVIC_EnableIRQ((IRQn_Type) ID_PIOA);
pio_enable_interrupt(PIOA, PIO_PA6 );
pio_handler_set(PIOA, ID_PIOA, PIO_PA6, (PIO_PULLUP | PIO_DEBOUNCE | PIO_IT_RISE_EDGE),TUM_Handler); //Interrupt on rising edge
pio_handler_set_priority(PIOA, (IRQn_Type) ID_PIOA, IRQ_PRIOR_PIO);
break;
case 2:
pio_disable_interrupt(PIOA, PIO_PA6 );
break;
default:
break;
}
}
上面的子程序SetTemIntType();是对PA6温度采集引脚的中断使能与取消,在采集具体数据时,需要采集PA6电平保持时间,为此,必须及时响应PA6发出的上升沿和下降沿,在这里用中断实现。
DATA用于微处理器与DHT11之间的通讯和同步,采用单总线数据格式,一次传送40位数据,高位先出。
校验位数据定义:
“8bit湿度整数数据 + 8bit湿度小数数据+8bit温度整数数据 + 8bit温度小数数据”8bit校验位等于所得结果的末8位。
示例一:接收到的40位数据为:
0011 0101 0000 0000 0001 1000 0000 0000 0100 1101
湿度高8位 湿度低8位 温度高8位 温度低8位 校验位
计算:
0011 0101+0000 0000+0001 1000+0000 0000= 0100 1101
接收数据正确
湿度:0011 0101=35H=53%RH
温度:0001 1000=18H=24℃
数据时序图:
用户主机(MCU)发送一次开始信号后,DHT11从低功耗模式转换到高速模式,待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,触发一次信采集。信号发送如图所示。
根据芯片时序图,可以看出这个芯片涉及到微秒的操作,所以利用了M3的System Tick记时,相当精确,再根据时序可以读到数据,
温湿度读取步骤:
第一步:DHT11上电,延时1s待稳定,测试温湿度,保存数据,DHT11的DATA数据线保持高电平,处于输入状态,检测外部信号。
第二步:微处理器IO设为输出低电平,保持大于18ms,然后转为输入状态,等待DHT11作出应答。
程序如下:(不同的版本管脚不同)
gpio_configure_pin(PIO_PA6_IDX ,PIO_OUTPUT_1|PIO_PULLUP | PIO_DEBOUNCE | PIO_IT_RISE_EDGE );// Set PA6 to output
gpio_set_pin_low(PIO_PA6_IDX);// set to low
mdelay(30);
gpio_configure_pin(PIO_PA6_IDX ,PIO_INPUT|PIO_PULLUP ); // Set io to input
首先设置为高电平输出,然后延时30毫秒,再将该端口设置为输入。
第三步:DHT11的DATA检测外部低电平结束,设为输出80微秒低电平作为应答和80微秒高电平通知外设准备接受数据,微处理器等待数据接受。
此时程序中需要延时微秒数量级所以采用了cortex-M3系列系统自带的System Tick。
以下程序作为延时:
for(i=0; i<3; i++)
{
SetTemIntType(i&0x01);//
while(pa6_counter == cnt_last);
cnt_last = pa6_counter;
}
上面这段程序的意思是:先判断是否是下降沿,当i=0,下降沿触发,继续执行,如果没有来下降沿的话,那么pa6_counter == cnt_last就永远是真,一直在这里阻塞,当下降沿来了,则触发了PA6中断,这个中断的处理TUM_Handler如下:
if (PIN_PUSHBUTTON_1_ID == id && PIN_PUSHBUTTON_1_MASK == mask)
{pa6_tc = SysTick->VAL;
pa6_counter++;
}
这时候 pa6_counter ++; pa6_counter与cnt_last不相等,跳出中断,然后重新使他们两个相等,等待i=1,重新进入循环,这时候就变成了上升沿触发,道理同上,然后再等待i=2,又变成了下降沿触发。经过这三次触发方式的改变之后,开始传输数据。
第四步:DHT的DATA输出40位数据,微处理器按高低电平接受40位数据;
位数据“0”的格式为: 50 微秒的低电平和 26-28 微秒的高电平,位数据“1”的格式为: 50 微秒的低电平加70微秒的高电平。位数据“0”、“1”格式信号如图所示:
for(i=0; i<40; i++)
{
SetTemIntType(1);// enable irq rising edge
while( pa6_counter == cnt_last);
cnt_last = pa6_counter;
tc1 = pa6_tc; //此时记录一下这个时刻嘀嗒倒计时计数器里面还有剩下多少。即箭头1标记的位置时刻,还剩多少
SetTemIntType(0);// enable irq falling edge
while(pa6_counter == cnt_last);
cnt_last = pa6_counter;
下面的if里面的意思是如果倒计时还没有执行到0,即还没有重新装载倒计时,那么,这个时间就是箭头1的计数器的值减去箭头2的计数器的值。
if(pa6_tc < tc1) //比较滴答当前值和中断产生时的大小
{
tc = tc1 - pa6_tc; //记录经过多少滴答
}
否则的话,就说明当读到箭头2的时候,计数器已经重新装载了,那么这段时间就是要用64000减去他们的绝对值。
else
{
tc = 64000 - (pa6_tc - tc1);
}
前八位是temp高8位:如果tc大于3200个,那么就是数据格式1了,如果小于,那么就是数据0
if(i < 8)
{
hum_10 <<= 1;
if(tc >= 3200)//根据,我们的时钟频率是64MHZ,即一个脉冲需要1/64us,
hum_10 |= 0x01; //3200个脉冲是 3200*(1/64)us,即50us
}
else if(i < 16)
{
hum_01 <<= 1;
if(tc >= 3200)
hum_01 |= 0x01;
}
else if(i < 24)
{
temp_10 <<= 1;
if(tc >= 3200)
temp_10 |= 0x01;
}
else if(i < 32)
{
temp_01 <<= 1;
if(tc >= 3200)
temp_01 |= 0x01;
}
else
{
chksum <<= 1;
if(tc >= 3200)
chksum |= 0x01;
}
}
setTemIntType(1);// enable irq rising edge
while( pa6_counter == cnt_last);
setTemIntType(2);// disable
*temp = temp10;
*(temp+1) = temp01;
*hum = hum10;
*(hum+1) = huM31;
chk = temp10;
chk += temp01;
chk += hum10;
chk += huM31;
if(chk == chksum)
return 1;
else
return 0;
要不停的读40次,因为每次都读1位,最后判断校验位,如果成功就读取成功,否则丢掉该数据。
结束信号:
DHT11发完后继续输出低电平50微秒转为输入高电平,再次检查温湿度,等待外部信号。
具体打印的当前温度
文章转载请注明出处