funpack3-5 - 基于Arduino UNO R4 WiFi 开发板的云端控制
该项目使用了Arduino UNO R4 WiFi 开发板,实现了云端收发信息的设计,它的主要功能为:通过网络连接到巴法云端,既可以远程控制LED的亮度以及灯板矩阵,又可以远程读取温湿度传感器的信息。
标签
Arduino
Funpack活动
WiFi
物联网
云端
hhh
更新2025-01-14
13

一.项目介绍

本次我参加的是Funpack的第三季第五期活动。Funpack活动是硬禾学堂联合DigiKey发起的“玩成功就全额退”活动。Funpack第三季第五期活动共推出三款活动板卡,活动参与者可从三款活动板卡中任选一款下单参加活动,并完成对应板卡的规定活动任务,规定时间内完成任务并提交报告即可获得对应返还。我选择的是Arduino UNO R4 WiFi这块板卡。Arduino UNO R4 WiFi 将瑞萨电子的 RA4M1 微处理器与乐鑫的 ESP32-S3 相结合,为创客打造了一款一体化工具,具有增强的处理能力和多样化的全新外设。凭借其内置的 Wi-Fi® 和蓝牙®功能,UNO R4 WiFi 使制造商能够探索无限的创意可能性。此外,这款多功能板拥有方便的板载 12x8 LED 矩阵和 Qwiic 连接器,为各个级别的创客提供了无与伦比的灵活性和可能性。


活动任务(以下任务任选其一完成即可):

任务1:点灯!通过网络连接到智能云端,尝试将设备模拟成可以控制的灯,远程端发送指令,将灯光开启关闭,或调整灯光的亮度,0到90%时,矩阵亮起逐渐变大的范围,90%以上时,灯板显示出太阳的图标

任务2:搭配传感器,并通过网络连接到智能云端,可以从远程获取传感器的信息

任务3:自命题。若您针对这个板卡有更好的创意,可自命题完成(难度不能低于以上任务,可自行选择搭配其他模块)


由于这块板卡是基于Arduino开发平台,有丰富的资源,方便设计,所以我把上面的三个任务都做完了。第三个自命题任务我是把任务一和任务二结合在一起,让云端平台既可以发送数据控制LED,又可以接收传感器的数据。


二、设计思路

任务一设计思路:

首先我先把整个任务分解成两个部分去完成,首先第一个部分是不用云端控制,直接定义一个变量LED来作为云端收到的数据,先把基本功能调通,然后第二个部分再去读取云端数据作为这个LED变量的输入。那么要实现第一个部分的功能,首先我需要控制一个LED的亮度,可以用PWM去实现这个功能。对于大多数Arduino板,PWM的范围是从0到255,这里我不想让LED太亮,所以我设置它的最大值为240,又因为我的输入是0-100的整数,所以我用了一个映射:PWM = (map(LED, 0, 100, 0, 240))。其次,我还要根据这个LED值去控制灯板矩阵的数量,我的实现方法是通过循环来改变一个8x12矩阵的值,之后再把这个矩阵值通过Arduino_LED_Matrix.h库直接向LED矩阵写入数据,控制LED的点亮和熄灭。把第一部分的功能调试正常之后就可以编写第二部分功能的代码了,包括wifi连接、创建云服务账号和凭证、服务器连接、订阅主题、接收数据、解析数据等。下图是任务一的程序框架图。



image.png

任务二设计思路:

任务二依然可以把整个任务分解成两个部分去完成,第一个部分是从传感器中读取数据并在串口打印出来,第二个部分是把数据发送到云端。下图是任务二的程序框架图。


image.png

任务三设计思路:

自命题任务就是把任务一和任务二结合在一起,经过前面任务一和任务二的设计,完成任务三就比较简单了,最后成果是可以让云端平台既可以发送数据控制LED,又可以接收传感器的数据。

三.硬件介绍

