2017-10-03 16:32:52 cumtzdlxm 阅读数 1146
  • MFC上位与STM32下位通讯精讲

    本课程主要介绍C++类库MFC上位机与STM32单片机的RS232、RS422、RS485、USB、LWIP以太网、CAN等接口进行稳定通信。课程主要从MFC和STM32基础开始,以编写上位机以及下位机为主,非常注重实践。

    9344 人正在学习 去看看 王凯杰

硬件:STC89C52开发板、1602数显、DHT温湿度传感器(利用其I2C数据线)

软件:Keil v4 C语言编译平台,stc-ipc-15xx-v6.85烧录程序

对DHT温度数据的解析有点问题,室温25度左右,温度的高、低位的最高位均为1,这与硬件说明文档不符,我就都屏蔽掉了最高位,得出的温度数据还比较靠谱,也不知怎么回事,各位读者如果知道的话不吝赐教!!!


DHT12的SDA、SCL分别接P2.0、P2.1,连接好电源、接地后采集结果如下图:


串口传出的数据分别通过P3.0 、P3.1传送给arm开发板
数据格式是每个数据前后各加一个空格,如:“空格78.5空格空格25.6空格……”,51单片机开发板的miniUSB是一个串口转USB的设备,通过它插入PC,就可以在PC端查看串口传出来的数据了,PC上通过串口助手看到单片机输出的结果,如下图:



代码:包括了利用I2C时序采集DHT的温湿度数据、1602显示采集到数据、利用串口中断将数据发送出去三大部分

#include <reg52.h>
#include <intrins.h> 

#define  AddWr 0xb8    //自己买的温湿度传感器DHT12器件地址

sbit sda=P2^0;
sbit scl=P2^1;

sbit rw = P1^1; 
sbit RS = P1^0;  
sbit LCDEN = P2^5; 

unsigned char bai,shi,ge;
unsigned char table1[]="wendu:";
unsigned char table2[]="shidu:";
unsigned char table3[]="0123456789";
unsigned char table4[]="CRC Error !!!";
unsigned char table5[]="Sensor Error !!";

int Temprature,Humi;


void delayUs()
{
    _nop_();
}

 void delayMs(unsigned int a)
{
    unsigned int i, j;
    for(i = a; i > 0; i--)
        for(j = 100; j > 0; j--);
 }


void delay(unsigned char dat)
{
	unsigned char i;
  for(i=dat;i>0;i--)_nop_();
}
	
/*******************************************************************
                     总线初始化              
函数原型: void  I2c_Init();  
功能:     启动I2C总线,即发送I2C起始条件.  
********************************************************************/
void I2c_Init()
{
  sda=1;         
  _nop_();        /*起始条件建立时间大于4.7us,延时*/
  _nop_();
  _nop_();
  _nop_();
  _nop_();  
  scl=1;
  _nop_();        /*起始条件建立时间大于4.7us,延时*/
  _nop_();
  _nop_();
  _nop_();
  _nop_(); 
}
	
/*******************************************************************
                     起动总线函数               
函数原型: void  IIC_Start();  
功能:     启动I2C总线,即发送I2C起始条件.  
********************************************************************/
void IIC_Start()
{
  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  IIC_Stop();  
功能:     结束I2C总线,即发送I2C结束条件.  
********************************************************************/
void IIC_Stop()
{
  sda=0;      /*发送结束条件的数据信号*/
  _nop_();       /*发送结束条件的时钟信号*/
  scl=1;      /*结束条件建立时间大于4μs*/
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  sda=1;      /*发送I2C总线结束信号*/
  _nop_();
  _nop_();
  _nop_();
  _nop_();
}

/*******************************************************************
                 字节数据发送函数               
函数原型: void  IIC_Send_Byte(UCHAR c);
功能:     将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
          此状态位进行操作.(不应答或非应答都使ack=0)     
           发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
********************************************************************/
void  IIC_Send_Byte(unsigned char  c)
{
 unsigned char  i;
 
 for(i=0;i<8;i++)  /*要传送的数据长度为8位*/
    {
     if((c<<i)&0x80)sda=1;   /*判断发送位*/
       else  sda=0;                
     _nop_();
     scl=1;               /*置时钟线为高,通知被控器开始接收数据位*/
      _nop_(); 
      _nop_();             /*保证时钟高电平周期大于4μs*/
      _nop_();
      _nop_();
      _nop_();         
     scl=0; 
    }    
    scl=0;
    _nop_();
    _nop_();
}

/*******************************************************************
                 字节数据接收函数               
函数原型: unsigned char  IIC_Read_Byte();
功能:     用来接收从器件传来的数据,并判断总线错误(不发应答信号),
          发完后请用应答函数应答从机。  
********************************************************************/    
unsigned char   IIC_Read_Byte()
{
  unsigned char  retc=0,i; 
  sda=1;                     /*置数据线为输入方式*/
  for(i=0;i<8;i++)
      {
        _nop_();           
        scl=0;                  /*置时钟线为低,准备接收数据位*/
        _nop_();
        _nop_();                 /*时钟低电平周期大于4.7μs*/
        _nop_();
        _nop_();
        _nop_();
        scl=1;                  /*置时钟线为高使数据线上数据有效*/
        _nop_();
        _nop_();
        retc=retc<<1;
        if(sda==1)retc=retc+1;  /*读数据位,接收的数据位放入retc中 */
        _nop_();
        _nop_(); 
      }
  scl=0;    
  _nop_();
  _nop_();
  return(retc);
}

/********************************************************************
                     主机主动应答子函数
函数原型:  void Ack_I2c(bit a);
功能:      主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)
********************************************************************/
void Ack_I2c(bit a)
{  
  if(a==0)sda=0;              /*在此发出应答或非应答信号 */
  else sda=1;				  /*0为发出应答,1为非应答信号 */
  _nop_();
  _nop_();
  _nop_();      
  scl=1;
  _nop_();
  _nop_();                    /*时钟低电平周期大于4μs*/
  _nop_();
  _nop_();
  _nop_();  
  scl=0;                     /*清时钟线,住I2C总线以便继续接收*/
  _nop_();
  _nop_();    
}
/********************************************************************
                     主机等待从机应答子函数
函数原型:  unsigned char IIC_Wait_Ack();
功能:      等待从机应答信号到来
           返回值:1,接收应答失败        0,接收应答成功
********************************************************************/
unsigned char IIC_Wait_Ack()
{
	unsigned char ucErrTime=0;
	scl=0;
	sda=1;
	delay(14);	   
	scl=1;
	scl=(14);	 
	while(sda)//等到SDA变低
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
		delay(1);
	}
	scl=0;//时钟输出0
	delay(50); //这个等待很重要,校验错误的几率小多了
	return 0;  
} 
/*****************************1602*******************************/
 

void writeComm(unsigned char comm)
{
    RS = 0;    
    P0 = comm;
    LCDEN = 1;
    delayUs();
    LCDEN = 0;
    delayMs(1);
}
void init1602()
 {
    rw = 0; 
    writeComm(0x38);
    writeComm(0x0c); 
    writeComm(0x06);
    writeComm(0x01); 
}

