前言
参考
1. 实战分析
1.1 开发步骤
- 获取串口设备路径
- 打开设备文件
- 配置串口
- 对该设备文件进行读写,相当于对该串口设备进行读写,即通信
- 关闭设备文件
以下代码段默认从 附件-最终串口测试源码 中摘取
1.1.1 获取串口设备路径
- 使用数组或者宏定义在相关文件前面定义默认串口路径,方便修改,源码段如下:
/* 不同的设备,不同的路径 */
const char def_uart_path[] = "/dev/ttymxc2" // 默认串口路径(备用)
- 串口路径优先从传入参数中获取,如果参数中没有传入,便使用
def_uart_path
默认路径
/* 终端设备选择 */
if(argc > 1)
path = argv[1];
else
path = (char *)def_uart_path;
1.1.2 打开设备文件
/* 打开终端 */
fd = open(path, O_RDWR);
if(fd < 0){
printf("[%s] open err!", path);
return 0;
}
1.1.3 配置串口
- 定义一个结构体 termios 用于获取、设置终端设备的参数
- 包括波特率、数据位数、校验位等
termios 结构体
- 成员值作用,推荐先看官方手册,看不懂再看本笔记中文表格
- termios 结构体定义在编译链接工具的头文件默认路径中的bits文件夹中
- 如如下源码来自 /usr/arm-linux-gnueabihf/include/bits/termios.h
成员 |
说明 |
c_iflag |
输入模式标志 |
c_oflag |
输出模式标志 |
c_cflag |
控制模式标志 |
c_lflag |
本地模式标志 |
c_line |
行控制 |
c_cc[NCCS] |
控制字符 |
c_ispeed |
输入波特率 |
c_ospeed |
输出波特率 |
struct termios
{
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
#define _HAVE_STRUCT_TERMIOS_C_ISPEED 1
#define _HAVE_STRUCT_TERMIOS_C_OSPEED 1
};
1. c_iflag 输入模式标志
选项值 |
说明 |
IGNBRK |
忽略输入中的 BREAK 状态。 (忽略命令行中的中断) |
BRKINT |
(命令行出 现中断时,可产生一插断)如果设置了IGNBRK,中断条件被忽略。如果没有设置IGNBRK而设置了BRKINT,中断条件清空输入输出队列中所有的数据并且向tty的前 台进程组中所有进程发送一个SIGINT信号。如果这两个都没有设置,中断条件会被看作一个0字符。这时,如果设置了PARMRK,当检测到一个帧误差时 将会向应用程序发送三个字节'/377''/0''/0',而不是只发送一个'/0' |
IGNPAR |
忽略桢错误和奇偶校验错 |
PARMRK |
如果设定了IGNPAR,则忽略接收到的数据的奇偶检验错误或帧错误(除了前面提到的中断条件)。如果没有设置IGNPAR而设置了PARMRK, 当接收到的字节存在奇偶检验错误或帧错误的时候。将向应用程序发送一个三字节的'/377''/0''/n'错误报告。其中n表示所接收到的字节。如果两 者都没有设置,除了接收到的字节存在奇偶检验错误或帧误差之外的中止条件都会向应用程序发送一个单字节('/0')的报告 |
INPCK |
如果设置,则进行奇偶校验。如果不进行奇偶检验,PARMRK和IGNPAR将对存在的奇偶校验错误不产生任何的影响 |
ISTRIP |
如果设置,所接收到的所有字节的高位将会被去除,保证它们是一个7位的字符 |
INLCR |
如果设置,所接收到的换行字符('/n')将会被转换成回车符('/r') |
IGNCR |
如果设置,则会忽略所有接收的回车符('/r') |
ICRNL |
如果设置,但IGNCR没有设置,接收到的回车符向应用程序发送时会变换成换行符 |
IUCLC |
如果IUCLC和IEXTEN都设置,接收到的所有大写字母发送给应程序时都被转换成小写字母。POSIX中没有定义该标记 |
IXON |
如果设置,接收到S后会停止向这个tty设备输出,接收到Q后会恢复输出 |
IXANY |
如果设置,则接到任何字符都会重新开始输出,而不仅仅是^Q字符 |
IXOFF |
如果设置,为避免tty设备的输入缓冲区溢出,tty设备可以向终端发送停止符S和开始符Q,要求终端停止或重新开始向计算机发送数据。通过停 止符和开始符来控制数据流的方式叫软件流控制,软件流控制方式较少用,我们主要还是用硬件流控制方式。硬件流控制在c_cflag标志中设置 |
IMAXBEL |
如果设置,当输入缓冲区空间满时,再接收到的任何字符就会发出警报符'/a'。POSIX中没有定义该标记 |
IUTF8 |
(不属于 POSIX)输入 IUTF8 ,这是允许 character-erase 在 cooked 模式下被正确执行 |
2. c_oflag 输出模式标志
选项值 |
说明 |
OPOST |
开启该标记,后面的输出标记才会生效。否则,不会对输出数据进行处理 |
OLCUC |
如果设置,大写字母被转换成小写字母输出 |
ONLCR |
如果设置,在发送换行符('/n')前先发送回车符('/r') |
OCRNL |
如果设置,回车符会被转换成换行符。另外,如果设置了ONLRET,则current column会被设为0 |
ONOCR |
如果设置,当current column为0时,回车符不会被发送也不会被处理 |
ONLRET |
如果设置,当一个换行符或回车符被发送的时候,current column会被设置为0 |
OFILL |
发送填充字符作为延时,而不是使用定时来延时 |
OFDEL |
(不属于 POSIX) 填充字符是 ASCII DEL (0177)。如果不设置,填充字符则是 ASCII NUL |
VTDLY |
竖直跳格延时掩码。取值为 VT0 或 VT1 |
3. c_cflag 控制模式标志
- 用于控制串口的基本参数,如数据位、停止位等,常用配置见下表,特别地,c_cflag结构体成员还包含了波特率的参数
选项值 |
说明 |
CLOCAL |
如果设置,modem的控制线将会被忽略。如果没有设置,则open()函数会阻塞直到载波检测线宣告modem处于摘机状态为止 |
CREAD |
只有设置了才能接收字符,该标记是一定要设置的 |
CSIZE |
设置传输字符的位数。CS5表示每个字符5位,CS6表示每个字符6位,CS7表示每个字符7位,CS8表示每个字符8位 |
CSTOPB |
设置停止位的位数,如果设置,则会在每帧后产生两个停止位,如果没有设置,则产生一个停止位。一般都是使用一位停止位。需要两位停止位的设备已过时 了 |
HUPCL |
如果设置,当设备最后打开的文件描述符关闭时,串口上的DTR和RTS线会减弱信号,通知Modem挂断。也就是说,当一个用户通过Modem拔号 登录系统,然后注销,这时Modem会自动挂断 |
PARENB |
允许输出产生奇偶信息以及输入的奇偶校验(启用同位产生与侦测) |
PARODD |
输入和输出是奇校验(使用奇同位而非偶同位) |
CRTSCTS |
使用硬件流控制。在高速(19200bps或更高)传输时,使用软件流控制会使效率降低,这个时候必须使用硬件流控制 |
4. c_lflag 本地模式标志
- 主要用于控制驱动程序与用户的交互,在串口通信中,实际上用不到该成员变量
选项值 |
说明 |
ISIG |
当接受到字符 INTR, QUIT, SUSP, 或 DSUSP 时,产生相应的信号 |
ICANON |
启用标准模式 (canonical mode)。允许使用特殊字符EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, 和WERASE,以及按行的缓冲 |
ECHO |
它可以让你阻止键入字元的回应 |
ECHOE |
如果同时设置了 ICANON,字符 ERASE 擦除前一个输入字符,WERASE 擦除前一个词 |
ECHOK |
如果同时设置了 ICANON,字符 KILL 删除当前行 |
ECHONL |
如果同时设置了 ICANON,回显字符 NL,即使没有设置 ECHO |
NOFLSH |
禁止在产生 SIGINT, SIGQUIT 和 SIGSUSP 信号时刷新输入和输出队列,即关闭queue中的flush |
TOSTOP |
向试图写控制终端的后台进程组发送 SIGTTOU 信号(传送欲写入的信息到后台 处理) |
IEXTEN |
启用实现自定义的输入处理。这个标志必须与 ICANON同时使用,才能解释特殊字符 EOL2,LNEXT,REPRINT 和WERASE,IUCLC 标志才有效 |
5. c_cc[NCCS] 控制字符
- 该数组包含了终端的所有特殊字符,可以修改特殊字符对应的键值(Ctrl+C产生的^C,ASCII码为0x03)
- 仅列出常用的
选项值 |
说明 |
VINTR |
中断字符。发出 SIGINT 信号。当设置了c_lflag的ISIG标志位时,该字母不再作为输入传递 |
VERASE |
删除字符。删除上一个还没有删掉的字符,但不删除上一个EOF 或行首。当设置 ICANON 时可被识别,不再作为输入传递 |
VIM |
设置非标准模式读取的最小字节数 |
VTIM |
设置非标准模式读取时的延时值,单位为十分之一秒 |
6. c_ispeed和c_ospeed 波特率
- 注意以 0 开头的数字在是 C语言 的 8进制 数字形式
#define B0 0000000 /* hang up */
#define B50 0000001
#define B75 0000002
#define B110 0000003
#define B134 0000004
#define B150 0000005
#define B200 0000006
#define B300 0000007
#define B600 0000010
#define B1200 0000011
#define B1800 0000012
#define B2400 0000013
#define B4800 0000014
#define B9600 0000015
#define B19200 0000016
#define B38400 000001
#define B57600 0010001
#define B115200 0010002
#define B230400 0010003
#define B460800 0010004
#define B500000 0010005
#define B576000 0010006
#define B921600 0010007
#define B1000000 0010010
#define B1152000 0010011
#define B1500000 0010012
#define B2000000 0010013
#define B2500000 0010014
#define B3000000 0010015
#define B3500000 0010016
#define B4000000 0010017
分析
- 以上只是介绍了 termios 结构体,在编写代码时,我们使用相关 api 去配置该结构体从而配置串口
- api 接口推荐先看本文推荐链接,不懂再看本文
// api 如下
#include <termios.h>
#include <unistd.h>
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);
int tcsendbreak(int fd, int duration);
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
void cfmakeraw(struct termios *termios_p);
speed_t cfgetispeed(const struct termios *termios_p);
speed_t cfgetospeed(const struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
int cfsetospeed(struct termios *termios_p, speed_t speed);
int cfsetspeed(struct termios *termios_p, speed_t speed);
/* 定义串口结构体 */
struct termios opt;
/* 清空串口接收缓冲区 */
tcflush(fd, TCIOFLUSH);
/* 获取串口参数 */
tcgetattr(fd, &opt);
/* 设置输入、输入波特率 */
cfsetospeed(&opt, B9600);
cfsetispeed(&opt, B9600);
/* 设置数据位数 */
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
/* 校验位 */
opt.c_cflag &= ~PARENB;
opt.c_iflag &= ~INPCK;
/* 设置停止位 */
opt.c_cflag &= ~CSTOPB;
/* 更新配置 */
tcsetattr(fd, TCSANOW, &opt);
1.1.4 串口收发测试
/* 发送测试 */
write(fd, bufW, strlen(bufW));
/* 接收测试 */
res = read(fd, bufR, 512);
if(res > 0){
printf("receive data is %s", bufR);
}
1.1.5 关闭设备文件
/* 关闭文件 */
close(fd);
附件
最终串口测试源码
/** @file main.c
* @brief 串口测试文件-系统调用版
* @details 详细说明
* @author lzm
* @date 2020-11-23 19:18:20
* @version v1.0
* @copyright Copyright By lizhuming, All Rights Reserved
*
**********************************************************
* @LOG 修改日志:
**********************************************************
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <string.h>
#include <sys/ioctl.h>
/* 不同的设备,不同的路径 */
const char def_uart_path[] = "/dev/ttymxc2"; // 默认串口路径(备用)
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(int argc, char *argv[])
{
int fd;
int res;
char *path;
char bufW[512] = "This is sys uart test!\n";
char bufR[512];
/* 终端设备选择 */
if(argc > 1)
path = argv[1];
else
path = (char *)def_uart_path;
/* 打开终端 */
fd = open(path, O_RDWR);
if(fd < 0){
printf("[%s] open err!", path);
return 0;
}
/* 定义串口结构体 */
struct termios opt;
/* 清空串口接收缓冲区 */
tcflush(fd, TCIOFLUSH);
/* 获取串口参数 */
tcgetattr(fd, &opt);
/* 设置输入、输入波特率 */
cfsetospeed(&opt, B9600);
cfsetispeed(&opt, B9600);
/* 设置数据位数 */
opt.c_cflag &= ~CSIZE;
opt.c_cflag |= CS8;
/* 校验位 */
opt.c_cflag &= ~PARENB;
opt.c_iflag &= ~INPCK;
/* 设置停止位 */
opt.c_cflag &= ~CSTOPB;
/* 更新配置 */
tcsetattr(fd, TCSANOW, &opt);
do{
/* 发送测试 */
write(fd, bufW, strlen(bufW));
/* 接收测试 */
res = read(fd, bufR, 512);
if(res > 0){
printf("receive data is %s", bufR);
}
}while(res >= 0);
/* 读取错误 */
printf("read error, res = %d", res);
/* 关闭文件 */
close(fd);
return 0;
}