ArduinoUNO R4 WiFi 所提供的功能:

  • 与 UNO 外形尺寸的硬件兼容性:UNO R4 WiFi 与其前身 UNO R3 保持相同的外形尺寸、引脚排列和 5 V 工作电压,确保现有扩展板和项目的无缝过渡。
  • 扩展内存和更快的时钟:UNO R4 WiFi 拥有更大的内存和更快的时钟速度,可实现更精确的计算和轻松处理复杂的项目。
  • 额外的板载外设:UNO R4 WiFi 引入了一系列板载外设,包括 12 位 DAC、CAN 总线和运算放大器,提供扩展的功能和设计灵活性。
  • 扩展的 24 V 容差:UNO R4 WiFi 支持更宽的输入电压范围,允许使用单一电源与电机、LED 灯带和其他执行器无缝集成。
  • HID 支持:凭借内置的 HID 支持,UNO R4 WiFi 通过 USB 连接到计算机时可以模拟鼠标或键盘,从而轻松发送击键和鼠标移动。
  • Wi-Fi® 和蓝牙®:UNO R4 WiFi 托管 ESP32-S3 模块,使创客能够为其项目添加无线连接。结合 Arduino IoT Cloud,创客可以远程监控和控制他们的项目。
  • Qwiic 连接器:UNO R4 WiFi 具有 Qwiic I2C 连接器,可以轻松连接到广泛的 Qwiic 生态系统中的节点。适配器电缆还可以与基于其他连接器的传感器和执行器兼容。
  • 支持电池供电的 RTC:UNO R4 WiFi 包含额外的引脚,包括用于关闭电路板的“OFF”引脚和用于保持内部实时时钟供电和运行的“VRTC”引脚。
  • LED 矩阵:UNO R4 WiFi 包含明亮的 12x8 红色 LED 矩阵,非常适合带有动画或绘制传感器数据的创意项目,无需额外的硬件。
  • 运行时错误诊断:UNO R4 WiFi 包含错误捕获机制,可检测运行时崩溃并提供有关导致崩溃的代码行的详细解释和提示。


 Arduino® UNO R4 WiFi 技术规格:

微控制器:R7FA4M1

RA4M1 Block Diagram

瑞萨电子 RA4M1 微控制器 (MCU) 使用高性能 Arm® Cortex®-M4 内核,为密集型 HMI 设计提供段式 LCD 控制器和电容式触摸感应单元输入。RA4M1 MCU 采用高效的低能耗工艺,由开放且灵活的生态系统概念提供支持,即基于 FreeRTOS 的灵活配置软件包 (FSP),能够扩展以使用其他 RTOSes 和中间件。RA4M1 适用于需要大量电容式触摸通道和段式 LCD 控制器的应用。


特性:

  • 48MHz Arm Cortex-M4
  • 256kB 闪存以及 32kB SRAM
  • 与 EEPROM 存储数据功能类似的 8kB 数据闪存
  • 可从 40 引脚封装扩展至 100 引脚封装
  • 段式 LCD 控制器
  • 14 位 A/D 转换器
  • 电容式触摸传感单元
  • 全速 USB 2.0
  • CAN 2.0B
  • SCI(UART、简单 SPI、简单 I2C)
  • SPI/ I2C 多主机接口


