当前位置: 首页 > news >正文

Linux串口应用编程

一、Linux串口驱动框架

串口通信协议介绍可参照STM32串口通信(寄存器与hal库实现)
在这里插入图片描述

1. 硬件层:串口通信的物理基础

“Hardware” 部分对应串口的物理传输链路:
Terminal:终端设备(如串口调试工具、嵌入式开发板的串口接口);
Physical line:物理传输介质(如 RS-232 线缆);
UART:通用异步收发器(硬件模块,负责串口数据的串并转换、波特率控制等)。

2. 内核驱动层:Linux 串口的核心处理流程

“Software” 的 “Kernel” 部分是串口驱动的核心,包含三个关键模块:

  • UART driver(UART 驱动)
    直接与硬件 UART 交互,负责底层硬件操作(如初始化 UART 寄存器、设置波特率 / 数据位 / 校验位、收发原始字节)。
  • Line discipline(行规程)
    对 UART 驱动收发的原始字节进行上层协议处理(如换行符转换、回显控制、特殊字符解析)。
    是 “原始字节” 到 “终端命令” 的中间层。例如,将串口收到的\r自动转换为\n,或处理Ctrl+C这类中断信号。
  • TTY driver(TTY 驱动)
    向上提供统一的终端设备接口(如 /dev/ttyS0),让用户进程可以像操作普通文件一样读写串口。
    将 UART 硬件和行规程的复杂逻辑封装,对外暴露简单的文件操作接口(read/write/ioctl等)。

3. 用户层:进程与串口的交互

图中 “User process” 是用户空间的应用程序,通过文件系统接口与串口交互:
应用程序通过 open/read/write/close 等系统调用操作 /dev/ttyS0 这类设备文件,底层由 TTY 驱动转发到行规程和 UART 驱动,最终完成数据收发。

4. 数据流向示例(以 “用户进程发送数据” 为例)

用户进程调用 write(“/dev/ttyS0”, “hello”, 5),数据进入 TTY 驱动;
TTY 驱动将数据传递给行规程,行规程根据配置(如是否需要添加\r)处理字节;
处理后的数据传递给 UART 驱动,UART 驱动将字节发送到硬件 UART;
硬件 UART 将字节通过物理线路发送到终端设备。

二、串口API

Linux 系统中,操作设备的统一接口就是:open/ioctl/read/write。

对于 UART,封装了相关API用来设置行规程和相关参数,所以对于 UART,编程的套路就是:

  • open;
  • 通过结构体termios设置行规程和相关参数,比如波特率、数据位、停止位、检验位、RAW 模式、一有数据就返回;
  • read/write;
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};

struct termios 是一个 “统一的配置接口”,它包含了两类参数 ——硬件相关参数(波特率、数据位等)和行规程相关参数(回显、规范模式等)。
设置 termios 时,硬件参数会被转发给串口驱动(UART 驱动)处理,而行规程参数才由行规程模块处理。

相关函数

函数名作用
tcgetattrget terminal attributes,获得终端的属性
tcsetattrset terminal attributes,修改终端参数
tcflush清空终端未完成的输入/输出请求及数据
cfsetispeedsets the input baud rate,设置输入波特率
cfsetospeedsets the output baud rate,设置输出波特率
cfsetspeed同时设置输入、输出波特率

三、串口收发实验

