11.1Linux串口应用程序开发

UART简介

UART的全称是Universal Asynchronous Receiver and Transmitter,即异步发送和接收。
串口在嵌入式中用途非常的广泛,主要的用途有:

  • 打印调试信息;
  • 外接各种模块:GPS、蓝牙;
    串口因为结构简单、稳定可靠(通过RXD 、TXD、GND三根线即可完成通信),广受欢迎。
    在这里插入图片描述

串口的参数

  • 波特率:常用的有9600、19200、115200、230400、921600等,其实意思就是每秒传输这么多个比特位数(bit)
  • 起始位:先发出一位或两位逻辑”0”的信号,表示传输数据的开始。
  • 数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。
  • 校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。
  • 停止位:它是一个字符数据的结束标志,通常一位或两位逻辑”1”。

串口传输数据

  • 双方约定好波特率(每一位占据的时间)。
  • 双方约定好传输电平,即高电平是多少v,低电平是但是v。
  • 双方约定好起始位位数。
  • 双方约定好奇偶校验。
  • 双方约定好停止位位数。
    在这里插入图片描述

tty 设备节点命名规则

  • /dev/ttyS0 、 /dev/ttySTM0 :串口终端。
  • /dev/tty1 、 /dev/tty2 、 …… :虚拟终端设备。
  • /dev/tty0 :当前正在使用的虚拟终端的别名。
  • /dev/tty :本进程自己的终端。
  • /dev/console :控制台,由内核的命令行参数确定,可以认为是一个拥有更高权限的终端,不管当前正在使用哪个终端,系统信息都会发送到控制台上。

串口编程

头文件

编写串口应用程序时需要先包含如下头文件:

#include <stdio.h>   /* Standard input/output definitions */
#include <string.h>  /* String function definitions */
#include <unistd.h>  /* UNIX standard function definitions */
#include <fcntl.h>   /* File control definitions */
#include <errno.h>   /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */

打开串口

int open_port(void)
{int fd;/* 打开串口* "/dev/ttyf1" 串口文件名* O_RDWR 以读写方式打开* O_NOCTTY 不将此端口作为控制终端* O_NDELAY 表示不关心 DCD 信号线的状态,同时它还将串口设置为非阻塞模式,在没有数据时进行读取返回0,*          后面可以通过fcntl(fd, F_SETFL, 0)将其设置为阻塞式**/fd = open("/dev/ttyf1", O_RDWR | O_NOCTTY | O_NDELAY);if (fd == -1){/* 打开失败 */perror("open_port: Unable to open /dev/ttyf1 - ");}else{/* 设置为阻塞式读取 */fcntl(fd, F_SETFL, 0);}return (fd);
}

读写串口

通过write函数写数据(发送数据),通过read函数都数据(接收数据)

关闭串口

通过close函数关闭串口设备

配置串口

可以通过如下来读取或配置串口参数:

 /* tcgetattr 获取串口配置参数, tcsetattr 设置串口配置参数- fd- optional_actions 配置模式:-                  TCSANOW 改变立即发生 -                  TCSADRAIN 改变在写入 fd 的数据都被传输后生效-                  TCSAFLUSH 改变在写入 fd 的数据都被传输后生效,且已接收但未读取的数据全部丢弃- termios_p 串口配置参数**/int tcgetattr(int fd, struct termios *termios_p)int tcsetattr(int fd, int optional_actions, const struct termios *termios_p)

串口的配置参数保存在struct termios 结构体中,此结构体至少包含以下成员:

	/* 输入控制标志 */tcflag_t c_iflag;/* 输出控制标志 */tcflag_t c_oflag;/* 控制模式标志 */tcflag_t c_cflag;/* 本地模式标志 */tcflag_t c_lflag;/* 行规程 */cc_t c_cc[NCCS];/* 输入波特率 */int c_ispeed;/* 输出波特率 */int c_ospeed;
  • 输入控制标志选项(c_iflag)
    INPCK :启用输入奇偶校验
    IGNPAR :忽略奇偶校验错误
    PARMRK :标记奇偶校验错误
    ISTRIP :去掉奇偶校验位
    IXON :启用输出的 XON/XOFF 流控制
    IXOFF :启用输入的 XON/XOFF 流控制
    IXANY :允许任何字符来重新开始输出
    IGNBRK :忽略输入中的 BREAK 状态
    BRKINT :检测到中断条件时发送 SIGINT
    INLCR :将换行映射到回车
    IGNCR :忽略回车
    ICRNL :将回车映射到换行
    IUCLC :将大写映射到小写
    启用输入奇偶校验并剥离奇偶校验位:
	c_iflag |= (INPCK | ISTRIP)
