TA的每日心情 | 奋斗 2025-7-11 08:33 |
|---|
签到天数: 2 天 连续签到: 1 天 [LV.1]初来乍到
童生
- 积分
- 164
|
✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 “配置环境” 转移到 “创意实现”,极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
项目概述
本项目基于零知增强板(主控STM32F407VET6)结合W5500以太网模块,实现了一套完整的UDP通信温湿度监控系统。系统通过DHT11传感器实时采集环境温湿度数据,通过W5500以太网模块建立UDP通信链路,将数据发送至PC上位机。同时,上位机可通过UDP协议发送控制指令,远程控制开发板上的LED灯开关状态 项目难点及解决方案
问题描述:多网卡路由冲突导致路由表混乱,UDP包丢失 解决方案:网段隔离与静态配置,将网络拓扑从混合网段改为独立网段;代码中禁用DHCP功能,网关和DNS强制指向PC的以太网IP;同时网段检测逻辑
一、系统硬件部分1.1 元件清单硬件名称数量备注
零知增强板(STM32F407VET6) 1主控核心板
W5500 以太网模块1带 SPI 接口的以太网模块
DHT11 温湿度传感器1数字型温湿度传感器
LED 发光二极管1用于远程控制演示
10K 上拉电阻1DHT11 数据脚需接
杜邦线若干连接各模块
网线1PC 与 W5500 直连
PC(带以太网口)1运行 Python 上位机
1.2 接线方案表请务必严格按照代码中的定义进行连接,否则会导致初始化失败。 零知增强板引脚外接设备设备引脚功能说明
7DHT11DATA温湿度数据传输
8LED正极LED 控制引脚(低电平熄灭)
GNDDHT11/LED/W5500GND公共接地
5VW5500/DHT115V / +供电(W5500 需 5V)
3.3V可选DHT11 (+)DHT11 也可接 3.3V
A5 (SCLK)W5500SCLKSPI 时钟线
A6 (MISO)W5500MISOSPI 主机输入 / 从机输出
A7 (MOSI)W5500MOSISPI 主机输出 / 从机输入
A4 (SCS)W5500CSW5500 片选引脚
1.3 接线示意图
W5500 的 SPI 接线必须严格对应零知增强板 的 SPI 引脚(SCK/MISO/MOSI/CS)
1.4 实物连接图
二、安装与使用部分2.1 开源平台-输入"W5500的UDP通信"并搜索-代码下载自动打开
2.2 连接-验证-上传
2.3 调试-串口监视器
三、核心代码讲解 本项目的代码设计体现了模块化和健壮性的特点,以下将对核心的四个部分进行详细剖析
3.1 网络初始化与配置 网络初始化是本项目的核心,采用PC直连静态IP模式,确保通信稳定
- // ==================== 网络配置 - PC直连模式 ====================
- byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
- // 静态IP配置
- IPAddress staticIP(192, 168, 10, 22); // W5500的IP
- IPAddress gateway(192, 168, 10, 1); // 设为PC的以太网卡IP
- IPAddress subnet(255, 255, 255, 0);
- IPAddress dnsip(192, 168, 10, 1); // DNS指向PC
- // PC的以太网卡IP
- IPAddress pcIP(192, 168, 10, 17);
- // 网络初始化函数
- void initNetwork() {
- // 静态IP配置
- Ethernet.begin(mac, staticIP, dnsip, gateway, subnet);
-
- // 验证网络配置
- IPAddress ip = Ethernet.localIP();
- if (ip == IPAddress(0, 0, 0, 0)) {
- Serial.println("✗✗✗ 错误: 以太网初始化失败! ✗✗✗");
- // 错误处理...
- }
-
- // 启动UDP服务
- Udp.begin(localPort);
- }
复制代码 PC以太网卡不提供DHCP服务,必须使用静态IP;网关设置指向PC,点对点直连网络
3.2 DHT11数据采集与处理 DHT11传感器数据采集需要精确的时序控制,并处理可能的读取失败情况
- // DHT11初始化
- DHT dht(DHTPIN, DHTTYPE);
- // 读取DHT数据函数
- void readDHTData() {
- unsigned long currentTime = millis();
-
- // 每10秒读取一次
- if (currentTime - lastDHTReadTime >= 10000) {
- lastDHTReadTime = currentTime;
-
- float h = dht.readHumidity();
- float t = dht.readTemperature();
-
- if (isnan(h) || isnan(t)) {
- Serial.println("✗ DHT11读取失败!");
- dhtValid = false;
- } else {
- humidity = h;
- temperature = t;
- dhtValid = true;
-
- // 发送数据
- sendDHTData();
- }
- }
- }
- // 发送DHT数据函数
- void sendDHTData() {
- if (dhtValid) {
- messageCount++;
-
- char tempStr[10];
- char humiStr[10];
- floatToString(tempStr, temperature, 2); // 2位小数精度
- floatToString(humiStr, humidity, 2);
-
- // 构建JSON格式数据
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"dht","count":%lu,"temp":%s,"humi":%s,"time":%lu}",
- messageCount, tempStr, humiStr, millis() / 1000);
-
- sendUDP(sendBuffer);
- }
- }
复制代码 DHT11设置为10秒的读取间隔,使用自定义floatToString()函数处理浮点数
3.3 UDP通信协议解析 实现简单的命令解析机制,支持多种控制指令
- // 协议解析函数
- void parseCommand(const char* cmd, IPAddress remoteIP, int remotePort) {
- // LED_ON命令
- if (strcmp(cmd, "LED_ON") == 0) {
- digitalWrite(LED_CONTROL_PIN, HIGH);
- ledControlState = true;
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"LED_ON","status":"success","led_state":true}");
-
- Udp.beginPacket(remoteIP, remotePort);
- Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
- Udp.endPacket();
- }
- // GET_DHT命令
- else if (strcmp(cmd, "GET_DHT") == 0) {
- float h = dht.readHumidity();
- float t = dht.readTemperature();
-
- if (isnan(h) || isnan(t)) {
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"GET_DHT","status":"error","error":"read_failed"}");
- } else {
- char tempStr[10];
- char humiStr[10];
- floatToString(tempStr, t, 2);
- floatToString(humiStr, h, 2);
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"GET_DHT","status":"success","temp":%s,"humi":%s}",
- tempStr, humiStr);
- }
-
- Udp.beginPacket(remoteIP, remotePort);
- Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
- Udp.endPacket();
- }
- // 其他命令处理...
- }
复制代码 支持的命令列表
指令功能说明返回信息
LED_ON点亮LED返回成功状态和LED状态
LED_OFF熄灭LED返回成功状态和LED状态
GET_DHT读取实时温湿度返回数据或错误信息
STATUS获取设备完整状态信息返回设备状态每个命令都有明确的成功/失败状态返回,接收到命令后立即处理并返回结果
3.4 系统状态维护与心跳机制 系统需要维护多个状态变量,并实现心跳机制确保连接正常 - // 全局状态变量
- unsigned long lastDHTReadTime = 0; // 上次DHT读取时间
- unsigned long lastSendTime = 0; // 上次发送时间
- unsigned long messageCount = 0; // 消息计数器
- unsigned long lastHeartbeat = 0; // 上次心跳时间
- bool dhtValid = false; // DHT数据有效性
- bool ledControlState = false; // LED控制状态
- bool networkInitialized = false; // 网络初始化状态
- unsigned long packetsSent = 0; // 发送数据包计数
- unsigned long packetsReceived = 0; // 接收数据包计数
- // 心跳包发送函数
- void sendHeartbeat() {
- unsigned long currentTime = millis();
-
- if (currentTime - lastHeartbeat >= 30000) { // 每30秒
- lastHeartbeat = currentTime;
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"heartbeat","uptime":%lu,"packets_sent":%lu,"packets_received":%lu}",
- millis() / 1000, packetsSent, packetsReceived);
- sendUDP(sendBuffer);
- }
- }
- // 状态LED指示函数
- void updateStatusLED() {
- unsigned long currentTime = millis();
-
- if (currentTime - lastBlinkTime >= 500) { // 每500ms闪烁一次
- lastBlinkTime = currentTime;
- ledBlinkState = !ledBlinkState;
- digitalWrite(LED_BUILTIN, ledBlinkState ? LOW : HIGH);
- }
- }
复制代码 统计发送和接收的数据包数量,用于监控通信质量;定期发送心跳包,让上位机知道设备在线状态
3.5 系统完整代码
- /**************************************************************************************
- * 文件: W5500_UDP_DHT11_Control.ino
- * 作者:零知实验室(深圳市在芯间科技有限公司)
- * -^^- 零知实验室,让电子制作变得更简单! -^^-
- * 时间: 2026-02-09
- * 网络拓扑:
- * 路由器(192.168.3.1) ←WiFi→ PC(WiFi: 192.168.3.17, 以太网: 192.168.10.1)
- * ↓ 直连网线
- * W5500(192.168.10.22)
- *
- * 功能说明:
- * W5500以太网模块UDP通信、DHT11温湿度传感器数据采集和上报、远程LED控制功能、简单协议解析和响应、修复JSON浮点数格式化问题(snprintf不支持%f)
- ************************************************************************************/
- #include <SPI.h>
- #include <Ethernet_STM.h>
- #include <EthernetUdp.h>
- #include "DHT.h"
- // ==================== 硬件配置 ====================
- #define DHTPIN 7
- #define DHTTYPE DHT11
- DHT dht(DHTPIN, DHTTYPE);
- #define LED_CONTROL_PIN 8
- // ==================== 网络配置 - PC直连模式 ====================
- #if defined(WIZ550io_WITH_MACADDRESS)
- // WIZ550io有内置MAC地址
- #else
- byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
- #endif
- // PC直连模式 - 必须使用静态IP!
- // PC的以太网卡不提供DHCP服务,所以DHCP无法工作
- #define USE_DHCP false
- // ==================== 重要: 网段配置说明 ====================
- // 使用 192.168.10.x 网段,与PC的WiFi网段(192.168.3.x)分开
- // 避免IP冲突和路由混乱
- // 静态IP配置
- IPAddress staticIP(192, 168, 10, 22); // W5500的IP
- IPAddress gateway(192, 168, 10, 1); // 设为PC的以太网卡IP
- IPAddress subnet(255, 255, 255, 0);
- IPAddress dnsip(192, 168, 10, 1); // DNS指向PC
- // PC的以太网卡IP (连接W5500的那个网卡)
- // 不是WiFi的IP (192.168.3.17)!
- IPAddress pcIP(192, 168, 10, 17);
- // UDP端口
- unsigned int localPort = 8888;
- unsigned int pcPort = 9003;
- // ==================== 如果您的PC以太网卡IP是其他值 ====================
- // 请相应修改上面的配置,例如:
- //
- // 如果PC以太网卡是 192.168.137.1 (启用了ICS):
- // IPAddress staticIP(192, 168, 137, 22);
- // IPAddress gateway(192, 168, 137, 1);
- // IPAddress dnsip(192, 168, 137, 1);
- // IPAddress pcIP(192, 168, 137, 1);
- //
- // 如果PC以太网卡是 192.168.3.215 (不推荐,会与WiFi冲突):
- // IPAddress staticIP(192, 168, 3, 22);
- // IPAddress gateway(192, 168, 3, 215); // 指向PC,不是路由器!
- // IPAddress dnsip(192, 168, 3, 215);
- // IPAddress pcIP(192, 168, 3, 215);
- // ==================== 全局变量 ====================
- EthernetUDP Udp;
- char receiveBuffer[256];
- char sendBuffer[512];
- // 定时器
- unsigned long lastDHTReadTime = 0;
- unsigned long lastSendTime = 0;
- unsigned long messageCount = 0;
- unsigned long lastHeartbeat = 0;
- // DHT数据
- float temperature = 0.0;
- float humidity = 0.0;
- bool dhtValid = false;
- // LED状态
- bool ledControlState = false;
- unsigned long lastBlinkTime = 0;
- bool ledBlinkState = false;
- // 网络状态
- bool networkInitialized = false;
- unsigned long lastSuccessTime = 0;
- unsigned long packetsSent = 0;
- unsigned long packetsReceived = 0;
- // ==================== 函数声明 ====================
- void floatToString(char* buffer, float value, int decimalPlaces);
- // ==================== 浮点数转字符串 ====================
- void floatToString(char* buffer, float value, int decimalPlaces) {
- int intPart = (int)value;
- int decPart = (int)((value - intPart) * pow(10, decimalPlaces));
-
- if (decPart < 0) decPart = -decPart;
-
- if (decimalPlaces == 1) {
- sprintf(buffer, "%d.%01d", intPart, decPart);
- } else if (decimalPlaces == 2) {
- sprintf(buffer, "%d.%02d", intPart, decPart);
- }
- }
- // ==================== 初始化 ====================
- void setup() {
- Serial.begin(115200);
- delay(100);
-
- Serial.println("\n\n");
- Serial.println("========================================");
- Serial.println(" W5500 UDP + DHT11温湿度监控系统");
- Serial.println(" 零知实验室");
- Serial.println(" 版本: v3.1 (PC直连专用版)");
- Serial.println("========================================\n");
-
- Serial.println(" 网络模式: PC直连(静态IP)");
- Serial.println(" 请确保PC的以太网卡已配置静态IP!\n");
-
- initHardware();
- initNetwork();
- initDHT();
-
- Serial.println("\n========================================");
- Serial.println("系统启动完成!");
- Serial.println("========================================\n");
-
- printSystemInfo();
- sendStartupMessage();
-
- Serial.println("\n开始工作...\n");
- Serial.println("----------------------------------------\n");
- }
- // ==================== 硬件初始化 ====================
- void initHardware() {
- Serial.println("[1/3] 初始化硬件...");
-
- pinMode(LED_BUILTIN, OUTPUT);
- digitalWrite(LED_BUILTIN, HIGH);
-
- pinMode(LED_CONTROL_PIN, OUTPUT);
- digitalWrite(LED_CONTROL_PIN, LOW);
- ledControlState = false;
-
- // 启动提示 - LED快闪3次
- for(int i = 0; i < 3; i++) {
- digitalWrite(LED_BUILTIN, LOW);
- delay(100);
- digitalWrite(LED_BUILTIN, HIGH);
- delay(100);
- }
-
- Serial.println("✓ 硬件初始化完成!");
- }
- // ==================== 网络初始化 ====================
- void initNetwork() {
- Serial.println("\n[2/3] 初始化W5500以太网模块...");
- Serial.println(" 模式: 静态IP (PC直连)");
- delay(500);
-
- // 静态IP配置
- #if defined(WIZ550io_WITH_MACADDRESS)
- Ethernet.begin(staticIP, dnsip, gateway, subnet);
- #else
- Ethernet.begin(mac, staticIP, dnsip, gateway, subnet);
- #endif
-
- delay(1000);
-
- // 验证网络配置
- IPAddress ip = Ethernet.localIP();
-
- if (ip == IPAddress(0, 0, 0, 0)) {
- Serial.println("\n✗✗✗ 错误: 以太网初始化失败! ✗✗✗");
- Serial.println("\n请检查:");
- Serial.println(" 1. W5500模块SPI接线是否正确");
- Serial.println(" 2. 网线是否连接到PC的以太网口");
- Serial.println(" 3. PC的以太网卡是否已配置静态IP");
- Serial.println("\n设备将进入错误指示模式");
-
- while(1) {
- digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
- delay(100);
- }
- }
-
- Serial.println("\n✓ W5500初始化成功!");
- Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
- Serial.print(" 本机IP: ");
- Serial.println(ip);
- Serial.print(" 子网掩码: ");
- Serial.println(Ethernet.subnetMask());
- Serial.print(" 网关: ");
- Serial.println(Ethernet.gatewayIP());
- Serial.print(" DNS: ");
- Serial.println(Ethernet.dnsServerIP());
- Serial.println(" 模式: 静态IP (PC直连)");
- Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
-
- // 显示PC配置提示
- Serial.println("\n📌 PC网络配置要求:");
- Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
- Serial.print(" 以太网卡IP: ");
- Serial.println(pcIP);
- Serial.print(" 子网掩码: ");
- Serial.println(subnet);
- Serial.println(" 默认网关: (留空)");
- Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
- Serial.println(" 请确保PC的以太网卡已设置为上述静态IP!");
- Serial.println(" 否则无法通信!\n");
-
- // 启动UDP
- Serial.println("[3/3] 启动UDP服务...");
- Udp.begin(localPort);
- Serial.print(" - 监听端口: ");
- Serial.println(localPort);
- Serial.print(" - 目标PC: ");
- Serial.print(pcIP);
- Serial.print(":");
- Serial.println(pcPort);
- Serial.println("✓ UDP服务启动成功!");
-
- networkInitialized = true;
- }
- // ==================== DHT11初始化 ====================
- void initDHT() {
- Serial.println("\n初始化DHT11传感器...");
- dht.begin();
- delay(2000);
-
- humidity = dht.readHumidity();
- temperature = dht.readTemperature();
-
- if (isnan(humidity) || isnan(temperature)) {
- Serial.println(" DHT11首次读取失败,将继续尝试...");
- dhtValid = false;
- } else {
- Serial.println("✓ DHT11初始化成功!");
- Serial.print(" - 温度: ");
- Serial.print(temperature);
- Serial.println(" °C");
- Serial.print(" - 湿度: ");
- Serial.print(humidity);
- Serial.println(" %");
- dhtValid = true;
- }
- }
- // ==================== 系统信息显示 ====================
- void printSystemInfo() {
- Serial.println("\n系统配置: ");
- Serial.print(" - DHT11引脚: ");
- Serial.println(DHTPIN);
- Serial.print(" - LED控制引脚: ");
- Serial.println(LED_CONTROL_PIN);
- Serial.println(" - 网络模式: PC直连静态IP");
- Serial.println("\n支持的命令:");
- Serial.println(" - LED_ON : 点亮LED");
- Serial.println(" - LED_OFF : 熄灭LED");
- Serial.println(" - GET_DHT : 立即获取温湿度");
- Serial.println(" - STATUS : 获取设备状态");
- Serial.println(" - PING : 测试连接");
- Serial.println("\n自动功能:");
- Serial.println(" - 每10秒自动上报温湿度数据");
- Serial.println(" - 每30秒发送心跳包");
- Serial.println(" - 板载LED慢闪表示正常运行");
-
- Serial.println("\n网络诊断:");
- Serial.print(" - W5500 IP: ");
- Serial.println(Ethernet.localIP());
- Serial.print(" - PC IP: ");
- Serial.println(pcIP);
- Serial.print(" - 是否同网段: ");
-
- // 检查是否在同一网段
- IPAddress w5500IP = Ethernet.localIP();
- bool sameSubnet = (w5500IP[0] == pcIP[0] &&
- w5500IP[1] == pcIP[1] &&
- w5500IP[2] == pcIP[2]);
-
- if (sameSubnet) {
- Serial.println("✓ 是");
- } else {
- Serial.println("✗ 否 (无法通信!)");
- Serial.println("\n [警告] IP地址不在同一网段!");
- Serial.println("请检查网络配置!");
- }
- }
- // ==================== 发送启动消息 ====================
- void sendStartupMessage() {
- IPAddress ip = Ethernet.localIP();
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"startup","ip":"%d.%d.%d.%d","port":%d,"mode":"pc_direct","pc_ip":"%d.%d.%d.%d"}",
- ip[0], ip[1], ip[2], ip[3],
- localPort,
- pcIP[0], pcIP[1], pcIP[2], pcIP[3]);
- sendUDP(sendBuffer);
-
- Serial.println("📤 已发送启动消息到PC");
- }
- // ==================== 发送心跳包 ====================
- void sendHeartbeat() {
- unsigned long currentTime = millis();
-
- if (currentTime - lastHeartbeat >= 30000) {
- lastHeartbeat = currentTime;
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"heartbeat","uptime":%lu,"packets_sent":%lu,"packets_received":%lu}",
- millis() / 1000, packetsSent, packetsReceived);
- sendUDP(sendBuffer);
-
- Serial.println(" 心跳包");
- }
- }
- // ==================== 读取DHT11数据 ====================
- void readDHTData() {
- unsigned long currentTime = millis();
-
- if (currentTime - lastDHTReadTime >= 10000) {
- lastDHTReadTime = currentTime;
-
- float h = dht.readHumidity();
- float t = dht.readTemperature();
-
- if (isnan(h) || isnan(t)) {
- Serial.println("✗ DHT11读取失败!");
- dhtValid = false;
- } else {
- humidity = h;
- temperature = t;
- dhtValid = true;
-
- Serial.println("📊 DHT11数据:");
- Serial.print(" 温度: ");
- Serial.print(temperature);
- Serial.println(" °C");
- Serial.print(" 湿度: ");
- Serial.print(humidity);
- Serial.println(" %");
-
- sendDHTData();
- }
- }
- }
- // ==================== 发送DHT数据 ====================
- void sendDHTData() {
- if (dhtValid) {
- messageCount++;
-
- char tempStr[10];
- char humiStr[10];
- floatToString(tempStr, temperature, 2);
- floatToString(humiStr, humidity, 2);
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"dht","count":%lu,"temp":%s,"humi":%s,"time":%lu}",
- messageCount, tempStr, humiStr, millis() / 1000);
-
- sendUDP(sendBuffer);
-
- Serial.print("📤 已发送温湿度数据 #");
- Serial.println(messageCount);
- }
- }
- // ==================== 发送UDP数据 ====================
- void sendUDP(const char* message) {
- if (!networkInitialized) {
- Serial.println(" 网络未初始化,无法发送");
- return;
- }
-
- Udp.beginPacket(pcIP, pcPort);
- Udp.write((uint8_t*)message, strlen(message));
- Udp.endPacket();
-
- packetsSent++;
- lastSuccessTime = millis();
- }
- // ==================== 协议解析 ====================
- void parseCommand(const char* cmd, IPAddress remoteIP, int remotePort) {
- Serial.print("📝 解析命令: ");
- Serial.println(cmd);
-
- // LED_ON
- if (strcmp(cmd, "LED_ON") == 0) {
- digitalWrite(LED_CONTROL_PIN, HIGH);
- ledControlState = true;
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"LED_ON","status":"success","led_state":true}");
-
- Udp.beginPacket(remoteIP, remotePort);
- Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
- Udp.endPacket();
-
- Serial.println("✓ LED已点亮");
- }
- // LED_OFF
- else if (strcmp(cmd, "LED_OFF") == 0) {
- digitalWrite(LED_CONTROL_PIN, LOW);
- ledControlState = false;
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"LED_OFF","status":"success","led_state":false}");
-
- Udp.beginPacket(remoteIP, remotePort);
- Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
- Udp.endPacket();
-
- Serial.println("✓ LED已熄灭");
- }
- // GET_DHT
- else if (strcmp(cmd, "GET_DHT") == 0) {
- float h = dht.readHumidity();
- float t = dht.readTemperature();
-
- if (isnan(h) || isnan(t)) {
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"GET_DHT","status":"error","error":"read_failed"}");
- } else {
- humidity = h;
- temperature = t;
- dhtValid = true;
-
- char tempStr[10];
- char humiStr[10];
- floatToString(tempStr, temperature, 2);
- floatToString(humiStr, humidity, 2);
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"GET_DHT","status":"success","temp":%s,"humi":%s}",
- tempStr, humiStr);
- }
-
- Udp.beginPacket(remoteIP, remotePort);
- Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
- Udp.endPacket();
-
- Serial.println("✓ 已发送DHT数据");
- }
- // PING
- else if (strcmp(cmd, "PING") == 0) {
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"PING","status":"success","message":"PONG"}");
-
- Udp.beginPacket(remoteIP, remotePort);
- Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
- Udp.endPacket();
-
- Serial.println("✓ PONG");
- }
- // STATUS
- else if (strcmp(cmd, "STATUS") == 0) {
- char tempStr[10];
- char humiStr[10];
- floatToString(tempStr, temperature, 2);
- floatToString(humiStr, humidity, 2);
-
- IPAddress ip = Ethernet.localIP();
-
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"STATUS","status":"success","uptime":%lu,"led_state":%s,"dht_valid":%s,"temp":%s,"humi":%s,"ip":"%d.%d.%d.%d","mode":"pc_direct","packets_sent":%lu,"packets_received":%lu}",
- millis() / 1000,
- ledControlState ? "true" : "false",
- dhtValid ? "true" : "false",
- tempStr, humiStr,
- ip[0], ip[1], ip[2], ip[3],
- packetsSent, packetsReceived);
-
- Udp.beginPacket(remoteIP, remotePort);
- Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
- Udp.endPacket();
-
- Serial.println("✓ 已发送设备状态");
- }
- // 未知命令
- else {
- snprintf(sendBuffer, sizeof(sendBuffer),
- "{"type":"response","cmd":"%s","status":"error","error":"unknown_command"}",
- cmd);
-
- Udp.beginPacket(remoteIP, remotePort);
- Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
- Udp.endPacket();
-
- Serial.println("✗ 未知命令");
- }
- }
- // ==================== 接收UDP数据 ====================
- void receiveUDP() {
- int packetSize = Udp.parsePacket();
-
- if (packetSize > 0) {
- int len = Udp.read(receiveBuffer, sizeof(receiveBuffer) - 1);
- if (len > 0) {
- receiveBuffer[len] = '\0';
-
- IPAddress remoteIP = Udp.remoteIP();
- int remotePort = Udp.remotePort();
-
- Serial.println("📥 收到消息:");
- Serial.print(" 来源: ");
- Serial.print(remoteIP);
- Serial.print(":");
- Serial.println(remotePort);
- Serial.print(" 内容: ");
- Serial.println(receiveBuffer);
-
- packetsReceived++;
- lastSuccessTime = millis();
-
- parseCommand(receiveBuffer, remoteIP, remotePort);
-
- Serial.println();
- }
- }
- }
- // ==================== LED状态指示 ====================
- void updateStatusLED() {
- unsigned long currentTime = millis();
-
- if (currentTime - lastBlinkTime >= 500) {
- lastBlinkTime = currentTime;
- ledBlinkState = !ledBlinkState;
- digitalWrite(LED_BUILTIN, ledBlinkState ? LOW : HIGH);
- }
- }
- // ==================== 主循环 ====================
- void loop() {
- // 读取DHT11数据(每10秒)
- readDHTData();
-
- // 发送心跳包(每30秒)
- sendHeartbeat();
-
- // 处理UDP数据
- receiveUDP();
-
- // 更新状态LED
- updateStatusLED();
-
- delay(10);
- }
复制代码 系统流程图
UDP传输协议原理
UDP用户数据报协议发送数据前不需要建立连接,直接发送;协议头仅8字节,相比TCP的20字节更节省带宽;没有握手和确认过程,延迟更低
UDP实现
- // 发送UDP数据
- void sendUDP(const char* message) {
- Udp.beginPacket(pcIP, pcPort); // 开始数据包
- Udp.write((uint8_t*)message, strlen(message)); // 写入数据
- Udp.endPacket(); // 结束并发送
-
- packetsSent++; // 更新统计
- }
- // 接收UDP数据
- void receiveUDP() {
- int packetSize = Udp.parsePacket(); // 检查是否有数据包
-
- if (packetSize > 0) {
- int len = Udp.read(receiveBuffer, sizeof(receiveBuffer) - 1);
- if (len > 0) {
- receiveBuffer[len] = '\0'; // 添加字符串结束符
- // 处理接收到的数据...
- }
- }
- }
复制代码 UDP数据包结构
四、项目结果演示4.1 操作流程 将PC以太网口的IP设为192.168.10.17,子网掩码255.255.255.0,网关192.168.10.1;运行 Python 代码,确认 UDP 服务启动,监听 9003 端口
以太网配置:
上位机启动:
通过命令行启动程序,执行python udp_dht_controller.py启动python端上位机
功能测试:
①查看串口监视器,确认每 10 秒采集一次温湿度并发送
②在 Python 菜单中选择 1/2 控制 LED 开关,选择 3 获取实时温湿度,选择 4 查看设备状态
③观察 PC 端是否能收到心跳包(每 30 秒一次)
4.2 网络调试助手演示 除 Python 上位机外,也可使用网络调试助手测试 UDP 通信,测试时确保python上位机关闭并且PC的IP地址和端口没有被占用
①打开网络调试助手,选择UDP客户端,本地端口9003,远程IP 192.168.10.22,远程端口8888
②发送'LED_ON',可收到设备端返回的JSON响应
③切换到UDP服务端,监听9003端口,可接收设备端发送的温湿度数据和心跳包
4.3 视频演示
基于W5500的UDP温湿度监控与LED 远程控制系统
视频演示了Python上位机和网络调试助手作为UDP客户端的完整操作流程。展示程序启动界面和配置网络调试助手的本地IP和端口,展示自动上报的温湿度数据。通过菜单选择,python上位机演示了LED点亮/熄灭控制、手动获取温湿度数据、查询设备状态等功能。网络调试助手向设备发送文本命令可以实时接收到JSON格式响应
五、W5500模块技术讲解W5500 核心特性
支持TCP、UDP、ICMP、IPv4、ARP、IGMP等协议;可同时处理8个网络连接、支持最高80MHz的SPI时钟频率
5.1 SPI通信接口W5500通过SPI接口与主控MCU通信,SPI时序图如下:
SPI帧格式
W5500 SPI帧由地址阶段的16位偏移地址、8位控制阶段以及N字节数据阶段组成。8位控制阶段由块选择位(BSB[4:0])、读/写访问模式位(RWB)和SPI操作模式(OM[1:0])重新配置。块选择位用于选择偏移地址所属的块。
W5500 支持连续数据读写。它从基准位置(为 2/4/N 字节连续数据处理设置的偏移地址)开始处理数据,并且通过将偏移地址增加 1(自动递增寻址)来处理下一个数据。
5.2 寄存器结构通用寄存器块
通用寄存器用于配置 W5500 的通用信息 IP 地址和 MAC 地址,通过 SPI 帧中的 BSB [4:0] 值选择该寄存器块
GAR(网关地址寄存器)存储网关IP地址、SUBR(子网掩码寄存器)存储子网掩码
Socket 寄存器块
W5500 支持 8 个用于通信通道的 Socket。每个 Socket 由 Socket n 寄存器块 控制(其中 0≤n≤7)。可通过 SPI 帧中的 BSB[4:0] 选择 Socket n 寄存器的 n 值。
Sn_MR(Socket模式寄存器)设置Socket工作模式、Sn_CR(Socket命令寄存器)执行Socket命令
W5500 在本项目中的工作原理
六、常见问题解答(FAQ)Q1: W5500模块无法初始化,串口显示IP为全0或者全255 A:可能的原因和解决方案:SPI接线错误、电源问题或者硬件故障,检查SCK、MISO、MOSI、CS、RST引脚,检查3.3V电源供电和SPI时钟频率配置,如果仍然有问题尝试更换W5500模块
Q2: PC无法ping通W5500设备 A:网络配置检查步骤:确认W5500和PC在同一网段,确认双方子网掩码相同、临时关闭Windows防火墙测试、尝试更换网线或检查RJ45接口、确认PC以太网卡已启用且配置了正确的静态IP
Q3: UDP通信不稳定,偶尔丢包 A:提高UDP通信稳定性的方法:确保单次发送数据不超过1472字节、重要数据可在应用层实现简单的确认重发、避免过高的发送频率,给网络处理留出时间
项目资源整合
网络调试助手和上位机程序:
W5500数据手册:
W5500以太网库文件: Ethernet_STM
|
|