You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Arduino与PC间TCP/UDP传感器数据传输消息间隔不稳定问题

问题:Arduino传感器数据网络传输时消息间隔不稳定

我尝试将Arduino(作为服务器)的传感器数据通过网络实时流式传输到电脑(客户端)。传感器数据每秒生成速率稳定,但消息到达客户端的时间间隔极不规则。

我测试了TCP和UDP的单向、双向通信极简示例来定位问题,还尝试过添加延迟、调整TCP_NODELAY等设置、减小消息大小、添加消息头等方式同步连接,但所有场景下,消息都以不稳定的突发形式到达:

消息突发到达的演示
消息内容固定为"test",打印帧号用于演示

请问该现象的原因是什么?如何让消息间隔更稳定?


问题原因分析

  • Arduino WiFi模块的底层缓冲机制:WiFiNINA模块会对小数据包做缓冲,为了提升传输效率,会攒够一定数据量或等待超时后才批量发送,导致客户端收到突发数据。
  • TCP Nagle算法的残留影响:即使客户端开启TCP_NODELAY,Arduino端的WiFiServer实现可能未同步禁用该算法,或模块底层仍会合并数据;此外TCP的滑动窗口、ACK机制也可能延迟数据发送。
  • delay()函数的精度误差:Arduino的delay()是阻塞式的,会被WiFi模块的任务调度、网络栈操作打断,导致实际发送间隔偏离预期。
  • UDP的无线传输优化:UDP虽为无连接协议,但WiFi模块为减少无线帧开销,会合并小UDP数据包后发送,尤其在低带宽或高干扰环境下更明显。

解决方案

1. 强制TCP立即发送数据

在Arduino的TCP服务器代码中,每次调用write()后添加client.flush(),强制模块立即发送缓冲数据:

void loop() {
  WiFiClient client = server.available();
  if (client.connected()) {
    client.write("test");
    client.flush(); // 强制立即发送
    delay(16);
  }
}

2. 用非阻塞定时替代delay()

改用millis()实现非阻塞定时,避免网络操作打断发送间隔:

unsigned long previousMillis = 0;
const long sendInterval = 16;

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= sendInterval) {
    previousMillis = currentMillis;
    WiFiClient client = server.available();
    if (client.connected()) {
      client.write("test");
      client.flush();
    }
  }
}

3. UDP数据包填充避免合并

将UDP数据包填充到无线帧最小长度(通常64字节),让模块无法合并数据包:

void loop() {
  Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
  Udp.write("test");
  // 填充到64字节
  for(int i=4; i<64; i++){
    Udp.write(0);
  }
  Udp.endPacket();
  // 非阻塞延迟
  unsigned long currentMillis = millis();
  while(millis() - currentMillis < 16);
}

4. 客户端时间戳校准

在Arduino发送的数据包中附加生成时间戳,客户端根据时间戳对齐数据,而非依赖接收时间:

  • Arduino端:Udp.write("test," + String(millis()));
  • 客户端:解析时间戳,计算数据实际生成间隔并做平滑处理。

测试用极简示例(优化版)

单向通信(服务器 -> 客户端)

TCP客户端

import time
import socket

ADDRESS = "xxx.xxx.xxx.xxx"
PORT = 80
SIZE = 4

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.connect((ADDRESS, PORT))

print("Waiting for connection...")
sock.recv(SIZE)
print("Connected")

t0 = 0
f = 0
while True:
    data = sock.recv(SIZE).decode()
    if f == 0:
        t0 = time.time()
        time.sleep(.0001)

    f += 1
    fps = int(f / (time.time() - t0))
    print(f"\r{f} FPS: {fps} data: {data}   ", end='')

TCP服务器

#include <WiFiNINA.h>

WiFiServer server(80);
unsigned long previousMillis = 0;
const long sendInterval = 16;

void setup() {
  Serial.begin(115200);
  int status = WL_IDLE_STATUS;
  while (status != WL_CONNECTED) {
    status = WiFi.begin("ssid", "password");
    delay(1000);
  }
  server.begin();

  Serial.println("connected");
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
  IPAddress ip = WiFi.localIP();
  Serial.println(ip);
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= sendInterval) {
    previousMillis = currentMillis;
    WiFiClient client = server.available();
    if (client.connected()) {
      client.write("test");
      client.flush();
    }
  }
}

UDP客户端

import socket
import time

