|
|
—
本帖被 香瑶 设置为精华,作者+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写命令,不去管其返回何值)。
- //串口接收中断
- //Len串口数据长度 rbuffer缓冲数组 Receive标志寄存器 T2_period定时器已定时时长,即定时多少个周期
- ISR ( SIG_UART_RECV )
- {
- unsigned char value;
- UCSRB&=~ ( 1<<RXCIE ); //关接受结束中断使能
- value=UDR; ///注意串口数据每次必须读取否则会导致程序出错
- if(Len<Receive_length*2)rbuffer[Len++]=value;
- if( !(Receive&0x02)){Receive|=0x02;} //进入定时器检测结束
- T2_period=0;//TCNT2=0;
- UCSRB|= ( 1<<RXCIE ); // 开接受结束中断使能
- }
- /************************************
- *功 能:定时器2比较中断,为判断串口数据是否接收完
- *备 注:中断服务程序 period 10ms
- *Judge_End判断多少周期后无数据认为接收完
- *************************************/
- SIGNAL(SIG_OUTPUT_COMPARE2)
- {
- int i=0;
- wdt_reset(); //喂狗指令;每次唤醒后喂狗
- if(WaitTime){WaitTime++;
- }
- if(Receive&0x02){
- T2_period++;
- if(T2_period>Judge_End)
- {T2_period=0;
- Receive &= 0xFD;//关闭识别
-
- rbuffer[Len]=0;
- if((Receive & 0x04)==0){//读取了才填充,数据处理后会将bit2置0
- Len1=Len;
- if(Len<Receive_length)Len=0;
- //将rsbuffer->com_data 注意Len1 超范围访问
- if(Len1<=Receive_length)//在没有超出缓冲区长度时才复制数据
- {
- while(i<Len1)
- {rbuffer[i+Receive_length]=rbuffer[i];i++; }
- }
- else {Len1=0; } //丢弃此包
- }
- Receive |= 0x04;//数据准备好标记,主程序可以处理数据了
- }
- }
- TIFR |= 0x80;//清定时器2标志位
- }
二、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端口,再发送。 下面是发送函数:
- unsigned char UDPsend(unsigned char *p)
- {
- unsigned char length=0,i=0;
- /********* 先发送数据长度 CIPSEND***********/
- if(Wifi_status&0x20)length=*p; //code的长度
- else length=strlen(p);
- strcpy(SendNum,"AT+CIPSEND=");
- //********* 将数字变为字符串接到SendNum数组后 *********//
- while(length!=0)
- {
- SendNum[29-i]=length%10+'0';//利用数组SendNum的29、28、27字节暂存数字变成的字符,此时数字的倒序的
- length/=10;i++;
- }
- length=11;
- while(i>0)//将上面转换的字符倒序
- {
- SendNum[length++]=SendNum[30-i];
- i--;
- }
- SendNum[length]=0;//加数组结束符
- Wifi_status &=0x7F;//关闭返校检测
- Judge_End=3;//////缩短结束判断,判断暂时缩短为3个周期
- CMD_W1(SendNum);//串口发送出去
- /************等待串口返回数据***********/
- while(i++<100)
- {if(Receive&0x04)//有数据
- {
- //若返回了‘>’即0x3E表示可以发送
- if(rbuffer[7+Receive_length]==0x20 && rbuffer[6+Receive_length]==0x3E)
- {i=0;}
- else if(rbuffer[Len1+Receive_length-1]==0x20 && rbuffer[Len1+Receive_length-2]==0x3E)//两种判别
- i=0;
- Receive&= 0xFB;//清零标志
- break;
- }
- delayms(5);
- }
- if(!i){//可以发送字节
- if(Wifi_status&0x20)
- {DataLength=*p; Wifi_status &= 0xDF;}
- CMD_W1(p);//串口发送出去
- DataLength=0;
- while(i++<200)
- {//send_date(Receive );///////////////////调试用
- if(Receive&0x04)//有数据 此处曾出现无法识别Receive&0x04,导致时间过长或死循环(编译器问题导致)
- {
- Receive&= 0xFB;//清零标志
-
- if(rbuffer[Len1-3]=='K' && rbuffer[Len1-4]=='O')//返回OK表示以发完。由于之前写成Len导致死循环
- {i=1;
- break;}
- }
- delay_1ms();
- }
- }
- else {i= 0; }
- Wifi_status |=0x80;//关闭返校检测
- Judge_End=18;/////恢复判断时长
- return i;
- }
接收则很简单,只要本地端口号对,收到数据后ESP8266会主动发送数据到串口,单片机直接提取即可。以下是主函数的主循环里读取接收的数据(主体框架):
- while(1)
- {
- if(Len>Receive_length*2-100 && (Wifi_status&0x04)!=0)Len=0;//数据流过大清零
-
- if(Receive&0x04)//标记有数据时
- {
- //查找+IPD
- for(i=0;i<Len1;i++)
- {
- if(rbuffer[i+Receive_length]==0x2B && rbuffer[i+1+Receive_length]==0x49 && rbuffer[i+3+Receive_length]==0x44)
- {
-
- Cmd_start=Cmd_end=0;
- //先校验字符数量是否一致
- //第一字节是总数据长度,提取它
- i += 5;
- while(rbuffer[i+Receive_length]!= ':' &&Cmd_end++<10)
- {
- Cmd_start=Cmd_start*10+rbuffer[i+Receive_length]-'0';
- i++;
- }
- if(Cmd_start==rbuffer[i+1+Receive_length]) //字节数量一致则有效
- {
- //先判断最后一字节的CRC校验码是否对,不对则舍弃数据
- if(rbuffer[Cmd_end+Receive_length+4]==CRC8(rbuffer+Receive_length+Cmd_start,4+Cmd_end-Cmd_start))//计算校验码并且校验通过
- {
- if(rbuffer[Cmd_start+Receive_length+3]==Self)//是发给自己则分析数据
- {
- //提取里面的控制指令并执行
- }
- else //转发出去
- {
- //若不是发给自己的则将数据转发出去,以便最终的设备收到数据。
- }
-
- }
- else
- {
- //校验码不对舍弃本条指令
- }
-
- }
- }
- }
- Receive&= ~0x04;//清零标志
- if(Len!=0 && !(Receive&0x02)){Receive|=0x02;T2_period=0;TCNT2=0;}//数据读完后各重要寄存器清零
- }
- if(!(Wifi_status&0x08) && !(Receive&0x02)){UDPstart(); Len=Len1=0;Receive &= 0xFB;} //若发现UDP处于未打开状态则自动开启
- delayms(50);//50ms为以周期监测、判断
- }
无线透传。这是一个很实用的功能,虽然本次程序没用到,但还是简单说下,它是直接将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:
- public boolean ConnectSocket() {
- boolean result = false;
- try {
- if (rUdp == null) {
- rUdp = new DatagramSocket(localPort);
- // System.out.println(" localport set ......"+localPort);/////////////////////
- }
- if (rPacket == null)
- rPacket = new DatagramPacket(rBuffer, rBuffer.length);
- startThread();
- result = true;
- } catch (SocketException se) {
- DisConnectSocket();
- System.out.println("open udp port error:" + se.getMessage());
- }
- return result;
- }
接着是UDP发送数据,先用新开个DatagramSocket(发送数据的socket),再往sPacket填充数据及端口配置等:
- sPacket = new DatagramPacket(sBuffer,sBuffer.length,InetAddress.getByName(remoteIP),remotePort);
再调用类发送数据:sUdp.send(sPacket); sUdp.close(); 发送数据后若未收到回复则会连续尝试3次,均无返回时则认为对方接收失败。 接收数据则是放入线程中处理:
- @Override
- public void run() {
- while (Thread.currentThread() == rthread) {
- try {
- recvData();
- vhandler.sendEmptyMessage(0);
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- private void recvData() {
- int Len=0;
- try {
- rUdp.receive(rPacket);
- //传递接收的数据
- Len=rPacket.getLength();
- fBuffer = new byte[Len];
- System.arraycopy(rBuffer, 0, fBuffer, 0,Len);
- Data.setIndata(fBuffer);//传递值
- if (isRHex)
- sRecvData = recvHexData(Len);
- else
- sRecvData = new String(rPacket.getData(),0,Len,currentRCodes).trim();
- //System.out.println(sRecvData);//显示收到的数据,
- //显示 发送IP 对方端口 时间 数据
- sRecvData = String.format("[%s:%d]%s-%s", rPacket.getAddress()
- .getHostAddress(), rPacket.getPort(), sdf
- .format(new Date()), sRecvData);
- } catch (IOException ie) {
- System.out.println("recvdata error:" + ie.getMessage());
- // Toast.makeText(ct, "接收数据错误:" + ie.getMessage(),
- // Toast.LENGTH_LONG);
- }
- }
在收到新数据后放到TEXT控件里,TEXT加入文字变化监测,当其中文字被刷新后就会产生一个事件,此时则可在其中处理收到的数据。 界面部分则需要很多基础才行,为此找了很多网上资料,哪个控件适用哪个布局合理......。 比如上次做的手机风扇遥控界面,既然是手机界面控制,所以最好只需一个界面就能快捷操作所有必要的功能,而且简洁点好,所以加入了滑动条代替下拉菜单,档位直接选择,开机自动与风扇“握手”,并将手机时间下发到风扇控制单片机里,若风扇不在线则显示红色,调节时间用滑块不好设置,用按钮不是很直观,于是采用一个圆圈,顺时针沿着这个圆滑动则增加定时时间,逆时针则减少,通过判断手势运动的大致方向来决定,目前精度及加减速度还需进一步优化,加入滤波效果应该会更好。如下图:
上图圆圈会根据总定时时间与剩余时间的比值按比例缩减。自动模式下回根据晚上时间来调节是否开启(半夜凉时则关闭或进一步降低单位风量)。操作界面利用滑动代替按钮,省去了部分界面空间,且不同的工作模式下去掉不必要的按钮等。
如Period那上下滑动修改,在移动事件中监测手指移动到何处坐标,进行Period的增减:
- @Override
- public boolean onTouch(View arg0, MotionEvent arg1) {
- float y = arg1.getY();
- switch (arg1.getAction()) {
- case MotionEvent.ACTION_DOWN: //按下
- timer_revisable=false;
- break;
- case MotionEvent.ACTION_MOVE: //移动
- if(y-y_last>8){if(period_T!=0)period_T--;
- period.setText(""+period_T);
- //重新计算周期
- period_T1=dutyratio*period_T/100;
- period_duty.setText("周期风(/min)- Duty "+period_T1);
- y_last=y;
- }
- else if(y_last-y>8){if(period_T!=255)period_T++;
- period.setText(Integer.toString(period_T));
- period_T1=dutyratio*period_T/100;
- period_duty.setText("周期风(/min)- Duty "+period_T1);
- y_last=y;
- }
- break;
- case MotionEvent.ACTION_UP: //抬起
- timer_revisable=true;
- break;
- }
- // TODO 自动生成的方法存根
- return false;
- }
在发送数据后会等待对方返回数据,若连续发3次均无返回则认为对方无法收到。发送握手去测试部分:
- private void handshake() {
- //发送握手 查询子站数据指令 IP地址
- byte[] temp_data=new byte[255];//临时数组
- temp_data[0]=11;temp_data[1]=0;temp_data[2]=0x45;
- temp_data[3]=Target;//对方地址码
- temp_data[4]=Self;//本机地址码
- temp_data[5]=Integer.valueOf(0x81).byteValue();
- //发送本机IP地址
- temp_data[6]=net_1;temp_data[7]=net_2;
- temp_data[8]=net_3;temp_data[9]=net_4;
- //其他数字
- temp_data[10]= 0x2c;temp_data[11]=0x5A;temp_data[12]=5;
- temp_data[13]= Integer.valueOf(0xA1).byteValue();
- temp_data[10]=Integer.valueOf(CRC8(temp_data,14)).byteValue();
- outdata=new byte[11];
- System.arraycopy(temp_data, 0, outdata, 0,11);//拷贝数据到发送缓冲区
- udp.setOutdata(outdata);
- new UdpSend("").start();
- receive_OK=0;//开启发送后返校
- }
定时器一直开启,在receive_OK<10时执行定时器里的程序,也就是直接将receive_OK赋值大于10则将不执行定时器里的程序,不过这样做操作虽方便,但是可能会增加系统的功耗。
- //定时器作用:发送完指令后等待返校(每3s一次)
- Timer Timer1 = new Timer();
- TimerTask task = new TimerTask() {
- @Override
- public void run() {
- if( receive_OK<10){
- receive_OK++;
- if(receive_OK==2){//再发一次
- udp.setOutdata(outdata);
- new UdpSend("").start();
- }
- else if(receive_OK>3){receive_OK=14;link_OK=false;
- //targetNo.setText("×");
- //targetNo.setBackgroundColor(0xAAFF3333);
- new AnotherTask().execute("×");
- }
- }
- }
- };
其次多线程由于线程安全是不能够直接更新界面的控件,控件的更新须由主线程来完成。 不过也可通过Handler 这个对象来对主线程中的控件进行更新。 下面是程序源码,不过若是真想自己做的话就没必要看它们了,理解总的思路自己做才能做得好:
最后,单片机的程序其实只要性能满足要求,51、AVR等都可以,掌握程序原理就可以移植到任何单片机上(前提是会它的基本硬件使用),之前改装的那风扇游人说不值,但我觉得值,里面的控制部分稍改程序就可以控制其它220V的家用电器等,以上是个人愚见,希望对制作wifi控制类的东西有一点点参考价值。
|