微信公众平台实时监控室内温度和湿度

摘要:这是一个记录软硬件结合的物联网项目,目的是打造微信实时监控室内环境,由于传感器简陋,这篇文章只介绍温度和湿度的监控,使用的是DHT11温湿度传感器,主机为Arduino UNO+W5100 Ethernet/SD扩展版,路由器为HG255D Pandorabox by lintel,物联网服务由乐联网提供,本篇文章主要内容有(1)DHT11硬件基础(2)Arduino读取DHT11的温湿度数据(3)LCD1602_I2C硬件基础及在LCD模块上显示DHT11获取的温湿度数据(4)将数据上传到乐联网(5)微信查询传感器数据

DHT11硬件基础

DHT11是一款性价比极高的含有已校准数字信号输出的温湿度复合传感器,传感器包括一个电阻式感湿元件和一个NTC测温元件并与一个高性能8位单片机相连接。

引脚说明

DHT11有四个引脚,一个引脚为空脚不连接元器件(所以一些采用DHT11传感器的封装好的温湿度传感器只能看到3个引脚),其中正极接VCC(工作电压为3-5.5VDC),负极接GND,由于DHT11是以串行接口以高低电平传递数据,所以数据总线(SDATA)引脚连接单片机或Arduino的数字信号(Digital)引脚,通常在元器件上正极标有(+),负极标有(—),数据引脚标有(S),我用的是KEYES的元器件,左边标有(S)为数据引脚,右边标有(-)为负极引脚,中间则为正极引脚,不同厂家的元器件引脚顺序不同,连接时要找准引脚,否者单片机则读取不到数据。


引脚说明

应用电路

数据传输原理

MCU发送开始信号(+-+)->触发DHT响应(-+-)数据采集->{[准备发送(-)+发送数据(+)]*5}->机械拉低(-)->MCU拉高(+)

DHT11采用的是串行接口(单线双向)的通讯方式,DATA用于微处理器与DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零.操作流程如下:一次完整的数据传输为40bit,高位先出。

数据格式:

40bit=8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据+8bit校验和数据

传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据”所得结果的末8位。

数据传输过程

用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据.从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集.采集数据后转换到低速模式。
通讯过程如图所示


通讯过程

总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号。DHT11接收到主机的开始信号后,等待主机开始信号结束,然后发送80us低电平响应信号.主机发送开始信号结束后,延时等待20-40us后, 读取DHT11的响应信号,主机发送开始信号后,可以切换到输入模式,或者输出高电平均可, 总线由上拉电阻拉高。

总线为低电平,说明DHT11发送响应信号,DHT11发送响应信号后,再把总线拉高80us,准备发送数据,每一bit数据都以50us低电平时隙开始,高电平的长短定了数据位是0还是1.格式见下面图示.如果读取响应信号为高电平,则DHT11没有响应,请检查线路是否连接正常.当最后一bit数据传送完毕后,DHT11拉低总线50us,随后总线由上拉电阻拉高进入空闲状态。
数字0信号表示方法如图所示


数字0信号表示方法

数字1信号表示方法如图所示


数字1信号表示方法

Arduino读取DHT11的温湿度数据

int DHpin=8;    //定义数字引脚8为DHT11数据接收/发送引脚
byte dat[5];    //声明数组,用于储存5组8bit的数据

void setup()    //只执行一次
{
  Serial.begin(9600);   //串口通信波特率(和电脑)
  pinMode(DHpin,OUTPUT);    //输出模式
  digitalWrite(DHpin,HIGH); //静息电平,空闲状态
}