Wi-Fi / 蓝⽛模块( ESP32-S3-MINI-1-N8

UNO R4 WiFi 上的 Wi-Fi®/ 蓝⽛ ® LE 模块来⾃ ESP32-S3 SoC 。它采⽤ Xtensa® 双核 32 位 LX7 MCU ,内置天线,⽀持 2.4 GHz 频段。

特性

  • Wi-Fi® 4 - 2.4 GHz 频段
  • ⽀持蓝⽛® 5 LE
  • 3.3V ⼯作电压
  • 384kB ROM
  • 512kB SRAM
  • 最⾼ 150 Mbps ⽐特率

这个模块充当了 UNO R4 WiFi 上的次级 MCU ,并使⽤逻辑电平转换器与 RA4M1 MCU 通信。


板卡框图:


板卡引脚指示图:


四.功能介绍

任务一:

这个程序可以通过网络连接到巴法云端,从巴法云端发送数字指令进而控制LED的亮度和灯板矩阵的灯亮起的数量,当输入数字0-90时,LED亮度对应在0-90的范围且灯板矩阵亮起对应数字的灯的数量;当输入数字在90-100时,LED亮度对应在90-100的亮度,灯板显示出太阳的图标。例如:当我在云端输入数字60,然后将云端数据发送到UNO R4 WiFi开发板上,可以观察到的现象为LED的亮度为60%,灯板矩阵的灯亮起了60个。

任务二:

这个程序可以通过网络连接到巴法云端,从本地的温湿度传感器读取温湿度值后,将温湿度值上传到巴法云端,可以在云端远程获取传感器的信息。

任务三:

这个程序是任务一和任务二的结合。能同时实现任务一和任务二的功能

五、功能展示

这是刚刚上电时的初始化状态。

这是通过云端控制LED亮度为30%的状态,且LED矩阵亮起数量为30个。

这是通过云端控制LED亮度为60%的状态,且LED矩阵亮起数量为60个。

这是通过云端控制LED亮度为95%的状态,且LED矩阵显示为太阳的图案。

这个是云端的控制台,可以通过这个界面输入数字控制LED的亮度和灯板矩阵


这个是云端平台可以看到的温湿度传感器的值。

六.主要代码

任务一函数主要功能:

  1. 包含库文件:程序开始处包含了所需的库文件,用于WiFi连接、创建WiFi客户端对象以及操作LED矩阵。
  2. 全局变量声明:声明了一些全局变量,包括LED矩阵的行列位置、计数器、LED的当前值和PWM值。
  3. LED矩阵数组初始化:初始化了一个8x12的数组LedArray,所有LED均关闭状态。
  4. 太阳图案数组:定义了一个太阳图案的数组SUN,用于在LED矩阵上显示太阳图案。
  5. WiFi凭据和TCP服务器信息:定义了WiFi网络的SSID和密码,以及TCP服务器的地址和端口号。
  6. WiFi和TCP客户端对象:创建了WiFi客户端对象。
  7. 函数声明:声明了订阅主题和发送数据到服务器的函数。
  8. setup函数:在程序开始时执行一次,初始化全局变量、设置PWM输出引脚、初始化LED矩阵、初始化串行通信、连接到WiFi网络、连接到TCP服务器,并订阅主题。
  9. loop函数:不断循环执行,检查是否有数据可读,提取命令和响应,根据接收到的消息更新LED值,并刷新显示。同时检查TCP客户端是否仍然连接,如果断开则尝试重新连接。
  10. updateLEDMatrix函数:根据LED的值更新LED矩阵显示,如果LED值在0到90之间,显示一个逐渐增加的LED矩阵;如果超过90,则显示太阳图案。
  11. clearLedArray函数:清除LED数组,将所有LED设置为关闭状态。
  12. subscribeToTopic函数:订阅指定的主题,发送订阅消息到服务器。
  13. sendToServer函数:发送数据到服务器。
// 包含所需的库文件
#include <WiFiS3.h> // 包含用于WiFi连接的库
#include <WiFiClient.h> // 包含用于创建WiFi客户端对象的库
#include "Arduino_LED_Matrix.h" // 包含用于操作LED矩阵的库

// 声明全局变量,用于在函数间共享和修改值
volatile int col; // 当前LED矩阵的列位置
volatile int row; // 当前LED矩阵的行位置
volatile int count; // 计数器,用于填充LED矩阵
volatile int LED; // 存储LED的当前值,该值可能会由网络消息更新
volatile int PWM; // 存储PWM值,用于控制LED的亮度
ArduinoLEDMatrix matrix; // 创建LED矩阵对象

// 初始化LED矩阵的数组,所有LED均关闭
byte LedArray[8][12] = {
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
};

// 定义一个太阳图案的数组,用于在LED矩阵上显示太阳图案
byte SUN[8][12] = {
  {0,1,0,0,0,1,0,0,0,1,0,0},
  {0,0,1,0,1,1,1,0,1,0,0,0},
  {0,0,0,1,0,0,0,1,0,0,0,0},
  {1,1,1,1,0,0,0,1,1,1,1,0},
  {0,0,0,1,0,0,0,1,0,0,0,0},
  {0,0,1,0,1,1,1,0,1,0,0,0},
  {0,1,0,0,0,1,0,0,0,1,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
};

// WiFi凭据
const char* ssid = "Mate50"; // 定义WiFi网络的SSID
const char* password = "h1234567"; // 定义WiFi网络的密码

// TCP服务器的详细信息
const char* server = "bemfa.com"; // 定义TCP服务器的地址
const int port = 8344;  // 定义服务器的端口号
const char* UID = "52f2faacc8f14856ba1b6d516d9febaf"; // 定义用于服务器认证的用户ID

// WiFi和TCP客户端对象
WiFiClient client; // 创建WiFi客户端对象

// 函数声明
void subscribeToTopic(String topic); // 声明订阅主题的函数
void sendToServer(String message); // 声明发送数据到服务器的函数

// setup函数,在程序开始时执行一次
void setup() {
  // 初始化全局变量
  col = 0;
  row = 0;
  count = 0;
  LED = 0;
  PWM = 0;

  // 设置PWM输出引脚
  pinMode(3, OUTPUT);

  // 初始化LED矩阵
  matrix.begin();

  // 初始化串行通信
  Serial.begin(9600);
  while (!Serial) {
    ; // 等待串行端口连接
  }

  // 连接到WiFi网络
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  // 等待WiFi连接成功
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address:");
  Serial.println(WiFi.localIP());

  // 连接到TCP服务器
  if (!client.connect(server, port)) {
    Serial.println("Connection to server failed");
    while (true); // 如果连接失败,进入无限循环
  }
  Serial.println("Connected to server");

  // 订阅主题
  subscribeToTopic("LED002");
}

// loop函数,不断循环执行
void loop() {
  // 检查是否有数据可读
  if (client.available()) {
    // 读取数据直到遇到换行符
    String receivedMsg = client.readStringUntil('\r\n');
    Serial.println("Received message:");
    Serial.println(receivedMsg);

    // 提取命令和响应
    int cmdIndex = receivedMsg.indexOf("cmd=1") + 5;
    int resIndex = receivedMsg.indexOf("&res=");
    String cmd = receivedMsg.substring(cmdIndex, resIndex);
    String res = receivedMsg.substring(resIndex + 5);
   
    // 检查是否为特定消息cmd=1&res=1
    if (receivedMsg.indexOf("cmd=1&res=1") != -1) {
      Serial.println("Response is 1, LED value unchanged.");
    } else {
      // 提取主题和消息内容
      int topicIndex = receivedMsg.indexOf("&topic=") + 7;
      int msgIndex = receivedMsg.indexOf("&msg=");
      String topic = receivedMsg.substring(topicIndex, msgIndex);
      String msg = receivedMsg.substring(msgIndex + 5);

      // 将接收到的消息转换为整数
      int newLED = msg.toInt();
      Serial.print("LED value received: ");
      Serial.println(newLED);

      // 如果新的LED值与旧的LED值不同,更新LED值并刷新显示
      if (newLED != LED) {
        LED = newLED;
        updateLEDMatrix(); // 更新LED矩阵显示
      }
    }
    // 清空消息内容,准备接收下一条消息
    receivedMsg = "";
  }

  // 检查TCP客户端是否仍然连接
  if (!client.connected()) {
    Serial.println("Disconnected from server, attempting to reconnect...");
    // 尝试重新连接到服务器
    if (client.connect(server, port)) {
      Serial.println("Reconnected to server");
      subscribeToTopic("LED002"); // 重新订阅主题 "LED002"
    } else {
      Serial.println("Reconnection failed");
    }
  }
}

// 更新LED矩阵显示的函数
void updateLEDMatrix() {
  PWM = (map(LED, 0, 100, 0, 240));
  analogWrite(3, PWM);
  clearLedArray();
  if (LED >= 0 && LED <= 90) {
    count = 0;
    col = 0;
    row = 0;
    while (count < LED) {
      LedArray[row][col] = 1;
      count++;
      col++;
      if (col >= 12) {
        col = 0;
        row++;
      }
      if (count == LED) {
        break;
      }
    }
    matrix.renderBitmap(LedArray, 8, 12);
  } else {
    matrix.renderBitmap(SUN, 8, 12);
  }
}

// 清除LED数组的函数
void clearLedArray() {
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 12; j++) {
      LedArray[i][j] = 0;
    }
  }
}

