最終效果
基于NodeMCU的物聯網窗簾控制系統設計
項目介紹
該項目是“物聯網實驗室監測控制系統設計(仿智能家居)”項目中的“家電控制設計”中的“窗簾控制”子項目,最前者還包括“物聯網設計”、“環境監測設計”、“門禁系統設計計”和“小程序設計”等內容。本文只介紹“窗簾控制”部分。
項目功能實現的大致思路為:當單片機接收到MQTT服務器傳來的窗簾新位置時,驅動步進電機轉動,使窗簾移動到指定位置。
硬件設計
接線
NodeMCU | ULN2003 | 28BYJ-48 | 電源 |
OUT1 | 1 | ||
OUT2 | 2 | ||
OUT3 | 3 | ||
OUT4 | 4 | ||
D4 | INT1 | ||
D3 | INT2 | ||
D2 | INT3 | ||
D1 | INT4 | ||
+ | 5 | 5V | |
GND | - | GND |
成本
NodeMCU | 28BYJ-48模組 |
27.9 | 8.53 |
其中共需36.5元左右來購買該項目所需的模塊。此外還需1根數據線、若干杜邦線、能提供5~12V中間任意電壓的電源。
機械模型搭建
為使演示更貼合實際,本系統制作了一個窗簾模型,模型圖見下文。
模型左上方的黑色絕緣膠帶表示窗簾的移動端,位于左側時表示窗簾閉合(遮住窗戶);位于右側時表示窗簾打開(露出窗戶)。在模型中,步進電機帶動齒輪旋轉,從而帶動傳送帶轉動,進而實現窗簾的移動。通過控制步進電機的旋轉,便可將窗簾移動至指定位置。該模型中的兩齒輪中心距為800mm,主動輪的周長約為74.61mm(比兩齒輪中心距的10%略小),步進電機旋轉10圈可將窗簾移動到另一側(窗簾行程留有冗余)。
器件圖





