linux串口接收0x03等特殊字符的问题

解决Linux串口程序中特殊字符接收问题
本文讨论了在Linux环境下编写串口程序时遇到的问题,特别是关于特殊字符如0x03、0x13、0x0D等如何影响数据接收。文章详细解释了这些字符可能引起的问题,并提供了解决方法。通过修改终端参数,可以有效避免此类问题,确保数据接收的准确性。

近日在写一个linux的串口程序,发现大多数情况下数据接收没问题,但是有时却有问题。主要是接收的字符串中包含有0x03这个字符,会造成与它相邻的字符同时也接收不到,搞了好久才发现这个错误。查找资料后发现许多ARM板也存着这个问题,存在问题的字符串还包括0x13、0x0D等特殊含义的字符。

解决方法

方法比较简单,接收数据前,对串口的文件描述符fd进行如下设置, 

<pre name="code" class="cpp">struct termios options;
    if  ( tcgetattr( fd,&options)  !=  0)
    {
        perror("SetupSerial 1");
        return(FALSE);
    }
    options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    options.c_oflag &= ~OPOST;
    options.c_cflag |= CLOCAL | CREAD;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tcsetattr(fd,TCSAFLUSH,&options);

 
代码
用于
修改与文件描述符fd相关的标志.c_oflag.c_oflag.c_iflag
,重置了一些特殊字符的处理方式。现在通过fd的串口接收任何字符都应该不成问题了。 


现在问题应该可以解决了。如果你还想了解的更深入的话,可以接着往下看看。

属性获取

函数:int tcgetattr(int fd, struct termios *termios_p);
成功返回零;失败返回非零,发生失败接口将设置errno错误标识。

参数说明:

tcgetattr函数用于获取与终端相关的参数。参数fd为终端的 文件描述符,返回的结果保存在 termios  结构体中,该结构体一般包括如下的成员:
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_cc[NCCS];
其具体意义如下。
c_iflag:输入模式标志,控制终端输入方式,具体参数如表1所示。
表1 c_iflag参数表
键 值
说 明
IGNBRK
忽略BREAK键输入
BRKINT
如果设置了IGNBRK,BREAK键输入将被忽略
IGNPAR
忽略奇偶校验错误
PARMRK
标识奇偶校验错误
INPCK
允许输入奇偶校验
ISTRIP
去除字符的第8个比特
INLCR
将输入的NL(换行)转换成CR(回车)
IGNCR
忽略输入的回车
ICRNL
将输入的回车转化成换行(如果IGNCR未设置的情况下)
IUCLC
将输入的大写字符转换成小写字符(非POSIX)
IXON
允许输出时对XON/XOFF流进行控制
IXANY
输入任何字符将重启停止的输出
IXOFF
允许输入时对XON/XOFF流进行控制
IMAXBEL
当输入队列满的时候开始响铃
c_oflag:输出模式标志,控制终端输出方式,具体参数如表2所示。
表2 c_oflag参数
键 值
说 明
OPOST
处理后输出
OLCUC
将输入的小写字符转换成大写字符(非POSIX)
ONLCR
将输入的NL(换行)转换成CR(回车)及NL(换行)
OCRNL
将输入的CR(回车)转换成NL(换行)
ONOCR
第一行不输出回车符
ONLRET
不输出回车符
OFILL
发送填充字符以延迟终端输出
OFDEL
以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符为NUL
NLDLY
换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)
CRDLY
回车延迟,取值范围为:CR0、CR1、CR2和 CR3
TABDLY
水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3
BSDLY
空格输出延迟,可以取BS0或BS1
VTDLY
垂直制表符输出延迟,可以取VT0或VT1
FFDLY
换页延迟,可以取FF0或FF1
c_cflag:控制模式标志,指定终端硬件控制信息,具体参数如表3所示。
表3 c_cflag参数
键 值
说 明
CBAUD
波特率(4+1位)(非POSIX)
CBAUDEX
附加波特率(1位)(非POSIX)
CSIZE
字符长度,取值范围为CS5、CS6、CS7或CS8
CSTOPB
设置两个停止位
CREAD
使用接收器
PARENB
使用奇偶校验
PARODD
对输入使用奇偶校验,对输出使用偶校验
HUPCL
关闭设备时挂起
CLOCAL
忽略调制解调器线路状态
CRTSCTS
使用RTS/CTS流控制
c_lflag:本地模式标志,控制终端编辑功能,具体参数如表4所示。
表4 c_lflag参数
键 值
说 明
ISIG
当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
ICANON
使用标准输入模式
XCASE
在ICANON和XCASE同时设置的情况下,终端只使用大写。
ECHO
显示输入字符
ECHOE
如果ICANON同时设置,ERASE将删除输入的字符
ECHOK
如果ICANON同时设置,KILL将删除当前行
ECHONL
如果ICANON同时设置,即使ECHO没有设置依然显示换行符
ECHOPRT
如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
TOSTOP
向后台输出发送SIGTTOU信号
c_cc[NCCS]:控制字符,用于保存终端驱动程序中的特殊字符,如输入结束符等。c_cc中定义了如表5所示的控制字符。
表5 c_cc支持的控制字符
说 明
说 明
VINTR
Interrupt字符
VEOL
附加的End-of-file字符
VQUIT
Quit字符
VTIME
非规范模式读取时的超时时间
VERASE
Erase字符
VSTOP
Stop字符
VKILL
Kill字符
VSTART
Start字符
VEOF
End-of-file字符
VSUSP
Suspend字符
VMIN
非规范模式读取时的最小字符数
   
