2

我需要用 C 为 Linux 的条形码阅读器编写驱动程序。条形码阅读器通过串行总线工作。当我向条形码阅读器发送一组命令时,条形码阅读器应该向我返回状态消息。我设法配置了端口并创建了一个信号处理程序。在信号处理程序中,我读取串行总线接收的数据。

所以问题是:我应该读取缓冲区中的数据然后使用它吗?我什至可以使用以这种方式配置的端口将数据写入缓冲区吗?当设备回复我时,根据该回复数据,我需要向设备发送另一个命令。另外,我可以write()用来写消息吗?如果我不能使用它,我应该使用什么命令?你能帮我写写命令吗?

我发送给设备的命令总是 7 个字节,但回复数据在 7-32 个字节之间变化。如果读取函数发送不同数量的读取字节,我如何确定我收到了所有数据,以便我可以使用它?

这是我写的一些代码。我是否朝着正确的方向前进?再一次,这个想法非常简单:我正在向设备发送命令,设备会打断我并回复。我阅读了发送的内容,处理回复数据,并根据该数据发送另一个命令。谢谢转发。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <errno.h>
#include <termios.h>

void signal_handler_IO (int status);   /* definition of signal handler */

int n;
int fd;
int connected;
char buffer[14];
int bytes;
struct termios termAttr;
struct sigaction saio;

int main(int argc, char *argv[])
{
     fd = open("/dev/ttyUSB1", O_RDWR | O_NOCTTY | O_NDELAY);
     if (fd == -1)
     {
        perror("open_port: Unable to open /dev/ttyO1\n");
        exit(1);
     }

     saio.sa_handler = signal_handler_IO;
     saio.sa_flags = 0;
     saio.sa_restorer = NULL; 
     sigaction(SIGIO,&saio,NULL);

     fcntl(fd, F_SETFL, FNDELAY);
     fcntl(fd, F_SETOWN, getpid());
     fcntl(fd, F_SETFL,  O_ASYNC ); 

     tcgetattr(fd,&termAttr);
     //baudRate = B115200; 
     cfsetispeed(&termAttr,B115200);
     cfsetospeed(&termAttr,B115200);
     termAttr.c_cflag &= ~PARENB;
     termAttr.c_cflag &= ~CSTOPB;
     termAttr.c_cflag &= ~CSIZE;
     termAttr.c_cflag |= CS8;
     termAttr.c_cflag |= (CLOCAL | CREAD);
     termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
     termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
     termAttr.c_oflag &= ~OPOST;
     tcsetattr(fd,TCSANOW,&termAttr);
     printf("UART1 configured....\n");

     connected = 1;
     while(connected == 1){
         //write function and read data analyze(Processing)
     }

     close(fd);
     exit(0);             
}

void signal_handler_IO (int status)
{
    bytes = read(fd, &buffer, sizeof(buffer));
    printf("%s\n", buffer);
}
4

1 回答 1

7

再一次,这个想法非常简单:我正在向设备发送命令,设备会中断我并重播。我阅读了发送的内容,处理重播数据,根据该数据,我正在发送另一个命令。

您正在描述一种常见且简单的主从设备安排:主设备发送命令或请求消息,从设备必须回复响应消息。

您正在使用正确的 POSIX 方法来修改标志。然而,系统调用fcntl()tcgetattr()并且tcsetattr()应该检查它们的返回码以确保没有错误。

您正在为非规范(又名原始)输入和输出配置串行端口。(所以你的接收缓冲区应该是unsigned char而不是有符号char的。)这种模式提供了一种基于字节数和/或串行链路静音的读取方法。没有理由使用涉及信号处理程序的异步读取。(您的信号处理程序与此问题有相同的错误。)主/从设备关系指示请求/响应消息交换。

要正确阅读完整的响应消息,您需要定义VMIN 和 VTIME 值。如果响应消息的长度为 7 到 32 个字节,则

