基于NodeMCU的物联网窗帘控制系统设计

最终效果

基于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博客

cpp 复制代码
//选择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();    // 连接WiFi
  
  mqttClient.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);
  }
}

不足之处

  1. 电机转速太慢
  2. 缺少获取窗帘当前位置的功能,无法处理手动和打滑。
相关推荐
Rousson9 分钟前
硬件学习笔记--82 连接器的选用原则与流程
笔记·单片机·学习
三佛科技-1341638421211 分钟前
高速风筒方案开发 高速风筒MCU控制方案设计
单片机·嵌入式硬件·智能家居·pcb工艺
清风6666666 小时前
基于单片机的螺旋藻生长大棚PH智能控制设计
单片机·嵌入式硬件·毕业设计·课程设计
ting_zh7 小时前
微控制器(Micro Controller Unit, MCU)基础整理
单片机·嵌入式硬件
清风6666667 小时前
基于单片机的图书馆智能座位管理平台
数据库·单片机·嵌入式硬件·毕业设计·课程设计
得单片机的运9 小时前
STM32的以太网的搭建
stm32·单片机·嵌入式硬件·物联网·以太网·iot·w5500
酷飞飞9 小时前
RTC和看门狗基于GD32F407VE的天空星的配置
stm32·单片机·嵌入式硬件·mcu
WD1372980155711 小时前
WD5030A,24V降5V,15A 大电流,应用于手机、平板、笔记本充电器
stm32·单片机·嵌入式硬件·智能手机·汽车·电脑·51单片机
日更嵌入式的打工仔11 小时前
GPIO 中断通用配置指南
stm32·单片机·嵌入式硬件
平凡灵感码头11 小时前
基于 STM32 的智能门锁系统,系统界面设计
stm32·单片机·嵌入式硬件