//写数据:RS=1, RW=0;
void writeData(unsigned char dat)
{
    RS = 1;
    P0 = dat;
    LCDEN = 1;
    delayUs();
    LCDEN = 0;
    delayMs(1);
}



void writeString(unsigned char * str, unsigned char length)
{
    unsigned char i;
    for(i = 0; i < length; i++)
    {
         writeData(str[i]);
     }
 }



/*------------------------------------------------
把uint 类型的温度、值转化为ascii码字符,
通过串口输出温度采集值
------------------------------------------------*/
void serialOutput(int num)	
{
     SBUF=' ';			   
	 delay(50);
	 SBUF=num/100+'0';		   	   
	 delay(50);		  
	 SBUF=num/10%10+'0';			   
	 delay(50);
	 SBUF='.';			   
	 delay(50);	
	 SBUF=num%10+'0';
	 delay(50);
	 SBUF=' ';			   
	 delay(50);		   
} 
/*------------------------------------------------
串口输出初始化函数
------------------------------------------------*/
void serial_init()
{
   SCON=0x40;//初始化串口方式1,REN=0不允许接受
   PCON=0x80; //SMOD=1 波特率倍增
   TMOD=0x20; //T1初始化为方式2,自动填充
   TL1=0xF4;  //波特率4800b/s
   TH1=0xF4;
   ES=1;	  //不打开串口中断,TI=1时依然可以串口输出,需要在上次输出后加点延时
   EA=1;	  //打开中断总开关
   TR1=1;	  //T1开始运转
}
/*------------------------------------------------
串口输出中断函数
------------------------------------------------*/ 
void SISR() interrupt 4
{
  T1=0;
}

/*********************************************************************************************************
         功能:DHT12读取温湿度函数
变量:Humi_H:湿度高位;Humi_L:湿度低位;Temp_H:温度高位;Temp_L:温度低位;Temp_CAL:校验位
数据格式为:湿度高位(湿度整数)+湿度低位(湿度小数)+温度高位(温度整数)+温度低位(温度小数)+ 校验位
校验:校验位=湿度高位+湿度低位+温度高位+温度低位
*********************************************************************************************************/
void sensor_read(void)
{	
	int i;
	unsigned char Humi_H,Humi_L,Temp_H,Temp_L,Temp_CAL,temp;

	IIC_Start();    //主机发送起始信号
	IIC_Send_Byte(0Xb8);    //发送IIC地址
	if(!IIC_Wait_Ack())  //等待从机应答信号(如无应答:考虑IIC通讯频率是否太快,或者传感器接线错误)
	{
		i=0;
		IIC_Send_Byte(0);
		while(IIC_Wait_Ack())//等待从机应答信号
		{
		 	if(++i>=500)
			{
			 	break;
			}		 
		} 
		i=0;
		IIC_Start();       //主机发送起始信号
		IIC_Send_Byte(0Xb9);     //发送读指令
		while(IIC_Wait_Ack())    //等待从机应答信号
		{
		 	if(++i>=500)
			{
			 	break;
			}			 
		}
	
		Humi_H=IIC_Read_Byte();   //读取湿度高位
		Ack_I2c(0);
		Humi_L=IIC_Read_Byte();   //读取湿度低位
		Ack_I2c(0);
		Temp_H=IIC_Read_Byte();   //读取温度高位
		Ack_I2c(0);
		Temp_L=IIC_Read_Byte();   //读取温度低位
		Ack_I2c(0);
		Temp_CAL=IIC_Read_Byte(); //读取校验位
		Ack_I2c(1);
		IIC_Stop();	   //发送停止信号	
		temp = (unsigned char)(Humi_H+Humi_L+Temp_H+Temp_L);//只取低8位
		if(Temp_CAL==temp)//如果校验成功,往下运行
		{
			Humi=Humi_H*10+Humi_L; //湿度
			//实践得出,在室温约27°时,温度高位Temp_H、温度低位Temp_L的最高位都是1,按照DHT12的技术说明,温度低位Temp_L的最高位是1的话表示是负值,明显不对
			//温度高位Temp_H的数值是10011011(155)也不对啊,那有这高的温度,将最高位去掉为0时,温度高位为27度,这还差不多。
			//温度高位Temp_H的数值是128-137,如果去掉最高位为零的话,那么是0-9,这是对的
			//Temp_H、Temp_L都去掉最高位后合体的温度约为27.5度,且可以环境温度上下浮动变化,感觉这样就对了。
			//那么我的这个传感器的负温度是怎么表示的呢?这个得在温度小于零的环境中试试Temp_H、Temp_L读到的都是什么值就可以了
			Temprature =(Temp_H&0x7F)*10+(Temp_L&0x7F);
	
			/*if(Temp_L&0X80)	//为负温度
			{
				//Temprature =0-(Temp_H*10+((Temp_L&0x7F)));
				// Temprature=Temp_L;
				//Temprature =(int)((char)0-(Temp_L&0x7F));
				//Temprature=Temp_H&0x7F;
				Temprature =(int)((char)0-((Temp_H&0x7F)*10+(Temp_L&0x7F)));
			}
			else   //为正温度
			{
				//Temprature=(Temp_H*10+(Temp_L&0x7F));//为正温度
				 Temprature=Temp_H;
			}*/ 	
			//判断温湿度是否超过测量范围(温度:-20℃~60摄氏度;湿度:20%RH~95%RH)		
			if(Humi>950) 
			{
			  Humi=950;
			}
			if(Humi<200)
			{
				Humi =200;
			}
			if(Temprature>1000)
			{
			  Temprature=1000;
			}
			if(Temprature<-200)
			{
				Temprature = -200;
			}
			
			i=Humi;
			bai=i/100;shi=i%100/10;ge=i%100%10;
			writeComm(0x80);
			writeString(table2,6);
			writeData(table3[bai]);
			writeData(table3[shi]);
			writeData('.');
			writeData(table3[ge]);
			writeData('%');
			writeData('R');
			writeData('H');
			
			i=Temprature;
			if(i<0)
			{
			  i=-i;
			  bai=i/100;shi=i%100/10;ge=i%100%10;
			  writeComm(0x80+0x40);
			  writeString(table1,6);
			  writeData('-');
			  writeData(table3[bai]);
			  writeData(table3[shi]);
			  writeData('.');
			  writeData(table3[ge]);
			  writeData(0xdf);
			  writeData('C');
           }else{ 
			  bai=i/100;shi=i%100/10;ge=i%100%10;
			  writeComm(0x80+0x40);
			  writeString(table1,6);
			  writeData('+');
			  writeData(table3[bai]);
			  writeData(table3[shi]);
			  writeData('.');
			  writeData(table3[ge]);
			  writeData(0xdf);         //"℃中的左上角圆圈的ascii码"
			  writeData('C');
            }
			serialOutput(Humi);
			serialOutput(Temprature);			
			
		}
		else //校验失败
		{
			writeComm(0x80);
			writeString(table4,12);
		}
	}else
	{
	   writeComm(0x80+0x40);
	   writeString(table5,15);
	}	

}

