回答

收藏

零知IDE——基于STM32与W5500的UDP通信实现温湿度监控

其他 其他 120 人阅读 | 0 人回复 | 2026-02-28

​  ✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 “配置环境” 转移到 “创意实现”,极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
www.lingzhilab.com

项目概述
       本项目基于零知增强板(主控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模式,确保通信稳定
  1. // ==================== 网络配置 - PC直连模式 ====================
  2. byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};

  3. // 静态IP配置
  4. IPAddress staticIP(192, 168, 10, 22);      // W5500的IP
  5. IPAddress gateway(192, 168, 10, 1);        // 设为PC的以太网卡IP
  6. IPAddress subnet(255, 255, 255, 0);        
  7. IPAddress dnsip(192, 168, 10, 1);          // DNS指向PC

  8. // PC的以太网卡IP
  9. IPAddress pcIP(192, 168, 10, 17);

  10. // 网络初始化函数
  11. void initNetwork() {
  12.   // 静态IP配置
  13.   Ethernet.begin(mac, staticIP, dnsip, gateway, subnet);
  14.   
  15.   // 验证网络配置
  16.   IPAddress ip = Ethernet.localIP();
  17.   if (ip == IPAddress(0, 0, 0, 0)) {
  18.     Serial.println("✗✗✗ 错误: 以太网初始化失败! ✗✗✗");
  19.     // 错误处理...
  20.   }
  21.   
  22.   // 启动UDP服务
  23.   Udp.begin(localPort);
  24. }
复制代码
PC以太网卡不提供DHCP服务,必须使用静态IP;网关设置指向PC,点对点直连网络

3.2 DHT11数据采集与处理
        DHT11传感器数据采集需要精确的时序控制,并处理可能的读取失败情况
  1. // DHT11初始化
  2. DHT dht(DHTPIN, DHTTYPE);

  3. // 读取DHT数据函数
  4. void readDHTData() {
  5.   unsigned long currentTime = millis();
  6.   
  7.   // 每10秒读取一次
  8.   if (currentTime - lastDHTReadTime >= 10000) {
  9.     lastDHTReadTime = currentTime;
  10.    
  11.     float h = dht.readHumidity();
  12.     float t = dht.readTemperature();
  13.    
  14.     if (isnan(h) || isnan(t)) {
  15.       Serial.println("✗ DHT11读取失败!");
  16.       dhtValid = false;
  17.     } else {
  18.       humidity = h;
  19.       temperature = t;
  20.       dhtValid = true;
  21.       
  22.       // 发送数据
  23.       sendDHTData();
  24.     }
  25.   }
  26. }

  27. // 发送DHT数据函数
  28. void sendDHTData() {
  29.   if (dhtValid) {
  30.     messageCount++;
  31.    
  32.     char tempStr[10];
  33.     char humiStr[10];
  34.     floatToString(tempStr, temperature, 2);  // 2位小数精度
  35.     floatToString(humiStr, humidity, 2);
  36.    
  37.     // 构建JSON格式数据
  38.     snprintf(sendBuffer, sizeof(sendBuffer),
  39.              "{"type":"dht","count":%lu,"temp":%s,"humi":%s,"time":%lu}",
  40.              messageCount, tempStr, humiStr, millis() / 1000);
  41.    
  42.     sendUDP(sendBuffer);
  43.   }
  44. }
复制代码
DHT11设置为10秒的读取间隔,使用自定义floatToString()函数处理浮点数

