切换到宽版
  • 30376阅读
  • 53回复

[AVR]自制安卓WiFi控制器原理解析 [复制链接]

上一主题 下一主题
离线ly7317090803
 

发帖
52
M币
5575
专家
58
粉丝
103
只看楼主 倒序阅读 我要置顶 楼主  发表于: 2016-01-26
— 本帖被 香瑶 设置为精华,作者+3000M币+5专家(2016-06-06) —
         首先写这篇帖子不是说一个能用安卓手机wifi控制的东西有什么价值或值不值,而是将原理、程序等各部分简单介绍下,可以用在其它很多东西上。之前为找相关资料走了很多弯路,在此分享自己当时做这个的一些知识,希望对他人有所帮助。

         总的就三样东西:1.受控部分的硬件。2.受控部分的程序(不局限于单片机类型)。3.Android部分程序。

●硬件部分

       本质上主要有三种东西:通讯模块、数据处理及控制(单片机)、执行部分(继电器、晶闸管)。
       数字通讯小模块有很多,如:NRF2401、HC-05(蓝牙)、ESP8266 wifi模块等,在此用的是ESP8266,因为其是价格低廉的wifi模块,操作它容易,稳定性满足日常要求,所以选它;
       数据处理,最简单的就是单片机了,不管事何种单片机,只要有串口,且速度不要太低就能满足ESP8266的操作,其余就是算法,原理不变,用在什么单片机上都是可行的。
       执行部分,那就要看要控制什么东西,有何特殊要求,最常见的就是继电器作常开节点控制,但继电器吸合时需要持续通电保持,对于省电有要求则需考虑其他办法。其实固态继电器是个很好的东西,就是贵了点,下面以光耦+晶闸管作为执行部分举例。
       下面是去年搞得一个东西,实质不是为遥控风扇,只是为验证wifi控制的整个系统。原帖实物可参看:http://bbs.mydigit.cn/read.php?tid=1370864


如上图,前2部分不变,后一部分改变即可实现控制不同要求的东西,此处用光耦+晶闸管,第一是为静音,第二是为降低控制部分的功耗。由于单片机与ESP8266就2根通讯线(TXD、RXD),在此也无需说明了,当然也可用SPI控制它,此外ESP8266还可给用户直接对其进行编程,这样就可以直接省去 上图的单片机部分,但需用官方提供的SDK,没用过所以目前也不会。

      下图为控制部分(执行部分)实际电路图:

PORTD5为单片机IO的PD5,上图为高档位的控制,其余几个档位同理。注意上图驱动方法不适用于51单片机无推挽输出的IO(STC89C52等无,STC12C5A IO有推挽输出),不然光耦不能很好导通。
       硬件就这么简单,最多再找个手机充电器提供5V电源(注意ESP8266用3.3V供电,用个ASM1117 3.3即可),整个硬件就完成了。其实硬件不是关键,软件部分才是核心,要控制什么东西,怎么控制,都需在软件中考虑。下面先说下单片机软件部分。

●单片机软件部分

       把源代码放出来就算开源,但是很多时候看别人的代码还真不知如何下手,一个因字数多,另一个因字数多没心思看,在此写出程序的主体流程及注意的关键点,分享程序的思路,同时以备日久忘记了程序的主要思路。
       总的来说,数据从串口中得到,再逐个数据分析解释,确认无误后就执行,简单理解就是接到上级通知,提取通知里要办的事,接着着手去做。串口基本每个单片机都有了(就算没有,都可以直接软件模拟串口),虽然整个流程很简单,但过程中总会遇到些麻烦。程序用AVR stdio6写的,其它单片机或编译器注意改串口初始化及中断服务程序等。下面分3块来说:

一、串口数据接收

       考虑到串口数据的接收及处理需要一定时间,响应速度不能慢但是又不能丢掉发来的指令,所以设置了一个串口数据缓冲区和一个待处理数据区,由于串口数据缓冲区有时接收的数量太多了(列出当前所有可用wifi时),设置的字符串数组就一定得够长,由于单片机内部寄存器数量的限制,是不可能设置那么长的,于是将数据缓冲区和待处理数据区合并到一个数组里,前270字节是数据缓冲区,后面全部是待处理区,数据接收完毕后将数据全部复制到待处理区。当数据缓冲过长则将占用待处理数据区(不使用待处理区,缓冲区同时作为待处理区,取消待处理区后将不再复制数据到原来的待处理区),直接在缓冲区内处理,再来新指令将暂时不理。当然一般情况下是没有这么多数据的。
       与ESP8266对接时,判断何时指令接收完毕是不能单单以0x0D 0x0A(换行)来判断,不同情况、不同指令会有不同的行数,故不能一概而论,其实最简单办法就是等待一小段时间看是否有新串口数据进入,无则认为接收完了,或者针对每种情况、每种指令写不同的结束识别,或者以单向控制(只管往ESP8266写命令,不去管其返回何值)。


  1. //串口接收中断
  2. //Len串口数据长度 rbuffer缓冲数组 Receive标志寄存器 T2_period定时器已定时时长,即定时多少个周期
  3. ISR ( SIG_UART_RECV )
  4. {
  5.         unsigned char value;
  6.         UCSRB&=~ ( 1<<RXCIE );      //关接受结束中断使能
  7.         value=UDR;     ///注意串口数据每次必须读取否则会导致程序出错
  8.         if(Len<Receive_length*2)rbuffer[Len++]=value;
  9.          if( !(Receive&0x02)){Receive|=0x02;}   //进入定时器检测结束
  10.         T2_period=0;//TCNT2=0;
  11.         UCSRB|= ( 1<<RXCIE );                         // 开接受结束中断使能
  12. }
  13. /************************************
  14. *功  能:定时器2比较中断,为判断串口数据是否接收完
  15. *备  注:中断服务程序   period 10ms
  16. *Judge_End判断多少周期后无数据认为接收完
  17. *************************************/
  18. SIGNAL(SIG_OUTPUT_COMPARE2)
  19. {
  20.         int i=0;
  21.          wdt_reset();       //喂狗指令;每次唤醒后喂狗        
  22.         if(WaitTime){WaitTime++;
  23.         }
  24.         if(Receive&0x02){
  25.         T2_period++;
  26.         if(T2_period>Judge_End)
  27. {T2_period=0;
  28.                 Receive &= 0xFD;//关闭识别
  29.                 
  30.                 rbuffer[Len]=0;
  31.                 if((Receive & 0x04)==0){//读取了才填充,数据处理后会将bit2置0
  32.                 Len1=Len;
  33.                 if(Len<Receive_length)Len=0;
  34.                 //将rsbuffer->com_data   注意Len1 超范围访问
  35.                 if(Len1<=Receive_length)//在没有超出缓冲区长度时才复制数据
  36.                 {
  37.                         while(i<Len1)
  38.                         {rbuffer[i+Receive_length]=rbuffer[i];i++; }
  39.                 }
  40.                 else {Len1=0; }   //丢弃此包
  41.                 }
  42.                 Receive |= 0x04;//数据准备好标记,主程序可以处理数据了
  43.         }
  44. }
  45.         TIFR |= 0x80;//清定时器2标志位
  46. }



