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

串口应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

串口应用编程

在这里插入图片描述

串口应用编程介绍

介绍

  • 串口定义:串行接口,数据按顺序传输

  • 串口特点:通信线路简单,距离远,速度较低

  • 应用领域:常用工业接口

  • Linux系统中的作用

    • 作为标准输入输出设备

    • 系统打印信息输出

    • 用户与系统交互

  • 串口与终端:在Linux系统中,串口被视为一种终端(Terminal)设备

终端 Terminal

  • 终端定义

    • 处理主机输入输出的设备,实现人机交互
  • 终端分类

    • 本地终端(如PC机显示器键盘组合)

    • 串口连接的远程终端

    • 基于网络的远程终端

    • 物理终端vs伪终端

      • 物理终端直接关联物理设备

        • 本地终端(如PC机显示器键盘组合)

        • 串口连接的远程终端

      • 伪终端不直接关联物理设备

        • 基于网络的远程终端
  • Linux中终端对应的设备节点

    • Linux终端原则:一切皆文件,每个终端在/dev目录下有对应设备节点

    • 本地终端设备节点

      • 格式:/dev/ttyX(X为数字编号)

      • 范围:/dev/tty1 ~ /dev/tty63,共63个

        • 本地终端设备节点
      • 特点:Linux内核初始化时生成

    • 伪终端设备节点

      • 格式:/dev/pts/X(X为数字编号)

        • 伪终端设备节点
      • 特点:远程登录时生成

    • 串口终端设备节点

      • 示例:/dev/ttymxcX(基于特定开发板)

        • 对于 ALPHA/Mini I.MX6U 开发板来说,有两个串口

          • 设备节点编号说明

            • 编号与硬件支持的串口数量和注册情况有关

            • 出厂系统只注册了 2 个串口外设,分别是 UART1 和 UART3,所以对应这个数字就是 0 和 2

      • 注意:命名与硬件平台相关,但通常以"tty"开头

    • 查看系统连接终端

      • 使用who命令可查看当前连接的终端

      • 可显示本地终端、串口终端和远程登录的伪终端

串口应用编程

  • 串口在Linux系统中的定位

    • 属于终端设备

    • 在特定开发板上对应/dev/ttymxc0和/dev/ttymxc2

  • 串口应用编程基本操作

    • 使用ioctl()配置串口

    • 使用read()读取数据

    • 使用write()写入数据

    • 就是这么简单!但是我们不这么做,Linux 为上层用户做了一层封装

  • Linux提供的串口编程封装

    • 封装了底层ioctl()操作

    • 提供了一套标准API,称为termios API

  • termios API特点

    • 是C库函数

    • 可通过man手册查看帮助信息

    • 适用于所有终端设备,不仅限于串口

  • termios API的应用范围

    • 串口设备

    • 本地连接的鼠标、键盘

    • 远程登录的伪终端

  • 使用termios API的准备

    • 在应用程序中包含termios.h头文件

