我需要为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);
}
答案 0 :(得分:7)
再一次,这个想法非常简单:我正在向设备发送命令,设备会中断并重放。我读了发送的内容,我使用了重放数据,根据这些数据,我发送了另一个命令。
你正在描述一个普通而简单的主人和安排。从设备:主设备发送命令或请求消息,从设备必须回复响应消息。
您正在使用正确的POSIX方法修改标志。但是,系统调用fcntl()
,tcgetattr()
和tcsetattr()
应检查其返回代码,以确保没有错误。
您正在为非规范(也称为原始)输入配置串行端口&amp;输出。 (因此,您的接收缓冲区应为unsigned char
而不是签名char
。)此模式提供了一种基于字节计数和/或串行链路静默的读取方法。没有理由使用涉及信号处理程序的异步读取。 (您的信号处理程序与this question具有相同的错误。)主/从设备关系指示请求/响应消息交换。
要正确阅读完整的回复消息,您需要定义VMIN and VTIME values。如果响应消息的长度为7到32个字节,那么
termAttr.c_cc[VMIN] = 7;
termAttr.c_cc[VTIME] = 5;
应该做的伎俩。您将期望至少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 */
}
附录:对评论中的问题的回复
首先,我可以在信号处理程序中使用VMIN和VTIME的定义值保留缓冲区的填充吗?
您尚未描述异步读取串行端口的任何要求。 (顺便说一句,这不是&#34;串行总线&#34; ; USB是串行总线; EIA / RS-232是串行链路。)如果这不是主/从配置,那么请说出来。否则,用于读取的信号处理器是不必要的复杂性,并且不必要的复杂性(通常)表示设计不良。 (例外情况是,如果此代码是完全由事件驱动的系统的一部分。)
请注意,串行端口是完全缓冲的,用于读取和写入。当数据到达串行端口时,您的程序不必具有挂起或活动read()
请求。当接收到数据时,内核将串行数据存储在内部缓冲区中。即使不使用异步读取和信号处理程序,您的程序也不会丢失或遗漏任何数据。
最后一个问题是关于fcntl(),tcgetattr()和tcsetattr()sistem调用。你能给我一些关于我应该检查什么错误标志的例子吗?
此类信息记录在Linux man 页面中
如果您使用的是Linux开发系统(并且应该),请确保安装了 man 页面。否则,您可以谷歌搜索手册页,例如"fcntl linux man page"
检查功能原型
返回的值通常是系统调用状态。如果指示了错误(通常是负值,例如-1),则应访问全局变量 errno 以获取更多详细信息。
rc = tcgetattr(fd, &termAttr);
if (rc < 0) {
printf("failed to get attr: %d, %s", rc, strerror(errno));
close(fd);
exit (-2);
}
P.S我正在发送一个我发送给设备的消息示例和一个回复,以便您可以看到我正在谈论的内容。
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
,则程序必须进入&#34;寻找同步&#34;模式以查找消息的开头(从此缓冲区中的第二个字节开始)
应检查消息长度的合理性,然后用于定位两个CRC字节,并验证消息内容。如果CRC检查失败,那么&#34;消息&#34;应该被忽略并且&#34;寻找同步&#34;应该开始(从这个&#34的第二个或第三个字节开始;消息&#34;)。
附录2 :回答关于&#34;最终代码的问题
所以问题是现在如何创建一个计时器?
Userland可以使用POSIX计时器和信号(即timer_create()
)安排定期计时器
但是你必须检查可用的时钟分辨率,以确定它是否会让你做1ms。
但IMO你走错了路
您忽略了我不使用异步I / O的建议
您在信号处理程序中调用read()
,如果它试图睡眠,这是一个潜在的问题
异步读取的信号处理程序与主线程没有同步或缓冲区管理,因此程序可能会丢失已读取的数据。
你忽略了我检查系统调用返回码的建议(write()s
除外)。
如果我正在编写此程序,我可能会使用状态机,并且只有一个write()
和read()
使用串行端口。
附录3 :对评论中的第3组问题的回应
请举例说明如何使用timer_create()函数创建计时器?
显然你没有注意我以前关于使用手册页的建议 timer_create() man page
中有一个代码示例关于此:“异步读取的信号处理程序与主线程没有同步或缓冲区管理,因此程序存在丢失已读取数据的风险。”我想我可以解决这个问题如果我之后调用clearRXbuffer函数信号处理程序中的每个read()函数,因此在这种情况下,缓冲区将只包含最后一个可以是7到32个字节的消息数据。因此,如果新消息到达,它将被写入缓冲区的开头。你觉得这个想法是好的,我是朝着正确的方向前进的吗?如果没有,请你给我一些其他的缓冲管理理念。
这不是一个可靠的解决方案。
信号处理程序相对于主线程异步执行
因此,所有共享变量和缓冲区都是 关键区域
关键区域必须使用同步构造进行保护,例如互斥锁(又名互斥锁)或信号量或条件变量。
让一个关键区域(如接收缓冲区)不受保护似乎在某些时候可以正常工作,但是不可靠并导致(未检测到)数据丢失或者#34;奇怪的&#34;程序行为。
你可以尝试各种各样的乐队援助&#34;比如清除缓冲区,但最后只有正确的同步结构(由原子执行的OS提供)才能可靠地工作。
当数据从串行端口到达时,系统为数据维护一个FIFO缓冲区,这样就不会丢失任何数据。您的信号处理程序通过使用单个目标缓冲区来抵消该FIFO,该缓冲区在每次执行信号处理程序时都会覆盖其缓冲区。此程序不会丢失任何数据的唯一方法是以某种方式确保每个write()
,read()
和CheckRXbuffer()
完美协调,并且远程设备的行为完美且响应于及时的方式。
但是,当您进行异步读取并且没有适当的同步机制时,这些不是程序可以依赖的条件!
多线程教程通常涵盖这些概念。
我可以使用一个write()函数,然后使用另一个write()函数,因为我试图这样做,但它只写了一次。你知道为什么会这样吗?
没有
......我正朝着正确的方向前进吗?
当您选择完全忽略我对使用异步I / O的主要主题(错误)的建议时,您很难在一个小项目上询问我的意见。
附录4 :对评论中的第4组问题的回复
哇,经过我的2次请求(原始回复和第1次附录),你终于在2天后提到为什么你认为你需要使用异步I / O.我想告诉你,在设备的数据表中提到我需要使用异步I / O,因为......
因为当我尝试启动设备或重置设备时,我应该发送N个命令,并且在N个命令之后设备响应我。(启动设备序列)。
这不需要&#34; async I / O&#34;。
当我向设备发送状态时,也会发生类似这样的事情,并且设备由于某种原因在一段时间内不会响应我,所以我应该重新发送命令(异步I / O)
这不需要&#34; async I / O&#34;或者,这也没有描述异步I / O.
因为它写在数据表中
这个数据表是否在线提供英文版?
您是否感到困惑&#34;异步通信&#34;使用&#34;异步I / O&#34;?
如果此设备仅响应命令发送数据,并且从不自发地发送数据(即客户端/服务器模型),那么这意味着您的程序可以预期或知道何时可以预期来自设备的消息。
这些将是“请求”的消息。或从设备请求。
该设备(根据您目前所描述的内容)从不发送未经请求的消息。
如果此设备确实发送了未经请求的消息,那么您的程序可能需要具有异步I / O功能才能及时处理这些消息。
但同样,该设备(根据您目前所描述的内容)仅在收到命令时发送请求的消息或没有消息。
设备不应该规定如何构建主机程序。它只能指定协议。