启用软件流控制:
	c_iflag |= (IXON | IXOFF | IXANY);
禁用软件流控:
	c_iflag &= ~(IXON | IXOFF | IXANY);
  • 输出控制标志选项(c_oflag)
    OPOST :启用输出处理(未设置 = 原始输出)
    OLCUC :将小写映射到大写
    ONLCR :将换行映射到回车+换行
    OCRNL :将回车映射到换行
    NOCR :不在第 0 列输出回车
    ONLRET :将换行映射到回车
    OFILL :发送填充字符作为延时,而不是使用定时来延时
    OFDEL :填充字符为 DEL
    NLDLY :新行延时掩码
    NL0 :新行没有延迟
    NL1 :换行后延迟100ms
    CRDLY :回车延时掩码
    CR0 :回车没有延迟
    CR1 :回车后的延迟取决于当前列位置
    CR2 :回车后延迟 100 毫秒
    CR3 :回车后延迟 150 毫秒
    TABDLY :tab延时掩码
    TAB0 :TAB 没有延迟
    TAB1 :TAB 后的延迟取决于当前列位置
    TAB2 :发送 TAB 后延迟 100 毫秒
    TAB3 :将 TAB 字符扩展为空格
    BSDLY :回退延时掩码
    BS0 :回退没有延迟
    BS1 :回退后延迟 50 毫秒
    VTDLY :竖直跳格延时掩码
    VT0 :竖直跳格无延迟
    VT1 :竖直跳格后延迟 2 秒
    FFDLY :进表延时掩码
    FF0 :进表没有延迟
    FF1 :进表后延迟 2 秒
    选择原始输出:
	/* 禁用OPOST选项时,将忽略 c_oflag中的所有其他选项位 */c_oflag &= ~OPOST;
  • 控制模式标志(c_cflag)
    CSIZE :字符长度掩码
    CS5 :5 个数据位
    CS6 :6 个数据位
    CS7 :7 个数据位
    CS8 :8 个数据位
    CSTOPB :2 个停止位(否则为 1 个)
    CREAD :启用接收器
    PARENB :启用奇偶校验位
    PARODD :使用奇校验而不是偶校验
    HUPCL :关闭时挂断 moden
    CLOCAL :忽略 modem 控制线
    CRTSCTS :启用硬件流控制
    设置字符大小:
	c_cflag &= ~CSIZE; /* 屏蔽字符大小位 */ c_cflag |= CS8; /* 选择 8 个数据位 */
设置奇偶校验:
	/*无奇偶校验 (8N1) */c_cflag &= ~PARENB;c_cflag &= ~CSTOPB;c_cflag &= ~CSIZE;c_cflag |= CS8;/* 偶校验(7E1) */c_cflag |= PARENB;c_cflag &= ~PARODD;c_cflag &= ~CSTOPB;c_cflag &= ~CSIZE;c_cflag |= CS7;/* 奇校验(7O1) */c_cflag |= PARENB;c_cflag |= PARODD;c_cflag &= ~CSTOPB;c_cflag &= ~CSIZE;c_cflag |= CS7;
设置硬件流控制:
c_cflag |= CRTSCTS;
禁用硬件流控制:
	c_cflag &= ~CRTSCTS;
  • 本地模式标志(c_lflag)
    ISIG :启用 SIGINTR、SIGSUSP、SIGDSUSP 和 SIGQUIT 信号
    ICANON :使能规范输入,这使 EOF 、 EOL 、 EOL2 、 ERASE 、 KILL 、 REPRINT 、 STATUS 、 WERASE 字符起作用,输入字符被装配成行
    XCASE :如果同时设置了 ICANON ,则输入被转换为小写(除有前缀 / 的字符以外),输出一个大写字符也在其前加一个 /
    ECHO :回显输入字符
    ECHOE :如果同时设置了 ICANON ,字符 ERASE 擦除前一个输入字符, WERASE 擦除前一个词
    ECHOK :如果同时设置了 ICANON,字符 KILL 删除当前行
    ECHONL :如果同时设置了 ICANON,回显字符 NL,即使没有设置 ECHO
    NOFLSH :禁止在产生 SIGINT, SIGQUIT 和 SIGSUSP 信号时刷新输入和输出队列
    IEXTEN :启用扩展字符处理,这个标志必须与 ICANON 同时使用才能解释特殊字符 EOL2 、 LNEXT 、 REPRINT 、 WERASE, IUCLC 标志才有效
    ECHOCTL :如果同时设置了 ECHO,除了 TAB, NL, START, 和 STOP 之外的 ASCII 控制字符被回显为 ^X (这里 X 是比控制字符加 0x40 的 ASCII 码)
    ECHOPRT :如果同时设置了 ICANON 和 IECHO,字符在删除的同时被打印
    ECHOKE :如果同时设置了 ICANON ,回显 KILL 时将删除一行中的每个字符
    FLUSHO :输出被刷新,这个标志可以通过键入字符 DISCARD 来开关
    PENDIN :在读入下一个字符时,输入队列中所有字符被重新输出
    TOSTOP :向试图写控制终端的后台进程组发送 SIGTTOU 信号
    选择规范输入:
	c_lflag |= (ICANON | ECHO | ECHOE);
