Вопрос

I'm trying to control a robot wirelessly through an Arduino (using a X360 controller on a computer), which requires very low latency. I chose Wifi for this reason (and the fact I'll be streaming video), and after a little test it turns out I have a huge lag using TCP. Is this normal (with 54Mbits/s, it shouldn't!)? How can I reduce it to be controllable?

Server code (Arduino sketch):

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x48, 0x0D }; 
byte ip[] = { 192, 168, 0, 11 };    
byte gateway[] = { 192, 168, 0, 254 };
byte subnet[] = { 255, 255, 255, 0 };
byte localPort = 99;

EthernetServer server = EthernetServer(localPort);

void setup()
{
  // initialize the ethernet device
  Ethernet.begin(mac, ip, gateway, subnet);
  Serial.begin(9600);

  // start listening for clients
  server.begin();
  Serial.println("Server ready");
}

void loop()
{
  // if an incoming client connects, there will be bytes available to read:
  EthernetClient client = server.available();
  if (client == true) {
    Serial.println("Received:");

    byte received = 0;
    while((received = client.read()) != -1) {
      Serial.println(received);
      server.write(received);
    }

    Serial.println("Over\n"); 
  }
}

Client code (PC, QtCreator):

#include <QTextStream>
#include <QTCPSocket>

QString arduinoIP = "192.168.0.11";
char arduinoPort = 99;

int main(void)
{
    QTcpSocket socket;
    QTextStream in(stdin);
    QTextStream out(stdout);

    out << "Connection... "; out.flush();
    socket.connectToHost(arduinoIP, arduinoPort);
    if(!socket.waitForConnected(5000)) {
        out << socket.errorString() << "\n";
    }
    else {
        out << "OK\n"; out.flush(); //I don't know why \n doesn't flush

        out << "Type a message to send to the Arduino or quit to exit\n"; out.flush();

        QString command;
        in >> command;

        while(command != "quit") {
            QByteArray bufOut = command.toUtf8();
            socket.write(bufOut);
            socket.waitForReadyRead(1000); //Wait for answer (temp)
            out << "Answer: " << socket.readAll() << "\n";
        }
    }

    return 0;
}

Thank you in advance for your help.

Regards, Mister Mystère

Это было полезно?

Решение

A TCP connection requires more packets to provide the reliable transport of the data. TCP is not meant for low latency, it is meant for reliably transmitting a stream of data. For example, if you are sending a file, you need all the packets to be received and to be pieced together in the correct order.

You are seeing the fact that bandwidth and latency are unrelated. Most streaming video systems pre-buffer data to provide the illusion of no interruptions in the transport stream. The underlying behavior is that transport latency can be jittery, but the buffered data keeps the perception of a continuous stream.

For your application, consider using UDP.

UDP on Arduino

TCP is a stream, UDP is for small messages. Your decision will pivot on the question:

What is the effect if a packet is sent but never received?

In the case of a controller input, it may be better to simply ignore the lost data and receive the next transmission. I presuming from your question that you will be repeatedly sending the state of the controller (up down left right?) If so, UDP is you choice. If you choose TCP, the behavior will be to keep retrying to recover this lost data before sending the next data. In your use case, you might as well send the next controller state rather than trying to recover the stream.

Другие советы

Did you check those quite general remarks?

1) Did you check your Wi-Fi spectrum. Overlapping channels causes drops of packets. These packets will be retransmitted with a small extra delay. A nice tool to help: http://www.metageek.net/products/inssider/

2) Is your Arduino not flooded with broadcast packets of other devices on your network. Maybe your network stack is heavily busy with checking and ignoring broadcast packets. Packets of your TCP-connection can be dropped and also retransmitted. Try a private AP for your PC and Arduino and look to the performance.

UDP is really faster, I used this test code instead:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTimer>
#include <QVBoxLayout>
#include <QSpinBox>
#include <QtNetwork/QUdpSocket>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:
    void onTimer();

private:
    QTimer timer;
    QVBoxLayout layout;
    QWidget centralW;
    QSpinBox sendBox;
    QSpinBox receiveBox;

    QUdpSocket *socket;
};

#endif // MAINWINDOW_H

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    connect(&timer, SIGNAL(timeout()), this, SLOT(onTimer()));
    timer.start(100);
    sendBox.setMaximum(1000);
    layout.addWidget(&sendBox);
    receiveBox.setMaximum(1000);
    layout.addWidget(&receiveBox);
    centralW.setLayout(&layout);
    setCentralWidget(&centralW);

    socket = new QUdpSocket(this);
}

MainWindow::~MainWindow()
{
}

void MainWindow::onTimer() {
    QByteArray datagram = QByteArray::number(sendBox.value());
    socket->writeDatagram(datagram.data(), datagram.size(), QHostAddress("192.168.0.11"), 99);

    if(socket->hasPendingDatagrams()) {
        datagram.resize(socket->pendingDatagramSize());
        socket->readDatagram(datagram.data(), datagram.size());
        receiveBox.setValue(QString(datagram.data()).toInt());
    }
}

And on the server side:

#include <SPI.h>         // needed for Arduino versions later than 0018
#include <Ethernet.h>
#include <EthernetUdp.h>         // UDP library from: bjoern@cs.stanford.edu 12/30/2008

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x48, 0x0D }; 
byte ip[] = { 192, 168, 0, 11 };    
byte gateway[] = { 192, 168, 0, 254 };
byte subnet[] = { 255, 255, 255, 0 };
byte localPort = 99;

EthernetUDP Udp;

void setup() {
  Ethernet.begin(mac,ip);
  Udp.begin(localPort);

  Serial.begin(9600);
}

void loop() {
  // if there's data available, read a packet
  int packetSize = Udp.parsePacket();
  if(packetSize)
  {
    static char buffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,

    //Udp.read does not erase the rest of the buffer, without that we would
    //get 989 instead of 98 after having entered 999 for example
    for(int i = 0; i < UDP_TX_PACKET_MAX_SIZE; ++i) buffer[i] = 0; 

    Udp.read(buffer,UDP_TX_PACKET_MAX_SIZE);
    Serial.println(buffer);

    // send a reply, to the IP address and port that sent us the packet we received
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(buffer);
    Udp.endPacket();
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top