15. Arduino 编程最佳实践

15.1 远程IO控制器

此项目通过阿里云 IoT Studio ,建立一个控制界面,用于控制IO扩展板的 8路继电器,读取每个输入通道的状态。

项目功能:

  • Edge101WE 主板叠加 IO扩展板,通过阿里云IoT上传输入通道数据。并接阿里云下发的继电器控制指令,控制继电器。

  • IoT Studio 建立Web操作界面,可控制每一路继电器。

  • 具备阿里OTA功能。

1、创建云端产品以及设备

image-20210701164028921

image-20210701164437213

image-20210701164713295

image-20210702084826657

image-20210702090023408

image-20210705091632472

image-20210702090145185

image-20210702090537449

2、Web应用

接下来,我们根据此设备开发一个Web应用,访问网址:https://www.aliyun.com/product/iotstudio

image-20210702091240361

image-20210702091437352

image-20210702092148354

image-20210702092403009

image-20210702093126532

image-20210702093908989

image-20210702094114020

image-20210702094407691

image-20210702094734206

image-20210702095013249

image-20210702095338658

image-20210702095611985

image-20210702103336515

image-20210705151346066

image-20210702132448768

image-20210702135153761

浏览器访问此链接,到这里我们就搭建完成一个Web应用了

image-20210705151448288

3、OTA 远程升级设置

接下来我们来搭建OTA功能,本系统采用的是自动升级,系统连接网络接上云端后,会自动上传固件版本号,若固件版本低于升级包的版本,将会进行空中升级。

image-20210702172803377

image-20210702173932999

image-20210702175614311

image-20210702180120078

4、例程代码

/*!
 * @file relayOTA.ino
 *
 * @brief Simulate esp32 as a bedroom light and use Aliyun as a cloud platform. 
 * @n Subscribe to the relay control topic, which is the relay that can control the extension board in the cloud, and report the status of the 8-way input channel of the extension board
 * @n Aliyun OTA
 * @copyright   Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @licence     The MIT License (MIT)
 * @author      [Yangfeng](feng.yang@dfrobot.com)
 * @version  V1.0
 * @date  2021-06-24
 * @get from https://www.dfrobot.com
 */
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "DFRobot_Iot.h"
#include "HttpsOTAUpdate.h"
#include "DFRobot_IOExpension.h"

/*Configure the WiFi name and password*/
const char * WIFI_SSID     = "++++++++";
const char * WIFI_PASSWORD = "++++++++";

/*Configure the device certificate information*/
String ProductKey = "++++++";
String ClientId = "1234";/*The custom ID*/
String DeviceName = "++++++";
String DeviceSecret = "++++++";

/*Configure the domain name and port number*/
String ALIYUN_SERVER = "iot-as-mqtt.cn-shanghai.aliyuncs.com";
uint16_t PORT = 1883;

/*Topics to be reported and subscribed to*/
const char * subTopic = "/sys/${ProductKey}/${DeviceName}/thing/service/property/set";
const char * subTopic1 = "/ota/device/upgrade/${ProductKey}/${DeviceName}";
const char * pubTopic1 = "/ota/device/inform/${ProductKey}/${DeviceName}";
const char * pubTopic = "/sys/${ProductKey}/${DeviceName}/thing/event/property/post";

static const char *server_certificate =     "-----BEGIN CERTIFICATE-----\r\n"
    "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\r\n" \
    "A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\r\n" \
    "b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\r\n" \
    "MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\r\n" \
    "YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\r\n" \
    "aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\r\n" \
    "jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\r\n" \
    "xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\r\n" \
    "1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\r\n" \
    "snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\r\n" \
    "U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\r\n" \
    "9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\r\n" \
    "BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\r\n" \
    "AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\r\n" \
    "yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\r\n" \
    "38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\r\n" \
    "AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\r\n" \
    "DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\r\n" \
    "HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\r\n" \
    "-----END CERTIFICATE-----";