void start_test()   //MCU发送开始信号
{
  digitalWrite(DHpin,LOW);  //拉低电平发送信号
  delay(30);
  digitalWrite(DHpin,HIGH); //拉高电平,发送信号结束
  delayMicroseconds(40);
  pinMode(DHpin,INPUT); //输入模式,开始检测DHT发来的信号
  while(digitalRead(DHpin)==LOW);   //DHT发来响应信号,低电平80us
  delayMicroseconds(80);
  while(digitalRead(DHpin)==HIGH);  //高电平80us,响应信号结束
  delayMicroseconds(80);
  for(int i=0;i<4;i++)  //开始读取数据
  dat[i]=read_data();
  pinMode(DHpin,OUTPUT);    //读取数据结束
  digitalWrite(DHpin,HIGH); //拉高电平,空闲状态
}

byte read_data()    //读取数据的方法
{
  byte data;
  for(int i=0;i<8;i++)  //1bit的读取数据
  {
    if(digitalRead(DHpin)==HIGH)    
    {
      while(digitalRead(DHpin)==HIGH);  //检测到高电平开始,延迟30us
      delayMicroseconds(30);
      if(digitalRead(DHpin)==HIGH)  //如果30us后,电平拉高,则为1,否者则为0
      data|=(1<<(7-i));
    }
  }
return data;
}

void loop() //串口输出数据
{
  start_test(); 
  Serial.print("t1:"); 
  Serial.print(dat[0],DEC); //显示湿度的整数位; 
  Serial.print('.'); 
  Serial.print(dat[1],DEC); //显示湿度的小数位; 
  Serial.print('%');
  Serial.print("t2:"); 
  Serial.print(dat[2],DEC); //显示温度的整数位; 
  Serial.print('.'); 
  Serial.print(dat[3],DEC); //显示温度的小数位; 
  Serial.print('C');
  delay(5000);      //延迟5s

}

LCD1602_I2C硬件基础

LCD1602_I2C由两部分组成,LCD1602和一个I2C并行串口电路构成,LCD提供液晶屏显示,I2C并行串口电路为单片机提供并行串口通讯,减少LCD1602所需的引脚。

LCD1602硬件基础

LCD:英文全称为Liquid Crystal Display,即为液态晶体显示,也就是我们常说的液晶显示了。1602则是表示这个液晶一共能显示2行数据,每一行显示16个字符。这个就是LCD1602的全部来由。

我们首先来看1602的引脚定义,1602的引脚是很整齐的SIP单列直插封装,所以器件手册只给出了引脚的功能数据表:


LCD1602引脚定义

我们只需要关注以下几个管脚:
3脚:VL,液晶显示偏压信号,用于调整LCD1602的显示对比度,一般会外接电位器用以调整偏压信号,注意此脚电压为0时可以得到最强的对比度。
4脚:RS,数据/命令选择端,当此脚为高电平时,可以对1602进行数据字节的传输操作,而为低电平时,则是进行命令字节的传输操作。命令字节,即是用来对LCD1602的一些工作方式作设置的字节;数据字节,即使用以在1602上显示的字节。这是一个选择数据寄存器还是选择指令寄存器的过程,值得一提的是,LCD1602的数据是8位的。
5脚:R/W,读写选择端。当此脚为高电平可对LCD1602进行读数据操作,反之进行写数据操作。笔者认为,此脚其实用处不大,直接接地永久置为低电平也不会影响其正常工作。但是尚未经过复杂系统验证,保留此意见。
6脚:E,使能信号,其实是LCD1602的数据控制时钟信号,利用该信号的上升沿实现对LCD1602的数据传输。
7~14脚:8位并行数据口,使得对LCD1602的数据读写大为方便。

LCD1602通讯过程

现在来看LCD1602的操作时序:

在此,我们可以先不读出它的数据的状态或者数据本身。所以只需要看两个写时序:

当我们要写入指令字节,设置LCD1602的工作方式时:需要把RS置为低电平,RW置为低电平,然后将数据送到数据口D0~D7,最后E引脚一个高脉冲将数据写入。
当我们要写入数据字节,设置LCD1602的工作方式时:需要把RS置为高电平,RW置为低电平,然后将数据送到数据口D0~D7,最后E引脚一个高脉冲将数据写入。
发现了么,写指令和写数据,差别仅仅在于RS的电平不一样而已。以下是LCD1602的读时序图:

读指令

当要写命令字节的时候,时间由左往右,RS变为低电平,R/W变为低电平,注意看是RS的状态先变化完成。然后这时,DB0~DB7上数据进入有效阶段,接着E引脚有一个整脉冲的跳变,接着要维持时间最小值为tpw=400ns的E脉冲宽度。然后E引脚负跳变,RS电平变化,R/W电平变化。这样便是一个完整的LCD1602写命令的时序。


读写指令

LCD1602基本指令

基本指令

Arduino与LCD1602的通讯

8针接法,数据输出口:DB0-DB7。

// 8pinlcd1602.ino
int DI = 12;    //定义数据/命令针脚
int RW = 11;    //定义读/写针脚
int DB[] = {3, 4, 5, 6, 7, 8, 9, 10};//使用数组来定义总线需要的管脚
int Enable = 2; //定义使能管脚
 
//LCD写命令的方法
void LcdCommandWrite(int value) {
 // 定义所有引脚
 int i = 0;
digitalWrite(DI, LOW);  //DI为O,RW为0,写入命令
 digitalWrite(RW, LOW);
 for (i=DB[0]; i <= DI; i++) //总线赋值
{
   digitalWrite(i,value & 01);  //获取每个字节的第一位
   value >>= 1; //2进制移位,将要获取的位移至第一位
 }
 digitalWrite(Enable,LOW);  //使能拉低电平,准备触发高脉冲
 delayMicroseconds(1);
 digitalWrite(Enable,HIGH); //Arduino拉高电平,高脉冲将数据泵入指令寄存器
 delayMicroseconds(1);  
 digitalWrite(Enable,LOW);  //使能恢复低电平,静息电平
 delayMicroseconds(1);  
}
 
void LcdDataWrite(int value) {
 // 定义所有引脚
 int i = 0;
 digitalWrite(DI, HIGH);    //DI为1,RW为0,写入数据
 digitalWrite(RW, LOW);
 for (i=DB[0]; i <= DB[7]; i++) {
   digitalWrite(i,value & 01);  //获取每个字节的第一位,0xXX & 01 =0 | 1
   value >>= 1;
 }
 digitalWrite(Enable,LOW);
 delayMicroseconds(1);
 digitalWrite(Enable,HIGH); //高脉冲泵入数据,写入数据寄存器
 delayMicroseconds(1);
 digitalWrite(Enable,LOW);
 delayMicroseconds(1);  
}
 
void setup (void) { //起始函数,只执行一次
 int i = 0;
 for (i=Enable; i <= DI; i++) { //将所有管脚设置为输出
   pinMode(i,OUTPUT);
 }
 delay(100);
 // 短暂的停顿后初始化LCD
 // 用于LCD控制需要
 LcdCommandWrite(0x38);  // 设置为8-bit接口,2行显示,5x7文字大小                     
 delay(64);                      
 LcdCommandWrite(0x38);  // 设置为8-bit接口,2行显示,5x7文字大小                        
 delay(50);                      
 LcdCommandWrite(0x38);  // 设置为8-bit接口,2行显示,5x7文字大小                        
 delay(20);                      
 LcdCommandWrite(0x06);  // 输入方式设定
                         // 自动增量,没有显示移位
 delay(20);                      
 LcdCommandWrite(0x0E);  // 显示设置
                         // 开启显示屏,光标显示,无闪烁
 delay(20);                      
 LcdCommandWrite(0x01);  // 屏幕清空,光标位置归零  
 delay(100);                      
 LcdCommandWrite(0x80);  // 显示设置
                         // 开启显示屏,光标显示,无闪烁
 delay(20);                      
}
 