// 订阅主题的函数
void subscribeToTopic(String topic) {
  String subscribeMessage = "cmd=1&uid=" + String(UID) + "&topic=" + topic + "\r\n";
  sendToServer(subscribeMessage);
  Serial.println("Subscribed to topic: " + topic);
}

// 发送数据到服务器的函数
void sendToServer(String message) {
  if (!client.connected()) {
    Serial.println("Client is not ready");
    return;
  }
  client.print(message);
}

任务二函数主要功能:

  1. 包含库文件:程序开始处包含了所需的库文件,用于WiFi连接、创建WiFi客户端对象以及操作DHT传感器。
  2. WiFi凭据和TCP服务器信息:定义了WiFi网络的SSID和密码,以及TCP服务器的地址和端口号。
  3. DHT传感器设置:定义了DHT传感器连接的GPIO引脚和传感器类型,并初始化了DHT传感器对象。
  4. 全局变量声明:声明了用于存储温度和湿度的字符串变量。
  5. WiFi和TCP客户端对象:创建了WiFi客户端对象。
  6. 函数声明:声明了发布消息到TCP服务器和发送数据到TCP服务器的函数。
  7. setup函数:在程序开始时执行一次,初始化串行通信、DHT传感器、连接到WiFi网络,并连接到TCP服务器。
  8. loop函数:不断循环执行,从DHT传感器读取温度和湿度数据,将读取结果转换为字符串,发布消息到服务器,并等待5秒。同时检查TCP客户端是否仍然连接,如果断开则尝试重新连接。
  9. publishMsg函数:构造要发送到TCP服务器的数据,并调用sendtoTCPServer函数发送数据。
  10. sendtoTCPServer函数:检查TCP客户端是否连接,如果连接则发送数据到TCP服务器。
