1.介绍与准备
1.1项目背景与动机
在这个寒假中,我抽出了点的时间,利用ESP32制作了一个简易的电风扇。这个项目的动机源于对物联网技术的兴趣以及对智能家居的向往。通过整理并记录这个项目,我希望能够与对此感兴趣的朋友们分享我的经验和成果。
1.2.硬件与软件准备
硬件主要材料包括:esp32核心板一个、简易机械臂一个(包括两个舵机sg90和机械臂材料,ps:某宝可以直接购买获得)、L9110风扇模块电风扇一个、面包板(有需要的话)。
软件的话采用,使用VSCODE上的platformio插件进行arduino编程。
2.ESP32基础
2.1. 什么是ESP32?
ESP32是由乐鑫信息科技(Espressif Systems)开发的一款低成本、低功耗的系统级芯片(SoC),广泛应用于物联网(IoT)设备。它整合了Wi-Fi和蓝牙功能,提供了强大的性能和灵活性。
2.2. 主要特性:
-
双核处理器: ESP32具备双核处理器,使其能够同时处理多个任务,提高系统的响应性。
-
Wi-Fi和蓝牙: ESP32支持2.4GHz Wi-Fi和蓝牙4.2/Bluetooth LE,使其成为连接智能设备的理想选择。
-
丰富的外设: ESP32集成了各种外设,如模数转换器(ADC)、脉冲宽度调制(PWM)等,增加了对各种传感器和执行器的支持。
-
低功耗设计: ESP32在设计上注重了功耗的优化,适用于需要长时间运行的电池供电设备。
2.3. ESP32在物联网中的应用:
-
智能家居: ESP32广泛用于智能家居设备,如智能灯具、智能插座等,通过Wi-Fi和蓝牙实现互联。
-
传感器网络: 由于其丰富的外设支持,ESP32适用于构建传感器网络,监测环境变量、收集数据等。
-
嵌入式系统: ESP32在嵌入式系统中也得到了广泛应用,用于控制和连接各种设备。
2.4. ESP32的开发生态:
-
开发环境: ESP32可以使用多种集成开发环境(IDE),如Arduino IDE、PlatformIO等,使得开发变得简单而灵活。
3.风扇控制与机身控制
3.1风扇控制基础
电风扇所采用的型号是L9110风扇模块,该模块集成了L9110驱动器,因此无需额外添加驱动模块。其控制方式仅需要通过两路PWM信号即可控制风扇的旋转速度和方向。
在arduino中控制方式如下
analogWrite(5,100);
analogWrite(6,0);
在这段代码中,通过引脚5和6来控制风扇的旋转。需要注意,在引脚的选择上,应该选择支持PWM的引脚。两个引脚的PWM范围是0到255,通过改变这两个引脚的PWM值,可以实现对风扇转速和方向的精确控制。
3.2机身控制基础
风扇的机身控制依赖于两个SG90舵机,一个用于水平旋转,另一个用于控制风扇的上下运动。
舵机控制关系:
舵机的运动受20毫秒左右的时基脉冲控制,该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分,可用于确定舵机的角度。对于SG90这款180度角度伺服舵机,我们可以参考以下关系:
- 0.5毫秒: 对应0度位置
- 1.0毫秒: 对应45度位置
- 1.5毫秒: 对应90度位置
- 2.0毫秒: 对应135度位置
- 2.5毫秒: 对应180度位置
通过调整脉冲的高电平部分,我们能够实现舵机在整个180度范围内的准确控制。这两个舵机的联动实现了风扇在水平和垂直方向上的全方位定向送风。
3.3mqtt通信基础
3.3.1. MQTT是什么?
MQTT是一种轻量级、开放标准的消息协议,旨在实现设备之间的高效通信。它适用于低带宽、高延迟或不稳定网络的场景,被广泛应用于物联网(IoT)领域。
3.3.2. MQTT的工作原理:
MQTT采用发布/订阅模式,其中有三个主要角色:发布者(Publisher)、订阅者(Subscriber)和代理服务器(Broker)。
-
发布者(Publisher): 向主题发布消息的设备。主题(Topic)是消息的分类,用于标识消息内容。
-
代理服务器(Broker): 负责接收发布者发送的消息并将其传递给对应订阅者。MQTT通信的中心组件。
-
订阅者(Subscriber): 订阅与特定主题相关的消息,接收发布者发送的消息。
3.3.3. MQTT主题和负载:
-
主题(Topic): 是消息的分类标识,它是一个由层级结构组成的字符串,形式类似于文件路径。发布者和订阅者通过主题来匹配和过滤消息。
-
负载(Payload): 是实际的消息内容。可以是任何二进制数据,根据项目需求而定。
3.3.4. 连接和断开:
-
连接: 设备通过TCP连接到MQTT代理服务器,并进行身份验证。连接后,设备可以发布和订阅消息。
-
断开: 设备可以主动断开连接,或者在网络故障等情况下,由代理服务器断开。
3.3.5. QoS级别:
MQTT支持不同的服务质量(QoS)级别,以确保消息的可靠传递。QoS级别包括0、1和2,级别越高,消息传递越可靠,但开销也越大。
3.3.6. 为什么选择MQTT:
-
轻量级: MQTT是一种轻量级协议,适用于资源受限的设备。
-
可靠性: 支持不同的QoS级别,确保消息可靠传递。
-
灵活性: 适用于各种不同的网络和设备类型,提供了灵活的通信机制。
3.3.7. MQTT在ESP32项目中的应用:
-
实现设备通信: 使用MQTT协议,ESP32可以与其他设备进行实时通信,适用于智能家居、监控系统等项目。
-
远程控制: 通过MQTT,可以实现对ESP32设备的远程控制,例如控制风扇的开关和风速以及风扇机身旋转及停止。
4.实际项目实现
4.1风扇控制实现
电风扇的控制比较简单,在MQTT的回调函数中,根据电风扇的控制指令输出两个PWM即可实现控制电风扇的风力。在使用前可以先测试一旋转方向保证这个是出风的。
参考代码如下:
if ((char)payload[0] == '1') {
analogWrite(25,70);
analogWrite(26,0);
Serial.print("run 1 ");
} else if ((char)payload[0] == '2'){
analogWrite(25,140);
analogWrite(26,0);
Serial.print("run 2");
}else if ((char)payload[0] == '3'){
analogWrite(25,210);
analogWrite(26,0);
Serial.print("run 3");
}
else if ((char)payload[0] == '0'){
analogWrite(25,0);
analogWrite(26,0);
Serial.print("stop ");
}
4.2机身控制实现
通过观察生活中电风扇的旋转,我们可以知道电风扇的旋转是,先匀速旋转到一个方向的最大值,然后反向匀速旋转到最大值,并不断重复,类似于一个不断的三角波。那么如何在代码中实现这个。
我们可以设定一个函数,在函数中设定一个最大值当函数超过最大值,数值就开始从最大值反向递减,数值没有超过最大值的话则不变。公式如下
代码如下
//数据处理函数
int coutTopos(int count,int max){
int result;
if (count<=max)
result=count;
else if(count>max)
result=2*max-count;
else if(count>=2*max)
result=0;
else
result=0;
return result;
}
使用时我们会发现当数据大于两倍的max不就是负的了吗,因此我们需要在计数器递增大于两倍max需要对他复位到0。这样我们就可以得到风扇水平旋转控制的程序了。代码如下
void horizontal_roatation(bool flag) {
if(flag==true){
myservo.write(h_pos); // tell servo to go to position in variable 'pos'
h_count++;
if(h_count>2*h_posmax)
h_count=0;
h_pos=coutTopos(h_count,h_posmax);
delay(15);
}
else{
delay(15);
}
}
其中舵机控制函数 myservo.write(h_pos)为arduino发的舵机控制库 <ESP32Servo.h>,提供控制位置的程序。通过给定角度即可实现位置控制。垂直控制方法类似不在赘述。舵机使用前需要一个进行一个初始化,程序如下:
void servossetup() {
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
myservo.setPeriodHertz(50); // standard 50 hz servo
myservo2.setPeriodHertz(50); // standard 50 hz servo
myservo.attach(servoPin, 500, 2500); // attaches the servo on pin 18 to the servo object
myservo2.attach(servo2Pin, 500, 2500); // attaches the servo on pin 18 to the servo object
myservo.write(h_pos);
myservo2.write(v_pos);
}
注:servoPin我设置了引脚18,控制水平旋转;servoPin2设置17用于控制垂直旋转。
4.3mqtt通信实现
mqtt的通信主要是利用<PubSubClient.h>库实现MQTT通信。在mqtt通信使用前需要对mqtt进行一个初始化,实现连接wifi、设置回调函数,订阅控制话题等。其代码如下
void mqttsetup() {
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
while (!client.connected()) {
Serial.println("Connecting to MQTT...");
if (client.connect("EFClient",mqttUser,mqttPassword )) {
Serial.println("connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
}
client.publish("outTopic","hello,i'm esp32");
client.subscribe(topic);
}
在回调函数里根据接收到的不同的数据位,可以实现风扇的旋转,及舵机的转动。在这里使用三位数,其中第一位数的1-3分别表示分速的级别,0表示停止;第二位及第三位则用于判断水平及垂直机械臂的旋转。具体实现如下
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Switch on the electric fan
if(strncmp(topic,"epp32/cyf",9)==0){
if ((char)payload[0] == '1') {
analogWrite(25,70);
analogWrite(26,0);
Serial.print("run 1 ");
} else if ((char)payload[0] == '2'){
analogWrite(25,140);
analogWrite(26,0);
Serial.print("run 2");
}else if ((char)payload[0] == '3'){
analogWrite(25,210);
analogWrite(26,0);
Serial.print("run 3");
}
else if ((char)payload[0] == '0'){
analogWrite(25,0);
analogWrite(26,0);
Serial.print("stop ");
}
if((char)payload[1]=='1'){
h_flag=true;
}
else {
h_flag=false;
}
if((char)payload[2]=='1'){
v_flag=true;
}
else {
v_flag=false;
}
}
}
在使用时可以通过MQTTx软件实现mqtt通信,后面可以通过发送三个数字实现风扇的控制。软件界面如下
在这里mqtt通信建议采用qos2,完成控制。
5.总结
无线电风扇控制主要由三个部分组成,第一个是风扇旋转控制(最简单的),第二个是MQTT通信(调库就行),最后一个是双舵机的控制(稍加修改就行)。总之这是个很简单的无线风扇设计。风扇如下,待mqtt接通后,通过三位mqttx界面发送三个就能实现电风扇控制了。
好了,下面到了激动人心的时刻了,直接贴程序。感兴趣的朋友可以试试。
#include <Arduino.h>
#include <ESP32Servo.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <stdio.h>
// Update these with values suitable for your network.
const char* ssid = "";//wifi账号
const char* password = "";//wifi密码
const char* mqtt_server = "";//mqtt服务器
const char* mqttUser = "emqx";//mqtt用户 自己取就行了
const char* mqttPassword = "public" ;//mqtt密码 自己取就行了
const char *topic="esp32/elefan"; //订阅话题对上就行了
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE (50)
char msg[MSG_BUFFER_SIZE];
int value = 0;
Servo myservo; // create servo object to control a servo
Servo myservo2;
int h_pos = 90; // variable to store the servo position
int v_pos = 30;
int h_count = 90; // variable to store the servo position
int v_count= 0;
int h_posmax=180;
int v_posmax=180;
bool h_flag=false;
bool v_flag=false;
int servoPin = 18;
int servo2Pin = 17;
int coutTopos(int count,int max);
void servossetup() {
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
myservo.setPeriodHertz(50); // standard 50 hz servo
myservo2.setPeriodHertz(50); // standard 50 hz servo
myservo.attach(servoPin, 500, 2500); // attaches the servo on pin 18 to the servo object
myservo2.attach(servo2Pin, 500, 2500); // attaches the servo on pin 18 to the servo object
myservo.write(h_pos);
myservo2.write(v_pos);
}
void horizontal_roatation(bool flag) {
if(flag==true){
myservo.write(h_pos); // tell servo to go to position in variable 'pos'
h_count++;
if(h_count>2*h_posmax)
h_count=0;
h_pos=coutTopos(h_count,h_posmax);
delay(15);
}
else{
delay(15);
}
}
void vertical_roatation(bool flag) {
if(flag==true){
myservo2.write(v_pos); // tell servo to go to position in variable 'pos'
v_count++;
if(v_count>2*v_posmax)
v_count=0;
v_pos=coutTopos(v_count,v_posmax);
delay(15);
}
else{
delay(15);
}
}
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password,6);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Switch on the electric fan
if(strncmp(topic,"epp32/cyf",9)==0){
if ((char)payload[0] == '1') {
analogWrite(25,70);
analogWrite(26,0);
Serial.print("run 1 ");
} else if ((char)payload[0] == '2'){
analogWrite(25,140);
analogWrite(26,0);
Serial.print("run 2");
}else if ((char)payload[0] == '3'){
analogWrite(25,210);
analogWrite(26,0);
Serial.print("run 3");
}
else if ((char)payload[0] == '0'){
analogWrite(25,0);
analogWrite(26,0);
Serial.print("stop ");
}
if((char)payload[1]=='1'){
h_flag=true;
}
else {
h_flag=false;
}
if((char)payload[2]=='1'){
v_flag=true;
}
else {
v_flag=false;
}
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "dfsClient-";
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe(topic);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void mqttsetup() {
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
while (!client.connected()) {
Serial.println("Connecting to MQTT...");
if (client.connect("EFClient",mqttUser,mqttPassword )) {
Serial.println("connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
}
client.publish("outTopic","hello,i'm esp32");
client.subscribe(topic);
}
void setup(){
servossetup();
mqttsetup();
}
void loop(){
if (!client.connected()) {
reconnect();
}
client.loop();
horizontal_roatation(h_flag);
vertical_roatation(v_flag);
}
//数据处理函数
int coutTopos(int count,int max){
int result;
if (count<=max)
result=count;
else if(count>max)
result=2*max-count;
else if(count>=2*max)
result=0;
else
result=0;
return result;
}
注:mqtt服务器可以自己通过emqtt或者mosquitto搭载,嫌麻烦也可以直接用公有的库,使用公共mqtt服务器通信失败的话,可以换一个试试。博主之前使用emqtt公共服务器,调试不出来,就用自己搭载发服务器重新测试了一下发现可以,后面又试了一下乐鑫公司的mqtt测试服务器mqtt.eclipseprojects.io(为啥我会先用这个试试,因为我一直是idf开发呀,之前用过,嘻嘻),也可以实现,如果你行可以换其他试试。