static HttpsOTAStatus_t otastatus;
DFRobot_IOExpension IOExpension(DFRobot_IOExpension_ADDR_H);
DFRobot_Iot myIot;
WiFiClient espClient;
PubSubClient client(espClient);
bool interruptFlag = false;
static void inputGetStatus();
static void interEvent();
void HttpEvent(HttpEvent_t *event)
{
  switch(event->event_id) {
    case HTTP_EVENT_ERROR:
      Serial.println("Http Event Error");
      break;
    case HTTP_EVENT_ON_CONNECTED:
      Serial.println("Http Event On Connected");
      break;
    case HTTP_EVENT_HEADER_SENT:
      Serial.println("Http Event Header Sent");
      break;
    case HTTP_EVENT_ON_HEADER:
      Serial.printf("Http Event On Header, key=%s, value=%s\n", event->header_key, event->header_value);
      break;
    case HTTP_EVENT_ON_DATA:
      break;
    case HTTP_EVENT_ON_FINISH:
      Serial.println("Http Event On Finish");
      break;
    case HTTP_EVENT_DISCONNECTED:
      Serial.println("Http Event Disconnected");
      break;
  }
}
void connectWiFi(){
  Serial.print("Connecting to ");
  Serial.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID,WIFI_PASSWORD);
  while(WiFi.status() != WL_CONNECTED){
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("WiFi connected");
  Serial.print("IP Adderss: ");
  Serial.println(WiFi.localIP());
}

void callback(char * topic, byte * payload, unsigned int len){
  Serial.print("Recevice [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < len; i++){
    Serial.print((char)payload[i]);
  }
  Serial.println();
  StaticJsonDocument<300>  jsonBuffer;
  auto error = deserializeJson(jsonBuffer, (char *)payload);
  if (error){
    Serial.println("parseObject() failed:");
    Serial.println(error.f_str());
  }
  if( strcmp(topic,subTopic1)==0){
    String str = jsonBuffer["data"]["url"];
    if(str.compareTo("null")!=0){
      String version0 =jsonBuffer["data"]["version"] ;
      String a = "\"";
      String version = a+version0+a;
      Serial.println(version);
      Serial.println(str);
      HttpsOTA.onHttpEvent(HttpEvent);
      Serial.println("Starting OTA");
      HttpsOTA.begin(const_cast<char *>(str.c_str()) , server_certificate); 
      Serial.println("Please Wait it takes some time ...");
      while(1){
        otastatus = HttpsOTA.status();
        if(otastatus == HTTPS_OTA_SUCCESS) { 
          client.publish(pubTopic1,("{\"id\":"+ClientId+",\"params\":{\"version\":"+version+",\"module\":\"esp32\"}}").c_str());
          Serial.printf("Firmware written successfully. Current version information: %s . To reboot device, call API ESP.restart() or PUSH restart button on device",version0); 
          Serial.println("Auto restart after 3 seconds");
          for(uint8_t i=0;i<2;i++){
            delay(1000);
            Serial.print(".");
          }
          delay(1000);
          Serial.println(".");
          ESP.restart();
        } else if(otastatus == HTTPS_OTA_FAIL) { 
          Serial.println("Firmware Upgrade Fail");
          break;
        }
      }
    }
  }else if( strcmp(topic,subTopic)==0){
    String param = jsonBuffer["params"];
    if(strstr(param.c_str(), "relay_1") != NULL){
      const bool relay_1 = jsonBuffer["params"]["relay_1"];
      IOExpension.setRelayStatus(/*channel= */1,/*mode=*/relay_1);
    }
    if(strstr(param.c_str(), "relay_2") != NULL){
      const bool relay_2 = jsonBuffer["params"]["relay_2"];
      IOExpension.setRelayStatus(/*channel= */2,/*mode=*/relay_2);
    }
    if(strstr(param.c_str(), "relay_3") != NULL){
      const bool relay_3 = jsonBuffer["params"]["relay_3"];
      IOExpension.setRelayStatus(/*channel= */3,/*mode=*/relay_3);
    }
    if(strstr(param.c_str(), "relay_4") != NULL){
      const bool relay_4 = jsonBuffer["params"]["relay_4"];
      IOExpension.setRelayStatus(/*channel= */4,/*mode=*/relay_4);
    }
    if(strstr(param.c_str(), "relay_5") != NULL){
      const bool relay_5 = jsonBuffer["params"]["relay_5"];
      IOExpension.setRelayStatus(/*channel= */5,/*mode=*/relay_5);
    }
    if(strstr(param.c_str(), "relay_6") != NULL){
      const bool relay_6 = jsonBuffer["params"]["relay_6"];
      IOExpension.setRelayStatus(/*channel= */6,/*mode=*/relay_6);
    }
    if(strstr(param.c_str(), "relay_7") != NULL){
      const bool relay_7 = jsonBuffer["params"]["relay_7"];
      IOExpension.setRelayStatus(/*channel= */7,/*mode=*/relay_7);
    }
    if(strstr(param.c_str(), "relay_8") != NULL){
      const bool relay_8 = jsonBuffer["params"]["relay_8"];
      IOExpension.setRelayStatus(/*channel= */8,/*mode=*/relay_8);
    }
    /*Publish the state of the relay*/
    String tempMseg = "{\"id\":" + ClientId + ",\"params\":"+param+",\"method\":\"thing.event.property.post\"}";
    char sendMseg[tempMseg.length()];
    strcpy(sendMseg, tempMseg.c_str());
    client.publish(pubTopic, sendMseg);
  }
}