#include <WiFiS3.h> // 包含用于WiFi连接的库
#include <WiFiClient.h> // 包含用于创建WiFi客户端的库
#include <DHT.h> // 包含用于DHT传感器的库

// WiFi凭证
const char* ssid = "Mate50"; // WiFi网络的SSID
const char* password = "h1234567"; // WiFi网络的密码

// TCP服务器详细信息
const char* server = "bemfa.com"; // TCP服务器的地址
const int port = 8344;  // 服务器的端口号
const char* UID = "52f2faacc8f14856ba1b6d516d9febaf"; // 用于服务器认证的用户ID

// DHT传感器设置
#define DHTPIN 2 // DHT传感器连接的GPIO引脚
#define DHTTYPE DHT11 // 使用的DHT传感器类型
DHT dht(DHTPIN, DHTTYPE); // 初始化DHT传感器对象

// 全局变量,用于存储温度和湿度
String Temp; // 用于存储温度的字符串变量
String Humidity; // 用于存储湿度的字符串变量

// WiFi和TCP客户端对象
WiFiClient client; // 创建WiFi客户端对象

// 函数声明
void publishMsg(String topic, String msg); // 声明发布消息到TCP服务器的函数
void sendtoTCPServer(String p); // 声明发送数据到TCP服务器的函数