3.3 UDP通信协议解析
        实现简单的命令解析机制,支持多种控制指令
  1. // 协议解析函数
  2. void parseCommand(const char* cmd, IPAddress remoteIP, int remotePort) {
  3.   // LED_ON命令
  4.   if (strcmp(cmd, "LED_ON") == 0) {
  5.     digitalWrite(LED_CONTROL_PIN, HIGH);
  6.     ledControlState = true;
  7.    
  8.     snprintf(sendBuffer, sizeof(sendBuffer),
  9.              "{"type":"response","cmd":"LED_ON","status":"success","led_state":true}");
  10.    
  11.     Udp.beginPacket(remoteIP, remotePort);
  12.     Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
  13.     Udp.endPacket();
  14.   }
  15.   // GET_DHT命令
  16.   else if (strcmp(cmd, "GET_DHT") == 0) {
  17.     float h = dht.readHumidity();
  18.     float t = dht.readTemperature();
  19.    
  20.     if (isnan(h) || isnan(t)) {
  21.       snprintf(sendBuffer, sizeof(sendBuffer),
  22.                "{"type":"response","cmd":"GET_DHT","status":"error","error":"read_failed"}");
  23.     } else {
  24.       char tempStr[10];
  25.       char humiStr[10];
  26.       floatToString(tempStr, t, 2);
  27.       floatToString(humiStr, h, 2);
  28.       
  29.       snprintf(sendBuffer, sizeof(sendBuffer),
  30.                "{"type":"response","cmd":"GET_DHT","status":"success","temp":%s,"humi":%s}",
  31.                tempStr, humiStr);
  32.     }
  33.    
  34.     Udp.beginPacket(remoteIP, remotePort);
  35.     Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
  36.     Udp.endPacket();
  37.   }
  38.   // 其他命令处理...
  39. }
复制代码
支持的命令列表
指令功能说明返回信息
LED_ON点亮LED返回成功状态和LED状态
LED_OFF熄灭LED返回成功状态和LED状态
GET_DHT读取实时温湿度返回数据或错误信息
STATUS获取设备完整状态信息返回设备状态每个命令都有明确的成功/失败状态返回,接收到命令后立即处理并返回结果

3.4 系统状态维护与心跳机制
        系统需要维护多个状态变量,并实现心跳机制确保连接正常
  1. // 全局状态变量
  2. unsigned long lastDHTReadTime = 0;    // 上次DHT读取时间
  3. unsigned long lastSendTime = 0;       // 上次发送时间
  4. unsigned long messageCount = 0;       // 消息计数器
  5. unsigned long lastHeartbeat = 0;      // 上次心跳时间

  6. bool dhtValid = false;                // DHT数据有效性
  7. bool ledControlState = false;         // LED控制状态
  8. bool networkInitialized = false;      // 网络初始化状态

  9. unsigned long packetsSent = 0;        // 发送数据包计数
  10. unsigned long packetsReceived = 0;    // 接收数据包计数

  11. // 心跳包发送函数
  12. void sendHeartbeat() {
  13.   unsigned long currentTime = millis();
  14.   
  15.   if (currentTime - lastHeartbeat >= 30000) {  // 每30秒
  16.     lastHeartbeat = currentTime;
  17.    
  18.     snprintf(sendBuffer, sizeof(sendBuffer),
  19.              "{"type":"heartbeat","uptime":%lu,"packets_sent":%lu,"packets_received":%lu}",
  20.              millis() / 1000, packetsSent, packetsReceived);
  21.     sendUDP(sendBuffer);
  22.   }
  23. }

  24. // 状态LED指示函数
  25. void updateStatusLED() {
  26.   unsigned long currentTime = millis();
  27.   
  28.   if (currentTime - lastBlinkTime >= 500) {  // 每500ms闪烁一次
  29.     lastBlinkTime = currentTime;
  30.     ledBlinkState = !ledBlinkState;
  31.     digitalWrite(LED_BUILTIN, ledBlinkState ? LOW : HIGH);
  32.   }
  33. }
复制代码
统计发送和接收的数据包数量,用于监控通信质量;定期发送心跳包,让上位机知道设备在线状态

