为条形码阅读器读取和写入缓冲区数据的最佳方法是什么?

Шиј*_*гор 2 c linux serial-port

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

所以问题是:我应该读取缓冲区中的数据然后使用它吗?我是否可以使用以这种方式配置的端口将数据写入缓冲区?当设备回复我时,根据该回复数据,我需要向设备发送另一个命令.另外,我可以write()用来写消息吗?如果我不能使用它,我应该使用什么命令?你可以用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);
}
Run Code Online (Sandbox Code Playgroud)

saw*_*ust 7

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

您正在描述主设备和从设备的通用和简单排列:主设备发送命令或请求消息,从设备必须回复响应消息.

您正在使用适当的POSIX方法修改标志.然而,系统调用fcntl(),tcgetattr()tcsetattr()应该有自己的返回代码检查,以确保没有错误.

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

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

termAttr.c_cc[VMIN] = 7;
termAttr.c_cc[VTIME] = 5;
Run Code Online (Sandbox Code Playgroud)

应该做的伎俩.您将期望至少7个字节的响应消息,并且当字节在半秒后停止到达时,则假设消息已完成.

#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 */
}
Run Code Online (Sandbox Code Playgroud)

附录:回答评论中的问题

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

您没有描述异步读取串行端口的任何要求.(顺便说一句,这不是"串行总线" ; USB是串行总线; EIA/RS-232是串行链路.)如果这不是主/从配置,那么请说明.否则,用于读取的信号处理器是不必要的复杂性,并且不必要的复杂性(通常)表示设计不良.(例外情况是,如果此代码是完全由事件驱动的系统的一部分.)

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

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

这些信息记录在Linux 手册页中.
如果您使用的是Linux开发系统(并且应该),请确保已安装手册页.否则你可以谷歌搜索手册页,例如"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);
}
Run Code Online (Sandbox Code Playgroud)

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().
但是你必须检查可用的时钟分辨率,以确定它是否会让你做1ms.

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

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

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

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

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

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

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

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

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

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

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

没有.

......我正朝着正确的方向前进吗?

当您选择完全忽略我对使用异步I/O的主要主题(错误)的建议时,您很难在一个小项目上询问我的意见.

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

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

哇,经过我的2次请求(原始回复和第1次附录),你终于提到2天后为什么你认为你需要使用异步I/O.

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

这不需要"异步I/O".

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

这也不需要"异步I/O",也不需要描述异步I/O.

因为它写在数据表中

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

您是否将"异步通信"与"异步I/O"混淆?

如果此设备仅响应命令发送数据,并且永远不会自行发送数据(即客户端/服务器模型),那么这意味着您的程序可以预期或知道何时可以预期来自设备的消息.
这些将是从设备"请求"或请求的消息.
设备(根据您目前所描述的内容)从不发送未经请求的消息.

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

但同样,该设备(从您到目前为止所描述的内容)仅在收到命令时才发送请求消息或没有消息.

设备不应该指示主机程序应该如何构建.它只能指定协议.