void setup() {
// 初始化串行通信
Serial.begin(9600); // 初始化串行通信,设置波特率为9600
while (!Serial) {
; // 等待串行端口连接。仅对原生USB端口需要
}

// 初始化DHT传感器
dht.begin(); // 初始化DHT传感器

// 连接到WiFi
Serial.print("Connecting to ");
Serial.println(ssid); // 打印连接的WiFi网络名称
WiFi.begin(ssid, password); // 开始连接到WiFi网络

while (WiFi.status() != WL_CONNECTED) { // 等待WiFi连接
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected"); // 打印WiFi连接成功的信息
Serial.println("IP address: "); // 打印IP地址
Serial.println(WiFi.localIP()); // 打印ESP模块的IP地址

// 连接到TCP服务器
if (!client.connect(server, port)) { // 尝试连接到TCP服务器
Serial.println("Connection to server failed"); // 如果连接失败,打印错误信息
while (true); // 进入无限循环
}
Serial.println("Connected to server"); // 打印连接到服务器成功的信息
}

void loop() {
// 从DHT11传感器读取温度和湿度
float h = dht.readHumidity(); // 从DHT传感器读取湿度
float t = dht.readTemperature(); // 从DHT传感器读取温度

// 如果任何读取失败则提前退出(再次尝试)。
if (isnan(h) || isnan(t)) { // 如果读取失败(返回NaN),打印错误信息并返回
Serial.println("Failed to read from DHT sensor!");
return;
}

// 将读数转换为字符串
Temp = String(t); // 将温度值转换为字符串
Humidity = String(h); // 将湿度值转换为字符串

// 向服务器发布消息
publishMsg("Sensor004", "Temp:" + Temp + ", Humidity:" + Humidity); // 向服务器发布消息

// 等待5秒钟后再发送下一条数据
delay(5000); // 等待5秒

// 检查客户端是否仍然连接
if (!client.connected()) { // 如果客户端断开连接
Serial.println("Disconnected from server, attempting to reconnect..."); // 打印尝试重新连接的信息
if (client.connect(server, port)) { // 尝试重新连接到服务器
Serial.println("Reconnected to server"); // 如果重新连接成功,打印信息
} else {
Serial.println("Reconnection failed"); // 如果重新连接失败,打印信息
}
}
}

// 向TCP服务器发布消息的函数
void publishMsg(String topic, String msg) {
String tcpTemp = "cmd=2&uid=" + String(UID) + "&topic=" + topic + "&msg=" + msg + "\r\n"; // 构造要发送到TCP服务器的数据
sendtoTCPServer(tcpTemp); // 发送数据到TCP服务器
Serial.println("A message has been published"); // 打印消息发布成功的信息
}

// 发送数据到TCP服务器的函数
void sendtoTCPServer(String p) {
if (!client.connected()) { // 如果TCP客户端未连接
Serial.println("Client is not ready"); // 打印客户端未就绪信息
return; // 返回
}
client.print(p); // 发送数据到TCP服务器
}

​任务三函数功能:

任务一与任务二的功能同时实现

// 包含所需的库文件
#include <WiFiS3.h> // 包含用于WiFi连接的库
#include <WiFiClient.h> // 包含用于创建WiFi客户端对象的库
#include "Arduino_LED_Matrix.h" // 包含用于操作LED矩阵的库
#include <DHT.h> // 包含用于DHT传感器的库

// DHT传感器设置
#define DHTPIN 2 // DHT传感器连接的GPIO引脚
#define DHTTYPE DHT11 // 使用的DHT传感器类型
DHT dht(DHTPIN, DHTTYPE); // 初始化DHT传感器对象

// 全局变量,用于存储温度和湿度
String Temp; // 用于存储温度的字符串变量
String Humidity; // 用于存储湿度的字符串变量

// 声明全局变量,用于在函数间共享和修改值
volatile int col; // 当前LED矩阵的列位置
volatile int row; // 当前LED矩阵的行位置
volatile int count; // 计数器,用于填充LED矩阵
volatile int LED; // 存储LED的当前值,该值可能会由网络消息更新
volatile int PWM; // 存储PWM值,用于控制LED的亮度
ArduinoLEDMatrix matrix; // 创建LED矩阵对象

// 初始化LED矩阵的数组,所有LED均关闭
byte LedArray[8][12] = {
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
};

// 定义一个太阳图案的数组,用于在LED矩阵上显示太阳图案
byte SUN[8][12] = {
  {0,1,0,0,0,1,0,0,0,1,0,0},
  {0,0,1,0,1,1,1,0,1,0,0,0},
  {0,0,0,1,0,0,0,1,0,0,0,0},
  {1,1,1,1,0,0,0,1,1,1,1,0},
  {0,0,0,1,0,0,0,1,0,0,0,0},
  {0,0,1,0,1,1,1,0,1,0,0,0},
  {0,1,0,0,0,1,0,0,0,1,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0},
};

// WiFi凭据
const char* ssid = "Mate50"; // 定义WiFi网络的SSID
const char* password = "h1234567"; // 定义WiFi网络的密码

// TCP服务器的详细信息
const char* server = "bemfa.com"; // 定义TCP服务器的地址
const int port = 8344;  // 定义服务器的端口号
const char* UID = "52f2faacc8f14856ba1b6d516d9febaf"; // 定义用于服务器认证的用户ID

// WiFi和TCP客户端对象
WiFiClient client; // 创建WiFi客户端对象

// 函数声明
void subscribeToTopic(String topic); // 声明订阅主题的函数
void publishMsg(String topic, String msg); // 声明发布消息到TCP服务器的函数
void sendToServer(String message); // 声明发送数据到服务器的函数

// setup函数,在程序开始时执行一次
void setup() {
  // 初始化全局变量
  col = 0;
  row = 0;
  count = 0;
  LED = 0;
  PWM = 0;

  // 设置PWM输出引脚
  pinMode(3, OUTPUT);

  // 初始化LED矩阵
  matrix.begin();

// 初始化DHT传感器
  dht.begin(); // 初始化DHT传感器

  // 初始化串行通信
  Serial.begin(9600);
  while (!Serial) {
    ; // 等待串行端口连接
  }

  // 连接到WiFi网络
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  // 等待WiFi连接成功
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address:");
  Serial.println(WiFi.localIP());

  // 连接到TCP服务器
  if (!client.connect(server, port)) {
    Serial.println("Connection to server failed");
    while (true); // 如果连接失败,进入无限循环
  }
  Serial.println("Connected to server");

  // 订阅主题
  subscribeToTopic("LED002");
}

//使用非阻塞延时,在等待期间继续处理其他任务,从而减少从云端推送数据到LED矩阵显示的延迟
unsigned long previousMillis = 0; // 记录上次更新时间
const long interval = 5000; // 设置间隔时间为5秒

// loop函数,不断循环执行
void loop() {
  unsigned long currentMillis = millis(); // 获取当前时间

  // 检查是否到了更新时间
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis; // 更新上次更新时间

  // 从DHT11传感器读取温度和湿度
    float h = dht.readHumidity(); // 从DHT传感器读取湿度
    float t = dht.readTemperature(); // 从DHT传感器读取温度

// 如果任何读取失败则提前退出(再次尝试)。
    if (isnan(h) || isnan(t)) { // 如果读取失败(返回NaN),打印错误信息并返回
      Serial.println("Failed to read from DHT sensor!");
      return;
    }

// 将读数转换为字符串
    Temp = String(t); // 将温度值转换为字符串
    Humidity = String(h); // 将湿度值转换为字符串

// 向服务器发布消息
    publishMsg("Sensor004", "Temp:" + Temp + ", Humidity:" + Humidity); // 发布消息到服务器
  }

  // 检查是否有数据可读
  if (client.available()) {
    // 读取数据直到遇到换行符
    String receivedMsg = client.readStringUntil('\r\n');
    Serial.println("Received message:");
    Serial.println(receivedMsg);

    // 提取命令和响应
    int cmdIndex = receivedMsg.indexOf("cmd=1") + 5;
    int resIndex = receivedMsg.indexOf("&res=");
    String cmd = receivedMsg.substring(cmdIndex, resIndex);
    String res = receivedMsg.substring(resIndex + 5);
   
    // 检查是否为特定消息cmd=1&res=1或者cmd=2&res=1
    if (receivedMsg.indexOf("cmd=1&res=1") != -1 || receivedMsg.indexOf("cmd=2&res=1") != -1) {
      Serial.println("Response is 1, LED value unchanged.");
    } else {
      // 提取主题和消息内容
      int topicIndex = receivedMsg.indexOf("&topic=") + 7;
      int msgIndex = receivedMsg.indexOf("&msg=");
      String topic = receivedMsg.substring(topicIndex, msgIndex);
      String msg = receivedMsg.substring(msgIndex + 5);

      // 将接收到的消息转换为整数
      int newLED = msg.toInt();
      Serial.print("LED value received: ");
      Serial.println(newLED);

      // 如果新的LED值与旧的LED值不同,更新LED值并刷新显示
      if (newLED != LED) {
        LED = newLED;
        updateLEDMatrix(); // 更新LED矩阵显示
      }
    }
    // 清空消息内容,准备接收下一条消息
    receivedMsg = "";
  }

  // 检查TCP客户端是否仍然连接
  if (!client.connected()) {
    Serial.println("Disconnected from server, attempting to reconnect...");
    // 尝试重新连接到服务器
    if (client.connect(server, port)) {
      Serial.println("Reconnected to server");
      subscribeToTopic("LED002"); // 重新订阅主题 "LED002"
    } else {
      Serial.println("Reconnection failed");
    }
  }
}

