Linux下串口编程流程介绍
目录
串口接口简称串口,也称串行通信接口(通常为COM接口),串行接口(Serial Interface)是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线可实现双向通信,根据通信的方向可分为单工、半双工和全双工三种。在Linux 下标准的串口节点名为 /dev/ttyS* ,如果是USB转串口,则为/dev/ttyUSB*:
一、串口编程中struct termios结构体
在串口编程中有一个很重要的结构体——struct termios,通过该结构体我们可对串口的属性(输入输出)进行控制:
struct termios
{
tcflag_t c_iflag; //输入模式标志
tcflag_t c_oflag; //输出模式标志
tcflag_t c_cflag; //控制模式标志
tcflag_t c_lflag; //本地模式标志
cc_t c_cc[NCCS]; //控制字符
}
其中tcflag_t定义为:
typedef unsigned int tcflag_t
在该结构体中c_cflag输入模式标志最为重要,可设置波特率、数据位、校验位、停止位。设置波特率需要在前加上B(B9600,B115200),然后对需要设置的位进行"与","或"操作,其中标志所代表的意义如下:
- c_cflag控制模式标志
位成员 | 代表含义 |
---|---|
CBAUD | 波特率掩码位 |
EXTA | 外部时钟 |
EXTB | 外部时钟 |
CSIZE | 数据位掩码位 |
CSTOPB | 2位停止位 |
CREAD | 接收使能 |
PARENB | 奇偶校验位使能 |
PARODD | 使用奇校验 |
CLOCAL | 忽略终端状态行 |
CRTSCTS | 硬件流控制使能位 |
通常情况下CLOCAL和CREAD两个选项总是被打开,这两个选项可保证你的程序不会变成端口的所有者,而端口所有者需要去处理发散性控制和挂断信号,同时还保证串行接口驱动会读取输入的数据字节。
- c_iflag输入模式标志
位成员 | 代表含义 |
---|---|
IGNBRK | 忽略BREAK键输入 |
BRKINT | 如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断 |
IGNPAR | 忽略奇偶校验错误 |
PARMRK | 标识奇偶校验错误 |
INPCK | 允许输入奇偶校验 |
ISTRIP | 去除字符的第8个比特 |
INLCR | 将输入的NL(换行)转换成CR(回车) |
IGNCR | 忽略输入的回车 |
ICRNL | 将输入的回车转化成换行(如果IGNCR未设置的情况下) |
IUCLC | 将输入的大写字符转换成小写字符(非POSIX) |
IXON | 允许输入时对XON/XOFF流进行控制 |
IXANY | 输入任何字符将重启停止的输出 |
IXOFF | 允许输入时对XON/XOFF流进行控制 |
IMAXBEL | 当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置 |
- c_oflag输出模式标志
位成员 | 代表含义 |
---|---|
OPOST | 处理后输出 |
OLCUC | 将输入的小写字符转换成大写字符(非POSIX) |
ONLCR | 将输入的NL(换行)转换成CR(回车)及NL(换行) |
OCRNL | 将输入的CR(回车)转换成NL(换行) |
ONOCR | 第一行不输出回车符 |
ONLRET | 不输出回车符 |
OFILL | 发送填充字符以延迟终端输出 |
OFDEL | 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘\0’)(非POSIX) |
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_lflag本地模式标志
位成员 | 代表含义 |
---|---|
ISIG | 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号 |
ICANON | 使用标准输入模式 |
ECHO | 显示输入字符 |
ECHOE | 如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词 |
ECHOK | 如果ICANON同时设置,KILL将删除当前行 |
ECHONL | 如果ICANON同时设置,即使ECHO没有设置依然显示换行符 |
ECHOPRT | 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX) |
TOSTOP | 向后台输出发送SIGTTOU信号 |
二、使用串口流程
1、打开串口
comport->fd = open(comport->dev_name,O_RDWR|O_NOCTTY|O_NDELAY) ;
openz函数除了普通参数外还有O_NOCTTY|O_NDELAY:
- O_NOCTTY:通知Linux系统,这个程序不会成为这个端口的控制终端;
- O_NDELAY:通知系统不管县DCD信号所线处的状态。
2、串口配置流程
a、tcgetattr() 与 tcsetattr()控制终端
tcgetattr()
-
参数:
int fd:打开串口文件后,获取到的文件描述符;
struct termios &termios_p: termios 类型的结构体,包含在 <termios.h> 头文件中,这里需要传地址或指针; -
功能:获取对应文件描述符相应串口的原始属性,保存在第二个参数中,通常获取串口需要对原始信息进行备份,在程序退出前需要修改回来,一边继续使用串口;
-
返回值:成功返回0,失败返回-1。
tcsetattr()
-
参数:
int fd: 要设置属性的文件描述符
int optional_actions: 设置属性时,可以控制属性生效的时刻,optional_actions可以取下面几个值:
TCSANOW: 立即生效;
TCADRAIN: 改变在所有写入fd 的输出都被传输后生效。这个函数应当用于修改影响输出的参数时使用。(当前输出完成时将值改变);
TCSAFLUSH :改变在所有写入fd 引用的对象的输出都被传输后生效,所有已接受但未读入的输入都在改变发生前丢弃(同TCSADRAIN,但会舍弃当前所有值)。
*termios termios_p: 用来设置的串口属性的结构体指针,对串口的termios配置好后,传入函数即可。 -
功能:设置终端参数的函数;
-
返回值:成功返回0,失败返回-1。
b、cfsetispeed() 与 cfsetospeed()设置波特率
//函数原型
static void set_baudrate (struct termios *opt, unsigned int baudrate)
{
cfsetispeed(opt, baudrate);
cfsetospeed(opt, baudrate);
}
参数:
- opt:通过结构体设置串口通信属性,这是指向该结构体的指针;
- baudrate:串口没有内部时钟所以为异步通信,为了通信双方达到收发消息统一,需要设置通信双方的波特率相同。
实现如下:
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400,19200, 9600, 4800, 2400, 1200, 300, };
/* 设置波特率 */
int set_baudrate(struct termios *opt,int baudrate)
{
int i;
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {
if(baudrate == name_arr[i])
{
if(cfsetispeed(opt,baudrate) < 0)
{
printf("cfsetispeed failure:%s\n", strerror(errno));
return -1;
}
if(cfsetospeed(opt,baudrate) < 0)
{
printf("cfsetospeed failure:%s\n", strerror(errno));
return -2;
}
}
}
return 0;
}
c、使用掩码设置数据位
/* 设置数据位 */
void set_databit(struct termios *opt,int databit)
{
opt->c_cflag |= (CLOCAL|CREAD ); //cLOCAL本地连接模式,CREAD开启串行数据接收
opt->c_cflag &= ~CSIZE; //字符长度,取值范围为CS5、CS6、CS7或CS8
switch(databit){
case 5:
opt->c_cflag |= CS5;
break;
case 6:
opt->c_cflag |= CS6;
break;
case 7:
opt->c_cflag |= CS6;
break;
case 8:
opt->c_cflag |= CS8;
break;
default:
opt->c_cflag |= CS8;
break;
}
}
d、使用c_cflag和c_iflag设置奇偶校验
/* 设置校验位 */
void set_parity(struct termios *opt,char parity)
{
switch(parity){
case 'n':
case 'N':
opt->c_cflag &= ~PARENB; //使用奇偶校验
break;
case 'e':
case 'E':
opt->c_cflag |= PARENB;
opt->c_cflag &= ~PARODD; //对输入使用奇偶校验,对输出使用偶校验
opt->c_cflag |= (INPCK | ISTRIP); //允许输入奇偶校验|去除字符第8比特
break;
case 'o':
case 'O':
opt->c_cflag |= (PARENB | PARODD); //使用输出奇偶校验
opt->c_cflag |= (INPCK | ISTRIP); // 允许输入奇偶校验|去除字符第8比特
break;
default:
opt->c_cflag &= ~PARENB; //默认使用奇偶校验
break;
}
}
e、设置停止位
/* 设置停止位 */
void set_stopbit(struct termios *opt, int stopbit)
{
switch(stopbit)
{
case 2:
opt->c_cflag |= CSTOPB; //设置两个停止位
break;
default:
opt->c_cflag &= ~CSTOPB; //停止位为1,则要清楚CSTOPB
break;
}
}
f、设置最少字符和等待时间
对于接收字符和等待时间没有特别要求时可设为0:
//串口超时设置
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
3、读写数据
写数据使用write系统调用,成功时会返回写入的字节数,失败时会返回-1,例如:
rv = write(fd, buf, buf_size);
if (rv < 0)
printf("write data failed!\n", strerror(errno));
读串口数据我们需要考虑到一个问题就是timeout超时问题,当端口在raw data mode操作模式下,那么read系统调用将返回从串口输入缓冲区中实际得到的字节数,如果没有数据可读,那么该系统调用将会被阻塞(block)直到有数据为止,如果超过一定时间仍然没有数据可读,那么将返回一个错误(读错误)。
rv = read(comport->fd, buf, buf_size);
if(rv < 0)
{
printf("Read data from dev failure:%s\n", strerror(errno));
return rv;
}
我们也可通过fcntl()
函数使read()
没有数据可读的情况下立即返回而不是被阻塞:
fcntl(fd, F_SETFL, FNDELAY);
//FNDELAY选项在read无数据可读立即返回0,实际调用如下:
fcntl(fd, F_SETFL, 0);
4、关闭串口
关闭串口就是关闭该串口下的文件描述符:
close(comport->fd);
上一篇: 游园不值