void loop (void) {
  LcdCommandWrite(0x01);  // 屏幕清空,光标位置归零  
  delay(10); 
  LcdCommandWrite(0x80+3); //0x80+为第一行命令地址,0x80+3表示第1行第4列的字符
  delay(10);                     
  // 写入欢迎信息 
  LcdDataWrite('W');
  LcdDataWrite('e');
  LcdDataWrite('l');
  LcdDataWrite('c');
  LcdDataWrite('o');
  LcdDataWrite('m');
  LcdDataWrite('e');
  LcdDataWrite(' ');
  LcdDataWrite('t');
  LcdDataWrite('o');
  delay(10);
  LcdCommandWrite(0xc0+1);  // 定义光标位置为第二行第二个位置  
  delay(10); 
  LcdDataWrite('M');
  LcdDataWrite('a');
  LcdDataWrite('k');
  LcdDataWrite('e');
  LcdDataWrite('B');
  LcdDataWrite('l');
  LcdDataWrite('a');
  LcdDataWrite('z');
  LcdDataWrite('e');
  delay(5000);
  LcdCommandWrite(0x01);  // 屏幕清空,光标位置归零  
  delay(10);
  LcdDataWrite('I');
  LcdDataWrite(' ');
  LcdDataWrite('a');
  LcdDataWrite('m');
  LcdDataWrite(' ');
  LcdDataWrite('R');
  LcdDataWrite('i');
  LcdDataWrite('c');
  LcdDataWrite('e');
  LcdDataWrite('L');
  LcdDataWrite('y');
  LcdDataWrite('n');
  delay(3000);
  LcdCommandWrite(0x02); //设置模式为新文字替换老文字,无新文字的地方显示不变。
  delay(10);
  LcdCommandWrite(0x80+5); //定义光标位置为第一行第六个位置
  delay(10);  
  LcdDataWrite('t');
  LcdDataWrite('h');
  LcdDataWrite('e');
  LcdDataWrite(' ');
  LcdDataWrite('a');
  LcdDataWrite('d');
  LcdDataWrite('m');
  LcdDataWrite('i');
  LcdDataWrite('n');
  delay(5000);
}

4针接法,数据输出口:DB4-DB7,传输一个字节分两次传输,每次传输4bit,需要2倍的时间。

// 4pinlcd1602.ino

int LCD1602_RS=12;   
int LCD1602_RW=11;   
int LCD1602_EN=10;   
int DB[] = { 6, 7, 8, 9};   //定义4位总线
char str1[]="Welcome to";   //自定义字符,数据格式为字符(一个数组的值等于一个字符)
char str2[]="MakeBlaze";
char str3[]="this is the";
char str4[]="4-bit interface";
 
void LCD_Command_Write(int command) //LCD写命令函数
{
 int i,temp;
 digitalWrite( LCD1602_RS,LOW);
 digitalWrite( LCD1602_RW,LOW);
 digitalWrite( LCD1602_EN,LOW);
 
 temp=command & 0xf0;   //切割字节,获取高4位,0xff=11110000
 for (i=DB[0]; i <= 9; i++)
 {
   digitalWrite(i,temp & 0x80); //获取最高位,0x80=10000000
   temp <<= 1;
 }
 
 digitalWrite( LCD1602_EN,HIGH);    //高脉冲泵入指令寄存器,位于栈底,D7位为0
 delayMicroseconds(1);
 digitalWrite( LCD1602_EN,LOW);
 
 temp=(command & 0x0f)<<4;  ////切割字节,获取低4位,0xff=11110000
 for (i=DB[0]; i <= 9; i++)
 {
   digitalWrite(i,temp & 0x80); //获取最高位,0x80=10000000
   temp <<= 1;
 }
 
 digitalWrite( LCD1602_EN,HIGH);    //高脉冲泵入指令寄存器,位于栈顶
 delayMicroseconds(1); 
 digitalWrite( LCD1602_EN,LOW);
}
 