// 更新LED矩阵显示的函数
void updateLEDMatrix() {
  PWM = (map(LED, 0, 100, 0, 240));
  analogWrite(3, PWM);
  clearLedArray();
  if (LED >= 0 && LED <= 90) {
    count = 0;
    col = 0;
    row = 0;
    while (count < LED) {
      LedArray[row][col] = 1;
      count++;
      col++;
      if (col >= 12) {
        col = 0;
        row++;
      }
      if (count == LED) {
        break;
      }
    }
    matrix.renderBitmap(LedArray, 8, 12);
  } else {
    matrix.renderBitmap(SUN, 8, 12);
  }
}

// 清除LED数组的函数
void clearLedArray() {
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 12; j++) {
      LedArray[i][j] = 0;
    }
  }
}

// 订阅主题的函数
void subscribeToTopic(String topic) {
  String subscribeMessage = "cmd=1&uid=" + String(UID) + "&topic=" + topic + "\r\n";
  sendToServer(subscribeMessage);
  Serial.println("Subscribed to topic: " + topic);
}

// 向TCP服务器发布消息的函数
void publishMsg(String topic, String msg) {
  String tcpTemp = "cmd=2&uid=" + String(UID) + "&topic=" + topic + "&msg=" + msg + "\r\n"; // 构造要发送到TCP服务器的数据
  sendToServer(tcpTemp); // 发送数据到TCP服务器
  Serial.println("A message has been published"); // 打印消息发布成功的信息
}