struct termios 结构体

  • 终端应用编程的两个主要方面

    • 配置

    • 读写

  • struct termios结构体的重要性

    • 描述终端的配置信息

    • 控制和影响终端的行为和特性

    • 是终端设备应用编程的核心

  • struct termios结构体的组成

    • 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 */
      };

      • 输入模式:c_iflag

        • 输入模式的作用

          • 控制输入数据在传递给应用程序前的处理方式

          • 处理来自串口或键盘的字符数据

        • 输入模式的配置方法

          • 通过设置struct termios结构体中的c_iflag成员

          • 使用预定义的宏来设置标志

            • 获取详细信息的方法

              • 可以通过man手册查询各个宏的详细描述

              • 使用命令"man 3 termios"查看相关信息

            • 配置的灵活性

              • 通过组合不同的宏,可以实现对输入模式的精细控制
        • 配置方式的通用性

          • c_oflag、c_cflag和c_lflag成员也采用类似的宏配置方式
      • 输出模式:c_oflag

        • 输出模式的定义

          • 控制输出字符的处理方式
        • 输出模式的作用范围

          • 处理应用程序发送的字符数据

          • 在数据传递到串口或屏幕之前进行处理

        • 配置方法

          • 通过设置struct termios结构体中的c_oflag成员

          • 使用预定义的宏来设置标志

      • 控制模式:c_cflag

        • 控制终端设备的硬件特性

        • 对串口设置尤为重要

        • 可配置的硬件特性

          • 波特率

          • 数据位

          • 校验位

          • 停止位等

        • 配置方法

          • 通过设置struct termios结构体中的c_cflag成员

          • 使用预定义的标志来配置

          • 波特率设置

            • Linux系统使用CBAUD位掩码来指定波特率

            • 其他系统可能使用c_ispeed和c_ospeed成员变量

          • 波特率操作函数

            • cfgetispeed()用于获取波特率

            • cfsetispeed()用于设置波特率

          • 不同系统可能有不同的波特率设置方法

      • 本地模式:c_lflag

        • 用于控制终端的本地数据处理和工作模式

        • 通过设置 struct termios 结构体中 c_lflag 成员的标志对本地模式进行配置

      • 特殊控制字符:c_cc

        • 特殊控制字符的定义

          • 特定的字符组合,如Ctrl+C、Ctrl+Z等

          • 触发终端的特殊处理

        • 实现机制

          • 通过struct termios结构体中的c_cc数组实现

          • 将特殊字符映射到对应的支持函数

        • 主要特殊控制字符及其功能

          • a) VEOF (Ctrl+D): 文件结尾符

            • 使终端驱动程序将输入行中的全部字符传递给
              正在读取输入的应用程序

            • 如果文件结尾符是该行的第一个字符,则用户
              程序中的 read 返回 0,表示文件结束

          • b) VEOL (Carriage return-CR): 附加行结尾符

            • 作用类似于行结束符
          • c) VEOL2 (LF): 第二行结尾符

          • d) VERASE (Backspace-BS): 删除操作符

            • 使终端驱动程序删除输入行中的最后一个字符
          • e) VINTR (Ctrl+C): 中断控制字符

            • 使终端驱动程序向与终端相连的进程发送
              SIGINT 信号
          • f) VKILL (Ctrl+U): 删除行符

          • g) VMIN: 非规范模式下最少读取字符数

          • h) VQUIT (Ctrl+Z): 退出操作符

            • 使终端驱动程序向与终端相连的进程发送
              SIGQUIT 信号
          • i) VSTART (Ctrl+Q): 开始字符

            • 重新启动被 STOP 暂停的输出
          • j) VSTOP (Ctrl+S): 停止字符

            • 字符作用“截流”,即阻止向终端的进一步输出

            • 用于支持 XON/XOFF 流控

          • k) VSUSP (Ctrl+Z): 挂起字符

            • 使终端驱动程序向与终端相连的进程发SIGSUSP 信号,用于挂起当前应用程序
          • l) VTIME: 非规范模式下字符间超时时间

            • 定读取的每个字符之间的超时时间(以
              分秒为单位)TIME
        • 特殊说明

          • VMIN和VTIME仅用于非规范模式

          • 用于控制非规范模式下read()调用的行为

        • 应用场景

          • 这些特殊字符用于控制终端行为和进程通信

          • 支持各种终端操作和信号发送

    • 小结

      • 主要内容

        • struct termios结构体的四个主要成员: c_iflag(输入模式) c_oflag(输出模式) c_cflag(控制模式) c_lflag(本地控制)

        • 这些参数控制和影响终端的行为特性

      • 成员变量赋值方法

        • 建议不要直接初始化

          • struct termios ter;

ter.c_iflag = IGNBRK | BRKINT | PARMRK;

		- 推荐使用"按位与"、"按位或"等操作添加或清除标志- ter.c_iflag |= (IGNBRK | BRKINT | PARMRK | ISTRIP);- 标志的适用性- 并非所有标志对所有终端设备都有效- 不同终端设备的硬件特性存在差异- 串口可以配置波特率、数据位、停止位等这些硬件参数,但是其它终端是不一定支持这些配置的,譬如本地终端键盘、显示器,这些设备它是没有这些硬件概念的- API的通用性和局限性- 所有终端设备使用同一套API进行编程- 由于硬件差异,某些配置参数可能对特定设备无效- 根据具体设备和需求选择合适的配置