ADDRESS = "xxx.xxx.xxx.xxx"
PORT = 2390
SIZE = 64

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(b"s", (ADDRESS, PORT))

print("Waiting for connection...")
sock.recvfrom(SIZE)
print("Connected")

t0 = 0
f = 0
while True:
    data, addr = sock.recvfrom(SIZE)
    data = data.decode().split('\0')[0]
    if f == 0:
        t0 = time.time()
        time.sleep(.0001)
    
    f += 1
    fps = int(f / (time.time() - t0))
    print(f"\r{f} FPS: {fps} data: {data}   ", end='')

UDP服务器

#include <WiFiNINA.h>
#include <WiFiUdp.h>

WiFiUDP Udp;
unsigned long previousMillis = 0;
const long sendInterval = 16;

void setup() {
  Serial.begin(115200);
  int status = WL_IDLE_STATUS;
  while (status != WL_CONNECTED) {
    status = WiFi.begin("ssid", "password");
    delay(1000);
  }
  Udp.begin(2390);

  Serial.println("connected");
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
  IPAddress ip = WiFi.localIP();
  Serial.println(ip);
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= sendInterval) {
    previousMillis = currentMillis;
    if (Udp.remoteIP() != IPAddress(0,0,0,0)) {
      Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
      Udp.write("test");
      for(int i=4; i<64; i++){
        Udp.write(0);
      }
      Udp.endPacket();
    }
  }
}

双向通信(服务器 <-> 客户端)

TCP客户端

import time
import socket

ADDRESS = "xxx.xxx.xxx.xxx"
PORT = 80
SIZE = 4

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.connect((ADDRESS, PORT))

print("Waiting for connection...")
sock.send(b's')
sock.recv(SIZE)
print("Connected")

t0 = 0
f = 0
while True:
    sock.send(b's')
    data = sock.recv(SIZE).decode()
    if f == 0:
        t0 = time.time()
        time.sleep(.0001)

    f += 1
    fps = int(f / (time.time() - t0))
    print(f"\r{f} FPS: {fps} data: {data}   ", end='')

TCP服务器

#include <SPI.h>
#include <WiFiNINA.h>

WiFiServer server(80);
boolean alreadyConnected = false;

void setup() {
  Serial.begin(9600);
  while (!Serial);

  Serial.print("Connecting to Wifi... ");
  int status = WL_IDLE_STATUS;
  while (status != WL_CONNECTED) {
    status = WiFi.begin("ssid", "password");
    delay(500);
  }
  Serial.println("connected.");
  server.begin();

  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
  IPAddress ip = WiFi.localIP();
  Serial.println(ip);
}

void loop() {
  WiFiClient client = server.available();
  if (client) {
    if (!alreadyConnected) {
      client.flush();
      alreadyConnected = true;
    }
    if (client.available() > 0) {
      char thisChar = client.read();
      client.write("test");
      client.flush();
    }
  }
}

UDP客户端

import socket
import time

ADDRESS = "xxx.xxx.xxx.xxx"
PORT = 2390
SIZE = 64

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(b"s", (ADDRESS, PORT))

print("Waiting for connection...")
sock.recvfrom(SIZE)
print("Connected")

t0 = 0
f = 0
while True:
    sock.sendto(b"s", (ADDRESS, PORT))
    data, addr = sock.recvfrom(SIZE)
    data = data.decode().split('\0')[0]
    if f == 0:
        t0 = time.time()
        time.sleep(.0001)
    
    f += 1
    fps = int(f / (time.time() - t0))
    print(f"\r{f} FPS: {fps} data: {data}   ", end='')

UDP服务器

#include <WiFiNINA.h>
#include <WiFiUdp.h>

WiFiUDP Udp;

void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.print("Connecting to Wifi... ");
  int status = WL_IDLE_STATUS;
  while (status != WL_CONNECTED) {
    status = WiFi.begin("wifi", "password");
    delay(500);
  }
  Serial.println("connected.");
  Udp.begin(2390);

  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());
  IPAddress ip = WiFi.localIP();
  Serial.println(ip);
}

void loop() {
  if (!Udp.parsePacket()) {
    return;
  }

  while (Udp.read() != -1);
  Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
  Udp.write("test");
  for(int i=4; i<64; i++){
    Udp.write(0);
  }
  Udp.endPacket();
}

内容的提问来源于stack exchange,提问作者dandev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 22:37:01