tcsetattr函数用于设置终端的相关参数。参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数。
optional_actions可以取如下的值:
TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。
错误信息:
EBADF:非法的文件描述符。
EINTR:tcsetattr函数调用被信号中断。
EINVAL:参数optional_actions使用了非法值,或参数termios中使用了非法值。
ENCTTY:非终端的文件描述符。

属性设置

函数:int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);

tcsetattr函数用于设置终端参数。函数在成功的时候返回0,失败的时候返回-1,并设置errno的值。

参数说明:

参数fd为打开的终端文件描述符,参数optional_actions用于控制修改起作用的时间,而结构体termios_p中保存了要修改的参数。optional_actions可以取如下的值。

TCSANOW:不等数据传输完毕就立即改变属性。
TCSADRAIN:等待所有数据传输结束才改变属性。
TCSAFLUSH:清空输入输出缓冲区才改变属性。
EBADF:非法的 文件描述符
EINTR:tcsetattr 函数调用被信号中断。
EINVAL:参数optional_actions使用了非法值,或参数termios中使用了非法值。
ENCTTY:非 终端文件描述符

示例

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
int main(void){
//term用于存储获得的终端参数信息
struct termios term;
int err;
//获得标准输入的终端参数,将获得的信息保存在term变量中
if(tcgetattr(STDIN_FILENO,&term)==-1){
perror("Cannot get standard input description");
return 1;
}
//修改获得的终端信息的结束控制字符
term.c_cc[VEOF]=(cc_t)0x07;
//使用tcsetattr函数将修改后的终端参数设置到标准输入中
//err用于保存函数调用后的结果
err=tcsetattr(STDIN_FILENO,TCSAFLUSH,&term);
//如果err为-1或是出现EINTR错误(函数执行被信号中断),
//给出相关出错信息
if(err==-1 && err==EINTR){
perror("Failed to change EOF character");
return 1;
}
return 0;
}
用gcc编译程序,得到可执行程序。在执行程序前,按“Ctrl+D”可以使终端结束。执行程序后,按“Ctrl+D”失去了作用,而输入“Ctrl+G”实现了原来“Ctrl+D”的功能。