终端的三种工作模式

  • 三种工作模式

    • a) 规范模式(canonical mode)

    • b) 非规范模式(non-canonical mode)

    • c) 原始模式(raw mode)

  • 规范模式特点

    • 基于行处理输入

    • 需要输入行结束符(回车符、EOF 等)才能读取

    • 支持行编辑

    • 一次read()最多读取一行

      • 如果在 read()函数中被请求读取的数据字节数小于当前行可读取的字节数,则 read()函数只会读
        取被请求的字节数,剩下的字节下次再被读取
  • 非规范模式特点

    • 输入即时有效

    • 不需要行结束符

    • 不支持行编辑

    • 通过MIN和TIME参数控制read()行为

      • MIN和TIME参数组合

        • MIN=0, TIME=0:立即返回

          • 若有可读数据,则读取数据并返回被读取的字节数;否则读取不到任何数据并返回 0
        • MIN>0, TIME=0:阻塞直到满足MIN个字符

          • 到有 MIN 个字符可以读取时才返回,返回值是读取的字符数量。到达文件尾时返回 0
        • MIN=0, TIME>0:有数据或超时立即返回

          • 只要有数据可读或者经过 TIME 个十分之一秒的时间,read()
            函数则立即返回,返回值为被读取的字节数

          • 如果超时并且未读到数据,则 read()函数返回 0

        • MIN>0, TIME>0:满足MIN个字符或字符间超时才返回

          • 在输入第一个字符后系统才会启动定时器,所
            以,在这种情况下,read()函数至少读取一个字节后才返回
  • 原始模式

    • 特殊的非规范模式

    • 以字节为单位处理输入

    • 禁用终端特殊处理

    • 通过cfmakeraw()函数设置

      • cfmakeraw()函数内部其实就是对 struct termios 结构体进行了如下配置:
        ermios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
        | INLCR | IGNCR | ICRNL | IXON);
        termios_p->c_oflag &= ~OPOST;
        termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
        termios_p->c_cflag &= ~(CSIZE | PARENB);
        termios_p->c_cflag |= CS8;
    • 原始模式应用场景

      • 串口作为数据传输接口时

      • 与其他设备或传感器通信

      • 需要直接处理原始数据,不进行ASCII转换

  • 终端模式设置

    • 通过struct termios结构体的c_lflag成员设置ICANON标志

    • 默认为规范模式

打开串口设备

  • 串口应用程序编写的第一步:打开串口设备,使用 open()函数打开串口的设备节点文件,得到文件描述符

    • int fd;

fd = open(“/dev/ttymxc2”, O_RDWR |O_NOCTTY);
if (0 > fd) {
perror(“open error”);
return -1;
}

  • 打开串口设备的方法

    • 使用open()函数

    • 打开串口的设备节点文件

    • 获取文件描述符

获取终端当前的配置参数:tcgetattr()函数

  • 获取终端当前配置参数的目的

    • 便于之后恢复终端到原始状态

    • 为安全和调试考虑

  • 使用tcgetattr()函数获取配置参数

    • #include <termios.h>
      #include <unistd.h>

int tcgetattr(int fd, struct termios *termios_p);

- 第一个参数:串口终端设备的文件描述符fd- 第二个参数:struct termios结构体指针,用于存储配置参数- 函数返回值:成功返回0

。失败返回-1,并设置errno

  • 函数调用前的准备

    • 定义struct termios结构体变量

    • 将结构体变量的指针作为第二个参数传入

  • 使用示例

    • struct termios old_cfg;

if (0 > tcgetattr(fd, &old_cfg)) {
/* 出错处理 */
do_something();
}

对串口终端进行配置

  • 1)配置串口终端为原始模式

    • 调用<termios.h>头文件中申明的 cfmakeraw()函数,这个函数没有返回值

    • struct termios new_cfg;

//内存清零 初始化结构体
memset(&new_cfg, 0x0, sizeof(struct termios));