二、ESP8266控制部分

        当初选择ESP8266 wifi模块,主要是因为它有很多先天优势,第一现在安卓手机很多,第二数据通讯方便,理论可接N个节点(ESP8266可自建AP,软件上在个节点转发数据,故可组网数量>255个),第三更可以介入家用路由器通过互联网控制(由于需申请官方账号且控制都是设定的,不好改,在此就不说它),当然最主要一点就是模块便宜。
        但是使用wifi模块还是必须知道wifi建立、通讯的流程,不然没法用好它。其实在官方的模块指令介绍也写的很详细,下面把主要的几点写下:wifi模块的STA、AP模式使用、TCP/UDP传输方式使用,另外说下透传模式(实质就是变成一个无线串口)。
        首先STA模式是接入到别人的无线热点里(或无线路由器),AP则是自身变成一个无线热点,别人接进来,最大可接5个。ESP8266两个模式可并存或只用一种模式,AP模式下很费电,注意ESP8266内部无路由功能(不用指望它能代替路由器提供上网)。


       AP模式开启只需发送AT+CWMODE=2即可(AT+CWMODE=3表示AP、STA模式同时开),此时接入到此AP的手机或wifi模块之间是不能互相访问的,也就是相互间收发不了数据,所以这个AP模式也只能开个热点让其它设备接入进来,所有的通讯只能是建AP的这个wifi模块与接入到它的wifi设备间,若要实现更自由的网络通讯要么就搞个路由器别用这个AP,或者自己在软件里加数据转发部分;
       STA模式只需发送AT+CWMODE=1即可开启,此时需考虑此模块接入到那个wifi信号(SSID)那,若不明确则可以进行搜索(AT+CWLAP即可列出当前全部可用的wifi),此时收到串口数据会很大(wifi信号多的话约有600多字节),单片机串口数据处理需注意,通过串口发送”AT+CWJAP=SSID号,密码”即可加入,此时一般等20s左右会有回复,成功则收到OK,失败则是ERROR或Fail。简单梳理如下:
   AP模式:
发送“AT+CWMODE=2”开启AP,默认情况下它的AP地址为192.168.4.1,注意不要与STA的IP地址同一网段(第三位不一样即可,如192.168.5.X)
   STA模式:
发送“AT+CWMODE=1”开启STA,若不明确要加入的SSID号或不确定SSID号是否可用,可以先发送AT+CWLAP列出当前全部可用wifi 的SSID,接着用”AT+CWJAP=SSID号,密码”即可进行接入,此时监测回复是否加入成功。
      一般在STA模式加入wifi失败则需主动开启AP模式等待其它wifi设备加入。第一次连好后,下次开机会自动连接,若无法连上可以发送“AT+CIPSTA?”查看是够有IP地址即可,STA的IP地址全为0表示为连上。新的模块时会自动建立AP,当手机接入后发送wifi的SSID及密码来自行切换到STA模式接入路由器。

      TCP/UDP传输方式,两个各有好处,TCP特点是每次发送数据大些,安全可靠,但建立的要求条件多,响应随时性差些;UDP相对TCP没那么安全,它只管发出去,不管对方是否收到,不要求先检测目标地址随时可以发数据出去,数据量少。TCP需要一个作为服务器,一个作客户端,其中一个不在线则此连接断开,需再次建立连接后才可发送数据,例如对方突然断电或其他原因不在线将无法发送数据,发送数据灵活性低。开启UDP的方式需指定本地端口号及远程端口号,为了方便将端口号统一,UDP建立及收发数据如下:


      先用CIPSTART建立一个UDP传输端口,例如:
“AT+CIPSTART=1,"UDP","192.168.11.2",4100,4100,0”,目标地址是192.168.11.2,端口号4100.
      接着发送数据的长度,如数据是5字节则“AT+CIPSEND=5”,在串口收到“>”后发送数据即可,发完后会收到“SEND OK”。先建立UDP端口,再发送。   下面是发送函数:
  1. unsigned char UDPsend(unsigned char *p)
  2. {
  3.         unsigned char length=0,i=0;
  4.         /********* 先发送数据长度 CIPSEND***********/
  5.         if(Wifi_status&0x20)length=*p;  //code的长度
  6.                                 else length=strlen(p);
  7.         strcpy(SendNum,"AT+CIPSEND=");
  8.         //********* 将数字变为字符串接到SendNum数组后 *********//
  9.         while(length!=0)
  10.         {
  11.                 SendNum[29-i]=length%10+'0';//利用数组SendNum的29、28、27字节暂存数字变成的字符,此时数字的倒序的
  12.                 length/=10;i++;
  13.         }
  14.         length=11;
  15.         while(i>0)//将上面转换的字符倒序
  16.         {
  17.                 SendNum[length++]=SendNum[30-i];
  18.                 i--;
  19.         }
  20.         SendNum[length]=0;//加数组结束符
  21.         Wifi_status &=0x7F;//关闭返校检测
  22.         Judge_End=3;//////缩短结束判断,判断暂时缩短为3个周期
  23.         CMD_W1(SendNum);//串口发送出去
  24.         /************等待串口返回数据***********/
  25.         while(i++<100)
  26.                 {if(Receive&0x04)//有数据
  27.                         {
  28.                                 //若返回了‘>’即0x3E表示可以发送
  29.                                 if(rbuffer[7+Receive_length]==0x20 && rbuffer[6+Receive_length]==0x3E)
  30.                                 {i=0;}
  31.                         else if(rbuffer[Len1+Receive_length-1]==0x20 && rbuffer[Len1+Receive_length-2]==0x3E)//两种判别
  32.                                 i=0;
  33.                                 Receive&= 0xFB;//清零标志
  34.                                 break;
  35.                         }
  36.                         delayms(5);
  37.                 }
  38.         if(!i){//可以发送字节
  39.              if(Wifi_status&0x20)
  40.                  {DataLength=*p; Wifi_status &= 0xDF;}
  41.                 CMD_W1(p);//串口发送出去
  42.                 DataLength=0;
  43.                         while(i++<200)  
  44.                         {//send_date(Receive );///////////////////调试用
  45.                                 if(Receive&0x04)//有数据      此处曾出现无法识别Receive&0x04,导致时间过长或死循环(编译器问题导致)
  46.                                 {
  47.                                         Receive&= 0xFB;//清零标志
  48.                                         
  49.                                         if(rbuffer[Len1-3]=='K' && rbuffer[Len1-4]=='O')//返回OK表示以发完。由于之前写成Len导致死循环
  50.                                         {i=1;
  51.                                         break;}
  52.                                 }
  53.                         delay_1ms();
  54.                         }
  55.                 }
  56.         else {i= 0;        }
  57.         Wifi_status |=0x80;//关闭返校检测
  58.         Judge_End=18;/////恢复判断时长
  59.         return i;
  60. }


         接收则很简单,只要本地端口号对,收到数据后ESP8266会主动发送数据到串口,单片机直接提取即可。以下是主函数的主循环里读取接收的数据(主体框架):
  1. while(1)
  2.     {
  3.                 if(Len>Receive_length*2-100 && (Wifi_status&0x04)!=0)Len=0;//数据流过大清零
  4.                 
  5.           if(Receive&0x04)//标记有数据时
  6.            {
  7.                 //查找+IPD
  8.                 for(i=0;i<Len1;i++)
  9.                 {
  10.                         if(rbuffer[i+Receive_length]==0x2B && rbuffer[i+1+Receive_length]==0x49 && rbuffer[i+3+Receive_length]==0x44)
  11.                         {
  12.                                 
  13.                                 Cmd_start=Cmd_end=0;  
  14.                                 //先校验字符数量是否一致
  15.                                 //第一字节是总数据长度,提取它
  16.                                 i += 5;
  17.                                 while(rbuffer[i+Receive_length]!= ':' &&Cmd_end++<10)
  18.                                 {
  19.                                         Cmd_start=Cmd_start*10+rbuffer[i+Receive_length]-'0';
  20.                                         i++;
  21.                                 }
  22.                                 if(Cmd_start==rbuffer[i+1+Receive_length]) //字节数量一致则有效
  23.                                 {
  24.                                 //先判断最后一字节的CRC校验码是否对,不对则舍弃数据
  25.                                         if(rbuffer[Cmd_end+Receive_length+4]==CRC8(rbuffer+Receive_length+Cmd_start,4+Cmd_end-Cmd_start))//计算校验码并且校验通过
  26.                                         {  
  27.                                                 if(rbuffer[Cmd_start+Receive_length+3]==Self)//是发给自己则分析数据
  28.                                                 {
  29.                                                 //提取里面的控制指令并执行
  30.                                                 }
  31.                                                 else //转发出去
  32.                                                 {
  33. //若不是发给自己的则将数据转发出去,以便最终的设备收到数据。                                
  34.                                                 }
  35.                                                 
  36.                                         }
  37.                                         else
  38.                                         {
  39.                                                 //校验码不对舍弃本条指令
  40.                                         }
  41.                                         
  42.                                 }
  43.                         }
  44.                 }
  45.                         Receive&= ~0x04;//清零标志
  46.                         if(Len!=0 &&  !(Receive&0x02)){Receive|=0x02;T2_period=0;TCNT2=0;}//数据读完后各重要寄存器清零
  47.            }
  48.         if(!(Wifi_status&0x08) &&  !(Receive&0x02)){UDPstart(); Len=Len1=0;Receive &= 0xFB;}  //若发现UDP处于未打开状态则自动开启
  49.           delayms(50);//50ms为以周期监测、判断
  50.     }


      无线透传。这是一个很实用的功能,虽然本次程序没用到,但还是简单说下,它是直接将wifi模块变成一个无线串口,前提是先设置好两方的端口号等。开启过程很简单,只要2个ESP8266能互相ping通就可使用。
      例如A处于AP模式,B是STA模式接到A模块上,假设此时A的AP IP地址为192.168.4.1,B的STA IP为192.168.4.2,则A的设置:
AT+CIPSTART="UDP","192.168.4.2",40000,40000,0
AT+CIPMODE=1
AT+CIPSEND
     B的设置:
AT+CIPSTART="UDP","192.168.4.1",40000,40000,0
AT+CIPMODE=1
AT+CIPSEND
      此外两者均需处于单链接模式AT+CIPMUX=0设置单链接模式,发送“+++”连续3个+终止透传,此时方可恢复正常操作,否则均将被当作数据发出去。       利用此功能可以将有线串口变为无线,比如可以无线下载STM32的程序、远程串口数据监视(数据量较大时)等。

三、数据解析及命令执行

       一串控制指令可以以直白的文字或英文字母来表述,但不适用于大信息量的控制,比如要控制100个灯的不同亮度,是否需要像这样呢?-> LED1:25 LED2:40 LED3:70….(25表示25%亮度,其余同理)
那得发送800个字节,或者分多次逐步发,所以就需考虑使用规约来,一串16进制数,按特定的格式放入数值,对方解析也是按此格式来。下面是个本程序里一个简化的规约,从简洁性及安全性上优化了下(其实主要是为了处理简单):


      本质上主要是5部分:功能说明+要发给谁+要控制什么东西+怎么控制+数据校验(全部是以16进制数表示)
      如:06 01 09 01 FF 81,06有6个字节,01表示功能(自己定义),09表示第9个控制器,01表示这个控制器的第一个设备,FF表示全亮或开,81则是校验码。
开始字节:表示这串指令有多长
功能码:当前这个指令时什么用处,是为了读取设备状态还是遥控它。
目标控制器地址:有的目标控制器地址不止控制一个设备,比如同时控制房间1的灯和房间2的灯。假如收到不是给自己的的命令则考虑丢掉或转发。
被控设备地址:与上面的目标控制器地址是不同的,如上面的房间1的灯就是被控设备地址,此处假设为1,表示目标控制器里的第一个可控对象。
写入的值:写入值为255(0xFF)表示全亮,0为灭,或者写入值不止一个字节,则可以搞定时之类的更多功能(如定时开关,则第一个字节表示定时多久关机、第二字节表示定时多久开机。若每个控制器能有准确的时钟则可以直接发送时间节点),完全靠自己去定义。
CRC8校验:为了保证数据的可靠性将前面一串数据进行计算,其实可以采用和校验、CRC16、CRC32校验,为了节省单片资源最后折中选择CRC8。

      有了规约的“规定”,不仅处理数据清晰明了,且可靠性也提高了,同一个大网下基本不会出现误控。解析指令时则是按照这样的流程:先判断数据是否可用,校验是否对、长度是否对等;接着看是不是给自己的,不是则丢掉或转发;最后根据被控设备地址对其进行控制。
       程序里也没什么需解释的,对串口里收到的数据逐个提取,其次是对不同的功能码可能会有不同长度的“写入值”(看怎么定义了),准确地控制好读取即可,不要识别错误即可,本来是读取功能码,却被程序当成地址读进去。程序里主要用了switch…case语句。