器件尺寸
未完待續
皮帶:未完待續
28BYJ-48型步進電機的軸:未完待續
木板:未完待續
軟件設計
本次的開發環境為Arduino IDE,開發板型號為NodeMCU 0.9 (ESP-12 Module)。
本系統軟件部分的流程如下圖所示。在初始化之后,等待小程序下發窗簾位置,據此驅動步進電機旋轉。
連接WiFi以及接收MQTT服務器傳來的消息,可參考:利用ESP-01S中繼實現STM32F103C8T6與MQTT服務器的串口雙向通信_mqtt和stm32開發板通信-CSDN博客
解析JSON數據,可參考:Arduino中解析JSON數據-CSDN博客
驅動28BYJ-48型步進電機轉動,可參考:NodeMCU驅動28BYJ-48型步進電機(Arduino)-CSDN博客
//選擇NodeMCU 0.9 (ESP-12 module)
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Arduino.h>// 設置wifi接入信息和MQTT服務器
const char* wifiname = "DOILMSBOIOT";
const char* password = "doilmsboiot";
const char* mqttServer = "broker.emqx.io";bool receive_message_flag = 0; //1表示收到信息但還未處理,0表示未收到信息或已處理WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);// 待解析的json文件,所需空間:13~15個字節,正好初始值為最多的字節;若初始化時空間不足,收到信息后無法賦值
String json = "{\"curtain\":000}";// 創建DynamicJsonDocument對象
const size_t capacity = JSON_OBJECT_SIZE(1) + 32 ; //1表示待解析的JSON對象中有1對數據,32為解析過程中需要的額外空間,可在此網站計算 https://arduinojson.org/v6/assistant/#/step1
DynamicJsonDocument doc(capacity);int curtain_position ; // 解析后的窗簾位置
int curtain_now_position =0 ; // 窗簾現在的位置void setup()
{Serial.begin(9600); // 啟動串口通訊WiFi.mode(WIFI_STA); //設置ESP8266工作模式為無線終端模式connectWifi(); // 連接WiFimqttClient.setServer(mqttServer, 1883); // 設置MQTT服務器和端口號mqttClient.setCallback(receiveCallback); // 設置MQTT訂閱回調函數connectMQTTserver(); // 連接MQTT服務器stepmotor_initial(); //步進電機初始化
}void loop()
{if (mqttClient.connected()) // 如果開發板成功連接服務器{ mqttClient.loop(); // 處理信息(收到信息后的回調函數)以及心跳} else // 如果開發板未能成功連接服務器{ connectMQTTserver(); // 則嘗試連接服務器并訂閱主題}if (receive_message_flag == 1) //收到信息但還未處理{ deserializeJson(doc, json); // 反序列化數據// 解析收到的數據信息curtain_position = doc["curtain"].as<int>();if(curtain_position - curtain_now_position > 0){Serial.print("電機要轉到的位置:");Serial.println(curtain_position);Serial.print("電機現在的位置:");Serial.println(curtain_now_position);int cycle = (int)(curtain_position - curtain_now_position)/10;Serial.println("開始轉動");Serial.println(cycle);for(int i=0; i < cycle; i++){clockwise_turn_one_circle();curtain_now_position += 10;Serial.print("轉過的圈數:");Serial.println(i);} Serial.println("結束轉動");Serial.print("電機現在的位置:");Serial.println(curtain_now_position);Serial.println("");}if(curtain_position - curtain_now_position < 0){Serial.print("電機要轉到的位置:");Serial.println(curtain_position);Serial.print("電機現在的位置:");Serial.println(curtain_now_position);int cycle = (int)(curtain_now_position - curtain_position)/10;Serial.println("開始轉動");Serial.println(-cycle);for(int i=0; i < cycle; i++){anti_clockwise_turn_one_circle();curtain_now_position -= 10;Serial.print("轉過的圈數:");Serial.println(i);} Serial.println("結束轉動");Serial.print("電機現在的位置:");Serial.println(curtain_now_position);Serial.println("");}receive_message_flag = 0; //已處理接收到的信息}}// 連接MQTT服務器并訂閱主題
void connectMQTTserver()
{// 根據ESP8266的MAC地址生成客戶端ID(避免與其它ESP8266的客戶端ID重名)String clientId = "esp8266-" + WiFi.macAddress();if (mqttClient.connect(clientId.c_str())) //如果成功連接MQTT服務器{ Serial.print("MQTT Server Has Connected. ");Serial.print("Server Address: ");Serial.println(mqttServer);Serial.print("ClientId: ");Serial.println(clientId);subscribeTopic(); // 訂閱指定主題} else {Serial.print("MQTT Server Connect Failed. Client State:");Serial.println(mqttClient.state());delay(3000);}
}// 收到信息后的回調函數
void receiveCallback(char* topic, byte* payload, unsigned int length)
{Serial.print("Message with the topic of [ ");Serial.print(topic);Serial.println(" ] has been received.");Serial.print("Content: ");for (int i = 0; i < length; i++) {Serial.print((char)payload[i]);json[i] = (char)payload[i]; //將收到的信息賦給json,以便后續解析和發射信號}Serial.println("");for (int i = length; i < 15; i++) //清除掉多余字符{json[i] = '\0';}receive_message_flag = 1; //表示收到信息但還未處理Serial.print("Message Length (Bytes) : ");Serial.println(length);Serial.println(" ");
}// 訂閱指定主題
void subscribeTopic()
{String topicString = "deviceControl3/curtain"; // 訂閱主題的名稱char subTopic[topicString.length() + 1]; strcpy(subTopic, topicString.c_str());if(mqttClient.subscribe(subTopic)) //如果成功訂閱主題{Serial.print("Subscrib Topic: ");Serial.println(subTopic);Serial.println("");} else {Serial.print("Subscribe Fail...");}
}// ESP8266連接wifi
void connectWifi()
{WiFi.begin(wifiname, password);Serial.println("Connecting to WiFi");while (WiFi.status() != WL_CONNECTED) //等待WiFi連接,當wifi未連接時,持續輸出".";成功連接后輸出連接成功信息{delay(1000);Serial.print(".");}Serial.println("");Serial.println("WiFi Connected!"); Serial.println("");
}void stepmotor_initial()
{pinMode(D1, OUTPUT);pinMode(D2, OUTPUT); pinMode(D3, OUTPUT);pinMode(D4, OUTPUT);
}void clockwise_turn_one_circle()
{for(int i=0;i<512;i++){digitalWrite(D1, HIGH);digitalWrite(D2, LOW);digitalWrite(D3, LOW);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, HIGH);digitalWrite(D2, HIGH);digitalWrite(D3, LOW);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, HIGH);digitalWrite(D3, LOW);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, HIGH);digitalWrite(D3, HIGH);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, LOW);digitalWrite(D3, HIGH);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, LOW);digitalWrite(D3, HIGH);digitalWrite(D4, HIGH);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, LOW);digitalWrite(D3, LOW);digitalWrite(D4, HIGH);delay(1);digitalWrite(D1, HIGH);digitalWrite(D2, LOW);digitalWrite(D3, LOW);digitalWrite(D4, HIGH);delay(1);}
}void anti_clockwise_turn_one_circle()
{for(int i=0;i<512;i++){digitalWrite(D1, LOW);digitalWrite(D2, LOW);digitalWrite(D3, LOW);digitalWrite(D4, HIGH);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, LOW);digitalWrite(D3, HIGH);digitalWrite(D4, HIGH);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, LOW);digitalWrite(D3, HIGH);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, HIGH);digitalWrite(D3, HIGH);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, LOW);digitalWrite(D2, HIGH);digitalWrite(D3, LOW);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, HIGH);digitalWrite(D2, HIGH);digitalWrite(D3, LOW);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, HIGH);digitalWrite(D2, LOW);digitalWrite(D3, LOW);digitalWrite(D4, LOW);delay(1);digitalWrite(D1, HIGH);digitalWrite(D2, LOW);digitalWrite(D3, LOW);digitalWrite(D4, HIGH);delay(1);}
}
不足之處
- 電機轉速太慢
- 缺少獲取窗簾當前位置的功能,無法處理手動和打滑。