//配置为原始模式
cfmakeraw(&new_cfg);

  • 2)接收使能

    • 在c_cflag成员中添加CREAD标志

    • new_cfg.c_cflag |= CREAD; //接收使能

  • 3)设置串口的波特率

    • 用户不能直接通过位掩码来操作

    • 使用cfsetispeed()和cfsetospeed()函数

      • cfsetispeed(&new_cfg, B115200);
        cfsetospeed(&new_cfg, B115200);

      • B115200 是一个宏

    • 一般来说,用户需将终端的输入和输出波特率设置成一样

    • 或使用cfsetspeed()函数同时设置输入输出波特率

      • cfsetspeed(&new_cfg, B115200);
    • 这几个函数在成功时返回 0,失败时返回-1。

  • 4)设置数据位大小

    • 清除CSIZE位掩码

    • 设置CS8为8位数据位

    • new_cfg.c_cflag &= ~CSIZE;
      new_cfg.c_cflag |= CS8; //设置为 8 位数据位

  • 5)设置奇偶校验位

    • 奇校验:设置PARODD和PARENB标志

    • 偶校验:设置PARENB标志,清除PARODD标志

    • 无校验:清除PARENB标志

    • //奇校验使能
      new_cfg.c_cflag |= (PARODD | PARENB);
      new_cfg.c_iflag |= INPCK;

//偶校验使能
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD; /* 清除 PARODD 标志,配置为偶校验 */
new_cfg.c_iflag |= INPCK;

//无校验
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;

  • 6)设置停止位

    • 一个停止位:清除CSTOPB标志

    • 两个停止位:添加CSTOPB标志

    • / 将停止位设置为一个比特
      new_cfg.c_cflag &= ~CSTOPB;

// 将停止位设置为 2 个比特
new_cfg.c_cflag |= CSTOPB;

  • 7)设置 MIN 和 TIME 的值

    • 影响非规范模式下read()调用的行为

    • 设置为0使read()立即返回,实现非阻塞读取

    • new_cfg.c_cc[VTIME] = 0;
      new_cfg.c_cc[VMIN] = 0;

缓冲区的处理

  • 缓冲区处理的必要性

    • 使用串口前需处理缓冲区中可能存在的数据
  • 相关函数

    • #include <termios.h>
      #include <unistd.h>

int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);

	- tcdrain() 函数- 调用后阻塞应用程序,直到输出缓冲区数据全部发送完毕- tcflow() 函数- 用于暂停或重启数据传输/接收- 参数 action 决定具体操作(TCOOFF, TCOON, TCIOFF, TCION)- TCOOFF:暂停数据输出(输出传输)- TCOON:重新启动暂停的输出- TCIOFF:发送  STOP 字符,停止终端设备向系统发送数据- TCION:发送一个 START 字符,启动终端设备向系统发送数据- tcflush() 函数- 清空输入/输出缓冲区- 参数 queue_selector 决定清空哪些缓冲区(TCIFLUSH, TCOFLUSH, TCIOFLUSH)- TCIFLUSH:对接收到而未被读取的数据进行清空处理- TCOFLUSH:对尚未传输成功的输出数据进行清空处理- TCIOFLUSH:包括前两种功能,即对尚未处理的输入/输出数据进行清空处理- 以上这三个函数,调用成功时返回  0;失败将返回-1、并且会设置 errno 
  • 常用处理方式

    • 使用 tcdrain() 阻塞等待数据发送完毕

      • tcdrain(fd);
    • 使用 tcflush() 清空缓冲区

      • tcflush(fd, TCIOFLUSH);

写入配置、使配置生效:tcsetattr()函数

  • 完成struct termios结构体配置后,需将参数写入终端设备使其生效

  • tcsetattr()函数

    • 用于将配置参数写入硬件设备

    • #include <termios.h>
      #include <unistd.h>

int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);

	- fd:文件描述符- optional_actions:指定配置生效时机- TCSANOW:配置立即生效- TCSADRAIN:配置在所有写入  fd 的输出都传输完毕之后生效- TCSAFLUSH:所有已接收但未读取的输入都将在配置生效之前被丢弃- termios_p:指向struct termios对象的指针- 调用成功时返回  0;失败将返回-1,并设置 errno
  • 调用 tcsetattr()将配置参数写入设备,使其立即生效

    • tcsetattr(fd, TCSANOW, &new_cfg);

写数据:read()、write()

  • 所有准备工作完成之后,接着便可以读写数据了,直接调用 read()、write()函数即可

串口应用编程实战