选择原始输入:
	c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
  • 控制字符(c_cc )
    VINTR :中断字符。发出 SIGINT 信号,当设置 ISIG 时可被识别
    VQUIT :退出字符。发出 SIGQUIT 信号,当设置 ISIG 时可被识别
    VERAS :删除字符。删除上一个还没有删掉的字符,但不删除上一个 EOF 或行首,当设置 ICANON 时可被识别
    VKILL :终止字符。删除自上一个 EOF 或行首以来的输入,当设置 ICANON 时可被识别
    VEOF :文件尾字符。这个字符使得 tty 缓冲中的内容被送到等待输入的用户程序中,而不必等到 EOL,当设置ICANON 时可被识别
    VEOL : 行结束字符,当设置 ICANON 时可被识别
    VEOL2 :替换的行结束,当设置 ICANON 时可被识别
    VMIN :要读取的最小字符数
    VTIME :等待数据的时间(100毫秒)
    MIN与TIME组合有以下四种:
//有数据立即读取,并返回读取的字节数,无数据立即返回0
MIN = 0 , TIME = 0;
//在 TIME 指定的时间内有数据则返回读取的字节数,无数据返回0
MIN = 0 , TIME > 0;
//在最少读取到 MIN 个字节数才返回
MIN > 0 , TIME = 0;
//读取到的一个字节时启动计时,此后每收到一个字符都会重新计时,在最少读取到 MIN 个字节数或超时返回读取的字节数
MIN > 0 , TIME > 0;
  • 波特率(c_ispeed & c_ospeed)
    波特率的设置和读取通过下列函数实现:
	/* 获取输入波特率 */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);

串口应用程序编写

原理图

在这里插入图片描述

在这里插入图片描述

编写设备树

在设备树stm32mp157d-atk.dtsi中引用uart3和uart4节点,并加入如下内容:

&usart3 {pinctrl-names = "default", "sleep";pinctrl-0 = <&usart3_pins_mx>;pinctrl-1 = <&usart3_sleep_pins_mx>;/delete-property/dmas;/delete-property/dma-names;status = "okay";
};&uart5 {pinctrl-names = "default", "sleep";pinctrl-0 = <&uart5_pins_mx>;pinctrl-1 = <&uart5_sleep_pins_mx>;/delete-property/dmas;/delete-property/dma-names;status = "okay";
};

在设备树stm32mp15-pinctrl.dtsi中引用的&pinctrl节点中加入如下内容:

	uart5_pins_mx: uart5_mx-0 {pins1 {pinmux = <STM32_PINMUX('B', 12, AF14)>; /* UART5_RX */bias-disable;};pins2 {pinmux = <STM32_PINMUX('B', 13, AF14)>; /* UART5_TX */bias-disable;drive-push-pull;slew-rate = <0>;};};uart5_sleep_pins_mx: uart5_sleep_mx-0 {pins {pinmux = <STM32_PINMUX('B', 12, ANALOG)>, /* UART5_RX */<STM32_PINMUX('B', 13, ANALOG)>; /* UART5_TX */};};usart3_pins_mx: usart3_mx-0 {pins1 {pinmux = <STM32_PINMUX('D', 8, AF7)>; /* USART3_TX */bias-disable;drive-push-pull;slew-rate = <0>;};pins2 {pinmux = <STM32_PINMUX('D', 9, AF7)>; /* USART3_RX */bias-disable;};};usart3_sleep_pins_mx: usart3_sleep_mx-0 {pins {pinmux = <STM32_PINMUX('D', 8, ANALOG)>, /* USART3_TX */<STM32_PINMUX('D', 9, ANALOG)>; /* USART3_RX */};};

在设备树stm32mp157d-atk.dts的aliases节点中增加如下内容:

		//usart3的设备文件名是ttySTM1//uart5的设备文件名是ttySTM2serial1 = &usart3;serial2 = &uart5;