将开发板串口的发送、接收引脚短接,实现自发自收:使用 write 函数发出字符,使用 read 函数读取字符。

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop)
{struct termios newtio, oldtio;// 获取当前串口配置if (tcgetattr(fd, &oldtio) != 0){perror("SetupSerial 1");return -1;}// 初始化新配置bzero(&newtio, sizeof(newtio));// 设置串口基本通信属性// CLOCAL:忽略调制解调器(Modem)的状态线(串口通常直接连接设备,无需 Modem 控制)// 确保串口 “本地拥有”,不受其他设备控制;// CREAD:启用接收功能(允许从串口读取数据)newtio.c_cflag |= CLOCAL | CREAD;// 清除数据位掩码// CSIZE 是数据位的 “掩码标志”(包含 CS5/CS6/CS7/CS8,分别对应 5/6/7/8 位数据)newtio.c_cflag &= ~CSIZE;// 输入模式:关闭规范模式、回显、中断信号响应newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/// 输出模式:关闭输出处理(不修改输出数据)newtio.c_oflag &= ~OPOST; /*Output*/// 设置数据位switch (nBits){case 7:newtio.c_cflag |= CS7;break;case 8:newtio.c_cflag |= CS8;break;}// 设置校验位(奇 / 偶 / 无校验)switch (nEvent){// 奇校验case 'O':newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;// 偶校验case 'E':newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;break;// 无校验case 'N':newtio.c_cflag &= ~PARENB;break;}// 设置波特率switch (nSpeed){case 2400:cfsetispeed(&newtio, B2400);cfsetospeed(&newtio, B2400);break;case 4800:cfsetispeed(&newtio, B4800);cfsetospeed(&newtio, B4800);break;case 9600:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;case 115200:cfsetispeed(&newtio, B115200);cfsetospeed(&newtio, B115200);break;default:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;}// 设置停止位if (nStop == 1)newtio.c_cflag &= ~CSTOPB;else if (nStop == 2)newtio.c_cflag |= CSTOPB;// 设置读取阻塞参数// 至少读到1个字节才返回newtio.c_cc[VMIN] = 1; /* 读数据时的最小字节数: 没读到这些数据我就不返回! */// 不设置超时(若没有数据,read 会一直等,直到有数据到达)newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间:* 比如VMIN设为10表示至少读到10个数据才返回,* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)* 假设VTIME=1,表示:*    10秒内一个数据都没有的话就返回*    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回*/tcflush(fd, TCIFLUSH);// 应用新配置// TCSANOW 表示 “立即生效”if ((tcsetattr(fd, TCSANOW, &newtio)) != 0){perror("com set error");return -1;}// printf("set done!\n");return 0;
}int open_port(char *com)
{int fd;// O_NOCTTY不要让设备成为控制终端fd = open(com, O_RDWR | O_NOCTTY);if (-1 == fd){return -1;}// 恢复串口为阻塞状态if (fcntl(fd, F_SETFL, 0) < 0){printf("fcntl failed!\n");return -1;}return fd;
}/** ./serial_send_recv <dev>*/
int main(int argc, char **argv)
{int fd;int iRet;char c;if (argc != 2){printf("Usage: \n");printf("%s </dev/ttySAC1 or other>\n", argv[0]);return -1;}fd = open_port(argv[1]);if (fd < 0){printf("open %s err!\n", argv[1]);return -1;}iRet = set_opt(fd, 115200, 8, 'N', 1);if (iRet){printf("set port err!\n");return -1;}printf("Enter a char:");while (1){scanf("%c", &c);iRet = write(fd, &c, 1);iRet = read(fd, &c, 1);if (iRet == 1){printf("get: %02x %c\n", c, c);}else{printf("can not get data\n");}}return 0;
}

编译执行

arm-buildroot-linux-gnueabihf-gcc serial_send_recv.c -o serial_send_recv ./serial_send_recv /dev/ttymxc5

对于开发板100ASK_IMX6ULL_mini
/dev/ttymxc5对应串口6,在没有IMX6ULL扩展板情况下,串口6的收发接口:9对应TX,11对应RX。
在这里插入图片描述

四、GPS模块实验

使用串口进行通讯,波特率为 9600bps,1bit 停止位,无校验位,无流控,默认每秒输出一次标准格式数据。

GPS接收到数据的格式如下:

$GPGGA ,<1> ,<2> ,<3> ,<4> ,<5> ,<6> ,<7> ,<8> ,<9> ,M ,<10> ,M ,<11> ,<12>*hh<CR><LF>

各字段含义