#define _GNU_SOURCE     //在源文件开头定义_GNU_SOURCE宏
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <termios.h>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/ttymxc2
--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/ttymxc2\n""  --type=TYPE      指定操作类型, 读串口还是写串口, 譬如--type=read(read表示读、write表示写、其它值无效)\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};   // 串口配置结构体,初始化为0char *device = NULL;    // 串口设备名称int rw_flag = -1;    // 读写标志unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44,0x55, 0x66, 0x77, 0x88};    //通过串口发送出去的数据int 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;        //读else if (!strcmp("write", &argv[n][7]))rw_flag = 1;        //写}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);}/* 串口初始化 */if (uart_init(device))exit(EXIT_FAILURE);/* 串口配置 */if (uart_cfg(&cfg)) {tcsetattr(fd, TCSANOW, &old_cfg);   //恢复到之前的配置close(fd);exit(EXIT_FAILURE);}/* 读|写串口 */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;}/* 退出 */tcsetattr(fd, TCSANOW, &old_cfg);   //恢复到之前的配置close(fd);exit(EXIT_SUCCESS);
}

用户参数解析

  • 串口设备节点、波特率、数据位、停止位、奇偶校验和读写模式

  • 如果用户输入了–help参数,程序会调用show_help()函数打印使用帮助信息,并退出程序

串口初始化

  • 调用uart_init()函数

    • 打开指定的串口终端设备

    • 获取当前的串口配置参数(保存到old_cfg中)

串口配置

  • 调用uart_cfg()函数

    • 设置为原始模式

    • 使能接收功能

    • 根据输入的波特率、数据位、停止位和奇偶校验进行相应的设置

    • 清空串口缓冲区并将新配置应用到串口设备

操作模式选择

  • 根据输入的–type参数决定进行读取还是写入操作

    • 如果是read,程序将进入异步读取模式

    • 如果是write,程序将循环写入数据到串口

异步I/O初始化(仅用于读取)

  • 调用async_io_init()为异步I/O设置信号处理

    • 使能异步I/O

    • 设置异步I/O的所有者

    • 定实时信号SIGRTMIN作为异步I/O通知信号

    • 注册io_handler()函数,当有数据可读时会被调用

      • 程序会读取串口中的数据并打印。一次最多读取8个字节,超出的数据将在后续的读取中处理

写入操作(仅用于写入模式)

  • 在写入模式下,程序循环向串口写入固定的数据(w_buf数组内容),并每隔1秒进行一次写入

退出

  • 在程序结束前,恢复串口到原来的配置(old_cfg),关闭串口设备,并正常退出

相关文章:

串口应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

串口应用编程 串口应用编程介绍 介绍 串口定义:串行接口,数据按顺序传输 串口特点:通信线路简单,距离远,速度较低 应用领域:常用工业接口 Linux系统中的作用 作为标准输入输出设备 系统打印信息输出 用户与系统交互 串口与终端:在Linux系统中,串口被视为一种终端&#…...

Canvas实现截图

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>canvas实现截图功能</title><style>.ca…...

Python高性能计算:进程、线程、协程、并发、并行、同步、异步

这里写目录标题 进程、线程、协程并发、并行同步、异步I/O密集型任务、CPU密集型任务 进程、线程、协程 进程、线程和协程是计算机程序执行的三种不同方式&#xff0c;它们在资源管理、执行模型和调度机制上有显著的区别。以下是对它们的详细解释和比较&#xff1a; 进程&…...

kafka基本操作

Kafka详解 一、Kafka概述 Kafka是一个开源的分布式事件流平台&#xff0c;它主要用于高性能数据管道、流分析、数据集成和关键任务应用。Kafka最初被设计为一个分布式的基于发布/订阅模式的消息队列&#xff0c;但随着时间的推移&#xff0c;它已发展成为一个功能强大的流处理…...

JavaFX布局-Accordion

JavaFX布局-Accordion 一个可扩展的面板&#xff0c;包括标题、内容与TitledPane配合一起使用 public static Accordion demo1() {// 创建AccordionAccordion accordion new Accordion();// 内边距accordion.setPadding(new Insets(10, 10, 10, 10));for (int i 1; i < 1…...

【轨物方案】评估光伏组件发电性能一致性方案