3.5 系统完整代码
  1. /**************************************************************************************
  2. * 文件: W5500_UDP_DHT11_Control.ino
  3. * 作者:零知实验室(深圳市在芯间科技有限公司)
  4. * -^^- 零知实验室,让电子制作变得更简单! -^^-
  5. * 时间: 2026-02-09
  6. * 网络拓扑:
  7. *   路由器(192.168.3.1) ←WiFi→ PC(WiFi: 192.168.3.17, 以太网: 192.168.10.1)
  8. *                                    ↓ 直连网线
  9. *                               W5500(192.168.10.22)
  10. *
  11. * 功能说明:
  12. * W5500以太网模块UDP通信、DHT11温湿度传感器数据采集和上报、远程LED控制功能、简单协议解析和响应、修复JSON浮点数格式化问题(snprintf不支持%f)
  13. ************************************************************************************/

  14. #include <SPI.h>
  15. #include <Ethernet_STM.h>
  16. #include <EthernetUdp.h>
  17. #include "DHT.h"

  18. // ==================== 硬件配置 ====================
  19. #define DHTPIN 7
  20. #define DHTTYPE DHT11
  21. DHT dht(DHTPIN, DHTTYPE);

  22. #define LED_CONTROL_PIN 8


  23. // ==================== 网络配置 - PC直连模式 ====================
  24. #if defined(WIZ550io_WITH_MACADDRESS)
  25.   // WIZ550io有内置MAC地址
  26. #else
  27.   byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
  28. #endif

  29. //  PC直连模式 - 必须使用静态IP!
  30. // PC的以太网卡不提供DHCP服务,所以DHCP无法工作
  31. #define USE_DHCP false

  32. // ==================== 重要: 网段配置说明 ====================
  33. // 使用 192.168.10.x 网段,与PC的WiFi网段(192.168.3.x)分开
  34. // 避免IP冲突和路由混乱

  35. // 静态IP配置
  36. IPAddress staticIP(192, 168, 10, 22);      // W5500的IP
  37. IPAddress gateway(192, 168, 10, 1);        // 设为PC的以太网卡IP
  38. IPAddress subnet(255, 255, 255, 0);        
  39. IPAddress dnsip(192, 168, 10, 1);          // DNS指向PC

  40. // PC的以太网卡IP (连接W5500的那个网卡)
  41. //  不是WiFi的IP (192.168.3.17)!
  42. IPAddress pcIP(192, 168, 10, 17);

  43. // UDP端口
  44. unsigned int localPort = 8888;
  45. unsigned int pcPort = 9003;

  46. // ==================== 如果您的PC以太网卡IP是其他值 ====================
  47. // 请相应修改上面的配置,例如:
  48. //
  49. // 如果PC以太网卡是 192.168.137.1 (启用了ICS):
  50. //   IPAddress staticIP(192, 168, 137, 22);
  51. //   IPAddress gateway(192, 168, 137, 1);
  52. //   IPAddress dnsip(192, 168, 137, 1);
  53. //   IPAddress pcIP(192, 168, 137, 1);
  54. //
  55. // 如果PC以太网卡是 192.168.3.215 (不推荐,会与WiFi冲突):
  56. //   IPAddress staticIP(192, 168, 3, 22);
  57. //   IPAddress gateway(192, 168, 3, 215);  // 指向PC,不是路由器!
  58. //   IPAddress dnsip(192, 168, 3, 215);
  59. //   IPAddress pcIP(192, 168, 3, 215);


  60. // ==================== 全局变量 ====================
  61. EthernetUDP Udp;
  62. char receiveBuffer[256];
  63. char sendBuffer[512];

  64. // 定时器
  65. unsigned long lastDHTReadTime = 0;
  66. unsigned long lastSendTime = 0;
  67. unsigned long messageCount = 0;
  68. unsigned long lastHeartbeat = 0;

  69. // DHT数据
  70. float temperature = 0.0;
  71. float humidity = 0.0;
  72. bool dhtValid = false;

  73. // LED状态
  74. bool ledControlState = false;
  75. unsigned long lastBlinkTime = 0;
  76. bool ledBlinkState = false;

  77. // 网络状态
  78. bool networkInitialized = false;
  79. unsigned long lastSuccessTime = 0;
  80. unsigned long packetsSent = 0;
  81. unsigned long packetsReceived = 0;


  82. // ==================== 函数声明 ====================
  83. void floatToString(char* buffer, float value, int decimalPlaces);


  84. // ==================== 浮点数转字符串 ====================
  85. void floatToString(char* buffer, float value, int decimalPlaces) {
  86.   int intPart = (int)value;
  87.   int decPart = (int)((value - intPart) * pow(10, decimalPlaces));
  88.   
  89.   if (decPart < 0) decPart = -decPart;
  90.   
  91.   if (decimalPlaces == 1) {
  92.     sprintf(buffer, "%d.%01d", intPart, decPart);
  93.   } else if (decimalPlaces == 2) {
  94.     sprintf(buffer, "%d.%02d", intPart, decPart);
  95.   }
  96. }


  97. // ==================== 初始化 ====================
  98. void setup() {
  99.   Serial.begin(115200);
  100.   delay(100);
  101.   
  102.   Serial.println("\n\n");
  103.   Serial.println("========================================");
  104.   Serial.println("  W5500 UDP + DHT11温湿度监控系统");
  105.   Serial.println("  零知实验室");
  106.   Serial.println("  版本: v3.1 (PC直连专用版)");
  107.   Serial.println("========================================\n");
  108.   
  109.   Serial.println("  网络模式: PC直连(静态IP)");
  110.   Serial.println("  请确保PC的以太网卡已配置静态IP!\n");
  111.   
  112.   initHardware();
  113.   initNetwork();
  114.   initDHT();
  115.   
  116.   Serial.println("\n========================================");
  117.   Serial.println("系统启动完成!");
  118.   Serial.println("========================================\n");
  119.   
  120.   printSystemInfo();
  121.   sendStartupMessage();
  122.   
  123.   Serial.println("\n开始工作...\n");
  124.   Serial.println("----------------------------------------\n");
  125. }


  126. // ==================== 硬件初始化 ====================
  127. void initHardware() {
  128.   Serial.println("[1/3] 初始化硬件...");
  129.   
  130.   pinMode(LED_BUILTIN, OUTPUT);
  131.   digitalWrite(LED_BUILTIN, HIGH);
  132.   
  133.   pinMode(LED_CONTROL_PIN, OUTPUT);
  134.   digitalWrite(LED_CONTROL_PIN, LOW);
  135.   ledControlState = false;
  136.   
  137.   // 启动提示 - LED快闪3次
  138.   for(int i = 0; i < 3; i++) {
  139.     digitalWrite(LED_BUILTIN, LOW);
  140.     delay(100);
  141.     digitalWrite(LED_BUILTIN, HIGH);
  142.     delay(100);
  143.   }
  144.   
  145.   Serial.println("✓ 硬件初始化完成!");
  146. }


  147. // ==================== 网络初始化 ====================
  148. void initNetwork() {
  149.   Serial.println("\n[2/3] 初始化W5500以太网模块...");
  150.   Serial.println("  模式: 静态IP (PC直连)");
  151.   delay(500);
  152.   
  153.   // 静态IP配置
  154.   #if defined(WIZ550io_WITH_MACADDRESS)
  155.     Ethernet.begin(staticIP, dnsip, gateway, subnet);
  156.   #else
  157.     Ethernet.begin(mac, staticIP, dnsip, gateway, subnet);
  158.   #endif
  159.   
  160.   delay(1000);
  161.   
  162.   // 验证网络配置
  163.   IPAddress ip = Ethernet.localIP();
  164.   
  165.   if (ip == IPAddress(0, 0, 0, 0)) {
  166.     Serial.println("\n✗✗✗ 错误: 以太网初始化失败! ✗✗✗");
  167.     Serial.println("\n请检查:");
  168.     Serial.println("  1. W5500模块SPI接线是否正确");
  169.     Serial.println("  2. 网线是否连接到PC的以太网口");
  170.     Serial.println("  3. PC的以太网卡是否已配置静态IP");
  171.     Serial.println("\n设备将进入错误指示模式");
  172.    
  173.     while(1) {
  174.       digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  175.       delay(100);
  176.     }
  177.   }
  178.   
  179.   Serial.println("\n✓ W5500初始化成功!");
  180.   Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  181.   Serial.print("  本机IP:   ");
  182.   Serial.println(ip);
  183.   Serial.print("  子网掩码: ");
  184.   Serial.println(Ethernet.subnetMask());
  185.   Serial.print("  网关:     ");
  186.   Serial.println(Ethernet.gatewayIP());
  187.   Serial.print("  DNS:      ");
  188.   Serial.println(Ethernet.dnsServerIP());
  189.   Serial.println("  模式:     静态IP (PC直连)");
  190.   Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  191.   
  192.   // 显示PC配置提示
  193.   Serial.println("\n📌 PC网络配置要求:");
  194.   Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  195.   Serial.print("  以太网卡IP: ");
  196.   Serial.println(pcIP);
  197.   Serial.print("  子网掩码:   ");
  198.   Serial.println(subnet);
  199.   Serial.println("  默认网关:   (留空)");
  200.   Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
  201.   Serial.println("  请确保PC的以太网卡已设置为上述静态IP!");
  202.   Serial.println("   否则无法通信!\n");
  203.   
  204.   // 启动UDP
  205.   Serial.println("[3/3] 启动UDP服务...");
  206.   Udp.begin(localPort);
  207.   Serial.print("  - 监听端口: ");
  208.   Serial.println(localPort);
  209.   Serial.print("  - 目标PC:   ");
  210.   Serial.print(pcIP);
  211.   Serial.print(":");
  212.   Serial.println(pcPort);
  213.   Serial.println("✓ UDP服务启动成功!");
  214.   
  215.   networkInitialized = true;
  216. }


  217. // ==================== DHT11初始化 ====================
  218. void initDHT() {
  219.   Serial.println("\n初始化DHT11传感器...");
  220.   dht.begin();
  221.   delay(2000);
  222.   
  223.   humidity = dht.readHumidity();
  224.   temperature = dht.readTemperature();
  225.   
  226.   if (isnan(humidity) || isnan(temperature)) {
  227.     Serial.println("    DHT11首次读取失败,将继续尝试...");
  228.     dhtValid = false;
  229.   } else {
  230.     Serial.println("✓ DHT11初始化成功!");
  231.     Serial.print("  - 温度: ");
  232.     Serial.print(temperature);
  233.     Serial.println(" °C");
  234.     Serial.print("  - 湿度: ");
  235.     Serial.print(humidity);
  236.     Serial.println(" %");
  237.     dhtValid = true;
  238.   }
  239. }


  240. // ==================== 系统信息显示 ====================
  241. void printSystemInfo() {
  242.   Serial.println("\n系统配置: ");
  243.   Serial.print("  - DHT11引脚: ");
  244.   Serial.println(DHTPIN);
  245.   Serial.print("  - LED控制引脚: ");
  246.   Serial.println(LED_CONTROL_PIN);
  247.   Serial.println("  - 网络模式: PC直连静态IP");
  248.   Serial.println("\n支持的命令:");
  249.   Serial.println("  - LED_ON     : 点亮LED");
  250.   Serial.println("  - LED_OFF    : 熄灭LED");
  251.   Serial.println("  - GET_DHT    : 立即获取温湿度");
  252.   Serial.println("  - STATUS     : 获取设备状态");
  253.   Serial.println("  - PING       : 测试连接");
  254.   Serial.println("\n自动功能:");
  255.   Serial.println("  - 每10秒自动上报温湿度数据");
  256.   Serial.println("  - 每30秒发送心跳包");
  257.   Serial.println("  - 板载LED慢闪表示正常运行");
  258.   
  259.   Serial.println("\n网络诊断:");
  260.   Serial.print("  - W5500 IP: ");
  261.   Serial.println(Ethernet.localIP());
  262.   Serial.print("  - PC IP:    ");
  263.   Serial.println(pcIP);
  264.   Serial.print("  - 是否同网段: ");
  265.   
  266.   // 检查是否在同一网段
  267.   IPAddress w5500IP = Ethernet.localIP();
  268.   bool sameSubnet = (w5500IP[0] == pcIP[0] &&
  269.                      w5500IP[1] == pcIP[1] &&
  270.                      w5500IP[2] == pcIP[2]);
  271.   
  272.   if (sameSubnet) {
  273.     Serial.println("✓ 是");
  274.   } else {
  275.     Serial.println("✗ 否 (无法通信!)");
  276.     Serial.println("\n [警告] IP地址不在同一网段!");
  277.     Serial.println("请检查网络配置!");
  278.   }
  279. }


  280. // ==================== 发送启动消息 ====================
  281. void sendStartupMessage() {
  282.   IPAddress ip = Ethernet.localIP();
  283.   
  284.   snprintf(sendBuffer, sizeof(sendBuffer),
  285.            "{"type":"startup","ip":"%d.%d.%d.%d","port":%d,"mode":"pc_direct","pc_ip":"%d.%d.%d.%d"}",
  286.            ip[0], ip[1], ip[2], ip[3],
  287.            localPort,
  288.            pcIP[0], pcIP[1], pcIP[2], pcIP[3]);
  289.   sendUDP(sendBuffer);
  290.   
  291.   Serial.println("📤 已发送启动消息到PC");
  292. }


  293. // ==================== 发送心跳包 ====================
  294. void sendHeartbeat() {
  295.   unsigned long currentTime = millis();
  296.   
  297.   if (currentTime - lastHeartbeat >= 30000) {
  298.     lastHeartbeat = currentTime;
  299.    
  300.     snprintf(sendBuffer, sizeof(sendBuffer),
  301.              "{"type":"heartbeat","uptime":%lu,"packets_sent":%lu,"packets_received":%lu}",
  302.              millis() / 1000, packetsSent, packetsReceived);
  303.     sendUDP(sendBuffer);
  304.    
  305.     Serial.println(" 心跳包");
  306.   }
  307. }


  308. // ==================== 读取DHT11数据 ====================
  309. void readDHTData() {
  310.   unsigned long currentTime = millis();
  311.   
  312.   if (currentTime - lastDHTReadTime >= 10000) {
  313.     lastDHTReadTime = currentTime;
  314.    
  315.     float h = dht.readHumidity();
  316.     float t = dht.readTemperature();
  317.    
  318.     if (isnan(h) || isnan(t)) {
  319.       Serial.println("✗ DHT11读取失败!");
  320.       dhtValid = false;
  321.     } else {
  322.       humidity = h;
  323.       temperature = t;
  324.       dhtValid = true;
  325.       
  326.       Serial.println("📊 DHT11数据:");
  327.       Serial.print("  温度: ");
  328.       Serial.print(temperature);
  329.       Serial.println(" °C");
  330.       Serial.print("  湿度: ");
  331.       Serial.print(humidity);
  332.       Serial.println(" %");
  333.       
  334.       sendDHTData();
  335.     }
  336.   }
  337. }


  338. // ==================== 发送DHT数据 ====================
  339. void sendDHTData() {
  340.   if (dhtValid) {
  341.     messageCount++;
  342.    
  343.     char tempStr[10];
  344.     char humiStr[10];
  345.     floatToString(tempStr, temperature, 2);
  346.     floatToString(humiStr, humidity, 2);
  347.    
  348.     snprintf(sendBuffer, sizeof(sendBuffer),
  349.              "{"type":"dht","count":%lu,"temp":%s,"humi":%s,"time":%lu}",
  350.              messageCount, tempStr, humiStr, millis() / 1000);
  351.    
  352.     sendUDP(sendBuffer);
  353.    
  354.     Serial.print("📤 已发送温湿度数据 #");
  355.     Serial.println(messageCount);
  356.   }
  357. }


  358. // ==================== 发送UDP数据 ====================
  359. void sendUDP(const char* message) {
  360.   if (!networkInitialized) {
  361.     Serial.println("  网络未初始化,无法发送");
  362.     return;
  363.   }
  364.   
  365.   Udp.beginPacket(pcIP, pcPort);
  366.   Udp.write((uint8_t*)message, strlen(message));
  367.   Udp.endPacket();
  368.   
  369.   packetsSent++;
  370.   lastSuccessTime = millis();
  371. }


  372. // ==================== 协议解析 ====================
  373. void parseCommand(const char* cmd, IPAddress remoteIP, int remotePort) {
  374.   Serial.print("📝 解析命令: ");
  375.   Serial.println(cmd);
  376.   
  377.   // LED_ON
  378.   if (strcmp(cmd, "LED_ON") == 0) {
  379.     digitalWrite(LED_CONTROL_PIN, HIGH);
  380.     ledControlState = true;
  381.    
  382.     snprintf(sendBuffer, sizeof(sendBuffer),
  383.              "{"type":"response","cmd":"LED_ON","status":"success","led_state":true}");
  384.    
  385.     Udp.beginPacket(remoteIP, remotePort);
  386.     Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
  387.     Udp.endPacket();
  388.    
  389.     Serial.println("✓ LED已点亮");
  390.   }
  391.   // LED_OFF
  392.   else if (strcmp(cmd, "LED_OFF") == 0) {
  393.     digitalWrite(LED_CONTROL_PIN, LOW);
  394.     ledControlState = false;
  395.    
  396.     snprintf(sendBuffer, sizeof(sendBuffer),
  397.              "{"type":"response","cmd":"LED_OFF","status":"success","led_state":false}");
  398.    
  399.     Udp.beginPacket(remoteIP, remotePort);
  400.     Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
  401.     Udp.endPacket();
  402.    
  403.     Serial.println("✓ LED已熄灭");
  404.   }
  405.   // GET_DHT
  406.   else if (strcmp(cmd, "GET_DHT") == 0) {
  407.     float h = dht.readHumidity();
  408.     float t = dht.readTemperature();
  409.    
  410.     if (isnan(h) || isnan(t)) {
  411.       snprintf(sendBuffer, sizeof(sendBuffer),
  412.                "{"type":"response","cmd":"GET_DHT","status":"error","error":"read_failed"}");
  413.     } else {
  414.       humidity = h;
  415.       temperature = t;
  416.       dhtValid = true;
  417.       
  418.       char tempStr[10];
  419.       char humiStr[10];
  420.       floatToString(tempStr, temperature, 2);
  421.       floatToString(humiStr, humidity, 2);
  422.       
  423.       snprintf(sendBuffer, sizeof(sendBuffer),
  424.                "{"type":"response","cmd":"GET_DHT","status":"success","temp":%s,"humi":%s}",
  425.                tempStr, humiStr);
  426.     }
  427.    
  428.     Udp.beginPacket(remoteIP, remotePort);
  429.     Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
  430.     Udp.endPacket();
  431.    
  432.     Serial.println("✓ 已发送DHT数据");
  433.   }
  434.   // PING
  435.   else if (strcmp(cmd, "PING") == 0) {
  436.     snprintf(sendBuffer, sizeof(sendBuffer),
  437.              "{"type":"response","cmd":"PING","status":"success","message":"PONG"}");
  438.    
  439.     Udp.beginPacket(remoteIP, remotePort);
  440.     Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
  441.     Udp.endPacket();
  442.    
  443.     Serial.println("✓ PONG");
  444.   }
  445.   // STATUS
  446.   else if (strcmp(cmd, "STATUS") == 0) {
  447.     char tempStr[10];
  448.     char humiStr[10];
  449.     floatToString(tempStr, temperature, 2);
  450.     floatToString(humiStr, humidity, 2);
  451.    
  452.     IPAddress ip = Ethernet.localIP();
  453.    
  454.     snprintf(sendBuffer, sizeof(sendBuffer),
  455.              "{"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}",
  456.              millis() / 1000,
  457.              ledControlState ? "true" : "false",
  458.              dhtValid ? "true" : "false",
  459.              tempStr, humiStr,
  460.              ip[0], ip[1], ip[2], ip[3],
  461.              packetsSent, packetsReceived);
  462.    
  463.     Udp.beginPacket(remoteIP, remotePort);
  464.     Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
  465.     Udp.endPacket();
  466.    
  467.     Serial.println("✓ 已发送设备状态");
  468.   }
  469.   // 未知命令
  470.   else {
  471.     snprintf(sendBuffer, sizeof(sendBuffer),
  472.              "{"type":"response","cmd":"%s","status":"error","error":"unknown_command"}",
  473.              cmd);
  474.    
  475.     Udp.beginPacket(remoteIP, remotePort);
  476.     Udp.write((uint8_t*)sendBuffer, strlen(sendBuffer));
  477.     Udp.endPacket();
  478.    
  479.     Serial.println("✗ 未知命令");
  480.   }
  481. }


  482. // ==================== 接收UDP数据 ====================
  483. void receiveUDP() {
  484.   int packetSize = Udp.parsePacket();
  485.   
  486.   if (packetSize > 0) {
  487.     int len = Udp.read(receiveBuffer, sizeof(receiveBuffer) - 1);
  488.     if (len > 0) {
  489.       receiveBuffer[len] = '\0';
  490.       
  491.       IPAddress remoteIP = Udp.remoteIP();
  492.       int remotePort = Udp.remotePort();
  493.       
  494.       Serial.println("📥 收到消息:");
  495.       Serial.print("  来源: ");
  496.       Serial.print(remoteIP);
  497.       Serial.print(":");
  498.       Serial.println(remotePort);
  499.       Serial.print("  内容: ");
  500.       Serial.println(receiveBuffer);
  501.       
  502.       packetsReceived++;
  503.       lastSuccessTime = millis();
  504.       
  505.       parseCommand(receiveBuffer, remoteIP, remotePort);
  506.       
  507.       Serial.println();
  508.     }
  509.   }
  510. }


  511. // ==================== LED状态指示 ====================
  512. void updateStatusLED() {
  513.   unsigned long currentTime = millis();
  514.   
  515.   if (currentTime - lastBlinkTime >= 500) {
  516.     lastBlinkTime = currentTime;
  517.     ledBlinkState = !ledBlinkState;
  518.     digitalWrite(LED_BUILTIN, ledBlinkState ? LOW : HIGH);
  519.   }
  520. }


  521. // ==================== 主循环 ====================
  522. void loop() {
  523.   // 读取DHT11数据(每10秒)
  524.   readDHTData();
  525.   
  526.   // 发送心跳包(每30秒)
  527.   sendHeartbeat();
  528.   
  529.   // 处理UDP数据
  530.   receiveUDP();
  531.   
  532.   // 更新状态LED
  533.   updateStatusLED();
  534.   
  535.   delay(10);
  536. }
