前记
最近老是找不到风扇遥控,搞得想整个手机APP远程控制风扇。说搞就搞,远程控制流程稍微一思考就定下来了。首先是信号转发设备通过WiFi模块连接服务器端,手机端再通过APP向服务器端发送控制指令,服务器端将控制指令下发到信号转发设备上,信号转发设备将控制指令翻译成红外信号发送给红外设备。制作这样一个产品需要几个技术栈:
- 服务端程序开发
- APP端/Web端应用开发
- 硬件相关知识及嵌入式开发
作为一个互联网民工,前两样技能都不是问题。唯独第三样技能接触得比较少。最终虽然把产品做出来了,但是对硬件和嵌入式开发的理解还只是半知半解。此文会重点记录开发过程中碰到的硬件和嵌入式开发的相关知识,权当是给自己做个笔记吧。
硬件选型
产品涉及到的硬件有WiFi模块和红外收发模块及MCU。WiFi模块功能就不用说了,红外接收功能用于解码遥控的信号,MCU用于接收WiFi信号并翻译控制红外发射器发送信号。 咨询做硬件的朋友后经过一番搜索筛选,最终选用ESP8266模块。ESP8266既有WiFi功能且有MCU可以烧写程序,这样一来就可以不用单独买一个MCU模块了。最终在某宝买了个带NodeMCU开发板很红外收发器的ESP8285模块(8285和8266的主要区别是flash大了1mb)。买到的模块是这样的:
USB转TTL模块
USB转TTL基本是嵌入式开发必备的,用来烧写或者调试嵌入式程序经常需要用到。具体功能我也不太懂,大概就是将USB接口转为类似RS232的串口信号。 TTL接口和ESP8266的接线如下: 5v – 5v rx – tx tx – rx GND – GND
开发调试
驱动
ESP8266和USB转TTL模块接好线之后插上电脑,需要安装驱动才能识别,驱动下载链接:https://sparks.gogo.co.nz/ch340.html。
调试工具
驱动安装好之后,下载一个串口调试软件,配置参数如下,然后打开串口。ESP8266自带的固件支持AT命令,支持的所有AT命令可以参考官方PDF文档。这里我们只需要用到AT+RST命令来测试我们前面的步骤是否都成功了。输入AT+RST命令之后,ESP8266会重启并且输出一系列信息,如果能在调试工具上看到串口输出的信息,就证明我们前面的操作都成功了。
引脚电平
这里需要注意的是ESP8266的IO0引脚。IO0引脚在短接GND的情况下是烧写模式,这个模式下进行调试是不会有任何反应的,需要去掉短接才能进行调试。
波特率
不管是ESP8266还是ESP8285,串口波特率都是115200,不用尝试其他的值。由于前期我没有足够的经验和知识,在调试这个步骤尝试换波特率进行连接,浪费了不少时间。
烧写固件
由于平时也有写一些Python代码,然后发现这个模块可以烧写Python运行环境–MicroPython,所以模块拿到手就立即烧了个MicroPyton环境。固件可以从MicroPython官网找到。MicroPython官方是推荐使用esptool烧写固件的。为了节省学习成本,我还是下载了个桌面客户端烧写程序。烧写时的配置: 如果是8266,选1MByte就好。如果烧写没反应,试一下拔插USB。且需要注意在拉低IO0引脚电平的情况下才能将固件烧写进去。
恢复固件
由于python的内存垃圾回收机制会导致程序短暂中断,而红外信号又是对频率比较敏感的,所以用python来进行红外信号收发可能不太合适。然后我又尝试刷回原来的固件。ESP8266的固件可以在官方网站找到。我下载的是ESP8266 NonOS AT Bin V1.7.4,下载下来是个压缩包,里面是有刷写说明的。打开压缩包at文件夹下的readme.md文件,可以看到如下内容:
### Flash size 8Mbit: 512KB+512KB boot_v1.2+.bin 0x00000 user1.1024.new.2.bin 0x01000 esp_init_data_default.bin 0xfc000 blank.bin 0x7e000 & 0xfe000
我们只需要安装这个列表配置烧写工具就行了。由于我们使用了NodeMCU开发板,所以不能使用官方的下载工具,要使用NodeMCU的下载工具,记得下载是需要短接IO0和GND引脚的。我的成功烧写配置如下:
Arduino配置
下载安装Arduino IDE之后还需要安装相关的插件才能对esp8266进行烧写程序,插件的安装过程可以参考这篇文章。大家配置好之后可以尝试烧写个简单的串口文字打印程序调试一下,我这里就不再详解代码相关的知识了。要注意烧写完程序之后重新上电才会运行,并且要断开IO0和GND引脚的短接。
IR库
从零手写代码完成红外信号的编解码是相当麻烦的,最好是找一个轮子支持一下。幸好github上有个好轮子https://github.com/crankyoldgit/IRremoteESP8266。可以按照这个IR库的说明安装并运行实例程序。
程序实现
我最终实现了我的想法:ESP8266上电连接WiFi并建立TCP连接到服务器,服务器接收App的HTTP请求并转发控制请求给ESP8266的TCP连接,下面贴上ESP8266的示例代码。由于我家的风扇品牌没有在IR支持的列表里,且不是NEC协议,所以只能用官方的示例代码接收遥控发出的红外信号打印并且记录下来,将每一个按键对应的信息都写死在程序里:
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include <ESP8266WiFi.h>
const uint16_t kIrLed = 4; // ESP8266 GPIO pin to use. Recommended: 4 (D2).
IRsend irsend(kIrLed); // Set the GPIO to be used to sending the message.
uint16_t power[107] = {2432, 3254, 430, 1240, 1232, 422, 1228, 390, 428, 1210, 1262, 390, 1262, 388, 1262, 390, 1262, 390, 1262, 420, 398, 1210, 432, 1208, 430, 1208, 430, 1208, 430, 1210, 430, 1208, 1266, 414, 398, 6898, 2464, 3254, 430, 1210, 1262, 390, 1262, 388, 430, 1206, 1264, 388, 1262, 422, 1232, 388, 1262, 422, 1230, 420, 398, 1208, 430, 1208, 430, 1208, 430, 1208, 432, 1208, 430, 1208, 1266, 414, 400, 6896, 2464, 3252, 432, 1208, 1264, 388, 1266, 420, 398, 1240, 1232, 420, 1230, 422, 1230, 388, 1262, 420, 1236, 386, 432, 1208, 430, 1210, 430, 1208, 430, 1210, 430, 1210, 430, 1210, 1264, 418, 398};
uint16_t plus[107] = {2462, 3258, 400, 1238, 1260, 392, 1236, 416, 402, 1236, 1262, 392, 1234, 418, 1262, 388, 1260, 392, 1264, 388, 1244, 410, 400, 1238, 402, 1238, 402, 1238, 402, 1238, 1236, 446, 1206, 412, 402, 6928, 2464, 3256, 402, 1236, 1264, 390, 1262, 392, 402, 1236, 1262, 392, 1260, 390, 1262, 390, 1260, 390, 1262, 390, 1262, 390, 402, 1238, 402, 1238, 400, 1238, 402, 1236, 1264, 390, 1250, 398, 400, 6930, 2464, 3254, 402, 1268, 1206, 416, 1264, 390, 402, 1236, 1262, 392, 1260, 390, 1262, 390, 1262, 390, 1236, 416, 1260, 390, 404, 1236, 400, 1240, 402, 1238, 400, 1238, 1262, 392, 1236, 412, 402};
uint16_t subtract[107] = {2462, 3256, 430, 1210, 1262, 390, 1260, 392, 428, 1212, 1262, 390, 1260, 392, 1260, 390, 1262, 390, 1262, 390, 1260, 392, 428, 1212, 430, 1242, 396, 1210, 1264, 390, 1260, 392, 428, 1208, 430, 6900, 2460, 3258, 430, 1210, 1262, 390, 1260, 392, 428, 1210, 1262, 392, 1262, 388, 1262, 392, 1260, 390, 1262, 390, 1262, 390, 428, 1210, 430, 1210, 430, 1208, 1262, 392, 1260, 392, 426, 1208, 428, 6932, 2432, 3256, 430, 1210, 1262, 390, 1262, 388, 430, 1210, 1264, 388, 1262, 388, 1264, 388, 1264, 420, 1230, 390, 1262, 422, 396, 1210, 430, 1210, 430, 1240, 1232, 390, 1262, 390, 428, 1208, 430};
uint16_t updown[107] = {2464, 3256, 406, 1234, 1262, 392, 1236, 416, 402, 1236, 1264, 388, 1264, 390, 1238, 446, 1204, 416, 1238, 416, 402, 1236, 1236, 416, 402, 1236, 402, 1236, 402, 1238, 402, 1236, 402, 1234, 402, 6954, 2434, 3258, 404, 1236, 1262, 390, 1262, 424, 368, 1238, 1236, 416, 1236, 416, 1260, 392, 1260, 392, 1236, 448, 370, 1236, 1236, 416, 402, 1236, 404, 1236, 402, 1238, 402, 1238, 404, 1234, 402, 6928, 2464, 3256, 402, 1236, 1238, 416, 1260, 392, 402, 1236, 1260, 392, 1260, 392, 1262, 390, 1238, 416, 1260, 390, 402, 1270, 1204, 416, 402, 1236, 402, 1238, 400, 1238, 402, 1238, 402, 1234, 402};
uint16_t leftright[107] = {2460, 3258, 428, 1210, 1264, 390, 1262, 390, 428, 1212, 1260, 392, 1260, 390, 1260, 392, 1262, 392, 1262, 392, 426, 1212, 428, 1210, 1264, 390, 428, 1212, 428, 1212, 428, 1212, 428, 1208, 428, 6902, 2462, 3258, 428, 1210, 1262, 392, 1260, 392, 426, 1244, 1230, 422, 1228, 392, 1260, 392, 1260, 394, 1258, 394, 428, 1212, 426, 1214, 1258, 424, 396, 1212, 428, 1212, 430, 1210, 428, 1226, 410, 6902, 2460, 3256, 430, 1242, 1230, 392, 1258, 394, 428, 1212, 1260, 394, 1258, 392, 1260, 392, 1258, 394, 1260, 394, 424, 1246, 394, 1212, 1260, 392, 426, 1214, 426, 1214, 426, 1212, 426, 1210, 426};
uint16_t baby[107] = {2434, 3284, 398, 1242, 1234, 418, 1234, 450, 366, 1242, 1232, 452, 1202, 450, 1200, 420, 1232, 420, 1234, 420, 1234, 418, 400, 1240, 400, 1240, 1234, 450, 1202, 450, 366, 1240, 400, 1238, 398, 6932, 2436, 3284, 400, 1272, 1200, 422, 1232, 420, 398, 1240, 1234, 418, 1232, 450, 1202, 452, 1200, 422, 1232, 420, 1232, 420, 398, 1242, 398, 1240, 1234, 420, 1232, 420, 398, 1242, 400, 1236, 398, 6932, 2434, 3284, 400, 1240, 1232, 420, 1234, 450, 368, 1242, 1230, 454, 1198, 420, 1232, 420, 1232, 420, 1232, 418, 1234, 450, 366, 1274, 366, 1240, 1234, 420, 1234, 420, 398, 1240, 398, 1238, 398};
uint16_t timing[107] = {2434, 3282, 398, 1240, 1238, 416, 1234, 418, 404, 1234, 1236, 422, 1230, 416, 1234, 420, 1230, 420, 1234, 418, 400, 1238, 428, 1210, 428, 1246, 1202, 418, 428, 1218, 420, 1210, 428, 1208, 426, 6902, 2436, 3282, 400, 1240, 1236, 416, 1236, 416, 428, 1210, 1236, 416, 1236, 416, 1234, 450, 1202, 450, 1202, 450, 398, 1208, 426, 1212, 400, 1238, 1262, 392, 428, 1208, 402, 1240, 400, 1236, 400, 6930, 2440, 3280, 424, 1216, 1262, 406, 1222, 436, 408, 1208, 1236, 448, 1204, 450, 1202, 450, 1204, 416, 1234, 418, 430, 1208, 402, 1238, 400, 1272, 1206, 450, 394, 1210, 424, 1214, 400, 1236, 400};
const char* host = "139.186.70.***";
void setup() {
irsend.begin();
#if ESP8266
Serial.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY);
#else // ESP8266
Serial.begin(115200, SERIAL_8N1);
#endif // ESP8266
//while (!Serial) // Wait for the serial connection to be establised.
// delay(50);
WiFi.begin("ABCDE", "***");
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Connected, IP address: ");
Serial.println(WiFi.localIP());
}
void sendIR(char c) {
switch(c){
case 'p':
irsend.sendRaw(power, 107, 38);
break;
case 'q':
irsend.sendRaw(plus, 107, 38);
break;
case 's':
irsend.sendRaw(subtract, 107, 38);
break;
case 'u':
irsend.sendRaw(updown, 107, 38);
break;
case 'l':
irsend.sendRaw(leftright, 107, 38);
break;
case 'b':
irsend.sendRaw(baby, 107, 38);
break;
case 't':
irsend.sendRaw(timing, 107, 38);
break;
default:
break;
};
}
void loop() {
WiFiClient client;
Serial.printf("\n[Connecting to %s ... ", host);
if (client.connect(host, 5000))
{
Serial.println("connected]");
Serial.println("[Sending a request]");
client.print(String("from-big-head\n"));
Serial.println("[Response:]");
while (client.connected() || client.available())
{
if (client.available())
{
String line = client.readStringUntil('\n');
sendIR(line[0]);
}
}
client.stop();
Serial.println("\n[Disconnected]");
}
else
{
Serial.println("connection failed!]");
client.stop();
}
delay(1000);
}
引用
- https://micropython.org
- https://github.com/espressif/esptool/
- https://github.com/nodemcu/nodemcu-flasher
- https://www.espressif.com/en/support/download/at
- https://www.qutaojiao.com/11532.html
- https://github.com/crankyoldgit/IRremoteESP8266
|