// 发送数据到服务器的函数
void sendToServer(String message) {
  if (!client.connected()) {
    Serial.println("Client is not ready");
    return;
  }
  client.print(message);
}

七.调试程序遇到的问题和解决

问题一:

在调试从云端接收LED数据这部分代码的时候,发现LED灯矩阵是直接已经点亮的(正常应该是全灭状态),然后通过串口监视器,把开发板接收到的信息打印出来,发现过一段时间就会收到服务器的消息:cmd=1&res=1。这样就会给LED赋值为1,点亮LED灯矩阵。所以我在这里做了一个判断:if (receivedMsg.indexOf("cmd=1&res=1") != -1) ,即如果收到这个信息时LED的值不改变,这样就不会影响我们正常从云端发送的消息。

image.png

问题二:

在把两个程序结合起来的时候,我通过云端控制LED时会发现延时的时间特别长,发送了信息后要过一段时间后LED灯矩阵才会显示出来。主要原因是程序一在读取传感器的值的时候有一个5秒的延时,导致整个程序运行时间变慢。解决方法是我在程序中使用了非阻塞式延时,它是一种在程序执行过程中暂停一段时间,同时允许CPU处理其他任务的技术。这种延时方式不会阻塞CPU,使得系统能够保持对其他任务或事件的响应能力。

image.png

八.活动总结

通过参与本次项目,我对ArduinoUNO R4 WiFi模块的功能和应用有了更深入的认识。在学习过程中,我不仅掌握了如何利用ArduinoUNO R4 WiFi模块进行网络连接和信息获取,还通过实践锻炼了我的动手操作能力和编程技巧。这次经历让我对嵌入式开发有了更加深刻的体会,也让我意识到了在实际操作中解决问题的重要性。

这次活动极大地提升了我的技术能力,同时也拓宽了我对电子芯片在日常生活中应用的视野。我学会了如何将理论知识与实践相结合,这对于我未来的职业发展具有重要意义。我期待能够再次参与类似的活动,以便继续学习新知识,掌握新技能。

附件下载
ABX00087-cad-files.zip
ABX00087-datasheet.pdf
ABX00087-full-pinout.pdf
ABX00087-schematics.pdf
UNO R4.zip
三个任务的代码
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号