概述
近来在一个项目开发中,在一个新的硬件平台下的linux系统,使用uart串口进行通讯,结果通讯不畅。代码是以前在其他硬件平台下验证完全没问题的代码,为什么会出问题呢?经过各方面查资料,最终定位为uart串口初始化的问题。在linux系统下,串口的初始化比较复杂,需要设置的东西比较多,如果有一些默认的配置与硬件和应用程序不匹配,而又没有重新配置,就会导致通讯失败的情况。经过对linux系统下串口初始化的进行了整理梳理,最终解决了问题。记录这批位置可以为其他小伙伴提供参考。
一、termios结构体
termios是在POSIX规范中定义的标准接口,表示终端设备,包括虚拟终端、串口等。串口通过termios进行配置。本文的内容,基本是基于这个这个结构体进行介绍,它是决定串口是否设置成功的关键。
这个结构体的定义如下:
struct termios
{
unsigned short c_iflag; // 输入模式标志
unsigned short c_oflag; // 输出模式标志
unsigned short c_cflag; // 控制模式标志
unsigned short c_lflag; // 本地模式标志
unsigned char c_line; // 线路规程
unsigned char c_cc[NCC]; // 控制特性
speed_t c_ispeed; // 输入速度
speed_t c_ospeed; // 输出速度
}
在这个结构体中包含了串口设置的绝大部分标志位,下面我们对这个结构体中的元素一一进行分析。
1、输入模式标志c_iflag
c_iflag成员负责控制串口输入数据的处理,下表是c_iflag的部分可用标志
标志
说明
INPCK
打开输入奇偶校验
IGNPAR
忽略奇偶错字符
PARMRK
标记奇偶错
ISTRIP
剥除字符第8位
IXON
启用/停止输出控制流起作用
IXOFF
启用/停止输入控制流起作用
IGNBRK
忽略BREAK条件
INLCR
将输入的NL转换为CR
IGNCR
忽略CR
ICRNL
将输入的CR转换为NL
设置输入校验
当c_cflag成员的PARENB(奇偶校验)选项启用时,c_iflag的也应启用奇偶校验选项。操作方法是启用INPCK和ISTRIP选项:
options.c_iflag |= (INPCK | ISTRIP);
如果关闭奇偶校验,则使用如下方法:
options.c_iflag &= ~(INPCK | ISTRIP);
设置软件流控制
使用软件流控制是启用IXON、IXOFF和IXANY选项:
options.c_iflag |= (IXON | IXOFF | IXANY);
相反,要禁用软件流控制是禁止上面的选项:
options.c_iflag &= ~(IXON | IXOFF | IXANY);
输入字符转换
linux系统可以将输入字符转换为其他字符,这个功能如果不注意可能会导致程序,比如,ICRNL这个标志位的作用是将输入的CR转换为NL ,如果要启用这个功能,则使用下面的方法:
options.c_iflag |= ICRNL;
如果要禁用这个功能,则使用下面的方法:
options.c_iflag &= ~ICRNL;
2、输入模式标志c_oflag
c_oflag成员管理输出模式,下表所示是c_oflag成员的部分选项标志。
标志
说明
BSDLY
退格延迟屏蔽
CMSPAR
标志或空奇偶性
CRDLY
CR延迟屏蔽
FFDLY
换页延迟屏蔽
OCRNL
将输出的CR转换为NL
OFDEL
填充符为DEL,否则为NULL
OFILL
对于延迟使用填充符
OLCUC
将输出的小写字符转换为大写字符
ONLCR
将NL转换为CR-NL
ONLRET
NL执行CR功能
ONOCR
在0列不输出CR
OPOST
执行输出处理
OXTABS
将制表符扩充为空格
启用输出处理
与c_iflag标志类似,串口的输出也会将一些字符进行转换,如果想使能这些转换,需要启用输出处理。启用输出处理需要在c_oflag成员中启用OPOST选项,其操作方法如下:
options.c_oflag |= OPOST;
使用原始输出
使用原始输出,就是禁用输出处理,使数据能不经过处理、过滤地完整地输出到串口接口。当OPOST被禁止,c_oflag其它选项也被忽略,其操作方法如下:
options.c_oflag &= ~OPOST;
3、控制模式标志c_cflag
c_cflag成员控制着波特率、数据位、奇偶校验、停止位以及流控制,下表列出了c_cflag可用的部分选项。
标志
说明
标志
说明
CBAUD
波特率位屏蔽
CSIZE
数据位屏蔽
B0
0位/秒(挂起)
CS5
5位数据位
B110
100位/秒
CS6
6位数据位
B134
134位/秒
CS7
7位数据位
B1200
1200位/秒
CS8
8位数据位
B2400
2400位/秒
CSTOPB
2位停止位,否则为1位
B4800
4800位/秒
CREAD
启动接收
B9600
9600位/秒
PARENB
进行奇偶校验
B19200
19200位/秒
PARODD
奇校验,否则为偶校验
B57600
57600位/秒
HUPCL
最后关闭时断开
B115200
115200位/秒
CLOCAL
忽略调制调解器状态行
B460800
460800位/秒
—
—
c_cflag成员的CREAD和CLOCAL选项通常是要启用的,这两个选项使驱动程序启动接收字符装置,同时忽略串口信号线的状态。
4、本地模式标志c_lflag
本地标志c_lflag控制着串口驱动程序如何管理输入的字符,下表所示是c_lflag的部分可用标志。
标志
说明
ISIG
启用终端产生的信号
ICANON
启用规范输入
XCASE
规范大/小写表示
ECHO
进行回送
ECHOE
可见擦除字符
ECHOK
回送kill符
ECHONL
回送NL
NOFLSH
在中断或退出键后禁用刷清
IEXTEN
启用扩充的输入字符处理
ECHOCTL
回送控制字符为^(char)
ECHOPRT
硬拷贝的可见擦除方式
ECHOKE
Kill的可见擦除
PENDIN
重新打印未决输入
TOSTOP
对于后台输出发送SIGTTOU
选择规范模式
规范模式是行处理的。调用read读取串口数据时,每次返回一行数据。当选择规范模式时,需要启用ICANON、ECHO和ECHOE选项:
options.c_lflag |= (ICANON | ECHO | ECHOE);
当串口设备作为用户终端时,通常要把串口设备配置成规范模式。
选择原始模式
在原始模式下,串口输入数据是不经过处理的,在串口接口接收的数据被完整保留。要使串口设备工作在原始模式,需要关闭ICANON、ECHO、ECHOE和ISIG选项,其操作方法如下:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
5、控制特性c_cc[NCC]
c_cc数组的长度是NCC,一般介于15-20之间。c_cc数组的每个成员的下标都用一个宏表示,下表列出了c_cc的部分下标标志名及其对应说明。
标 志
说 明
VINTR
中断
VQUIT
退出
VERASE
擦除
VEOF
行结束
VEOL
行结束
VMIN
需读取的最小字节数
VTIME
与“VMIN”配合使用,是指限定的传输或等待的最长时间
二、实例分析
下面通过一个实例来分析如何使用嵌入式linux的串口,在这个实例中,包含三个文件,分别为:main.cpp、uart.cpp、uart.h。这个实例的作用是通过串口接收数据,并将接收的每个数据加1后再通过串口返回到发送方。
1、uart.h
uart.h的代码如下所示:
#ifndef UART_H
#define UART_H
#include #include #include #include #include #include #include #include #include #define UART1_DEV "/dev/ttyTHS2"
#define UART2_DEV "/dev/ttyTHS1"
#define UART1 1
#define UART2 2
#define BAUDRATE1 B115200
#define BAUDRATE2 B115200
using namespace std;
class Uart {
public :
~Uart();
Uart();
int uartOpen(int port, int flag, speed_t buadrate);
int uartWrite(int fd, char *data, int num);
int uartRead(int fd, char *data, int num);
void uartClose(int fd);
private:
int uartInit(int fd,speed_t buadrate);
static int uart1_only_fd;
static int uart2_only_fd;
static bool uart1_inited_flag;
static bool uart2_inited_flag;
};
#endif // UART_H
在这个头文件中,定义了若干变量,和基本的串口操作函数。
2、uart.cpp
uart.cpp的代码如下所示:
#include "uart.h"
int Uart::uart1_only_fd = 0;
int Uart::uart2_only_fd = 0;
bool Uart::uart1_inited_flag = false;
bool Uart::uart2_inited_flag = false;
Uart::Uart()
{
}
int Uart::uartInit(int fd,speed_t buadrate)
{
struct termios opt;
tcgetattr(fd,&opt);
cfsetispeed(&opt,buadrate);
cfsetospeed(&opt,buadrate);
opt.c_cflag |= CLOCAL | CREAD;
opt.c_cflag &= ~CRTSCTS;
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
opt.c_cflag &= ~PARENB;
opt.c_cflag &= ~CSTOPB;
opt.c_iflag &= ~INPCK;
opt.c_iflag &= ~(ICRNL|BRKINT|ISTRIP);
opt.c_iflag &= ~(IXON|IXOFF|IXANY);
opt.c_oflag &= ~OPOST;
opt.c_lflag = ~(ICANON | ECHO | ECHOE | ISIG);
tcflush(fd,TCIFLUSH);
if (tcsetattr(fd,TCSANOW,&opt) != 0)
{
return -1;
}
return 0;
}
int Uart::uartOpen(int port, int flag, speed_t buadrate)
{
if(port == UART1)
{
if(uart1_inited_flag == false)
{
uart1_only_fd = open(UART1_DEV, flag);
if(uart1_only_fd < 0)
{
return -1;
}
else
{
uartInit(uart1_only_fd,buadrate);
uart1_inited_flag = true;
return uart1_only_fd;
}
}
else
{
return uart1_only_fd;
}
}
else if(port == UART2)
{
if(uart2_inited_flag == false)
{
uart2_only_fd = open(UART2_DEV, flag);
if(uart2_only_fd < 0)
{
return -1;
}
else
{
uartInit(uart2_only_fd,buadrate);
uart2_inited_flag = true;
return uart2_only_fd;
}
}
else
{
return uart2_only_fd;
}
}
return -1;
}
int Uart::uartWrite(int fd, char *data , int num)
{
int ret = -1;
ret = write(fd, data, num);
return ret;
}
int Uart::uartRead(int fd, char *data, int num)
{
int ret = -1;
int i=0;
char buf[1024] = {0};
if(num > 1024)
{
ret = read(fd, buf, 1024);
}
else
{
ret = read(fd, buf, num);
}
for (i=0;i0)
close(fd);
}
Uart::~Uart()
{
}
在这个文件中,int Uart::uartInit(int fd,speed_t buadrate)为串口初始化函数,在这个函数中,首先通过tcgetattr(fd,&opt);函数将操作系统原来的串口配置结构体读出来,并且在这个结构体的基础上进行配置。这样做的好处是在原有配置的基础上进行修改,成功率比较高,如果在一个全新的结构体上修改,很容易导致失败。
读取配置结构体之后,在下面的代码中对结构体的成员进行配置。之后调用tcflush(fd,TCIFLUSH);函数来清空输入队列。再之后调用tcsetattr(fd,TCSANOW,&opt)函数来配置串口。
在这个文件中,int Uart::uartOpen(int port, int flag, speed_t buadrate)函数的作用是开启串口,在函数里调用uartInit()函数来初始化串口,并返回串口对应的文件描述符。
int Uart::uartWrite(int fd, char *data , int num)为串口输出函数,int Uart::uartRead(int fd, char *data, int num)为读取串口的函数,void Uart::uartClose(int fd)为关闭串口的函数。
3、main.cpp
main.cpp中调用uart.cpp中的函数实现串口的接收和发送,代码如下:
#include "uart.h"
#include #include #include int main(void)
{
Uart uart;
int uart1_fd;
int num;
char rbuff[200];
uart1_fd = uart.uartOpen(UART1,O_RDWR |O_NONBLOCK | O_NOCTTY | O_NDELAY,BAUDRATE1);
if(uart1_fd < 0)
cout<0)
{
for(int i = 0;i < num;i++)
{
rbuff[i] = rbuff[i]+1;
}
uart.uartWrite(uart1_fd,rbuff,num);
}
}
return 0;
}
最后
以上就是灵巧棒球为你收集整理的Linux下uart_opt结构体使用,嵌入式Linux系统uart串口编程详解及实例分析的全部内容,希望文章能够帮你解决Linux下uart_opt结构体使用,嵌入式Linux系统uart串口编程详解及实例分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复