在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop)
{struct termios newtio, oldtio;// 获取当前串口配置if (tcgetattr(fd, &oldtio) != 0){perror("SetupSerial 1");return -1;}// 初始化新配置bzero(&newtio, sizeof(newtio));// 设置串口基本通信属性// CLOCAL:忽略调制解调器(Modem)的状态线(串口通常直接连接设备,无需 Modem 控制)// 确保串口 “本地拥有”,不受其他设备控制;// CREAD:启用接收功能(允许从串口读取数据)newtio.c_cflag |= CLOCAL | CREAD;// 清除数据位掩码// CSIZE 是数据位的 “掩码标志”(包含 CS5/CS6/CS7/CS8,分别对应 5/6/7/8 位数据)newtio.c_cflag &= ~CSIZE;// 输入模式:关闭规范模式、回显、中断信号响应newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/// 输出模式:关闭输出处理(不修改输出数据)newtio.c_oflag &= ~OPOST; /*Output*/// 设置数据位switch (nBits){case 7:newtio.c_cflag |= CS7;break;case 8:newtio.c_cflag |= CS8;break;}// 设置校验位(奇 / 偶 / 无校验)switch (nEvent){// 奇校验case 'O':newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;// 偶校验case 'E':newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;break;// 无校验case 'N':newtio.c_cflag &= ~PARENB;break;}// 设置波特率switch (nSpeed){case 2400:cfsetispeed(&newtio, B2400);cfsetospeed(&newtio, B2400);break;case 4800:cfsetispeed(&newtio, B4800);cfsetospeed(&newtio, B4800);break;case 9600:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;case 115200:cfsetispeed(&newtio, B115200);cfsetospeed(&newtio, B115200);break;default:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;}// 设置停止位if (nStop == 1)newtio.c_cflag &= ~CSTOPB;else if (nStop == 2)newtio.c_cflag |= CSTOPB;// 设置读取阻塞参数// 至少读到1个字节才返回newtio.c_cc[VMIN] = 1; /* 读数据时的最小字节数: 没读到这些数据我就不返回! */// 不设置超时(若没有数据,read 会一直等,直到有数据到达)newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间:* 比如VMIN设为10表示至少读到10个数据才返回,* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)* 假设VTIME=1,表示:*    10秒内一个数据都没有的话就返回*    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回*/tcflush(fd, TCIFLUSH);// 应用新配置// TCSANOW 表示 “立即生效”if ((tcsetattr(fd, TCSANOW, &newtio)) != 0){perror("com set error");return -1;}// printf("set done!\n");return 0;
}int open_port(char *com)
{int fd;// O_NOCTTY不要让设备成为控制终端fd = open(com, O_RDWR | O_NOCTTY);if (-1 == fd){return -1;}// 恢复串口为阻塞状态if (fcntl(fd, F_SETFL, 0) < 0){printf("fcntl failed!\n");return -1;}return fd;
}int read_gps_raw_data(int fd, char *buf)
{int i = 0;int iRet;char c;int start = 0;while (1){iRet = read(fd, &c, 1);if (iRet == 1){if (c == '$')start = 1;if (start){buf[i++] = c;}if (c == '\n' || c == '\r'){buf[i] = '\0';return 0;}}else{return -1;}}
}/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF> */
int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
{char tmp[10];if (buf[0] != '$')return -1;else if (strncmp(buf + 3, "GGA", 3) != 0)return -1;else if (strstr(buf, ",,,,,")){printf("Place the GPS to open area\n");return -1;}else{// printf("raw data: %s\n", buf);//  [^,] 表示 “匹配所有 不是逗号(,) 的字符”;sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", tmp, time, lat, ns, lng, ew);return 0;}
}/** ./serial_send_recv <dev>*/
int main(int argc, char **argv)
{int fd;int iRet;char c;char buf[1000];char time[100];char Lat[100];char ns[100];char Lng[100];char ew[100];float fLat, fLng;if (argc != 2){printf("Usage: \n");printf("%s </dev/ttySAC1 or other>\n", argv[0]);return -1;}fd = open_port(argv[1]);if (fd < 0){printf("open %s err!\n", argv[1]);return -1;}iRet = set_opt(fd, 115200, 8, 'N', 1);if (iRet){printf("set port err!\n");return -1;}while (1){/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF>*/iRet = read_gps_raw_data(fd, buf);if (iRet == 0){printf("GPS raw data: %s\n", buf);iRet = parse_gps_raw_data(buf, time, Lat, ns, Lng, ew);}if (iRet == 0){printf("Time : %s\n", time);printf("ns   : %s\n", ns);printf("ew   : %s\n", ew);printf("Lat  : %s\n", Lat);printf("Lng  : %s\n", Lng);/* 纬度格式: ddmm.mmmm */sscanf(Lat + 2, "%f", &fLat);// 将分转换为度fLat = fLat / 60;// Lat[0] - '0'将字符转换为数字fLat += (Lat[0] - '0') * 10 + (Lat[1] - '0');/* 经度格式: dddmm.mmmm */sscanf(Lng + 3, "%f", &fLng);fLng = fLng / 60;fLng += (Lng[0] - '0') * 100 + (Lng[1] - '0') * 10 + (Lng[2] - '0');printf("Lng,Lat: %.06f,%.06f\n", fLng, fLat);}}return 0;
}
http://www.dtcms.com/a/524898.html

相关文章:

  • 微信连接微网站吗奉化网站关键词优化费用
  • 做网站要多大的画布婚庆网站建设需求分析
  • Java Record 详解
  • UVa 1635 Irrelevant Elements
  • 个人网站怎么做推广网站设计怎么收费
  • 做招聘网站需要多少钱网站建设需要工作计划
  • Java—继承
  • 开发网站需要什么硬件网站建设公司调研汇报ppt
  • SMDJ15CA双向TVS瞬态抑制二极管:15V双向电压SMC封装
  • 【基础复习2】过采样,欠采样与SMOTE:含代码示例
  • Spring/Spring Boot工程中Bean间歇性存在的问题
  • FactionTemplate.db2
  • AI 工具网站如何快速起量?一篇讲清新词、外链与选品逻辑
  • 坪洲网站建设惠济区建设局网站
  • UVa 13099 Tobby and the Line Game
  • bash的“进程替换 + 重定向”和“传统管道”
  • 4-ARM-PEG-Olefin(2)/Biotin(2),四臂聚乙二醇-烯烃/生物素多功能支链分子,多功能分子构建
  • 网站的建设与维护工资网站建设的原则有哪些内容
  • 日常推荐电脑小知识——1
  • 外贸网站建设及推广网站优化员seo招聘
  • PLM实施专家宝典:离散制造企业研发数据“数字基因”构建方案
  • 攻克 PCB 高低差字符检测痛点!维视自聚焦系统解锁电子制造智能新范式
  • 网站设计方案怎么写莱州人才网
  • 基于单片机的鱼缸监测与远程管理系统设计
  • ECSide标签<ec:table>表格对不齐问题处理
  • 南平住房和城乡建设部网站微信引流被加软件
  • 场馆预定系统小程序
  • Tauri 为什么仍未取代 Electron?
  • 个人网站做联盟营销网站集约化建设的优势
  • Nginx反向代理WebSocket配置笔记