//******************main()主函数********************************
void main()
{
	void I2c_Init();
	init1602();
	serial_init();

	while(1)
	{
		sensor_read();
		delayMs(2000);
  }	
}


                                
2019-03-17 12:30:53 tongxin082 阅读数 5617
  • MFC上位与STM32下位通讯精讲

    本课程主要介绍C++类库MFC上位机与STM32单片机的RS232、RS422、RS485、USB、LWIP以太网、CAN等接口进行稳定通信。课程主要从MFC和STM32基础开始,以编写上位机以及下位机为主,非常注重实践。

    9344 人正在学习 去看看 王凯杰

温湿度检测设计。基于51单片机、ESP8266WiFi模块、温湿度DHT11传感器、Android APP完成。首先先展示一下设计好的实物,接下来将从系统方案、硬件设计、软件设计这三个方面来阐述。

1、系统方案 

DHT11温湿度传感器采集数据传送给单片机,单片机将数据处理之后通过ESP8266WiFi模块将数据发送给手机App。WiFi模块有两个作用:一是串口转WiFi,单片机通过串口将数据发送给Wifi模块,对于单片机编程而言,与Wifi模块通信就相当于与串口通信,不必知道WiFi协议;二是WiFi模块当做WiFi 热点AP,手机搜索8266建立的WiFi名称就能与其连接。显然这种方式只是局域网通信,不能进行远程连接,远程连接需要服务器端的支持,现在常采用的方法是通过阿里云、机智云等,远程连接比较复杂,我们以后再研究。

2、硬件设计

整个设计电路图如下所示:

ESP8266WiFi模块我们采用的是ESP-01S模组,安信可公司出品的,注意ESP-01S的电源是3.3V,而单片机电源是5V,所以需要一个5V转3.3V的模块,我们选用的是LM1117T-3.3V这个器件, LM1117这个器件管脚一定不要接错,否则会发热非常严重然后烧毁。我第一次焊接的时候把LM1117管脚焊错了,上电后用手触碰了一下差点把手烫伤,赶紧把电源拔掉。LM1117的接线如下所示:

ESP8266的接线如下图所示:

RX:模块串口通信的接收引脚,接到单片机的TX引脚。
GND:接地
TX:模块的发射端,接单片机的RX接口。
CH_PD / EN:接3.3v高电平。
VCC:接3.3V的高电平。

DHT11温湿度传感器负责采集环境中的温湿度数据,在单片机软件设计部分会详细的介绍该传感器的使用步骤。

引脚说明:
VDD 供电3.3~5.5V DC
DATA 串行数据,单总线
NC 空脚
GND 接地,电源负极

3.单片机软件设计

单片机程序主要是两个点,一是读取DHT11传感器的温湿度数据,二是串口通信。DHT11的官方文档写的很规范,有关于读取数据的详细步骤,文档更新也比较及时,最新的更新日期是2017年3月31号,官网的下载地址:http://www.aosong.com/products-21.html

DHT11采用单总线通信,单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。

  • 传送数据位定义

DATA 管脚用于DHT11与单片机之间的通讯和同步,采用单总线数据格式,一次传送40 位数据,高位先出。
数据格式:
8bit 湿度整数数据+ 8bit 湿度小数数据+ 8bit 温度整数数据+ 8bit 温度小数数据+ 8bit 校验位。
注:其中湿度小数部分为0。

  • 校验位数据定义

8bit 湿度整数数据 +  8bit 湿度小数数据 +  8bit 温度整数数据 +  8bit 温度小数数据 = 8bit 校验位
如果以上等式成立,则本次传感器采集的数据有效,否则无效。

先看采集数据有效的示例,接收到的40 位数据为:
0011 0101     0000 0000     0001 1000     0000 0100      0101 0001
湿度高8 位     湿度低8 位     温度高8 位     温度低8 位    校验位
计算: 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 = 0101 0001,接收数据正确。
湿度:0011 0101(整数)=35H=53%RH 0000 0000(小数)=00H=0.0%RH =>53%RH + 0.0%RH = 53.0%RH
温度:0001 1000(整数)=18H=24℃ 0000 0100(小数)=04H=0.4℃ =>24℃ + 0.4℃ = 24.4℃

采集数据无效的示例,接收到的40 位数据为:
0011 0101     0000 0000     0001 1000    0000 0100     0100 1001
湿度高8 位    湿度低8 位     温度高8 位    温度低8 位    校验位
计算: 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 不等于0100 1001,本次接收的数据不正确,放弃,重新接收数据。

通过以上两个示例可以清楚DHT11数据格式以及数据如何去校验有效性。

  • 数据时序图

用户主机(MCU)发送一次开始信号后,DHT11 从低功耗模式转换到高速模式,待主机开始信号结束后,DHT11 发送响应信号,送出40bit 的数据,并触发一次信采集。信号发送如图所示。这里的主机是指单片机,从机是指DHT11传感器。

下面这个图表罗列了时序图相关的参考时间,在读取数据的详细步骤中会用到这些数值。

根据时序图和表中的参考时间,我们可以得出读取传感器数据的步骤。

step1:单片机输出低电平保持20ms

step2:单片机拉高电平保持13us等待DHT11传感器的低电平响应信号

step3:判断DHT11是否给出低电平响应,如果有低电平响应则进入步骤4,否则等待下一轮的尝试。

step4:通过while语句等待83us的低电平响应时间结束

step5:通过while语句等待87us的高电平响应时间结束

step6:计算温湿度数据

step7:单片机输出高电平结束一次数据采集的读取

step8:校验数据

在时序图中可以看到,数据读取是每次一位进行的,数据0位和数据1位的低电平时间是相同的,即54us。数据0位的高电平时间是24us,而数据1为的高电平时间是71us,通过高电平时间的差异我们就可以判断出是数据0还是数据1。所以单独写了一个函数用来计算数据0位和1位,由于温湿度的整数和小数部分分别是由8位表示的,我们定义该函数得到8位数据之后给出返回值。步骤6对应的函数computeData() 用来完成上述工作。我们对步骤6进行详细的描述:

step 6.1:等待54us低电平结束

step 6.2:延时30us判断高电平是否结束,因为数据0位的电平最大时长是27us,如果超过27us之后高电平结束,则为数据0位,否则为数据1位。

step 6.3:通过while语句等待高电平结束

step 6.4:通过移位和或与的方式保存一个数据位

step 6.5:循环6.1到6.4步骤8次,得到一个字节的数据