termAttr.c_cc[VMIN] = 32;
termAttr.c_cc[VTIME] = 5;

应该做的伎俩。您会期待不超过 32 个字节的响应消息,或者当字节在半秒后停止到达时,则假定消息已完成。

#define CMDLEN  7
unsigned char buffer[32];
unsigned char cmd[CMDLEN];

while (connected == 1) {

    /* construct a new command */

    /* send request */
    nbytes = write(fd, cmd, CMDLEN);
    if (nbytes != CMDLEN) {
        /* problem! */
    }
    /* get response, wait if necessary */
    nbytes = read(fd, buffer, sizeof(buffer));
    if (nbytes < 7) {
        /* problem! */
    }

    /* process response of nbytes */
}

附录:回复评论中的问题

首先,我可以用定义的 VMIN 和 VTIME 值在信号处理程序中填充缓冲区吗?

您没有描述异步读取串行端口的任何要求。(顺便说一句,这不是“串行总线”;USB 是串行总线;EIA/RS-232 是串行链路。)如果这不是主/从配置,请说出来。否则,用于读取的信号处理程序是不必要的复杂性,而不必要的复杂性(通常)表明设计不佳。(如果此代码是专门由事件驱动的系统的一部分,则例外。)

请注意,串行端口已完全缓冲以进行读取和写入。read()当数据到达串行端口时,您的程序不必有挂起或活动的请求。内核在接收数据时将串行数据存储在内部缓冲区中。即使您的程序不使用异步读取和信号处理程序,它也不会丢失或丢失任何数据。

最后一个问题是关于 fcntl()、tcgetattr() 和 tcsetattr() 系统调用。你能给我一些关于我应该检查什么错误标志的例子吗?

此类信息记录在 Linux手册页中。
如果您使用的是 Linux 开发系统(并且应该),请确保安装了手册页。否则,您可以 google 查找手册页,例如“fcntl linux 手册页”
检查函数原型。
返回值通常是系统调用状态。如果指示错误(通常是负值,例如 -1),则应访问全局变量errno以获取更多详细信息。

rc = tcgetattr(fd, &termAttr);
if (rc < 0) {
    printf("failed to get attr: %d, %s", rc, strerror(errno));
    close(fd);
    exit (-2);
}

PS 我正在发送我发送到设备的消息示例和响应,以便您了解我在说什么。
tx_buffer = 0xFC 0x05 0x11 0x27 0x56 rx_buffer = 0xFC 0x05 0x40 0x27 0x56 0xFC 始终相同 0x05 字节数 0x11 命令 0x40 响应代码 0x27 crc_low 0x56 crc_high
一个命令包和响应包的示例

这是二进制协议的典型消息格式。
作为读取处理的一部分,必须在执行任何消息处理之前验证收到的每条消息。
必须检查标0xFC头字节以确保您具有消息帧同步。如果第一个字节不等于0xFC,则程序必须进入“寻找同步”模式以查找消息的开头(从该缓冲区中的第二个字节开始)。
应检查消息长度是否合理,然后用于定位两个 CRC 字节,从而可以验证消息内容。如果 CRC 校验失败,那么“消息”应该被忽略并且应该开始“寻找同步”(从这个“消息”的第二个或第三个字节开始)。

附录 2:对“最终代码”问题的回应

所以现在的问题是如何创建一个计时器?

Userland 可以使用 POSIX 定时器和信号来调度周期性定时器,即timer_create().
但是您必须检查可用的时钟分辨率以确定它是否允许您执行 1 毫秒。

但是 IMO 你走错了路。
你忽略了我关于不使用异步 I/O 的建议。
您正在调用read()信号处理程序,如果它试图休眠,这是一个潜在的问题。
您用于异步读取的信号处理程序与主线程没有同步或缓冲区管理,因此程序有丢失已读取数据的风险。
你忽略了我关于检查系统调用返回码的建议(除了write()s)。

