为什么串行端口在发送数据时跳过数据

Why is serial port skipping data when sending data?

本文关键字:数据 串行端口 为什么      更新时间:2023-10-16

我已经编写了一些C++代码,可以通过串行方式与我的arduino对话。它只是试图使用正弦和余弦在两个伺服电机上产生振荡,但它跳过了数据。我不知道为什么会发生这种事。我在用termios.h做连续剧。C++的输出类似于"V180H90",即垂直180,水平90。我以前使用fstream和usleep()发送数据,它很有效,但我想使用一种比延迟任意数字更好的方法。

感谢您的帮助或指导。

我的arduino代码

#include <Servo.h>
typedef enum { NONE, GOT_V, GOT_H } states;
states state = NONE;
Servo pan;
Servo tilt;
int laser = 11;
unsigned int currentValue;
int v_pan = 0;
int v_tilt = 0;
void setup()
{
  pan.attach(10);
  tilt.attach(9);
  Serial.begin(9600);
  state = NONE;
}
void processVertical(const unsigned int value)
{
  Serial.print("Vertical = ");
  Serial.println(value);
  int result = 1300 + (value - 90) * 2;
  //Serial.println(result);
  tilt.writeMicroseconds(result);
}
void processHorizontal(const unsigned int value)
{
  Serial.print("Horizontal = ");
  Serial.println(value);
  int result = 1500 + (value - 180) * 1;
  //Serial.println(result);
  pan.writeMicroseconds(result);
}
void handlePreviousState()
{
  switch(state)
  {
    case GOT_V:
      processVertical(currentValue);
      break;
    case GOT_H:
      processHorizontal(currentValue);
      break;
  }
  currentValue = 0;
}
void processIncomingByte (const byte c)
{
  if (isdigit(c))
  {
    currentValue *=10;
    currentValue += c - '0';
  }
  else
  {
    handlePreviousState();
    switch (c)
    {
      case 'V':
        state = GOT_V;
        break;
      case 'H':
        state = GOT_H;
        break;
      default:
        state = NONE;
        break;
    }
  }
}
void loop()
{
  if(Serial.available() > 0) 
  { 
    processIncomingByte(Serial.read());
  } 
  digitalWrite(laser, HIGH);
}
//check out writeMicroseconds

我的C++代码

// Program for sending data to serial
#include <iostream>
#include <sstream>
#include <string>
#include <termios.h>
#include <fcntl.h>
#include <math.h>
using namespace std;
//open serial port
int openPort(string path)
{
  int fd; //file descriptor for port
  fd = open(path.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
  if (fd == -1)
    cerr << "Cannot open port" << endl;
  else
    fcntl(fd, F_SETFL, 0);
  return (fd);
}
//set options for an open serial port
void setOptions(int fd)
{
  struct termios options;
  tcgetattr(fd, &options);
  cfsetispeed(&options, B9600);
  cfsetospeed(&options, B9600);
  //No parity 8N1
  options.c_cflag &= ~PARENB;
  options.c_cflag &= ~CSTOPB;
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS8;
  //No flow control
  options.c_cflag &= ~CRTSCTS;
  //Turn off s/w flow control
  options.c_iflag &= ~(IXON | IXOFF | IXANY);
  //Turn on read and ignore ctrl lines
  options.c_cflag |= (CLOCAL | CREAD); 
  if( tcsetattr(fd, TCSANOW, &options) < 0) { 
    cerr << "Could not set attributes" << endl; 
  }
}
//write to serial port
void writePort(int fd, string data)
{
  int n = write(fd, data.c_str(), 9);
  if (n < 0)
    cerr << "Cannot write to port" << endl;
}
int main() {
  string path = "/dev/tty.usbmodemfd131";
  //string path = "/dev/tty.usbmodemfa141";
  int fd = openPort(path);
  setOptions(fd);
  stringstream ss;
  string output;
  unsigned short vertical = 0;
  unsigned short horizontal = 0; 
  unsigned short freq = 10;
  for(int i = 0; i < 360; i++) {
    vertical = ((cos(i * freq * ((M_PI)/180))) + 1) * 90;
    horizontal = ((sin(i * freq * ((M_PI)/180))) + 1) * 90;
    ss << "V" << vertical << "H" << horizontal << endl; 
    output = ss.str();
    ss.str("");
    writePort(fd, output);
//    cout << output; //DEBUG
  }
  close(fd);
  return 0;
}

设备内部的"processIncomingByte"循环可能遇到速度问题,因为您在接收到新模式后立即处理以前的状态(handlePreviousState)。

该问题可能是由于在相应的函数中执行Serial.print而值数据字节仍从PC连续输入造成的。串行打印在微控制器逻辑中是一个相对缓慢的过程。

我不熟悉Arduino的硬件,但一些低端的微控制器板正在使用比特敲击的方法执行软件串行接口,所以当你发送时,接收完全停止。为了验证这一点,您可以对Serial.print进行注释,看看它是否有帮助。

无论如何,在输入数据流中间进行长时间处理总是有问题的,除非你在设备中有一个带有大量FIFO缓冲区的硬件串行接口。

解决这个问题的正确方法是首先在缓冲区内接收整个消息,然后只有在接收到消息结束标记时才对其进行处理。例如,将您的消息插入[]对中,如[V180H90]。在"["时重置缓冲区,并在收到"]"后处理缓冲区。在将字节收集到缓冲区时,请确保同时检查缓冲区是否溢出。

如果你只是把数据塞进端口的喉咙,它会尽最大努力不起火,但多余的数据不会被发送。毕竟,端口以有限的速度运行,是一个非常有限的转储设备。

因此,在向端口发送字符之前,您需要检查端口的状态,看看它是否真的准备好接受另一个字符的数据进行传输。一些串行端口甚至可以在占用更多数据时生成中断,以帮助您避免浪费的状态轮询。

此外,有时两个设备上的两个串行端口可以与额外的一对非数据信号(RTSCTS)连接,以指示接收侧是否准备好接收更多数据。如果你已经连接了这些,并且你的设备正在使用它们来指示它的准备状态,那么你的程序也应该考虑设备的CTS的状态。

很明显,您的设备读取/处理数据的速度比通过串行端口发送数据的速度慢。我在这里看到了一些可能的解决方案:

1) 实现流量控制,并在阻塞模式下通过串行端口发送数据。发送后您仍然需要等待,但只需等待设备读取和处理数据所需的时间即可。

2) 实现双向通信,使您的设备发送确认消息(即任何单个ASCII符号),以表明它已准备好接受数据。

3) 将代码分成两个并行部分,即:主循环(或ISR)只从串行端口读取数据并将其存储在环形缓冲区中,另一个循环轮询环形缓冲区,并在有数据可用时立即从中获取/处理数据。这是三种解决方案中最困难的一种,因为您需要两个独立的线程(或一个线程和一个ISR)来保护环形缓冲区免受并发访问,但也是最强大和最灵活的。

您将数据写入串行设备的速度过快,而设备本身输出数据的速度快于您在设备另一端读取数据的速度。

应对这种情况的正确方法是限制对串行设备的写入速度,以避免数据泛滥。