void LCD_Data_Write(int dat)    //LCD写数据的函数
{
 int i=0,temp;
 digitalWrite( LCD1602_RS,HIGH);
 digitalWrite( LCD1602_RW,LOW);
 digitalWrite( LCD1602_EN,LOW);
 
 temp=dat & 0xf0;
 for (i=DB[0]; i <= 9; i++)
 {
   digitalWrite(i,temp & 0x80);
   temp <<= 1;
 }
 
 digitalWrite( LCD1602_EN,HIGH);
 delayMicroseconds(1);
 digitalWrite( LCD1602_EN,LOW);
 
 temp=(dat & 0x0f)<<4;
 for (i=DB[0]; i <= 9; i++)
 {
   digitalWrite(i,temp & 0x80);
   temp <<= 1;
 }
 
 digitalWrite( LCD1602_EN,HIGH);
 delayMicroseconds(1); 
 digitalWrite( LCD1602_EN,LOW);
}
 
void LCD_SET_XY( int x, int y ) //设置字符位置
{
  int address;
  if (y ==0)    address = 0x80 + x; //如果是第一行,则地址设置为0x80+
  else          address = 0xC0 + x; //如果是第二行,则地址设置为0xC0+
  LCD_Command_Write(address); //写入地址寄存器
}
 
void LCD_Write_Char( int x,int y,int dat)   //设置LCD显示数据
{
  LCD_SET_XY( x, y );   //设置LCD位置
  LCD_Data_Write(dat);  //将字模对应的地址编号写入数据寄存器
}
 
void LCD_Write_String(int X,int Y,char *s)
{
    LCD_SET_XY( X, Y );    //设置地址 
    while (*s)             //写字符串
    {
      LCD_Data_Write(*s);   
      s ++;
    }
}
 
void setup (void) 
{
  int i = 0;    //设置所有管脚为输出
  for (i=6; i <= 12; i++) 
   {
     pinMode(i,OUTPUT);
   }
  delay(100);
  LCD_Command_Write(0x28);  //4线 2行 5x7,8线我0x38
  delay(50); 
  LCD_Command_Write(0x06);  //写一个字符后地址指针加1,光标向后移一位
  delay(50); 
  LCD_Command_Write(0x0c);  //打开显示,不显示光标
  delay(50); 
  LCD_Command_Write(0x80);  // 显示设置
                            // 开启显示屏,光标显示,无闪烁
  delay(50); 
  LCD_Command_Write(0x01);  //显示清屏
  delay(50); 
 
}
 
void loop (void)
{
   LCD_Command_Write(0x01);
   delay(50);
   LCD_Write_String(3,0,str1);//第1行,第4个地址起
   delay(50);
   LCD_Write_String(1,1,str2);//第2行,第2个地址起
   delay(5000);
   LCD_Command_Write(0x01);
   delay(50);
   LCD_Write_String(0,0,str3);
   delay(50);
   LCD_Write_String(0,1,str4);
   delay(5000);
 
}

LCD1602内部构造

LCD1602内部构造

LCD1602模块内部由一块LCD显示屏(LCDpanel),控制器(controller),列驱动器(segment driver)和偏压产生电路构成。
控制器接收来自MPU(这里为Arduino)的指令和数据,控制着整个模块的运行,控制器主要指令寄存器(IR),数据寄存器(DR),忙标志(BF),地址计数器(AC),显示数据寄存器(DDRAM),字符发生器(CGROM(内置字符库),CGRAM(自定义字符库))构成。

Arduino传输过来的指令储存在指令寄存器之中,内部存储着DDRAM和CGRAM中数据显示的指令代码或地址信息,然后主控芯片(SoC)读取数据,储存DDRAM中根据指令代码执行具体的命令,如字符的位置,光标的开关,字符和光标的移位等等。
而Arduino传输过来的数据则储存在数据寄存器之中,从CGROM中查找到想要显示的字符的字符码,送入DDRAM之中,在LCD显示屏上与DDRAM存储单元对应的规定位置显示出该字符。