用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树,然后用新的.dtb文件启动系统

编写应用程序

编写一个应用程序,从串口读取数据,然后显示读取到的字节数,并将读取的数据返回到PC,程序包含以下几个部分:

  1. 打开串口设备
  2. 配置串口设备
  3. 读写串口设备
    完整的应用程序如下:
#include <stdio.h>   /* Standard input/output definitions */
#include <string.h>  /* String function definitions */
#include <unistd.h>  /* UNIX standard function definitions */
#include <fcntl.h>   /* File control definitions */
#include <errno.h>   /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */int set_port(int fd, int baud_rate, int n_bits, char parity, int n_stop)
{struct termios options;/* 读取配置 */if(tcgetattr(fd, &options) < 0) { perror("SetupSerial 1");return -1;}options.c_iflag = 0x00;options.c_oflag = 0x00;options.c_cflag = 0x00;options.c_lflag = 0x00;/* 禁用软件流控 */options.c_iflag &= ~(IXON | IXOFF | IXANY);if((parity == 'O') || (parity == 'E')) {/* 启用输入奇偶校验并剥离奇偶校验位 */options.c_iflag |= (INPCK | ISTRIP);}/* 选择原始输出 */options.c_oflag &= ~OPOST;/* 设置字符大小 */options.c_cflag &= ~CSIZE;switch(n_bits){case 7:options.c_cflag |= CS7;break;case 8:default:options.c_cflag |= CS8;break;}/* 设置奇偶校验 */switch(parity){case 'O':options.c_cflag |= PARENB;options.c_cflag |= PARODD;break;case 'E':options.c_cflag |= PARENB;options.c_cflag &= ~PARODD;break;case 'N':default:options.c_cflag &= ~PARENB;break;}/* 禁用硬件流控制 */options.c_cflag &= ~CRTSCTS;/* 设置停止位 */switch(n_stop){case 2:options.c_cflag |= CSTOPB;break;case 1:default:options.c_cflag &= ~CSTOPB;break;}/* 启用接收器,并忽略 modem 控制线 */options.c_cflag |= CLOCAL | CREAD; /* 选择原始输入 */options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);/* 设置波特率 */switch(baud_rate){case 2400:cfsetispeed(&options, B2400);cfsetospeed(&options, B2400);break;case 4800:cfsetispeed(&options, B4800);cfsetospeed(&options, B4800);break;case 9600:cfsetispeed(&options, B9600);cfsetospeed(&options, B9600);break;case 115200:default:cfsetispeed(&options, B115200);cfsetospeed(&options, B115200);break;}/* 设置读取超时和每次读取的最小字节数 */options.c_cc[VMIN] = 1;options.c_cc[VTIME] = 1;/* 刷新缓冲区 */tcflush(fd, TCIFLUSH);/* 配置串口 */if((tcsetattr(fd, TCSANOW, &options))!=0){perror("com set error");return -1;}return 0;
}int open_port(const char *com)
{int fd;/* 打开串口 */fd = open(com, O_RDWR | O_NOCTTY | O_NDELAY);if (fd == -1){/* 打开失败 */perror("open_port");}else{/* 设置为阻塞式读取 */fcntl(fd, F_SETFL, 0);}return (fd);
}int main(int argc, char **argv)
{int fd;int result;char buffer[64];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;}/* 设置串口设备 */result = set_port(fd, 115200, 8, 'N', 1);if(result < 0){printf("set port err!\n");return -1;}while(1){/* 读取串口接收到的数据 */result = read(fd, &buffer, sizeof(buffer));if(result > 0){printf(" read %d bytes\r\n", result);/* 通过串口发送数据 */result = write(fd, &buffer, result);}elseperror(NULL);}return 0;
}

上机测试

  1. 根据原理图修改设备树,主要是使能需要用到的串口,然后编译设备树,用新的设备树启动串口
  2. 从这里下载代码,并进行编译,然后将编译得到的可执行程序拷贝到目标板的root目录中
  3. 连接串口线到电脑
  4. 在终端运行命令./uart_teat.out /dev/ttySTM2进行串口测试,其中/dev/ttySTM2是串口设备文件名,如下分别是串口测试软件输出和串口调试软件的截图
    串口测试软件输出串口调试助手

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/299730.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTTP 原理

HTTP 原理 HTTP 是一个无状态的协议。无状态是指客户机&#xff08;Web 浏览器&#xff09;和服务器之间不需要建立持久的连接&#xff0c;这意味着当一个客户端向服务器端发出请求&#xff0c;然后服务器返回响应(response)&#xff0c;连接就被关闭了&#xff0c;在服务器端…