●Android软件部分

       安卓部分除了数据解析部分(和上面那个同样道理,按定义的规定解析或发出控制命令),就剩底层通讯及交互界面。
       安卓方面我也是半路出家,只是在网上找资料学的点皮毛,系统性不强,以做这个手机控制端为目的去学的,不对之处只好让大家批评纠正了。
       编译器是用eclipse,当然微软的VS也能编译安卓软件,没玩过。eclipse基础部分我也没必要说了,直接说主要的吧。
       软件在打开后自动发指令搜索在线设备(IP X.X.X.255即可发往此网段内全部设备),也可手动切换被控设备,实现开启即用。有规约的控制即无需任何密码之类的繁琐操作。

      利用socket通信,主要在udpthread.java里,try尝试建立UDP:
  1. public boolean ConnectSocket() {
  2.                 boolean result = false;
  3.                 try {
  4.                         if (rUdp == null) {
  5.                                 rUdp = new DatagramSocket(localPort);
  6.                                 // System.out.println(" localport set ......"+localPort);/////////////////////
  7.                         }
  8.                         if (rPacket == null)
  9.                                 rPacket = new DatagramPacket(rBuffer, rBuffer.length);
  10.                         startThread();
  11.                         result = true;
  12.                 } catch (SocketException se) {
  13.                         DisConnectSocket();
  14.                         System.out.println("open udp port error:" + se.getMessage());
  15.                 }
  16.                 return result;
  17.         }


       接着是UDP发送数据,先用新开个DatagramSocket(发送数据的socket),再往sPacket填充数据及端口配置等:
  1. sPacket = new DatagramPacket(sBuffer,sBuffer.length,InetAddress.getByName(remoteIP),remotePort);

     再调用类发送数据:sUdp.send(sPacket); sUdp.close();
     发送数据后若未收到回复则会连续尝试3次,均无返回时则认为对方接收失败。
     接收数据则是放入线程中处理:
  1. @Override
  2.         public void run() {
  3.                 while (Thread.currentThread() == rthread) {
  4.                         try {
  5.                                 recvData();
  6.                                 vhandler.sendEmptyMessage(0);
  7.                                 Thread.sleep(100);
  8.                         } catch (InterruptedException e) {
  9.                                 e.printStackTrace();
  10.                         }
  11.                 }
  12.         }
  13. private void recvData() {
  14.                 int Len=0;
  15.                 try {
  16.                         rUdp.receive(rPacket);
  17.                         //传递接收的数据
  18.                         Len=rPacket.getLength();
  19.                         fBuffer = new byte[Len];
  20.                         System.arraycopy(rBuffer, 0, fBuffer, 0,Len);
  21.                         Data.setIndata(fBuffer);//传递值
  22.                         if (isRHex)
  23.                                 sRecvData = recvHexData(Len);
  24.                         else
  25.                                 sRecvData = new String(rPacket.getData(),0,Len,currentRCodes).trim();
  26.                         //System.out.println(sRecvData);//显示收到的数据,
  27.                         //显示 发送IP  对方端口  时间  数据
  28.                         sRecvData = String.format("[%s:%d]%s-%s", rPacket.getAddress()
  29.                                         .getHostAddress(), rPacket.getPort(), sdf
  30.                                         .format(new Date()), sRecvData);
  31.                 } catch (IOException ie) {
  32.                         System.out.println("recvdata error:" + ie.getMessage());
  33.                         // Toast.makeText(ct, "接收数据错误:" + ie.getMessage(),
  34.                         // Toast.LENGTH_LONG);
  35.                 }
  36.         }


      在收到新数据后放到TEXT控件里,TEXT加入文字变化监测,当其中文字被刷新后就会产生一个事件,此时则可在其中处理收到的数据。
