【问题标题】:How to ensure correct data is received, if serial receiver execution time varies如果串行接收器执行时间不同,如何确保接收到正确的数据
【发布时间】:2021-05-26 12:19:26
【问题描述】:

我在 STM32 和 Linux 计算机之间进行了串行通信。 STM 每 10ms 传输 6 个字节的数据“iiiiiiC”(i = transmission_counter % 10, C = character 'C')。 然而,接收器没有特定的迭代时间,读取串行的速度可能快于或慢于 10 毫秒。另外,读取函数应该是非阻塞的。

这意味着有时会收到数据:

RD = '111'
RD = '11C2'
RD = '222'
RD = '2C33'

我想要 usable_data = 22222C

有时:

RD = '333C44444C'
RD = '55555C66666'
RD = 'C77777C8888'

我想要 usable_data = 77777C

代码类似如下:

int main() {

  int serial_port = open("/dev/ttyACM1", O_RDWR);
  struct termios tty;

  // Read in existing settings, and handle any error
  if(tcgetattr(serial_port, &tty) != 0) {
      printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
      return 1;
  }

  tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
  tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
  tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size 
  tty.c_cflag |= CS8; // 8 bits per byte (most common)
  tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
  tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)

  tty.c_lflag &= ~ICANON;
  tty.c_lflag &= ~ECHO; // Disable echo
  tty.c_lflag &= ~ECHOE; // Disable erasure
  tty.c_lflag &= ~ECHONL; // Disable new-line echo
  tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
  tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
  tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes

  tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
  tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
  // tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX)
  // tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX)

  tty.c_cc[VTIME] = 0;    // No blocking, return immediately with what is available
  tty.c_cc[VMIN] = 0;

  cfsetispeed(&tty, B115200);
  cfsetospeed(&tty, B115200);

  // Save tty settings, also checking for error
  if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
      printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
      return 1;
  }
  
while (1){

    char read_buf [100]; // Large serial buffer so even if the receiver is slow the most recent data will be in this array

    memset(&read_buf, '\0', sizeof(read_buf));

    int num_bytes = read(serial_port, &read_buf, sizeof(read_buf));

    std::cout << num_bytes << "  " << read_buf << std::endl;;

    ... unknown time ahead ...
  }

  close(serial_port);
  return 0; // success
}

我尝试了一些解决方法,但它们并没有解决所有问题,或者我对它们不满意:

// For cases where receiver is slow and receives more than one complete message at once
char part [6];    
uint16_t index = 0;
    for (index; index < 6; index++){
      if (read_buf[index] == 'C'){
        while (read_buf[index] == 'C'){
          index += 6;
        }
        index -= 6;
        break;
      }
    }    
memcpy(part, read_buf + index - 5, 6);

.

// For cases where received data does not fit into read_buffer, i.e. we are reading old data - flush it.
int num_bytes = read(serial_port, &read_buf, sizeof(read_buf));
while (num_bytes == sizeof(read_buf)){
  num_bytes = read(serial_port, &read_buf, sizeof(read_buf));
}

.

// For cases where received data is out of phase (message does not end with a 'C'). Reads one character at a time until message ends with 'C'. Has many problems.
if (read_buf[sizeof(read_buf)-1] != 'C'){
  read(serial_port, &read_buf, 1);
}

【问题讨论】:

  • 这能回答你的问题吗? Non-blocking call for reading descriptor
  • @Frank 不是真的,我想。我正在寻找一种从接收到的数据中获取 usable_data 的方法,但是由于 usable_data 可能会被拆分为多个部分(当前消息中的一部分 usable_data,前一个消息中的一部分),因此很难将正确的字节合并在一起。跨度>
  • 你必须写一个parser,它会解析数据并提取你感兴趣的部分。std::cout &lt;&lt;这是stm32吗,我非常喜欢 i> 怀疑你会想要使用 iostream。
  • 好吧,while (num_bytes == sizeof(read_buf)) - 每次阅读时都必须“添加”到 num_bytes。
  • @KamilCuk 谢谢,我会试试我写解析器的运气。 iostream 是临时的,仅用于调试。与 STM32 的通信是使用 write(serialport...)。

标签: c++ parsing serial-communication


【解决方案1】:

但由于 usable_data 可能会被拆分为多个部分(当前消息中的一部分可用数据,前一条消息中的一部分),因此很难将正确的字节合并在一起。

处理该特定挑战的最佳方法是让read() 为您处理,利用以下事实:您不必将read() 的第二个参数指向开头缓冲区。您可以让它从上次停止的地方写入数据。

constexpr std::size_t msg_len = 6;
std::array<char, msg_len> read_buf;
int received = 0;

void poll_port() {
  auto count = read(
     serial_port, 
     read_buf.data() + received, // Append to whatever we already have
     msg_len - received);        // Only read data up to the end of the buffer.

  if(count != -1) {
    received += count;
    if(received == msg_len) {
      handle_message(read_buf);
      received = 0;
    }
  }
  else if(errno != EAGAIN) {
    // we failed for some other reason than lack of data.
  }
}

将此与根据https://stackoverflow.com/a/5616108/4442671 将端口配置为非缓冲模式相结合,以使read() 非阻塞,您将拥有几乎所需的一切。

显然,这是一个简化的示例,假设所有消息的长度相同,并且在此过程中没有发生数据损坏,但它应该让您走上一般路径。

【讨论】:

    【解决方案2】:

    如果串行接收器执行时间不同,如何确保接收到正确的数据

    编写一个解析器,它将解析传入的数据并将它们累积在缓冲区中,并仅在收到完整的数据包时通知“上层处理阶段”。

    数据字节“iiiiC”

    编写起来非常简单,示例解析器可能如下所示:

    struct myrecv {
       std::array<char, 6> buf;
       unsigned pos; 
       // pointer to a function that return EOF or valid character
       std::function<int()> readc;
       // pointer to callback function to call when full buffer is received
       std::function<void(std::array<char, 6>&)> cb;
    
       myrecv(std::function<int()> readc, 
            std::function<void(std::array<char, 6>&)> cb) :
            readc(readc), cb(cb), pos(0) {}
    
       int process() {
           buf[pos] = readc();
           if (buf[pos] == EOF) return ETIMEDOUT;
           pos++;
           if (pos != buf.size()) {
              return 0;
           }
           pos = 0;
    
           // check message integrity
           if (buf.end() != 'C') {
              return -EINVAL;
           }
    
           cb(buf);
           return 1;
       }
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-27
      • 1970-01-01
      • 2021-11-11
      • 1970-01-01
      • 1970-01-01
      • 2015-12-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多