//--------------------------------
//-----湿度读取子程序 ------------
//--------------------------------
//----以下变量均为全局变量--------
//----温度高8位== temperature_H------
//----温度低8位== temperature_L------
//----湿度高8位== humidity_H-----
//----湿度低8位== humidity_L-----
//----校验 8位 == checkdata-----
//--------------------------------
void readData()
{
    U8  humidity_H_temp,humidity_L_temp,temperature_H_temp,temperature_L_temp,checkdata_temp;
    //step1:单片机输出低电平保持20ms
    P2_0=0;
    delayms(20);
    //step2:单片机拉高电平保持13us等待DHT11传感器的低电平响应信号
    P2_0=1;
    delay13us();
    //step3:判断DHT11是否给出低电平响应,如果有低电平响应则进入步骤4,否则等待下一轮的尝试。
    if(P2_0==0)
    {
        //step4:通过while语句等待83us的低电平响应时间结束
        while(P2_0==0);	
        //step5:通过while语句等待87us的高电平响应时间结束			
        while(P2_0==1);				
        //step6:计算温湿度数据
        humidity_H_temp = computeData();
        humidity_L_temp = computeData();
        temperature_H_temp = computeData();
        temperature_L_temp = computeData();
        checkdata_temp = computeData();
        //step7:单片机输出高电平结束一次数据采集的读取
        P2_0 = 1;		
        //step8:校验数据
        if(checkdata_temp == humidity_H_temp + humidity_L_temp + temperature_H_temp + temperature_L_temp)
        {
            humidity_H = humidity_H_temp;
            humidity_L = humidity_L_temp;
            temperature_H = temperature_H_temp;
            temperature_L = temperature_L_temp;
            checkdata = checkdata_temp;
        }
    }

}

/**
*根据时序计算温湿度值
*/
U8 computeData()
{
    U8 i,U8comdata;
    for(i=0; i<8; i++)
    {
        //step 6.1:等待54us低电平结束
        while(P2_0==0);
        //step 6.2:延时30us判断高电平是否结束	
        Delay_10us();					
        Delay_10us();
        Delay_10us();
        U8temp=0;
        if(P2_0==1)						
        {											
            U8temp=1;
        }
        //step 6.3:通过while语句等待高电平结束
        while(P2_0==1);
        //step 6.4:通过移位和或与的方式保存一个数据位			
        U8comdata<<=1;
        U8comdata|=U8temp;
    }
    return U8comdata;
}

温湿度数据读取完毕,接下来就是通过串口发送出去,串口发送数据的代码相对简单了,我们在主函数中对串口通信进行初始化,然后在一个while语句中每隔2s读取数据然后发送。

//----------------------------------------------
//main()功能描述:  STC89C52RC  11.0592MHz   串口发送温湿度数据,波特率 9600
//----------------------------------------------
void main()
{
    U8  i;
    TMOD=0x20;		//定时器1工作在方式2
    TH1 = 0xfd;		//波特率9600
    TL1 = 0xfd;
    SM0=0;				//串口工作在方式1
    SM1=1;
    EA = 1;				//开总中断
    REN = 1;			//使能串口
    TR1 = 1;			//定时器1开始计时
    delayms(1000);
	sendString("AT+CWMODE=2\r\n");		//ap模式
	delayms(1000);	
	sendString("AT+CIPMUX=1\r\n");		//允许多连接
	delayms(1000);	
	sendString("AT+CIPSERVER=1\r\n");	//建立TCP Server
	delayms(1000);
    ES = 1;				//开串口中断
	
    while(1)
    {
        //调用温湿度读取子程序
        readData();
        str[0]=humidity_H;
        str[1]=humidity_L;
        str[2]=temperature_H;
        str[3]=temperature_L;
        str[4]=checkdata;
        //发送到串口
        delayms(2);					//发送温度数据
        sendString( "AT+CIPSTART=1,\"TCP\",\"192.168.4.2\",5000\r\n");
        delayms(2);
        sendString("AT+CIPSEND=1,5\r\n");
        delayms(2);
        for(i=0; i<5; i++)
        {
            sendOneChar(str[i]);
        }
        //读取模块数据周期不易小于 2S
        delayms(2000);
    }
}

至此,单片机端的主要代码就讲解完了,可以看到核心代码是如何读取DHT11的数据。

4.手机APP软件设计

APP是用Android Studio(AS)开发的,不建议初学者学习Eclipse结合ADT(Android Eclipse Tools)插件的方式开发Android APP,这种方式已经过时并且以后会被淘汰,Google在2016年底已经停止了对ADT的更新,我之前所在的公司已经将Eclispe的代码全部迁移到AS平台了,推荐使用Google自家的AS集成开发环境。AS有很多优点,但是在使用时也有问题,AS借助gradle进行项目构建,至于为什么Google利用gradle进行Android app项目构建,读者可以自行上网搜索。gradle插件版本要和AS版本相对应,不同的开发者的gradle版本可能不同,所以当你拿到另外一个开发者的代码在自己的AS运行时时有可能会构建失败。这个现象对于国外开发者而言不是一个问题,AS可以自动去下载所需要的gradle插件版本,但是在国内,由于众所周知的原因,如果不会科学上网那么AS直接尝试下载gradle插件时会失败,会令很多初学者不知所措。在以后有时间我会单独写一篇blog来讲解如何去解决这个问题。最近听到Google要重返中国市场,如果能回归成功,对于国内的很多开发者和学术研究者而言是个好消息。

言归正传,本设计APP的代码主要分成两个部分,一是WiFi数据的接收,二是图表显示。

在OnCreate()方法中进行控件的初始化,并开启一个温度数据接收线程来接收数据。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //实例化控件
        mTemperatureTv = findViewById(R.id.tv_temperature);
        mHumidityTv = findViewById(R.id.tv_humidity);
        mTemperatureAlert = findViewById(R.id.tv_temperature_alert);
        mHumidityAlert = findViewById(R.id.tv_humidity_alert);
        mTemperatureRangeTv = findViewById(R.id.tv_temperature_range);
        mHumidityRangeTv = findViewById(R.id.tv_humidity_range);
        mTemperatureRangeTv.setText("(" + mTempLow + " - " + mTempHigh + "℃)");
        mHumidityRangeTv.setText("(" + mHumidityLow + " - " + mHumidityHigh + "%)");
        mLineChart = findViewById(R.id.chart);
        //初始化图表属性
        initChart();
        //开启温度显示线程
        new ReceiveDataThread().start();
    }

单独开辟一个线程用来循环读取WiFi模块传输过来的数据,对于App而言单片机通过Wifi模块传输的数据相当与网络数据,用网络编程相关的就可以,线程的相关定义如下所示:

/**
     * 温湿度数据接收线程
     */
    private class ReceiveDataThread extends Thread {
        private DataInputStream in;
        private byte[] receive;

        @Override
        public void run() {
            try {
                //在手机端建立一个ServerSocket,负责接收ESP8266发送的数据,端口为5000
                serverSocket = new ServerSocket(5000);
                client = serverSocket.accept();
                while (true) {
                    //循环读取数据
                    in = new DataInputStream(client.getInputStream());
                    receive = new byte[5];
                    in.read(receive);
                    String data = new String(receive);
                    //刷新UI
                    doUIRrefresh(data);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

物联网开发技术交流群:

2019-12-22 21:39:58 BluseLIBB 阅读数 61
  • MFC上位与STM32下位通讯精讲

    本课程主要介绍C++类库MFC上位机与STM32单片机的RS232、RS422、RS485、USB、LWIP以太网、CAN等接口进行稳定通信。课程主要从MFC和STM32基础开始,以编写上位机以及下位机为主,非常注重实践。

    9344 人正在学习 去看看 王凯杰

上一篇:温湿度采集并上传到服务器,并通过网络显示

这次使用蓝牙,用安卓App显示
思路同样简单:
首先,使用DHT11温湿度传感器,单片机根据其特定时序读取数据,显示到液晶屏上;
然后,通过串口发送到蓝牙模块,手机连接蓝牙模块;
再然后,写安卓程序接受数据,并显示。

这次不一样的是,自己做硬件。

Demo如下:
DHT11读取温湿度

uint8_t DHT11_ReadTempAndHumi(float *temp,float *humi)
	{
		uint8_t buf[5];
		uint8_t i;
		DHT11_Reset();
		if(DHT11_IsOnline() =/= 0)
		{
			//四位数据位 1位检验位
			//第一位为湿度整数位		第二位为湿度小数位
			//第三位为温度整数位     第四位为温度小数位     第五位为检验位
			for(i = 0; i < 5; i ++)
			{
				buf[i] = DHT11_ReadByte();
			}
			if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
			{
				*humi = (float)buf[0];
				*temp = (float)buf[2];
			}
		}
		else 
		{
			return 1;
		}
		return 0;	    
	}

液晶屏显示 液晶屏用的tft1.44 spi传输数据(网上很多例程)
在这里插入图片描述
至于蓝牙传输数据,就是将单片机和蓝牙模块的TX,RX交叉连起来。单片机程序往串口写数据即可。
在这里插入图片描述
再通过串口助手发AT指令配置蓝牙模块,手机直接搜索、配对连接。手机下载App蓝牙串口,即可收到数据
在这里插入图片描述
至于自己的安卓App,目前还在学,到时候偶写好了再贴

前面都是热身,硬件才是重点
硬件说简单也简单,单片机最小系统+传感器+蓝牙模块,模块都是现成的直接买,但是要做好还是比较难。
我不甘心只做个最小系统(实际上还是个最小系统,哈哈),所以还得画图,让别个加工板子,焊接,最后再调。
先上个原理图
在这里插入图片描述
PCB图 图二为手动布线,图一为自动布线,手动布线还是好看很多哈,毕竟花了七八个小时。
在这里插入图片描述
这是我第三次画图,前面两次都没成功,这次应该可以,不行也得行
在这里插入图片描述
明天再检查一下,覆个铜,调下丝印,然后下单做板子。中间这些时间就拿来做安卓程序。
在这里插入图片描述
铺铜OK,然后做了电器检查,然后输出制造文件,前往嘉立创下单,备齐元器件,等待板子回来
在这里插入图片描述
OK,等了几天板子终于回来了,元器件也差不多了。
在这里插入图片描述
然后板子我也焊接好了,调试也通过了

在这里插入图片描述
在这里插入图片描述
在这儿总结一下
板子上留了超级多GPIO,但是排针的封装搞错了,,,,以至于闲置了大部分面积,排针我连焊的心情都没了,买的USB的封装跟画的也不一样。
当然板子会再改一次,哎呀就是最小系统。。
MCU使用的Stm32F103RCT6 然后买成了Stm32F030RCT6 对比一下一两个数字之差,程序下不了,调了两个小时。
最开始画板子是想着尽可能的多花点自己接触过的传感器,芯片啥子,现在实际上看来没多大作用,还是要做什么画什么的好。。

嘿嘿,又重新画了一个,修改了封装,部分电路,我觉得还可以。在这里插入图片描述
最终效果
在这里插入图片描述

接下来还剩下安卓的蓝牙程序,目前安卓刚刚入门,会点简单的界面了。没有找到例程,只有自己慢慢学了。

先告于段落,安卓的弄好了再更。。

2018-08-18 21:50:42 tongxin082 阅读数 9942
  • MFC上位与STM32下位通讯精讲

    本课程主要介绍C++类库MFC上位机与STM32单片机的RS232、RS422、RS485、USB、LWIP以太网、CAN等接口进行稳定通信。课程主要从MFC和STM32基础开始,以编写上位机以及下位机为主,非常注重实践。

    9344 人正在学习 去看看 王凯杰

今天给大家介绍的是一个温湿度检测设计,基于51单片机、蓝牙模块、温湿度传感器、Android APP完成。首先先展示一下设计好的实物,接下来将从系统方案、硬件设计、软件设计这三个方面来阐述。

1.系统方案

先来看一下整体的架构图:硬件部分由STC89C52单片机、DHT11温湿度传感器、BT08蓝牙串口模块和Android手机组成。传感器将采集到的温湿度数据传送给单片机,然后单片机通过蓝牙串口模块将数据发送到手机APP,从而将温湿度在APP显示出来。在APP上可以设置温湿度告警的阈值,超过阈值将显示“偏高”或者“偏低”的相关信息。

2.硬件设计

整个设计的原理图如下所示,由单片机最小系统、蓝牙串口模块、温湿度传感器组成。传感器的DATA管脚连接单片机的P2^0口,蓝牙串口模块的RXD、TXD分别连接单片机的TXD、RXD。

蓝牙串口模块的功能是串口协议和蓝牙协议之间的相互转换,在单片机上自己编写一套蓝牙驱动代码是非常复杂的,借助这个模块我们在编写单片机代码时只需要编写串口收发的代码即可,该模块得到串口数据后会转成蓝牙数据。对于APP它接收到的是蓝牙数据,开发APP时只需要编写蓝牙相关的代码,Android封装了蓝牙相关的API,所以开发起来简单。蓝牙串口模块的引脚图如下图所示,在这个设计中用到了四个引脚,VCC、GND接5V电源和地,蓝牙模块的TXD接单片机的RXD,RXD接TXD。

DHT11温湿度传感器负责采集环境中的温湿度数据,在单片机软件设计部分会详细的介绍该传感器的使用步骤。引脚说明:
VDD 供电3.3~5.5V DC
DATA 串行数据,单总线
NC 空脚
GND 接地,电源负极

 

3.单片机软件设计

单片机程序主要是两个点,一是读取DHT11传感器的温湿度数据,二是串口通信。DHT11的官方文档写的很规范,有关于读取数据的详细步骤,文档更新也比较及时,最新的更新日期是2017年3月31号,官网的下载地址:http://www.aosong.com/products-21.html

DHT11采用单总线通信,单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。

  • 传送数据位定义

DATA 管脚用于DHT11与单片机之间的通讯和同步,采用单总线数据格式,一次传送40 位数据,高位先出。
数据格式:
8bit 湿度整数数据+ 8bit 湿度小数数据+ 8bit 温度整数数据+ 8bit 温度小数数据+ 8bit 校验位。
注:其中湿度小数部分为0。

  • 校验位数据定义

8bit 湿度整数数据 +  8bit 湿度小数数据 +  8bit 温度整数数据 +  8bit 温度小数数据 = 8bit 校验位
如果以上等式成立,则本次传感器采集的数据有效,否则无效。

先看采集数据有效的示例,接收到的40 位数据为:
0011 0101     0000 0000     0001 1000     0000 0100      0101 0001
湿度高8 位     湿度低8 位     温度高8 位     温度低8 位    校验位
计算: 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 = 0101 0001,接收数据正确。
湿度:0011 0101(整数)=35H=53%RH 0000 0000(小数)=00H=0.0%RH =>53%RH + 0.0%RH = 53.0%RH
温度:0001 1000(整数)=18H=24℃ 0000 0100(小数)=04H=0.4℃ =>24℃ + 0.4℃ = 24.4℃

采集数据无效的示例,接收到的40 位数据为:
0011 0101     0000 0000     0001 1000    0000 0100     0100 1001
湿度高8 位    湿度低8 位     温度高8 位    温度低8 位    校验位
计算: 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 不等于0100 1001,本次接收的数据不正确,放弃,重新接收数据。

通过以上两个示例可以清楚DHT11数据格式以及数据如何去校验有效性。

  • 数据时序图

用户主机(MCU)发送一次开始信号后,DHT11 从低功耗模式转换到高速模式,待主机开始信号结束后,DHT11 发送响应信号,送出40bit 的数据,并触发一次信采集。信号发送如图所示。这里的主机是指单片机,从机是指DHT11传感器。

下面这个图表罗列了时序图相关的参考时间,在读取数据的详细步骤中会用到这些数值。

根据时序图和表中的参考时间,我们可以得出读取传感器数据的步骤。

step1:单片机输出低电平保持20ms

step2:单片机拉高电平保持13us等待DHT11传感器的低电平响应信号

step3:判断DHT11是否给出低电平响应,如果有低电平响应则进入步骤4,否则等待下一轮的尝试。

step4:通过while语句等待83us的低电平响应时间结束

step5:通过while语句等待87us的高电平响应时间结束

step6:计算温湿度数据

step7:单片机输出高电平结束一次数据采集的读取

step8:校验数据

在时序图中可以看到,数据读取是每次一位进行的,数据0位和数据1位的低电平时间是相同的,即54us。数据0位的高电平时间是24us,而数据1为的高电平时间是71us,通过高电平时间的差异我们就可以判断出是数据0还是数据1。所以单独写了一个函数用来计算数据0位和1位,由于温湿度的整数和小数部分分别是由8位表示的,我们定义该函数得到8位数据之后给出返回值。步骤6对应的函数computeData() 用来完成上述工作。我们对步骤6进行详细的描述:

step 6.1:等待54us低电平结束

step 6.2:延时30us判断高电平是否结束,因为数据0位的电平最大时长是27us,如果超过27us之后高电平结束,则为数据0位,否则为数据1位。

step 6.3:通过while语句等待高电平结束

step 6.4:通过移位和或与的方式保存一个数据位

step 6.5:循环6.1到6.4步骤8次,得到一个字节的数据

//--------------------------------
//-----湿度读取子程序 ------------
//--------------------------------
//----以下变量均为全局变量--------
//----温度高8位== temperature_H------
//----温度低8位== temperature_L------
//----湿度高8位== humidity_H-----
//----湿度低8位== humidity_L-----
//----校验 8位 == checkdata-----
//--------------------------------
void readData()
{
    U8  humidity_H_temp,humidity_L_temp,temperature_H_temp,temperature_L_temp,checkdata_temp;
    //step1:单片机输出低电平保持20ms
    P2_0=0;
    delayms(20);
    //step2:单片机拉高电平保持13us等待DHT11传感器的低电平响应信号
    P2_0=1;
    delay13us();
    //step3:判断DHT11是否给出低电平响应,如果有低电平响应则进入步骤4,否则等待下一轮的尝试。
    if(P2_0==0)
    {
        //step4:通过while语句等待83us的低电平响应时间结束
        while(P2_0==0);	
        //step5:通过while语句等待87us的高电平响应时间结束			
        while(P2_0==1);				
        //step6:计算温湿度数据
        humidity_H_temp = computeData();
        humidity_L_temp = computeData();
        temperature_H_temp = computeData();
        temperature_L_temp = computeData();
        checkdata_temp = computeData();
        //step7:单片机输出高电平结束一次数据采集的读取
        P2_0 = 1;		
        //step8:校验数据
        if(checkdata_temp = humidity_H_temp + humidity_L_temp + temperature_H_temp + temperature_L_temp)
        {
            humidity_H = humidity_H_temp;
            humidity_L = humidity_L_temp;
            temperature_H = temperature_H_temp;
            temperature_L = temperature_L_temp;
            checkdata = checkdata_temp;
        }
    }

}

/**
*根据时序计算温湿度值
*/
U8 computeData()
{
    U8 i,U8comdata;
    for(i=0; i<8; i++)
    {
        //step 6.1:等待54us低电平结束
        while(P2_0==0);
        //step 6.2:延时30us判断高电平是否结束	
        Delay_10us();					
        Delay_10us();
        Delay_10us();
        U8temp=0;
        if(P2_0==1)						
        {											
            U8temp=1;
        }
        //step 6.3:通过while语句等待高电平结束
        while(P2_0==1);
        //step 6.4:通过移位和或与的方式保存一个数据位			
        U8comdata<<=1;
        U8comdata|=U8temp;
    }
    return U8comdata;
}

温湿度数据读取完毕,接下来就是通过串口发送出去,串口发送数据的代码相对简单了,我们在主函数中对串口通信进行初始化,然后在一个while语句中每隔2s读取数据然后发送。

//----------------------------------------------
//main()功能描述:  STC89C52RC  11.0592MHz   串口发送温湿度数据,波特率 9600
//----------------------------------------------
void main()
{
    U8  i;
    TMOD = 0x20;          //定时器T1使用工作方式2
    TH1 = 253;        // 设置初值
    TL1 = 253;
    TR1 = 1;          // 开始计时
    SCON = 0x50;          //工作方式1,波特率9600bps,允许接收
    ES = 1;
    EA = 1;           // 打开所以中断
    TI = 0;
    RI = 0;
    Delay(1);         //延时100US(12M晶振)
    while(1)
    {
        //调用温湿度读取子程序
        readData();
        str[0]=humidity_H;
        str[1]=humidity_L;
        str[2]=temperature_H;
        str[3]=temperature_L;
        str[4]=checkdata;
        //发送到串口
        for(i=0; i<5; i++)
        {
            sendOneChar(str[i]);
        }
        //读取模块数据周期不易小于 2S
        delayms(2000);
    }

}

至此,单片机端的主要代码就讲解完了,可以看到核心代码是如何读取DHT11的数据。

4.手机APP软件设计

APP是用Android Studio(AS)开发的,不建议初学者学习Eclipse结合ADT(Android Eclipse Tools)插件的方式开发Android APP,这种方式已经过时并且以后会被淘汰,Google在2016年底已经停止了对ADT的更新,我之前所在的公司已经将Eclispe的代码全部迁移到AS平台了,推荐使用Google自家的AS集成开发环境。AS有很多优点,但是在使用时也有问题,AS借助gradle进行项目构建,至于为什么Google利用gradle进行Android app项目构建,读者可以自行上网搜索。gradle插件版本要和AS版本相对应,不同的开发者的gradle版本可能不同,所以当你拿到另外一个开发者的代码在自己的AS运行时时有可能会构建失败。这个现象对于国外开发者而言不是一个问题,AS可以自动去下载所需要的gradle插件版本,但是在国内,由于众所周知的原因,如果不会科学上网那么AS直接尝试下载gradle插件时会失败,会令很多初学者不知所措。在以后有时间我会单独写一篇blog来讲解如何去解决这个问题。最近听到Google要重返中国市场,如果能回归成功,对于国内的很多开发者和学术研究者而言是个好消息。

言归正传,本设计APP的代码主要分成两个部分,一是蓝牙数据的接收,二是图表显示。

4.1 APP蓝牙软件设计

蓝牙通信的三个基本步骤:搜索、配对、连接。这之后就可以进行数据传输了。

  • 权限

在蓝牙通信中需要获取Android系统的以下三个权限,如果不能给APP授予相关的权限会影响蓝牙的正常使用:
BLUETOOTH:允许配对的设备进行连接
BLUETOOTH_ADMIN:允许搜索和配对设备
ACCESS_COARSE_LOCATION:广播接收器接收BluetoothDevice.ACTION_FOUND广播需要改权限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

在下文中还会提到在Android6.0及以上的版本中关于ACCESS_COARSE_LOCATION权限的申请。

  • 开启蓝牙

建立蓝牙通信之前需要验证是否有蓝牙设备,以及蓝牙设备是否已经开启。对于一个Android系统而言只有一个蓝牙适配器,通过getDefaultAdapter()方法可以返回其一个实例,如果返回为null,则说明该设备不支持蓝牙。

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
        // device doesn't support Bluetooth
}

接下来是检查蓝牙设备是否已经开启,如果没有开启,可以调用startActivityForResult()方法来弹出对话框让用户选择开启,这种方式不会停止当前的应用。

if (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
  • 搜索设备

搜索设备可以分成两部分,一是查找已经与本机配对的设备,通过getBondedDevices()方法返回已经配对的设备信息:

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pariedDevices.size > 0) {
        for (BluetoothDevice device: pairedDevices) {
                String deviceName = device.getName();
                String deviceMACAddress = device.getAddress();
        }
}

二是搜索周围可用的但是还未配对的设备。
系统在发现蓝牙设备会通过广播的形式通知app,所以在搜索设备之前需要注册广播接收器来接收发现蓝牙设备的消息,在销毁Activity时注销广播接收器。

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceiver(Context context, Intent intent) {
                String action = intent.getAction();
                if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                        BluetoothDevie device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                }
        }
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    // Register for broadcasts when a device is discovered.
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    registerReceiver(mReceiver, filter);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // Don't forget to unregister the ACTION_FOUND receiver.
    unregisterReceiver(mReceiver);
}

BluetoothDevice.ACTION_FOUND广播需要ACCESS_COARSE_LOCATION权限,该权限是个危险权限,在Android 6.0及以上,除了在manifest中声明还需要在java代码中申请。获取了该权限之后,在搜索蓝牙设备时才能收到系统发出的蓝牙设备发现的广播。搜索设备调用startDiscovery()方法,当周围有可用设备时,系统会通过广播的形式通知应用。

//检查ACCESS_COARSE_LOCATION权限
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION)
                        == PackageManager.PERMISSION_GRANTED){
                    Toast.makeText(MainActivity.this,"搜索回调权限已开启",Toast.LENGTH_SHORT).show();
                    if(mBluetoothAdapter.isDiscovering()){
                        mBluetoothAdapter.cancelDiscovery();
                    }
                    mBluetoothAdapter.startDiscovery();
                }else{
                    Toast.makeText(MainActivity.this,"搜索回调权限未开启",Toast.LENGTH_SHORT).show();
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_ACCESS_COARSE_LOCATION);
                }

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode==REQUEST_ACCESS_COARSE_LOCATION){
            if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
                mBluetoothAdapter.startDiscovery();
                if(mBluetoothAdapter.isDiscovering()){
                    mBluetoothAdapter.cancelDiscovery();
                }
                mBluetoothAdapter.startDiscovery();

            } else {
                Toast.makeText(MainActivity.this,"action found is not granted.",Toast.LENGTH_LONG).show();
            }

        }
    }
  • 建立连接