<think>首先,用户的问题是关于串口通信的:如何接收0x24开头和0x2a结尾的数据帧。0x24是ASCII中的'$',0x2a是ASCII中的'*'。所以,数据帧以'$'开头,以'*'结尾。 我需要参考用户提供的引用,这些引用是关于串口通信的: - 引用[1]:提到模块TX引脚输出,通过CH340串口调试助手相连,验证收发数据。 - 引用[2]:解释串口通信的基本原理,包括有线通信、串口线(Tx, Rx, GND),以及通信参数。 - 引用[3]:提到端口占用问题串口通信案例,如上位机与下位机的数据交换。 - 引用[4]:详细描述了消息帧的传输要求,包括字符时间、停顿时间等,强调帧必须连续传输,避免停顿导致的不完整帧错误。 - 引用[5]:关于设备管理和Modbus协议,提到寄存器设置,但可能不直接相关。 用户的问题是如何实现接收以特定字符开头和结尾的数据帧。所以,我应该基于这些引用构建一个清晰的回答。 关键点从引用[4]:消息帧必须连续传输,如果停顿超过1.5个字符时间,接收设备会刷新不完整的消息。如果新消息在3.5个字符时间内开始,可能被视为前一消息的延续。因此,在实现时,需要处理这些时序问题。 现在,回答结构: 1. **理解需求**:解释用户的问题——接收0x24('$')开头和0x2a('*')结尾的数据帧。 2. **基本原理**:基于引用,简述串口通信如何工作,包括设置通信参数(波特率、数据位、停止位等),引用[2]提到收发双方事先规定好通信参数。 3. **实现方法**:描述如何编程或配置来接收这样的数据帧。这包括: - 使用串口库或API(如Python的pySerial、Arduino的Serial库等)。 - 在接收数据时,检查每个字节。 - 当检测到起始字节(0x24)时,开始收集数据。 - 继续收集直到检测到结束字节(0x2a)。 - 处理可能的错误,如超时、不完整帧等,基于引用[4]的时序要求。 4. **代码示例**:提供简单的代码示例来演示。用户是中文的,所以代码注释用中文。 5. **注意事项**:提到引用[3]的端口占用问题,确保串口未被其他程序占用。 6. **相关引用**:在回答中自然地添加引用标识,如[^4]。 7. **生成相关问题**:在最后,生成几个相关问题,以帮助用户深入学习。 系统级指令要求: - 所有行内数学表达式用$...$,但这里可能没有数学,主要是串口通信。 - 独立公式用$$...$$,但可能不需要。 - LaTeX语法正确,但在这个上下文中,可能不涉及复杂数学。 - 使用中文回答。 - 生成相关问题。 回答结构: - 标题:清晰的问题概述。 - 步骤:分步解释。 - 代码块:如果适用。 - 相关问题:在最后。 最终回答大纲: 1. 介绍:简述问题,基于引用。 2. 实现步骤: a. 设置串口参数。 b. 接收数据并解析帧。 c. 处理时序和错误。 3. 代码示例:例如用Python。 4. 注意事项。 5. 相关问题。</think>### 串口通信中接收0x24开头和0x2a结尾的数据帧实现方法 在串口通信中,接收特定格式的数据帧(如以0x24(ASCII字符 '$')开头、0x2a(ASCII字符 '*')结尾)是常见需求,常用于协议解析(如自定义通信协议或类似Modbus的应用)。这需要正确处理数据流的连续性、超时和错误检测,以避免不完整或错误帧。基于您提供的引用(尤其是引用[4]的帧时序要求),我将逐步解释实现方法,并提供代码示例。关键点包括: - **帧结构要求**:数据帧必须以0x24开头、0x2a结尾,中间包含有效数据(如传感器读数或控制指令)。 - **时序约束**:引用[4]强调,帧传输必须连续;如果停顿超过1.5个字符时间(character time),接收端会丢弃不完整帧;如果新帧在3.5个字符时间内开始,可能被视为前一帧的延续,导致CRC错误[^4]。 - **通信基础**:串口通信需要设置一致参数(波特率、数据位、停止位等),并使用Tx、Rx和GND线(引用[2])[^2]。同时,确保端口未被占用(引用[3])[^3]。 下面,我将从原理、实现步骤、代码示例和注意事项展开。实现的核心是状态机(state machine)解析:检测起始字节、收集数据、验证结束字节,并处理超时。 #### 1. **理解原理和需求** - **数据帧格式**:一个完整帧示例:`0x24, data1, data2, ..., 0x2a`。其中: - `0x24`('$')为起始标识。 - 中间数据(data)为任意字节序列。 - `0x2a`('*')为结束标识。 - **为什么需要时序处理**:引用[4]指出,帧内字节间停顿不能超过1.5个字符时间(计算基于波特率,例如115200波特率时,1个字符时间 ≈ 87μs),否则接收端会重置解析;帧间间隔需大于3.5个字符时间,避免帧粘连[^4]。 - **工具和平台**:常用串口库(如Python的pySerial、Arduino的Serial)或嵌入式系统(如STM32 HAL库)。以下以Python为例,因易演示。 #### 2. **实现步骤** 以下是通用实现逻辑,分为初始化、接收解析和错误处理。假设使用标准参数:波特率9600、8位数据位、1位停止位、无校验(引用[2])[^2]。 a. **初始化串口**: - 打开串口设备(如COM3或/dev/ttyUSB0)。 - 设置通信参数(波特率等)。 - 检查端口是否可用(避免引用[3]的占用问题)[^3]。 b. **接收解析数据帧(状态机方法)**: - **状态0:等待起始字节**。持续读取字节,直到检测到`0x24`。 - **状态1:收集数据**。一旦起始字节确认,开始收集后续字节到缓冲区。 - **状态2:检测结束字节**。当收到`0x2a`时,结束收集,处理完整帧。 - **超时处理**:基于引用[4],在收集数据时启动计时器: - 如果字节间延迟超过1.5个字符时间,重置状态(丢弃当前帧)。 - 如果收到结束字节后帧不完整(如长度不足),视为无效帧。 - **帧验证**:处理后,可添加CRC校验(可选,基于具体协议)。 c. **错误处理**: - 处理不完整帧(如缺少结束字节)。 - 日志记录或重发机制,提高鲁棒性。 #### 3. **代码示例(Python使用pySerial库)** 以下Python代码演示如何实现上述状态机。需先安装pySerial:`pip install pyserial`。 ```python import serial import time # 字符时间计算函数(基于波特率) def char_time(baud_rate): return (1 / baud_rate) * 10 # 10位/字符(8数据位+1起始位+1停止位) # 初始化串口 ser = serial.Serial( port='COM3', # 根据设备修改,如'/dev/ttyUSB0' on Linux baudrate=9600, # 设置波特率 bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=0.1 # 读超时,单位秒 ) # 状态变量 state = 0 # 0:等待起始字节, 1:收集数据 frame_buffer = bytearray() start_time = None char_timeout = 1.5 * char_time(9600) # 1.5个字符时间(单位秒) try: while True: if ser.in_waiting > 0: byte = ser.read(1) # 读取一个字节 if not byte: continue current_time = time.time() # 状态0:等待起始字节 (0x24) if state == 0: if byte == b'\x24': # 检测到0x24 frame_buffer = bytearray(byte) # 重置缓冲区并添加起始字节 state = 1 start_time = current_time # 记录起始时间 # 状态1:收集数据 elif state == 1: # 检查超时(字节间延迟 > 1.5字符时间) if start_time and (current_time - start_time > char_timeout): print("超时重置:帧不完整") state = 0 frame_buffer = bytearray() continue frame_buffer.extend(byte) start_time = current_time # 更新最后字节时间 # 检测结束字节 (0x2a) if byte == b'\x2a': # 完整帧处理(例如:打印或解析) print(f"收到完整帧: {frame_buffer.hex(' ')}") # 可选:验证CRC或长度 state = 0 # 重置状态 frame_buffer = bytearray() # 处理无数据时状态重置(可选) if state == 1 and start_time and (time.time() - start_time > char_timeout): print("全局超时:丢弃不完整帧") state = 0 frame_buffer = bytearray() except KeyboardInterrupt: ser.close() print("串口关闭") ``` **代码说明**: - **字符时间计算**:`char_time`函数基于波特率计算单个字符传输时间(引用[4]时序要求)[^4]。例如,9600波特率时,字符时间 ≈ 1.04ms,超时设为1.5倍。 - **状态机**:使用`state`变量切换状态,确保只处理完整帧。 - **超时处理**:在收集数据时,检测字节间延迟;如果超过阈值,重置解析(避免不完整帧)。 - **输出**:当帧完整(以0x2a结尾)时,打印帧内容(十六进制格式)。 - **扩展**:可添加CRC校验(如引用[4]的CRC域)[^4],或集成到上位机软件(引用[5]的设备管理)[^5]。 #### 4. **注意事项** - **端口占用**:确保串口未被其他程序占用(引用[3]),使用工具如Windows设备管理器或Linux `lsof`检查[^3]。 - **参数设置**:收发双方必须一致(波特率、数据位等),否则数据错误(引用[2])[^2]。 - **时序优化**:在嵌入式系统(如STM32)中,使用中断或DMA接收数据,减少CPU负载。例如: - 中断触发时,快速读取字节并更新状态机。 - 使用硬件定时器处理超时。 - **错误率**:在高噪声环境中,添加重试机制或前向纠错(FEC)。 - **测试工具**:使用串口调试助手(如Tera Term)发送测试帧:`$data*`,验证接收逻辑(引用[1])[^1]。 - **性能**:对于高速波特率(如115200),确保代码效率,避免解析延迟导致帧丢失。 #### 5. **相关问题** 为帮助您深入理解,以下是基于此主题的扩展问题: 1. 如何在嵌入式系统(如Arduino或STM32)中实现串口数据帧的接收和解析? 2. 串口通信中如何处理和校验CRC错误,以确保数据完整性? 3. 在Modbus协议中,如何自定义类似0x24/0x2a的帧结构(引用[5]寄存器设置)? 4. 如何优化串口接收代码以减少CPU占用率? 5. 在多设备通信中,如何扩展此方法以支持地址域(如引用[4]的消息帧格式)? 如果您提供更多细节(如具体硬件平台或协议),我可以进一步优化实现方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值