如果我正在编写这个程序,我可能会使用一个状态机,并且只有一个write()和一个read()使用串行端口。

附录 3:对评论中第三组问题的回应

你能给我一个简单的例子来说明如何使用 timer_create() 函数创建一个计时器吗?

显然你没有听我之前关于使用手册页的建议。timer_create() 手册页
中有一个代码示例

关于这一点:“您用于异步读取的信号处理程序与主线程没有同步或缓冲区管理,因此程序有丢失已读取数据的风险。” 我想我可以解决这个问题,如果我在信号处理程序中的每个 read() 函数之后调用 clearRXbuffer 函数,那么在这种情况下,缓冲区将只包含最后一个消息数据,它可以是 7 到 32 个字节。因此,如果新消息到达,它将被写入缓冲区的开头。你怎么看,这个想法好不好,我是否朝着正确的方向前进?如果没有,你能否给我一些其他的缓冲区管理想法。

这不是一个可靠的解决方案。

信号处理程序相对于主线程异步执行。
因此,所有共享变量和缓冲区都是关键区域
必须使用诸如互斥锁(又名互斥锁)或信号量或条件变量之类的同步结构来保护关键区域。
让关键区域(如接收缓冲区)不受保护在某些时候似乎可以正常工作,但会不可靠并导致(未检测到)数据丢失或“奇怪”的程序行为。
您可以尝试各种“创可贴”,例如清除缓冲区,但最终只有正确的同步结构(由原子执行的操作系统提供)才能可靠地工作。

当数据从串行端口到达时,系统会为数据维护一个 FIFO 缓冲区,因此不会丢失任何数据。您的信号处理程序通过使用单个目标缓冲区来抵消该 FIFO,该目标缓冲区在每次执行信号处理程序时都会覆盖其缓冲区。该程序不会丢失任何数据的唯一方法是以某种方式确保每个和完美协调write(),并且远程设备始终表现完美并及时响应。 但是当你有异步读取并且没有同步机制时,这些不是程序可以依赖的条件!read()CheckRXbuffer()

多线程教程通常涵盖这些概念。

我可以使用一个 write() 函数,然后使用另一个 write() 函数吗,因为我试图这样做,但它只写一次。你知道为什么会这样吗?

不。

...我的方向正确吗?

奇怪的是,当您选择完全忽略我对(错误)使用异步 I/O 的主要主题的建议时,您会费心征求我对小项目的意见。

附录 4:评论中对第四组问题的回应

我想告诉你,在设备的数据表中提到我需要使用异步 I/O,因为......

哇,经过我的 2 个请求(原始响应和第一个附录),您终于在 2 天后提到了为什么您认为需要使用异步 I/O。

因为当我尝试启动设备或重置设备时,我应该发送 N 个命令,并且在 N 个命令之后设备响应我。(启动设备序列)。

这不需要“异步 I/O”。

当我向设备发送状态时也会发生这样的事情,并且设备由于某种原因在一段时间内不会响应我,所以我应该重新发送命令(异步 I/O)。

这也不需要“异步 I/O”,也不需要描述异步 I/O。

因为它写在数据表中

该数据表是否在线提供英文版?

您是否将“异步通信”与“异步 I/O”混淆了?

如果该设备仅响应命令发送数据,并且从不主动发送数据(即客户端/服务器模型),那么这意味着您的程序可以期待或知道何时可以期待来自设备的消息。
这些将是从设备“请求”或请求的消息。
该设备(根据您到目前为止所描述的)从不发送未经请求的消息。

如果此设备确实发送了未经请求的消息,那么是的,您的程序可能需要具有异步 I/O 功能才能及时处理这些消息。

但同样,设备(根据您到目前为止所描述的)在收到命令时仅发送请求消息或不发送消息。

设备不应该规定主机程序的结构。它只能指定协议。

于 2013-06-26T19:54:15.093 回答