光伏电站建设后运行周期长达二十多年&#xff0c;对于电站运营者来说&#xff0c;基础设施的稳定、安全、高效的运行是至关重要的。然而从近些年光伏的发展过程中看到&#xff0c;在电站规划到后期运维整个过程中可能存在着诸多问题&#xff0c;如设备选型不当、施工建设质量差…...

安全基础学习-keil调试汇编代码

初始目的是为了通过汇编编写CRC功能。 但是基础为0,所以目前从搭建工程开始记录。 大佬绕路。 (一)创建项目 1. 新建项目 打开 Keil uVision。选择 Project -> New uVision Project 创建一个新项目。选择你的目标设备(如 ARM Cortex-M 系列处理器),我这里一开始选择…...

Unity复制对象时让私有变量也被复制的简单方法

Unity复制对象时&#xff0c;如果一个变量为公共变量&#xff08;public&#xff09;&#xff0c;那么这个变量的值会被复制到新的对象中去&#xff0c;但是如果一个变量是私有变量&#xff08;private&#xff09;&#xff0c;默认是不会被复制的&#xff0c;如果希望被复制&a…...

Flink 实时数仓(二)【DIM 层搭建】

1、DIM 层搭建 1.1、设计要点 DIM层设计要点&#xff1a; DIM层存的是维度表&#xff08;环境信息&#xff0c;比如人、场、货等&#xff09;DIM层的数据存储在 HBase 表中DIM层表名的命名规范为dim_表名 DIM 层表是用于维度关联的&#xff0c;要通过主键&#xff08;维度外…...

知识图谱开启了一个可以理解的人工智能未来

概述 本文是对利用知识图谱&#xff08;KG&#xff09;的综合人工智能&#xff08;CAI&#xff09;的全面调查研究&#xff0c;其中 CAI 被定义为可解释人工智能&#xff08;XAI&#xff09;和可解释机器学习&#xff08;IML&#xff09;的超集。 首先&#xff0c;本文澄清了…...

借助Aspose.html控件, 将SVG 转PNG 的 C# 图像处理库

Aspose.HTML for .NET 不仅提供超文本标记语言 ( HTML ) 文件处理&#xff0c;还提供流行图像文件格式之间的转换。您可以利用丰富的渲染和转换功能将SVG文件渲染为PNG、JPG或其他广泛使用的文件格式。但是&#xff0c;我们将使用此C# 图像处理库以编程方式在 C# 中将 SVG 转换…...

vs-2015安装教程

双击安装包 2-如图先选自定义&#xff0c;然后选安装路径&#xff08;英文路径&#xff09; 3-安装选项一个就够了&#xff0c;如图 4-点击下一步&#xff0c;之后如下图 5-点击安装 启动&#xff0c;如图则恭喜你成功安装...

Stable Diffusion绘画 | 文生图设置详解—随机种子数(Seed)

随机种子数&#xff08;Seed&#xff09; Midjourney 也有同样的概念&#xff0c;通过 --seed 种子数值 来使用。 每次操作「生成」所得到的图片&#xff0c;都会随机分配一个 seed值&#xff0c;数值不同&#xff0c;生成的画面就会不同。 默认值为 -1&#xff1a;每次随机分…...

56、php实现N的阶乘末尾有多个0

题目&#xff1a; php实现N的阶乘末尾有多个0 描述&#xff1a; 阶乘 N! 123*…N; 比如 5! 12345 120 末端有1个0 解题思路&#xff1a; N! K*(10^M) N的阶乘为K和10的M次方的乘积&#xff0c;那么N!末尾就有M个0。如果将N的阶乘分解后&#xff0c;那么N的阶乘可以分解为&…...

混合域注意力机制(空间+通道)

在计算机视觉任务中&#xff0c;空间域注意力通常关注图像中不同位置的重要性&#xff0c;例如突出图像中的关键对象或区域。而通道域注意力则侧重于不同通道&#xff08;特征图&#xff09;的重要性&#xff0c;决定哪些特征对于任务更具判别力。混合域注意力机制结合了空间域…...

springboot长春旅游安全地图平台-计算机毕业设计源码90075