在建立连接时需要一个UUID,UUID是用来标识不同设备的ID,对于蓝牙串口设备而言其对应的UUID是“00001101-0000-1000-8000-00805F9B34FB”。

If you are connecting to a Bluetooth serial board then try using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB
https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html

手机端是作为客户端与蓝牙模块进行连接的。
在蓝牙socket进行connect之前,一定要调用BluetoothAdapter的cancelDiscovery()方法。连接的第一步是通过调用BluetoothDevice的createRfcommSocketToServiceRecord(UUID)获取BluetoothSocket.第二步是调用BluetoothSocket的connect()方法发起连接。由于connect()为阻塞调用,因此该连接过程应该在主线程之外的线程中执行。在调用connect()时,应始终确保设备未在执行设备发现。如果正在进行发现操作,则会大幅降低连接尝试的速度,并增加连接失败的可能性。

String macAddr = "20:15:05:25:02:43";
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(macAddr);
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
try {
    mSocket = device.createRfcommSocketToServiceRecord(uuid);
} catch (IOException e) {
    e.printStackTrace();
}
new Thread(){
    @Override
    public void run() {
        mBluetoothAdapter.cancelDiscovery();
        try {
            mSocket.connect();
        } catch (IOException e) {
            try {
                mSocket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }
        super.run();
    }
}.start();

确保在建立连接之前始终调用cancelDiscovery(),而且调用时无需实际检查其是否正在运行,如果确实想要执行检查,请调用isDiscovering()。

  • 发送数据

try {
     OutputStream os = mSocket.getOutputStream();
     os.write("发送的数据".getBytes());
     } catch (IOException e) {
         e.printStackTrace();
     }

系统的整个设计过程如上所述,我已经把该设计的实物挂在了淘宝上,如果想买来玩一玩,欢迎大家点击以下链接:

https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-3312521594.2.21332cf8bVg4fB&id=575320673715

物联网开发技术讨论群:

2018-11-13 10:25:13 mashaokang1314 阅读数 1989
  • MFC上位与STM32下位通讯精讲

    本课程主要介绍C++类库MFC上位机与STM32单片机的RS232、RS422、RS485、USB、LWIP以太网、CAN等接口进行稳定通信。课程主要从MFC和STM32基础开始,以编写上位机以及下位机为主,非常注重实践。

    9344 人正在学习 去看看 王凯杰

运行环境
Linux系统,Python语言
实现功能
从单片机串口接收采集到的温度和湿度,将数据存到数据库中,并实时显示在折线图上。
使用软件
Pycharm
使用python库
数据库层:pymysql
数据可视化层:matplotlib
串口通信层:pyserial

实现流程
在这里插入图片描述

串口通信模块

串口所在位置:"/dev/ttyUSB0"
波特率:9600
超时时间:timeout=None,由于是实时监测,所以将timeout设置为None,只有数据传输过来的时候才执行后面程序。
传输字节:bytesize=8

import random
import serial
import time
from Humiture1.WriteData import Mysqlclass


class Serailset:

    def __init__(self):
        self.mc=Mysqlclass()
        self.ser=serial.Serial("/dev/ttyUSB0",9600,timeout=None,bytesize=8)//初始化配置
	
	#关闭串口
    def close(self):
        self.ser.close()
	
	#读取串口数据,每次读取一行
    def read(self):
        return self.ser.readline()


if __name__ == '__main__':
    ser=Serailset()
    print(ser.insertMysql())
ser.close()

数据库层
使用pymysql模块与数据库进行交互,将数据库操作封装成一个类,方便操作。

import pymysql as mysql
import time
import random

#连接数据库
db_config = {
    'host':'localhost',
    'user':'root',
    'passwd':'',
    'db':'Data',
    'port':3306,
    'charset':'utf8'
}

class Mysqlclass:
	
	#初始化,连接数据库,创建游标
    def __init__(self):
        try:
            self.db_config=db_config
            self.conn=mysql.connect(**self.db_config)
            self.cur=self.conn.cursor()
        except Exception as e:
            print("连接数据库失败")
        else:
            print("连接数据库成功")
	
	#关闭连接,关闭游标
    def close(self):
        self.cur.close()
        self.conn.close()



    #往A数据表存入数据
    def insertsql_A(self,list):
        li=[]
        li.append(tuple(list))
        insert_sql='INSERT INTO humitures_A(measuring_time,temperature,humidity) VALUES (%s,%s,%s)'
        self.cur.executemany(insert_sql,li)
        last_id_A=int(self.cur.lastrowid)
        self.conn.commit()
        return last_id_A

#往B数据表存入数据
    def insertsql_B(self, list):
        li = []
        li.append(tuple(list))
        insert_sql = 'INSERT INTO humitures_B(measuring_time,temperature,humidity) VALUES (%s,%s,%s)'
        self.cur.executemany(insert_sql, li)
        last_id_B=int(self.cur.lastrowid)
        self.conn.commit()
        return last_id_B

	#读取数据表中最大行,方便访问最新插入的数据
    def read_maxcount(self,site):
        self.cur.execute('select count(*) from humitures_%s' %(site))
        return self.cur.fetchall()[0][0]


    #读取数据
    def readData_A(self,after):
        time.sleep(0.5)
        self.cur.execute('select * from humitures_A where id between %s and %s' %(after-10,after))
        self.conn.commit()
        return self.cur.fetchall()


    def readData_B(self,after):
        time.sleep(0.5)
        self.cur.execute('select * from humitures_B where id between %s and %s' %(after-10,after))
        self.conn.commit()
        return self.cur.fetchall()

数据可视化

import matplotlib.pyplot as plt
from Humiture1.WriteData import Mysqlclass
import numpy as np
import time

class PictureShow:
    
    def __init__(self):
        self.mc = Mysqlclass()
        self.fig = plt.figure()
        plt.ion()

    def showA(self,last_id_A):
        ax1=plt.subplot(211)   #实现多幅图创建
        plt.grid(True)
        after=last_id_A
        tuple=self.mc.readData_A(after)
        x1_datelist = [str(i[1])[11:] for i in tuple]
        y1_tmplist=[i[2] for i in tuple]
        y2_humlist=[i[3] for i in tuple]
    

        ax1.plot(x1_datelist,y1_tmplist,c='red')
        ax1.set_title("Site_A_Temperature")
        ax1.set_ylabel('Temperature(C)', fontsize=16)
        ax2=ax1.twinx()       #绑定ax1和ax2实现双Y轴折线图
        ax2.plot(x1_datelist, y2_humlist, c='blue')
        ax2.set_xlabel('Time(s)', fontsize=16)
        ax2.set_ylabel('humiture(%)', fontsize=16)


    def showB(self,last_id_B):
        ax3=plt.subplot(212)
        plt.grid(True)
        after = last_id_B
        tuple=self.mc.readData_B(after)

        x2_datelist = [str(i[1])[11:] for i in tuple]
        y3_tmplist = [i[2] for i in tuple]
        y4_humlist = [i[3] for i in tuple]

        ax3.plot(x2_datelist, y3_tmplist, c='red')
        ax3.set_title("Site_B_Temperature")
        ax3.set_ylabel('Temperature(C)', fontsize=16)

        ax4 = ax3.twinx()
        ax4.plot(x2_datelist, y4_humlist, c='blue')
        ax4.set_xlabel('Time(s)', fontsize=16)
        ax4.set_ylabel('humiture(%)', fontsize=16)
        self.fig.autofmt_xdate()


    def showWindow(self,last_id_A,last_id_B):
        plt.clf()  # 清除画布
        self.showA(last_id_A)
        self.showB(last_id_B)
        plt.pause(0.001)


主操作
从串口中读取数据,判断A、B两地的数据,A地的信息存入数据库中的A数据表,B地的信息存入数据库中B地的数据表。每插入一条信息折线图模块从数据库中读取一条最新插入的数据,在折线图上进行显式。

import time
from Humiture1.WriteData import Mysqlclass
from Humiture1.showpicture import PictureShow
from Humiture1.Serailset import Serailset

class Manager:
    def __init__(self):
        self.mc=Mysqlclass()
        self.ps=PictureShow()
        self.ser=Serailset()
	

    def insertMysqlandshow(self):
        while True:
            infotuple = []
            info = str(self.ser.read())
            date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
            infotuple.append(date)
            infotuple.append(info[8:10])
            infotuple.append(info[15:17])
            last_id_A = int(self.mc.read_maxcount("A"))
            last_id_B = int(self.mc.read_maxcount("B"))
            if info[2] == "A":
                self.mc.insertsql_A(infotuple)
                self.ps.showWindow(last_id_A, last_id_B)
                print(info)
            elif info[2] == "B":
                self.mc.insertsql_B(infotuple)
                self.ps.showWindow(last_id_A, last_id_B)
                print(info)


if __name__ == '__main__':
    mg=Manager()
    mg.insertMysqlandshow()


温湿度采集 一

阅读数 573

没有更多推荐了,返回首页