复制代码
系统流程图

UDP传输协议原理
        UDP用户数据报协议发送数据前不需要建立连接,直接发送;协议头仅8字节,相比TCP的20字节更节省带宽;没有握手和确认过程,延迟更低
UDP实现

  1. // 发送UDP数据
  2. void sendUDP(const char* message) {
  3.   Udp.beginPacket(pcIP, pcPort);      // 开始数据包
  4.   Udp.write((uint8_t*)message, strlen(message));  // 写入数据
  5.   Udp.endPacket();                     // 结束并发送
  6.   
  7.   packetsSent++;  // 更新统计
  8. }

  9. // 接收UDP数据
  10. void receiveUDP() {
  11.   int packetSize = Udp.parsePacket();  // 检查是否有数据包
  12.   
  13.   if (packetSize > 0) {
  14.     int len = Udp.read(receiveBuffer, sizeof(receiveBuffer) - 1);
  15.     if (len > 0) {
  16.       receiveBuffer[len] = '\0';  // 添加字符串结束符
  17.       // 处理接收到的数据...
  18.     }
  19.   }
  20. }
复制代码
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字节、重要数据可在应用层实现简单的确认重发、避免过高的发送频率,给网络处理留出时间
项目资源整合
网络调试助手和上位机程序:  
resource.zip (636.16 KB, 下载次数: 0)

W5500数据手册:                  
W5500_ds_v110e.pdf (1.81 MB, 下载次数: 0)

W5500以太网库文件:            Ethernet_STM

分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /3 下一条