摘 要 本文详细阐述了基于微信小程序前端和Spring Boot后端框架的长春旅游安全地图平台的设计思路与实现过程。该平台旨在为长春游客提供安全、便捷的旅游服务&#xff0c;同时为旅游管理部门提供高效的信息管理和应急响应机制。 在平台设计上&#xff0c;我们充分考虑了用户体…...

apex正则表达式匹配富文本字段内容,如何只匹配文本而忽略富文本符号

在Apex中处理富文本字段时&#xff0c;如果你只想匹配其中的纯文本而忽略富文本符号&#xff0c;可以使用正则表达式来去除HTML标签&#xff0c;然后再进行文本匹配。以下是一个示例代码&#xff0c;展示了如何实现这一点&#xff1a; public class RichTextHandler {// Funct…...

空气净化器对去除宠物毛有效吗?小型猫毛空气净化器使用感受

作为一个养猫多年的猫奴&#xff0c;家里有两只可爱的小猫咪&#xff1a;小白和小花。虽然相处起来很开心&#xff0c;但也给生活带来了一些小麻烦。谁懂啊&#xff0c;我真的受够了&#xff0c;每天都在粘毛。猫窝的猫毛一周不清理就要堆成山&#xff0c;空气中也全是浮毛&…...

vue的nextTick是下一次事件循环吗

如题&#xff0c;nextTick的回调是在下一次事件循环被执行的吗&#xff1f; 是不是下一次事件循环取决于nextTick的实现&#xff0c;如果是用的微任务&#xff0c;那么就是本次事件循环&#xff1b;否则如果用的是宏任务&#xff0c;那么就是下一次事件循环。 我们看下Vue3中…...

5.4.软件工程-系统设计

考试占比不高 概述 系统设计的主要目的就是为系统制定蓝图&#xff0c;在各种技术和实施方法中权衡利弊&#xff0c;精心设计&#xff0c;合理地使用各种资源&#xff0c;最终勾画出新系统的详细设计方案。系统设计的主要内容包括新系统总体结构设计、代码设计、输出设计、输…...

Apache Kylin与BI工具集成:数据可视化实战

Apache Kylin与BI工具集成&#xff1a;数据可视化实战 1. 引言 Apache Kylin是一个开源的分布式分析引擎&#xff0c;专注于大数据的OLAP&#xff08;在线分析处理&#xff09;。它可以快速地对大量数据进行多维分析&#xff0c;并支持与多种BI&#xff08;商业智能&#xff…...

通过idea图形化界面就能push到github流程

建好自己要提交的项目 建好github想提交的地址 git initgit remote add origin https://github.com/usernamezhaozhao/github2test/tree/maingit branch maingit checkout main创建一个文件&#xff0c;我起了一个a.txt git pull origin main 好了&#xff0c;可以idea打开了 …...

C语言初阶(10)

1.野指针 野指针就是指向未知空间的指针&#xff0c;有以下几种情况 &#xff08;1)指针未初始化 int main() {int a0;int*b;return 0; } 上面指针就是没有初始化&#xff0c;形成一种指向一个随机空间的地址的指针&#xff0c;我们可以修改成 int main() {int a0;int*bNU…...

Javaweb用过滤器写防跳墙功能和退出登录

一、什么是防跳墙功能&#xff1a; 防跳墙功能通常指的是防止用户在未完成认证的情况下直接访问受保护资源的功能。在 Web 开发中&#xff0c;这种功能通常被称为“登录拦截”或“身份验证拦截”。 在 Spring MVC 中&#xff0c;实现这种功能通常使用的是“拦截器”&#xff08…...

小试牛刀-Telebot区块链游戏机器人(TS升级)

目录 1.编写目的 2.为什么使用TypeScript实现? 3.实现功能 3.1 AI图片生成 3.2 签到 3.3 邀请 3.4 WalletConnect连接 4.功能实现详解 4.1 AI图片生成 4.2 签到 4.3 邀请 4.4 WalletConnect连接 5.功能截图 ​6.问题整理 Welcome to Code Blocks blog 本篇文章主…...

MySQL:Prepared Statement 预处理语句