void ConnectAliyun(){
  while (!client.connected()){
    Serial.print("Attempting MQTT connection...");
    /*A device connected to the cloud platform based on an automatically calculated username and password*/
    if (client.connect(myIot._clientId, myIot._username, myIot._password)){
      Serial.println("connected");
      client.subscribe(subTopic);
      client.subscribe(subTopic1);
    } else{
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}
void setup(){
  Serial.begin(115200);
  while(!Serial);
  IOExpension.begin();
  IOExpension.setRelayStatus(/*channel= */1,/*mode=*/0);
  IOExpension.setRelayStatus(/*channel= */2,/*mode=*/0);
  IOExpension.setRelayStatus(/*channel= */3,/*mode=*/0);
  IOExpension.setRelayStatus(/*channel= */4,/*mode=*/0);
  IOExpension.setRelayStatus(/*channel= */5,/*mode=*/0);
  IOExpension.setRelayStatus(/*channel= */6,/*mode=*/0);
  IOExpension.setRelayStatus(/*channel= */7,/*mode=*/0);
  IOExpension.setRelayStatus(/*channel= */8,/*mode=*/0);
  /* connect WIFI */
  connectWiFi();
  
  /*Initializes Alinyun's configuration to automatically calculate user name and password*/
  myIot.init(ALIYUN,ALIYUN_SERVER,ProductKey,ClientId,DeviceName,DeviceSecret,PORT);
  
  client.setServer(myIot._mqttServer,PORT);
  
  /*Set the callback function that executes when a subscription is received*/
  client.setCallback(callback);
  
  /*Connect to the Aliyun*/
  ConnectAliyun();

  /*Report firmware version information*/
  client.publish(pubTopic1,("{\"id\":"+ClientId+",\"params\":{\"version\":\"V1.0\",\"module\":\"esp32\"}}").c_str());
 /*Reports the status of relays and input channels*/
  client.publish(pubTopic, ("{\"id\":" + ClientId + ",\"params\":{\"relay_1\":0,\"relay_2\":0,\"relay_3\":0,\"relay_4\":0,\"relay_5\":0,\"relay_6\":0,\"relay_7\":0,\"relay_8\":0},\"method\":\"thing.event.property.post\"}").c_str());
  client.publish(pubTopic, ("{\"id\":" + ClientId + ",\"params\":{\"INPUT_1\":0,\"INPUT_2\":0,\"INPUT_3\":0,\"INPUT_4\":0,\"INPUT_5\":0,\"INPUT_6\":0,\"INPUT_7\":0,\"INPUT_8\":0},\"method\":\"thing.event.property.post\"}").c_str());
}
uint8_t statusLast,statusNow;
void loop(){ 
  if(!client.connected()){
    ConnectAliyun();
  }
  client.loop();
  statusNow = IOExpension.readInputStauts();
  if(statusNow!=statusLast){
    inputGetStatus(statusNow);
    statusLast = statusNow;
  }
  delay(500);
}
static void inputGetStatus(uint8_t status)
{ 
    bool switch_1,switch_2,switch_3,switch_4,switch_5,switch_6,switch_7,switch_8;
    if(status & SWITCH_COUNTER_1){
      switch_1 = 1;
    } else{
      switch_1 = 0;  
    }
    if(status & SWITCH_COUNTER_2){
      switch_2 = 1;
    } else{
      switch_2 = 0;  
    }
    if(status & SWITCH_COUNTER_3){
      switch_3 = 1;
    } else{
      switch_3 = 0;  
    }
    if(status & SWITCH_COUNTER_4){
      switch_4 = 1;
    } else{
      switch_4 = 0;  
    }
    if(status & SWITCH_COUNTER_5){
      switch_5 = 1;
    } else{
      switch_5 = 0;  
    }
    if(status & SWITCH_COUNTER_6){
      switch_6 = 1;
    } else{
      switch_6 = 0;  
    }
    if(status & SWITCH_COUNTER_7){
      switch_7 = 1;
    } else{
      switch_7 = 0;  
    }
    if(status & SWITCH_COUNTER_8){
      switch_8 = 1;
    } else{
      switch_8 = 0;  
    }
    client.publish(pubTopic, ("{\"id\":" + ClientId + ",\"params\":{\"INPUT_1\":"+switch_1+",\"INPUT_2\":"+switch_2+",\"INPUT_3\":"+switch_3+",\"INPUT_4\":"+switch_4+",\"INPUT_5\":"+switch_5+",\"INPUT_6\":"+switch_6+",\"INPUT_7\":"+switch_7+",\"INPUT_8\":"+switch_8+"},\"method\":\"thing.event.property.post\"}").c_str());
}

15.2 工业脉冲计数器和OEE有效产能统计系统

http://www.yotochn.com/Products/bqdqcrxlyb.html

https://item.taobao.com/item.htm?spm=a230r.1.14.28.715c7934vR9PXm&id=579697230552&ns=1&abbucket=4#detail

工业场景中脉冲计数器通常连接接近开关、光电开关、霍尔开关等传感器用于对工业现场进行计数,也可以外接按钮进行人工按键计数。

可实现一次感应计一个数、一次感应计多个数、多次感应计一个数。实现自动复位和手动复位。可设定报警输出。意外断电可继续计数。

项目功能:

  • 使用DFRobot的光电传感器和 I2C 数码管模块实现一个包装机工业计数器,当包装好的货物通过传送带时,光电传感器检测后记录数量,并通过数码管显示出来。

  • 主板意外断电后可继续计数。

  • 外接一个清零开关,当按下超过5秒后,计数值清零。

  • 通过阿里云显示当天是否开机、开机的时间长度、包装数量。

  • 可通过阿里云OTA进行程序升级。

Gravity: 8位数码管显示模块: https://www.dfrobot.com.cn/goods-2602.html

工业级背景抑制光电传感器: https://www.dfrobot.com.cn/goods-3111.html

一、搭建云平台

1、创建本次产品以及设备

首先访问阿里云首页(https://www.aliyun.com/)并登录自己的账号,进入自己的控制台。

image-20210707150510014

image-20210707151349563

image-20210707151944362

image-20210707152022970

接下来,我们就开始来创建一个产品和设备

image-20210707152222495

image-20210707152454166

创建好产品后我们先来为产定义物理模型

image-20210707153043568

image-20210707153212561

image-20210707153358434

这里我们创建两个功能,一个用来记录系统开机时间,一个用来记录包装数量。

image-20210707155332045

image-20210707155934799

image-20210707160227452

接下来我们为此产品创建一个设备

image-20210707160535866

image-20210707160606874

这里保存设备证书信息,并写入代码中

image-20210707160732784

image-20210707160942692

到这里,我们就可以将设备证书填写到代码中,并且修改配网信息,运行一次程序,使得云端设备激活。

2、创建一个Web应用

访问链接 https://www.aliyun.com/product/iotstudio

image-20210707164842280

image-20210707164955449

image-20210707165048249

image-20210707165136845

执行完上步骤后自动跳转到此界面

image-20210707165345264

执行完上步骤后自动跳转到此界面

image-20210707165625522

image-20210707170452066

image-20210707170528148

image-20210707170714804

image-20210707170919510

关联好产品和设备后,我们回到Web应用编辑界面

image-20210707171029842

image-20210707171227756

image-20210707173202179

根据自己的喜好配置一些界面可以使其更加美观,当编辑好界面后,点击“发布“。

image-20210707174312379

image-20210707174453834

访问刚刚记录的链接,到这里我们就可以实时观察系统的包装数量和系统开机时间长度了。

image-20210707174542280

3、OTA升级任务的创建

image-20210707175612579

image-20210707180017350

创建一次升级任务

image-20210707180116738

image-20210707180328594

image-20210707183200091

image-20210707183327053

到这里我们就创建好了一次升级任务,在升级任务作用的时间内,设备上线并且符合升级要求,将会进行自动升级。

升级完成样式如图。

image-20210707183728391

二、例程

# include "DFRobot_LedDisplayModule.h"
#include "DFRobot_IOExpension.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "DFRobot_Iot.h"
#include "HttpsOTAUpdate.h"
//#define CONNECT_ALIYUN
/*Configure the WiFi name and password*/
const char * WIFI_SSID     = "++++++++";
const char * WIFI_PASSWORD = "++++++++";

/*Configure the device certificate information*/
String ProductKey = "++++++";
String ClientId = "1234";/*The custom ID*/
String DeviceName = "++++++";
String DeviceSecret = "++++++";

/*Configure the domain name and port number*/
String ALIYUN_SERVER = "iot-as-mqtt.cn-shanghai.aliyuncs.com";
uint16_t PORT = 1883;

/*Topics to be reported and subscribed to*/
const char * subTopic = "/sys/${ProductKey}/${DeviceName}/thing/service/property/set";
const char * subTopic1 = "/ota/device/upgrade/${ProductKey}/${DeviceName}";
const char * pubTopic1 = "/ota/device/inform/${ProductKey}/${DeviceName}";
const char * pubTopic = "/sys/${ProductKey}/${DeviceName}/thing/event/property/post";

static const char *server_certificate =     "-----BEGIN CERTIFICATE-----\r\n"
    "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\r\n" \
    "A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\r\n" \
    "b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\r\n" \
    "MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\r\n" \
    "YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\r\n" \
    "aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\r\n" \
    "jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\r\n" \
    "xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\r\n" \
    "1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\r\n" \
    "snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\r\n" \
    "U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\r\n" \
    "9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\r\n" \
    "BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\r\n" \
    "AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\r\n" \
    "yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\r\n" \
    "38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\r\n" \
    "AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\r\n" \
    "DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\r\n" \
    "HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\r\n" \
    "-----END CERTIFICATE-----";

static HttpsOTAStatus_t otastatus;
DFRobot_Iot myIot;
WiFiClient espClient;
uint32_t valLast = 0;
uint8_t userKey = 38;
volatile int keyUpTime,keyDownTime,keyTime;
static uint8_t errnum;
bool interruptFlag = false;
DFRobot_LedDisplayModule LED(Wire, 0xE0);
DFRobot_IOExpension IOExpension(DFRobot_IOExpension_ADDR_H);
PubSubClient client(espClient);
#define MODE_A  0
#define MODE_B  1
#define MODE_C  2
int mode;
int counterValue = 0;
void ISR_FALLING()
{
  keyDownTime = millis ();
  attachInterrupt(digitalPinToInterrupt(38),ISR_RISING,RISING);
}
void ISR_RISING()
{
  keyUpTime = millis ();
  keyTime = keyUpTime - keyDownTime;
  if(keyTime>= 5000){
    interruptFlag = true;
    keyTime = 0;
  }else{
    keyDownTime = keyUpTime;
  }
  attachInterrupt(digitalPinToInterrupt(38),ISR_FALLING,FALLING);
}
void HttpEvent(HttpEvent_t *event)
{
  switch(event->event_id) {
    case HTTP_EVENT_ERROR:
      Serial.println("Http Event Error");
      break;
    case HTTP_EVENT_ON_CONNECTED:
      Serial.println("Http Event On Connected");
      break;
    case HTTP_EVENT_HEADER_SENT:
      Serial.println("Http Event Header Sent");
      break;
    case HTTP_EVENT_ON_HEADER:
      Serial.printf("Http Event On Header, key=%s, value=%s\n", event->header_key, event->header_value);
      break;
    case HTTP_EVENT_ON_DATA:
      break;
    case HTTP_EVENT_ON_FINISH:
      Serial.println("Http Event On Finish");
      break;
    case HTTP_EVENT_DISCONNECTED:
      Serial.println("Http Event Disconnected");
      break;
  }
}
void connectWiFi(){
  Serial.print("Connecting to ");
  Serial.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID,WIFI_PASSWORD);
  while(WiFi.status() != WL_CONNECTED){
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("WiFi connected");
  Serial.print("IP Adderss: ");
  Serial.println(WiFi.localIP());
}

void callback(char * topic, byte * payload, unsigned int len){
  Serial.print("Recevice [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < len; i++){
    Serial.print((char)payload[i]);
  }
  Serial.println();
  StaticJsonDocument<300>  jsonBuffer;
  auto error = deserializeJson(jsonBuffer, (char *)payload);
  if (error){
    Serial.println("parseObject() failed:");
    Serial.println(error.f_str());
  }
  
  if( strcmp(topic,subTopic1)==0){
    String str = jsonBuffer["data"]["url"];
    if(str.compareTo("null")!=0){
      String version0 =jsonBuffer["data"]["version"] ;
      String a = "\"";
      String version = a+version0+a;
      Serial.println(version);
      Serial.println(str);
      HttpsOTA.onHttpEvent(HttpEvent);
      Serial.println("Starting OTA");
      HttpsOTA.begin(const_cast<char *>(str.c_str()) , server_certificate); 
      Serial.println("Please Wait it takes some time ...");
      while(1){
        otastatus = HttpsOTA.status();
        if(otastatus == HTTPS_OTA_SUCCESS) { 
          client.publish(pubTopic1,("{\"id\":"+ClientId+",\"params\":{\"version\":"+version+",\"module\":\"esp32\"}}").c_str());
          Serial.printf("Firmware written successfully. Current version information: %s . To reboot device, call API ESP.restart() or PUSH restart button on device",version0); 
          Serial.println("Auto restart after 3 seconds");
          for(uint8_t i=0;i<2;i++){
            delay(1000);
            Serial.print(".");
          }
          delay(1000);
          Serial.println(".");
          ESP.restart();
          break;
        } else if(otastatus == HTTPS_OTA_FAIL) { 
          Serial.println("Firmware Upgrade Fail");
          break;
        }
      }
    }
  }
}

void ConnectAliyun(){
  while (!client.connected()){
    Serial.print("Attempting MQTT connection...");
    /*A device connected to the cloud platform based on an automatically calculated username and password*/
    if (client.connect(myIot._clientId, myIot._username, myIot._password)){
      Serial.println("connected");
      client.subscribe(subTopic);
      client.subscribe(subTopic1);
    } else{
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}
void setup() 
{
  Serial.begin(115200);
  while(!Serial);
  while(IOExpension.begin()!=0){
    delay(1000);
  }
  /*Wait for the chip to be initialized completely, and then exit*/
  while(LED.begin8() != 0)
  {
    Serial.println("");
    delay(1000);
  }
  LED.setDisplayArea8(2,3,4,5,6,7);
  LED.print8("H","E","L","L","O","!");
  delay(2000);
  Serial.println("Please select count mode:");
  Serial.println("  Mode A: one number at a time;");
  Serial.println("  Mode B: multiple numbers at a time;");
  Serial.println("  Mode C: multiple induction meter a number;");
  Serial.println("Enter 'A' for mode A; Enter 'B' for mode B; Enter 'C' for mode C.");
  while(1){
    if (Serial.available()){
      counterValue = 0;
      int inputchar = Serial.read();
      if (char(inputchar) == 'A'){
        Serial.read();
        Serial.println("The system will operate in A mode");
        mode  = MODE_A;
        break;
      } else if (char(inputchar) == 'B'){
        bool flag = true;
        Serial.read();
        Serial.read();
        Serial.println("The system will operate in B mode");
        Serial.println("Please input the corresponding count value of an induction");
        while (1){
          uint8_t a=Serial.available();
          if(a>0){
            flag = true;
            for(uint8_t i=0;i<a;i++){
              int value = Serial.read();
              if(value>=48 && value<=57){
              counterValue = counterValue * 10;
              counterValue = counterValue + value - '0';
              }else if (char(value) == '\r'|| char(value) == '\n'|| char(value) == '\0'){             
              }else{
                Serial.println("Invalid value, please reselect");
                flag = false;
                for(;i<a;i++){
                  Serial.read();
                }
                counterValue = 0;
                break;
              }
            }
            if(flag == false){
              continue;
            }else{
              break;
            }
         }
        }
        Serial.println(counterValue);
        Serial.println("Set up the success");
        mode  = MODE_B;
        break;
      } else if (char(inputchar) == 'C'){
        bool flag = true;
        Serial.read();
        Serial.read();
        Serial.println("The system will operate in C mode");
        Serial.println("Please input the corresponding induction value for induction");
        while (1){
          uint8_t a=Serial.available();
          if(a>0){
            flag = true;
            for(uint8_t i=0;i<a;i++){
              int value = Serial.read();
              if(value>=48 && value<=57){
              counterValue = counterValue * 10;
              counterValue = counterValue + value - '0';
              }else if (char(value) == '\r'|| char(value) == '\n'|| char(value) == '\0'){             
              }else{
                Serial.println("Invalid value, please reselect");
                flag = false;
                for(;i<a;i++){
                  Serial.read();
                }
                counterValue = 0;
                break;
              }
            }
            if(flag == false){
              continue;
            }
            break;
         }
        }
        Serial.println(counterValue);
        Serial.println("Set up the success");
        mode  = MODE_C;
        break;
      }else{
        Serial.println("Invalid working mode, please reselect");
      }
    }
  }
  IOExpension.setFilterTimeOfInput(/*counter= */SWITCH_COUNTER_8,/*filtertime =*/100000);
  pinMode(38,INPUT);
  attachInterrupt(digitalPinToInterrupt(38),ISR_FALLING,FALLING);
  delay(1000);
  
#if defined(CONNECT_ALIYUN)
  /* connect WIFI */
  connectWiFi();
  
  /*Initializes Alinyun's configuration to automatically calculate user name and password*/
  myIot.init(ALIYUN,ALIYUN_SERVER,ProductKey,ClientId,DeviceName,DeviceSecret,PORT);
  
  client.setServer(myIot._mqttServer,PORT);
  
  /*Set the callback function that executes when a subscription is received*/
  client.setCallback(callback);
  
  /*Connect to the Aliyun*/
  ConnectAliyun();

  /*Report firmware version information*/
  client.publish(pubTopic1,("{\"id\":"+ClientId+",\"params\":{\"version\":\"V1.0\",\"module\":\"esp32\"}}").c_str());
 /*Report light brightness information*/
  unsigned long initTime =  millis();
  double pubTime = initTime/1000/3600.0;
  client.publish(pubTopic, ("{\"id\":" + ClientId + ",\"params\":{\"countValue\":0,\"StartingTimeLength\":"+pubTime+"},\"method\":\"thing.event.property.post\"}").c_str());
#endif
}
void loop() 
{
#if defined(CONNECT_ALIYUN)
  if(!client.connected()){
    ConnectAliyun();
  }
  client.loop();
#endif
  if(interruptFlag){
    interruptFlag = false;
    IOExpension.setCountnerValue(/*counter=*/SWITCH_COUNTER_8,/*value=*/0);
    Serial.println("Manual reset!");
    LED.setDisplayArea8(1,2,3,4,5,6,7,8);
    LED.print8(0.0); 
  }    
  uint32_t val = IOExpension.readCountnerValueOfInput(/*counter=*/SWITCH_COUNTER_8);
  if(val>99999999 ){
    LED.setDisplayArea8(1,2,3,4,5,6,7,8);
    uint32_t err = 88888888;
    LED.print8(err);
    Serial.println(val,HEX);
    errnum++;
    if(errnum>10){  
      IOExpension.setCountnerValue(/*counter=*/SWITCH_COUNTER_8,/*value=*/0);
      Serial.println("Number of times out of range :10. The counter has automatically reset to zero!");
      LED.setDisplayArea8(1,2,3,4,5,6,7,8);
      LED.print8(0.0);
      errnum = 0;
    }
  } else{ 
    if(mode == MODE_A){
    } else if(mode == MODE_B){
      val = val * counterValue;
    } else if(mode == MODE_C){
      val = val / counterValue;
    }
    //Serial.println(val);
    LED.setDisplayArea8(1,2,3,4,5,6,7,8);
    LED.print8(val);
#if defined(CONNECT_ALIYUN)
    if(valLast!=val){
      unsigned long nowTime =  millis();
      double pubTime = nowTime/1000/3600.0;
      client.publish(pubTopic, ("{\"id\":" + ClientId + ",\"params\":{\"countValue\":"+val+",\"StartingTimeLength\":"+pubTime+"},\"method\":\"thing.event.property.post\"}").c_str());
      }
    valLast=val;
#endif
  }
  delay(500);
}

1、填写代码中的设备证书

_images/image15_2_16.png

2、添加WiFi信息

image-20210707184342573

3、编译运行

4、按照串口信息,配置工作模式

image-20210707184715539

配置成功后成功运行,此时观察数码管计数,或者上到云端观察数据即可。

image-20210707184933737

15.3 PID温度控制器

项目使用PT100温度传感器采集温度,传感器电压量输出连接到Edge101WE主板,输出连接固态继电器,固态继电器控制220V加热器加热。

通过PID算法控制加热器的加热值,控制精度达到 +- 1摄氏度。

项目功能:

  • 测量的当前温度,将实时温度 和 温度/日期时间曲线显示在 阿里云IoT Studio建立的Web界面。

  • 可通过web界面修改PID的参数。

  • web界面可控制温度控制装置的启停。

  • web界面设置控制的温度。

  • 具备阿里云OTA功能。

SEN0198 Gravity: 350度高温传感器: https://www.dfrobot.com.cn/goods-1286.html

附件:

https://www.jianshu.com/p/894789a5ea1e

全新3.0版本的XC系列PLC在本体部分加入了PID控制指令,并提供了自整定功能。用户可以通过自整定得到最佳的采样时间及PID参数值,从而提高了控制精度。还可以自整定参数,温控效果比较明显,大型场合一般在1度左右。 1.输出可以是数据形式D,也可以是开关量形式Y,在编程时可以自由选择。 2.通过自整定可得到最佳的采样时间及PID参数值,提高了控制精度。 3.可通过软件设置来选择逆动作还是正动作。前者用于常规加热控制,后者常用于空调冷却控制。 4.PID控制可以脱离与扩展模块的联系,扩展了该功能的灵活性。

[b]德维森的V80本体也带脉冲输出[/b] V80系列PLC中具有增强功能(用“/S”表示)的CPU本体单元提供2个通道(P1和P2)的高速脉冲输出(PLS)功能 (必须在V+和V-端子另加外部24V的电源),每个脉冲输出通道都可以单独产生高速脉冲串输出(PTO-Pulse Train Output)或产生脉冲宽度调制输出(PWM-Pulse Width Modulated)。

德维森的PLC的脉宽调制的频率能达到200K吗??同时脉宽分辨率有达到1000吗??我用永宏的做是达不到的。哪种PLC能达到呢??

到1000,这句话不是很理解, 如要要达到1/1000的分辨率对于V80PLC来说非常easy的,而且可以确保!不过由于我们的PLC的最小周期是1US,那么PWM的频率就要相应低一些,如果楼主要频率到200k,并且可以实现1/1000或者(10位)精度,那么意味着最小脉宽时间是0.005us了。不隔离的电路都难做到,这个毛刺太容易给滤掉了,呵呵。 V80 PLC的PWM周期最小可设置为20us,最大周期可为65535ms(呵呵,可以用来做次声波武器吧)最小脉宽分辨率为1us,驱动功率大,可达20W。 5路热电偶输入模块自带有5路周期为2048msPWM输出,控制方便,直接映射成PLC的一个寄存器,操作方便,直接向寄存器MOVE一个0~2048的的数据可实现占空比的调整。实际模块自带5路pid控制,5路PWM可有pid输出控制

本人设计一套设备控制系统,其中有四路温度控制。采用LG的PLC和温度模块。控制输出是通过输出点脉宽调制控制固态继电器的通断,从而控制温度。这种输出方式较为方便,省去了D/A转换和可控硅触发电路。

https://www.eet-china.com/mp/a34504.html