一、简介
串口全称叫做串行接口,串行接口指的是数据一个一个的按顺序传输,通信线路简单。使用两条线即可.
实现双向通信,一条用于发送,一条用于接收。串口通信距离远,但是速度相对会低,串口是一种很常用的工业接口。
关于串口的基础知识以及通行原理、通行数据格式等之类的问题。串口(UART)在嵌入式 Linux 系统中常作为系统的标准输入、输出设备,系统运行过程产生的打印信息通过串口输出;同理,串口也作为系统的标准输入设备,用户通过串口与 Linux 系统进行交互。
更加详细介绍说明读者可自行查阅相关资料。
二、环境搭建
本次测试uart通信的应用例程是运行在ubuntu pc上的;当然也是可以运行在linux开发板 或相关linux设备上的。
如果在Linux开发板上运行,需要有交叉编译工具。
本次测试实现的是自发自收,在PC上插入一个串口模块(uart转CH340模块,其它模块也可),然后将RX 引脚 与TX引脚 通过杜邦线连接即可。
三、例程代码
本次代码会使用单独的一个c文件用来编写uart代码,用以接收数据并在终端打印;
代码如下:
/***************************************************************
Copyright © OneFu Co., Ltd. 1998-2022. All rights reserved.
文件名 : uart.c
作者 : waterfxw
版本 : V1.0
描述 : uart 示例代码
其他 : 主要是测试 使用cmake
日志 : 初版 V1.0 2023/03/20 waterfxw创建
***************************************************************/#define _GNU_SOURCE //在源文件开头定义_GNU_SOURCE 宏
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include typedef struct uart_hardware_cfg {unsigned int baudrate; /* 波特率 */unsigned char dbit; /* 数据位 */char parity; /* 奇偶校验 */unsigned char sbit; /* 停止位 */
} uart_cfg_t;static struct termios old_cfg; //用于保存终端的配置参数
static int fd; //串口终端对应的文件描述符/**
** 串口初始化操作
** 参数 device 表示串口终端的设备节点
**/
static int uart_init(const char *device) {/* 打开串口终端 */fd = open(device, O_RDWR | O_NOCTTY);if (0 > fd) {fprintf(stderr, "open error: %s: %s\n", device, strerror(errno));return -1;}/* 获取串口当前的配置参数 */if (0 > tcgetattr(fd, &old_cfg)) {fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));close(fd);return -1;}return 0;
}/**
** 串口配置
** 参数 cfg 指向一个 uart_cfg_t 结构体对象
**/
static int uart_cfg(const uart_cfg_t *cfg) {struct termios new_cfg = {0}; //将 new_cfg 对象清零speed_t speed;/* 设置为原始模式 */cfmakeraw(&new_cfg);/* 使能接收 */new_cfg.c_cflag |= CREAD;/* 设置波特率 */switch (cfg->baudrate) {case 1200: speed = B1200;break;case 1800: speed = B1800;break;case 2400: speed = B2400;break;case 4800: speed = B4800;break;case 9600: speed = B9600;break;case 19200: speed = B19200;break;case 38400: speed = B38400;break;case 57600: speed = B57600;break;case 115200: speed = B115200;break;case 230400: speed = B230400;break;case 460800: speed = B460800;break;case 500000: speed = B500000;break;default: //默认配置为 115200speed = B115200;printf("default baud rate: 115200\n");break;}if (0 > cfsetspeed(&new_cfg, speed)) {fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno));return -1;}/* 设置数据位大小 */new_cfg.c_cflag &= ~CSIZE; //将数据位相关的比特位清零switch (cfg->dbit) {case 5:new_cfg.c_cflag |= CS5;break;case 6:new_cfg.c_cflag |= CS6;break;case 7:new_cfg.c_cflag |= CS7;break;case 8:new_cfg.c_cflag |= CS8;break;default: //默认数据位大小为 8new_cfg.c_cflag |= CS8;printf("default data bit size: 8\n");break;}/* 设置奇偶校验 */switch (cfg->parity) {case 'N': //无校验new_cfg.c_cflag &= ~PARENB;new_cfg.c_iflag &= ~INPCK;break;case 'O': //奇校验new_cfg.c_cflag |= (PARODD | PARENB);new_cfg.c_iflag |= INPCK;break;case 'E': //偶校验new_cfg.c_cflag |= PARENB;new_cfg.c_cflag &= ~PARODD; /* 清除 PARODD 标志,配置为偶校验 */new_cfg.c_iflag |= INPCK;break;default: //默认配置为无校验new_cfg.c_cflag &= ~PARENB;new_cfg.c_iflag &= ~INPCK;printf("default parity: N\n");break;}/* 设置停止位 */switch (cfg->sbit) {case 1: //1 个停止位new_cfg.c_cflag &= ~CSTOPB;break;case 2: //2 个停止位new_cfg.c_cflag |= CSTOPB;break;default: //默认配置为 1 个停止位new_cfg.c_cflag &= ~CSTOPB;printf("default stop bit size: 1\n");break;}/* 将 MIN 和 TIME 设置为 0 */new_cfg.c_cc[VTIME] = 0;new_cfg.c_cc[VMIN] = 0;/* 清空缓冲区 */if (0 > tcflush(fd, TCIOFLUSH)) {fprintf(stderr, "tcflush error: %s\n", strerror(errno));return -1;}/* 写入配置、使配置生效 */if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) {fprintf(stderr, "tcsetattr error: %s\n", strerror(errno));return -1;}/* 配置 OK 退出 */return 0; }/***
--dev=/dev/ttyUSB0
--brate=115200
--dbit=8
--parity=N
--sbit=1
--type=read
***/
/**打印帮助信息**/
static void show_help(const char *app) {printf("Usage: %s [选项]\n""\n 必选选项:\n"" --dev=DEVICE 指定串口终端设备名称, 譬如--dev=/dev/ttyUSB0\n"" --type=TYPE 指定操作类型, 读串口还是写串口, 譬如--type=read(read 表示读、write 表示写、readwrite表示读写、其它值无效)\n""\n 可选选项:\n"" --brate=SPEED 指定串口波特率, 譬如--brate=115200\n"" --dbit=SIZE 指定串口数据位个数, 譬如--dbit=8(可取值为: 5/6/7/8)\n"" --parity=PARITY 指定串口奇偶校验方式, 譬如--parity=N(N 表示无校验、O 表示奇校验、E 表示偶校验)\n"" --sbit=SIZE 指定串口停止位个数, 譬如--sbit=1(可取值为: 1/2)\n"" --help 查看本程序使用帮助信息\n\n", app);
}/**
** 信号处理函数,当串口有数据可读时,会跳转到该函数执行
**/
static void io_handler(int sig, siginfo_t *info, void *context) {unsigned char buf[10] = {0};int ret;int n;if(SIGRTMIN != sig)return;/* 判断串口是否有数据可读 */if (POLL_IN == info->si_code) {ret = read(fd, buf, 8); //一次最多读 8 个字节数据printf("[ ");for (n = 0; n < ret; n++)printf("0x%hhx ", buf[n]);printf("]\n");}
}/**
** 异步 I/O 初始化函数
**/
static void async_io_init(void) {struct sigaction sigatn;int flag;/* 使能异步 I/O */flag = fcntl(fd, F_GETFL); //使能串口的异步 I/O 功能flag |= O_ASYNC;fcntl(fd, F_SETFL, flag);/* 设置异步 I/O 的所有者 */fcntl(fd, F_SETOWN, getpid());/* 指定实时信号 SIGRTMIN 作为异步 I/O 通知信号 */fcntl(fd, F_SETSIG, SIGRTMIN);/* 为实时信号 SIGRTMIN 注册信号处理函数 */sigatn.sa_sigaction = io_handler; //当串口有数据可读时,会跳转到 io_handler 函数sigatn.sa_flags = SA_SIGINFO;sigemptyset(&sigatn.sa_mask);sigaction(SIGRTMIN, &sigatn, NULL);
}int main(int argc, char *argv[])
{uart_cfg_t cfg = {0};char *device = NULL;int rw_flag = -1;unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; //通过串口发送出去的数据int n;printf("解析出参数 \n");/* 解析出参数 */for (n = 1; n < argc; n++) {if (!strncmp("--dev=", argv[n], 6))device = &argv[n][6];else if (!strncmp("--brate=", argv[n], 8))cfg.baudrate = atoi(&argv[n][8]);else if (!strncmp("--dbit=", argv[n], 7))cfg.dbit = atoi(&argv[n][7]);else if (!strncmp("--parity=", argv[n], 9))cfg.parity = argv[n][9];else if (!strncmp("--sbit=", argv[n], 7))cfg.sbit = atoi(&argv[n][7]);else if (!strncmp("--type=", argv[n], 7)) {if (!strcmp("read", &argv[n][7]))rw_flag = 0; //读if (!strcmp("write", &argv[n][7]))rw_flag = 1; //写if (!strcmp("readwrite", &argv[n][7]))rw_flag = 2; //读写}else if (!strcmp("--help", argv[n])) {show_help(argv[0]); //打印帮助信息exit(EXIT_SUCCESS);}}if (NULL == device || -1 == rw_flag) {fprintf(stderr, "Error: the device and read|write type must be set!\n");show_help(argv[0]);exit(EXIT_FAILURE);}printf("串口初始化 \n");/* 串口初始化 */if (uart_init(device))exit(EXIT_FAILURE);printf("串口配置 \n");/* 串口配置 */if (uart_cfg(&cfg)) {tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置close(fd);exit(EXIT_FAILURE);}printf("读|写串口 \n");/* 读|写串口 */switch (rw_flag) {case 0: //读串口数据async_io_init(); //我们使用异步 I/O 方式读取串口的数据,调用该函数去初始化串口的异步 I/Ofor ( ; ; )sleep(1); //进入休眠、等待有数据可读,有数据可读之后就会跳转到 io_handler()函数break;case 1: //向串口写入数据for ( ; ; ) { //循环向串口写入数据write(fd, w_buf, 8); //一次向串口写入 8 个字节sleep(1); //间隔 1 秒钟}break;case 2: //向串口写入数据async_io_init(); //我们使用异步 I/O 方式读取串口的数据,调用该函数去初始化串口的异步 I/Ofor ( ; ; ) { //循环向串口写入数据write(fd, w_buf, 8); //一次向串口写入 8 个字节sleep(2); //间隔 1 秒钟}break;}/* 退出 */tcsetattr(fd, TCSANOW, &old_cfg); //恢复到之前的配置close(fd);exit(EXIT_SUCCESS);
}
上述是uart收发源码。
在这次例程中我们使用cmake来编译,如果有不熟悉cmake的可自行查阅相关资料,或作者的另一篇文章:linux开发工具-之-CMake简单例程[初见]
在uart.c文件的同级目录下创建文件“CMakeLists.txt”
编写cmake,在“CMakeLists.txt”中编写一下内容:
project(UART)
add_executable(uart ./uart.c)
四、编译
首先:cmake编译:
cmake ./
运行cmake后,在同级目录下会得到一个Makefile文件
其次再进行Makefile编译,Makefile编译直接运行make即可
make
即可得到执行文件uart
五、运行验证
查看文件类型
file uart
作者想串口模块插入ubuntu PC后得到的设备是 “/dev/ttyUSB0”
可通过“ls -a /dev/tty*” 命令查看,如下:
运行可执行程序uart;
查看uart的帮助信息:
./uart --help
查看帮助如下:
进行读写运行测试命令:
sudo ./uart --dev=/dev/ttyUSB0 --type=readwrite
效果如下图:
至此,uart的读写测试完成。
此例程只是简单实现读写,在实际运用中会比这复杂很多。
cmake相关文章,作者后续会持续更新,尽请关注。
上一篇: 秋季小学开学典礼活动方案
下一篇: 最新赞美祖国的诗歌