预处理语句&#xff08;Prepared Statement&#xff09; 是一种在数据库管理系统中使用的编程概念&#xff0c;用于执行对数据库进行操作的 SQL 语句。 使用预处理语句的具体方式和语法依赖于所用的编程语言和数据库管理系统。常见的编程语言如 Java、PHP、Python 和 C# 都提供…...

Java:Thread类以及线程状态

文章目录 Thread类等待一个线程 - join()获取当前线程的引用sleep 线程状态 Thread类 等待一个线程 - join() 操作系统,针对多个线程的执行,是一个"随机调度,抢占式执行“的过程. 线程等待就是在确定两个线程的"结束顺序”. 我们无法确定两个线程调度执行的顺序,但…...

如何通过前端表格控件实现自动化报表?

背景 最近伙伴客户的项目经理遇见一个问题&#xff0c;他们在给甲方做自动化报表工具&#xff0c;项目已经基本做好了&#xff0c;但拿给最终甲方&#xff0c;业务人员不太买账&#xff0c;项目经理为此也是天天抓狂&#xff0c;没有想到合适的应对方案。 现阶段主要面临的问…...

Upload-labs靶场Pass01-Pass21全解

文章目录 Pass-01 前端JSJS绕过上传或者用burp抓包的方式 Pass-02 MIME检测Pass-03 特殊文件后缀黑白名单绕过特殊文件名绕过 Pass-04 .htacess上传Pass-05 user.ini文件上传Pass-06 大小写绕过Pass-07 空格绕过Pass-08 .绕过Pass-09 ::$DATA绕过Pass-10 .空格.绕过Pass-11 双写…...

使用openpyxl库对Excel数据有效性验证

哈喽,大家好,我是木头左! 本文将重点介绍如何使用openpyxl库进行Excel数据验证。 什么是Excel数据验证 Excel数据验证是一种功能,可以限制单元格中输入的数据类型和范围。例如,可以设置一个单元格只能输入日期,或者只能输入大于0的数字。这样,可以确保数据的准确性和一…...

武汉企业制作网站/网站制作报价

可以使用Python的os模块和re模块来实现&#xff1a; import os import re 获取目录下的所有文件 for file in os.listdir(directory_path): with open(file) as f: # 读取文件中的每一行并使用正则表达式查找包含字符串abcd的行 for line in f.readlines(): if re.findall(abcd…...

wordpress七牛云使用/郑州谷歌优化外包

准备环境&#xff1a;eclipse 3.6maven 3.0.4struts 2.1.8JFreeChart 1.0.9JFreeChart 是一组功能强大、灵活易用的Java绘图API&#xff0c;使用它可以生成多种通用性的报表&#xff0c;包括柱状图、饼图、曲线图、甘特图等。目前 JFreeChart 的最新版本是 1.0.14&…...

做第三方网站注意什么意思/seo排名优化软件价格

在局域网中&#xff0c;因没有校时服务器&#xff0c;系统时间会逐渐变慢。因此如何将电脑的时间与使用的重要系统的时间保持一致非常重要。下面是学习啦小编给大家整理的一些有关局域网中设置系统时间与服务器同步的方法&#xff0c;希望对大家有帮助!局域网中设置系统时间与服…...

wordpress body在哪/网站开发的基本流程

首先强调一下&#xff0c;Ganglia采用组播模式&#xff08;多播模式&#xff09;进行数据请求。gmetad发送一个请求到一个组播地址&#xff08;239.2.11.71&#xff09;&#xff0c;由于是组播地址&#xff0c;所以gmetad只需发送一次请求包即可完成对所有gmond的轮询。gmond收…...

做网站横幅价格/网站在线优化工具

一&#xff1a;前言 有关Time的时间其实很少有用到。但是用到就很纠结了&#xff0c;转换和保存&#xff0c;都是烦人的事情&#xff0c;我自己就在这上面吃过一个亏&#xff0c;所以就加载下来吧&#xff01; 二&#xff1a;内容 &#xff08;1&#xff09;&#xff1a;被坑的…...

模版建站/推广计划书范文

<1> 字体设置&#xff1a; /* 单词之间间距 */ letter-spacing:3px; /* 定义以空格间隔文字的间距(就是空格本身的宽度) */ word-spacing:50px; /* 定义文本的大小写状态 capitalize首字母大写 | uppercase大写 …...