案例分析:西门子智能工厂

西门子全球首家原生数字化工厂&#xff0c;以其独特的数字化技术&#xff0c;在虚拟世界中构建了工厂的数字孪生&#xff0c;从而实现了从需求分析、规划设计、施工实施到生产运营全过程的数字化。这一原生数字化工厂的创新之处在于&#xff0c;它开创性地运用了原生数字孪生理…

说个真事,裁员真的会降本增笑

最近互联网公司放烟花的次数有些高&#xff0c;基本都扎堆 Q3~Q4 出现各类事件/事故。吃瓜都快跟不上了。 作为互联网民工&#xff0c;为什么裁员后会导致降本增笑呢&#xff1f;今天我们一起来聊聊。 各种事故烟花 现阶段各大厂都领上号了&#xff0c;阿里先崩&#xff0c;…

Apache Commons BeanUtils: JavaBean操作的艺术

第1部分&#xff1a;Apache Commons BeanUtils 简介 咱们今天聊聊Apache Commons BeanUtils。这货简直就是处理JavaBean的利器&#xff0c;用起来又方便又快捷。不管是属性拷贝、类型转换&#xff0c;还是动态访问&#xff0c;BeanUtils都能轻松应对。 BeanUtils是啥&#xf…

2023大数据十大关键词

随着我国大数据产业政策日趋完善、产业基础日益巩固、数据要素市场建设不断深化&#xff0c;大数据产业再次迎来巨大发展空间。6月26日至28日&#xff0c;由中国信息通信研究院、中国通信标准化协会主办&#xff0c;中国通信标准化协会大数据技术标准推进委员会&#xff08;CCS…

Web前端VScode/Vue3/git/nvm/node开发环境安装

目录 1 基本配置 2 安装vscode 3 安装vue 4 配置bash 5 安装nvm 6 安装node 7 安装yarn 8 新建项目 9 运行helloworld 1 基本配置 本篇是为了做前端开发的环境而写。使用的操作系统是windows 10 64位 2 安装vscode 现在做vue和node基本就是vscode和webstorm&#x…

canvas基础教学

Canvas <canvas>是一个可以使用脚本&#xff08;通常是JavaScript&#xff09;来绘制图形的HTML元素&#xff0c;例如&#xff0c;它可以用于绘制图表、制作图片构图或者制作简单的动画。 本篇博客从一些就基础开始&#xff0c;描述了如何使用<canvas>元素来绘制…

数据库管理-第127期 LSM Tree(202301225)

数据库管理-第127期 LSM Tree&#xff08;202301225&#xff09; 说起分布式数据库&#xff0c;绕不开的一个话题就是LSM Tree&#xff0c;全称为log-structured merge-tree&#xff0c;回到吕海波老师授权过的那句话“没搞过Oracle的&#xff0c;但又是数据库圈里的人&#x…

《C++避坑神器·二十五》简单搞懂json文件的读写之遍历json文件读写

json.hpp库放在文章末尾 1、遍历json文件读写 &#xff08;1&#xff09;插入新键值对到json之情形1 原来json文件如下所示&#xff1a; {"Connection": {"IpAddress": "192.168.20.1","Rock": 0,"Solt": 1}, "Data…

Linux操作系统——进程(四)进程切换与命令行参数

进程切换 概念引入 下面我们先了解几个概念&#xff1a; 竞争性: 系统进程数目众多&#xff0c;而CPU资源只有少量&#xff0c;甚至1个&#xff0c;所以进程之间是具有竞争属性的。为了高效完成任务&#xff0c;更合理竞争相关资源&#xff0c;便具有了优先级 独立性: 多进程…

Centos7安装Docker和Docker-Compose

环境 操作系统&#xff1a;Centos 7.9 root环境 Docker安装 卸载原先的Docker环境 如果你先前的操作系统安装了Docker环境&#xff0c;请卸载 Docker 相关的软件包&#xff0c;没有则忽略这一步。 yum remove docker \docker-client \docker-client-latest \docker-common \doc…

python:改进型鳟海鞘算法(SSALEO)求解23个基本函数

一、改进型鳟海鞘算法SSALEO 改进型鳟海鞘算法&#xff08;SSALEO&#xff09;由Mohammed Qaraad等人于2022年提出。 参考文献&#xff1a;M. Qaraad, S. Amjad, N. K. Hussein, S. Mirjalili, N. B. Halima and M. A. Elhosseini, "Comparing SSALEO as a Scalable Larg…