界面部分则需要很多基础才行,为此找了很多网上资料,哪个控件适用哪个布局合理......。
     比如上次做的手机风扇遥控界面,既然是手机界面控制,所以最好只需一个界面就能快捷操作所有必要的功能,而且简洁点好,所以加入了滑动条代替下拉菜单,档位直接选择,开机自动与风扇“握手”,并将手机时间下发到风扇控制单片机里,若风扇不在线则显示红色,调节时间用滑块不好设置,用按钮不是很直观,于是采用一个圆圈,顺时针沿着这个圆滑动则增加定时时间,逆时针则减少,通过判断手势运动的大致方向来决定,目前精度及加减速度还需进一步优化,加入滤波效果应该会更好。如下图:


      上图圆圈会根据总定时时间与剩余时间的比值按比例缩减。自动模式下回根据晚上时间来调节是否开启(半夜凉时则关闭或进一步降低单位风量)。操作界面利用滑动代替按钮,省去了部分界面空间,且不同的工作模式下去掉不必要的按钮等。

       如Period那上下滑动修改,在移动事件中监测手指移动到何处坐标,进行Period的增减:
  1. @Override
  2.         public boolean onTouch(View arg0, MotionEvent arg1) {
  3.             float y = arg1.getY();
  4.                  switch (arg1.getAction()) {                                
  5.                  case MotionEvent.ACTION_DOWN:                                        //按下        
  6.                          timer_revisable=false;
  7.                          break;                                
  8.                          case MotionEvent.ACTION_MOVE:                                        //移动                        
  9.                                         if(y-y_last>8){if(period_T!=0)period_T--;
  10.                                          period.setText(""+period_T);
  11.                                          //重新计算周期
  12.                                                 period_T1=dutyratio*period_T/100;
  13.                                                 period_duty.setText("周期风(/min)- Duty "+period_T1);
  14.                                          y_last=y;
  15.                                         }
  16.                                         else if(y_last-y>8){if(period_T!=255)period_T++;
  17.                                                 period.setText(Integer.toString(period_T));
  18.                                                 period_T1=dutyratio*period_T/100;
  19.                                                 period_duty.setText("周期风(/min)- Duty "+period_T1);
  20.                                          y_last=y;
  21.                                         }
  22.                                  break;                                
  23.                                  case MotionEvent.ACTION_UP:                                        //抬起                        
  24.                                          timer_revisable=true;
  25.                                          break;                                
  26.                                          }                                
  27.                 // TODO 自动生成的方法存根
  28.                 return false;
  29.         }


         在发送数据后会等待对方返回数据,若连续发3次均无返回则认为对方无法收到。发送握手去测试部分:
  1. private void handshake() {
  2.                 //发送握手  查询子站数据指令  IP地址
  3.                 byte[] temp_data=new byte[255];//临时数组                                        
  4.                 temp_data[0]=11;temp_data[1]=0;temp_data[2]=0x45;
  5.                 temp_data[3]=Target;//对方地址码
  6.                 temp_data[4]=Self;//本机地址码
  7.                 temp_data[5]=Integer.valueOf(0x81).byteValue();
  8.                 //发送本机IP地址
  9.                 temp_data[6]=net_1;temp_data[7]=net_2;
  10.                 temp_data[8]=net_3;temp_data[9]=net_4;
  11.                 //其他数字
  12. temp_data[10]= 0x2c;temp_data[11]=0x5A;temp_data[12]=5;
  13.                 temp_data[13]= Integer.valueOf(0xA1).byteValue();
  14.                 temp_data[10]=Integer.valueOf(CRC8(temp_data,14)).byteValue();
  15.                 outdata=new byte[11];
  16.                 System.arraycopy(temp_data, 0, outdata, 0,11);//拷贝数据到发送缓冲区
  17.                 udp.setOutdata(outdata);
  18.                 new UdpSend("").start();
  19.                 receive_OK=0;//开启发送后返校
  20.         }


       定时器一直开启,在receive_OK<10时执行定时器里的程序,也就是直接将receive_OK赋值大于10则将不执行定时器里的程序,不过这样做操作虽方便,但是可能会增加系统的功耗。
  1. //定时器作用:发送完指令后等待返校(每3s一次)
  2.                 Timer Timer1 = new Timer();  
  3.                  TimerTask task = new TimerTask() {  
  4.               @Override  
  5.                      public void run() {  
  6.                       if( receive_OK<10){
  7.                               receive_OK++;
  8.                               if(receive_OK==2){//再发一次
  9.                                           udp.setOutdata(outdata);
  10.                                 new UdpSend("").start();
  11.                               }
  12.                               else if(receive_OK>3){receive_OK=14;link_OK=false;
  13.                              //targetNo.setText("×");
  14.                                 //targetNo.setBackgroundColor(0xAAFF3333);
  15.                               new AnotherTask().execute("×");  
  16.                               }
  17.                       }
  18.                    }  
  19.           };