主控芯片根据DDRAM中储存的信息,从数字引脚中发出40SEG的扫描信号外加将信息传输到Segment Driver,由Segment Driver构成的40SEG扫描信号,构成LCD显示屏的列,行由公共电极(ROM),原理类似扫描键盘,从而控制整个LCD显示屏的输出。


I2C并口扩展电路

我这里用的是一颗PCF8574T芯片,其他型号的芯片应该工作原理相同,它通过两条双向总线(I2C)可以使大多数的MCU(这里是Arduino)实现远程I/O口扩展,当然要以牺牲部分性能为代价,不过这里我们传递的数据量小,所以性能的丢失可以忽略不计,该器件包含一个8位准双向口(这里用于连接LCD1602,采用4位接法,加上RS,RW,E共占用7个I/O,当然也可以使用I/O口更多的芯片实现8位接法)和一个I2C总线接口(这里连接Arduino,占用2个数字引脚)。


PCF8574T针脚定义

I2C通讯过程

I2C总线由两条线组成,一条串行数据中线(SDA)和一条串行时钟总线(SCL),当总线空闲时,SDA和SCL均保持高电平,这时如果,SDA电平由高电平变化为低电平,标志着起始信号的开始,随后SCL电平由高变低开始位传输,SDA开始传递数据(电平拉高或拉低),SCL随后由低变高,并保持一段时间,若SDA线上的电平保持稳定,则认为SDA是在传输数据bit,此时SDA上高电平表示1,低电平表示0,SLC由高变低,改变数据,准备检测下一个bit,连续传递8个bit(7个数据位加一个R/W操作位1bit),等待PCF8574T应答,第9个clock,若从IC发ACK,SDA会被拉低,若没有ACK,SDA会被置高,这会引起Master发生RESTART或STOP流程,一个字节的写入完成。电平由低至高变化定义为总线的停止信号。


I2C时序图

PCF8574T内部结构


由MCU传递过来的数据存储在每个引脚的寄存器中,然后写入LCD1602。完成通过I2C控制LCD1602的过程。

I2C驱动

********************************************************************/
#include 
#include 
#define uchar unsigned char /*宏定义*/
#define uint unsigned int
#define _Nop() _nop_()        /*定义空指令*/

sbit SDA=P3^4;            /*模拟I2C数据传送位*/
sbit SCL=P3^5;            /*模拟I2C时钟控制位*/
bit ack;          /*应答标志位*/
/*******************************************************************
                     起动总线函数               
函数原型: void Start_I2c(); 
功能:     启动I2C总线,即发送I2C起始条件. 
********************************************************************/
void Start_I2c()
{
SDA=1;   /*发送起始条件的数据信号*/
_Nop();
SCL=1;
_Nop();    /*起始条件建立时间大于4.7us,延时*/
_Nop();
_Nop();
_Nop();
_Nop();     SDA=0;   /*发送起始信号*/
_Nop();    /* 起始条件锁定时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();       
SCL=0;   /*钳住I2C总线,准备发送或接收数据 */
_Nop();
_Nop();
}
/*******************************************************************
                      结束总线函数               
函数原型: void Stop_I2c(); 
功能:     结束I2C总线,即发送I2C结束条件. 
********************************************************************/
void Stop_I2c()
{
SDA=0; /*发送结束条件的数据信号*/
_Nop();   /*发送结束条件的时钟信号*/
SCL=1; /*结束条件建立时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA=1; /*发送I2C总线结束信号*/
_Nop();
_Nop();
_Nop();
_Nop();
}
/*******************************************************************
                 字节数据发送函数               
函数原型: void SendByte(uchar c);
功能:     将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并

对
          此状态位进行操作.(不应答或非应答都使ack=0)     
        发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
********************************************************************/
void SendByte(uchar c) {
uchar BitCnt;

for(BitCnt=0;BitCnt<8;BitCnt++) /*要传送的数据长度为8位*/
    {
     if((c<

I2C&LCD连接电路

I2C&LCD连接电路

你可能感兴趣的:(微信公众平台实时监控室内温度和湿度)