其次多线程由于线程安全是不能够直接更新界面的控件,控件的更新须由主线程来完成。 不过也可通过Handler 这个对象来对主线程中的控件进行更新。

       下面是程序源码,不过若是真想自己做的话就没必要看它们了,理解总的思路自己做才能做得好:




       最后,单片机的程序其实只要性能满足要求,51、AVR等都可以,掌握程序原理就可以移植到任何单片机上(前提是会它的基本硬件使用),之前改装的那风扇游人说不值,但我觉得值,里面的控制部分稍改程序就可以控制其它220V的家用电器等,以上是个人愚见,希望对制作wifi控制类的东西有一点点参考价值。
本文内容包含图片或附件,获取更多资讯,请 登录 后查看;或者 注册 成为会员获得更多权限
本帖最近打赏记录:共27条打赏M币+89专家+1
离线bg4rff

发帖
1518
M币
1237
专家
8
粉丝
32
只看该作者 1楼 发表于: 2016-01-26
请登录后查看
离线jinpeng.li

发帖
1631
M币
280
专家
2
粉丝
37
只看该作者 2楼 发表于: 2016-01-27
请登录后查看
离线z1w2

发帖
6339
M币
6976
专家
2
粉丝
51
只看该作者 3楼 发表于: 2016-01-27
请登录后查看
离线lizhifeng

发帖
3505
M币
2564
专家
1
粉丝
61
只看该作者 4楼 发表于: 2016-01-27
请登录后查看
离线200birds

发帖
11518
M币
2038
专家
94
粉丝
500
只看该作者 5楼 发表于: 2016-01-27
请登录后查看
离线风中有你

发帖
121
M币
10
专家
3
粉丝
11
只看该作者 6楼 发表于: 2016-01-27
请登录后查看
离线czthisme
发帖
950
M币
944
专家
1
粉丝
56
只看该作者 7楼 发表于: 2016-01-27
请登录后查看
离线hongo

发帖
53039
M币
149632
专家
275
粉丝
15182
只看该作者 8楼 发表于: 2016-01-27
请登录后查看
离线muer1
发帖
6
M币
-490
专家
0
粉丝
0
只看该作者 9楼 发表于: 2016-01-29
请登录后查看
快速回复
限80 字节
温馨提示:欢迎交流讨论,请勿发布纯表情、纯引用等灌水帖子;以免被删除
 
上一个 下一个