Linux串口编程

简介: Linux串口编程

前言

本篇文章将讲解如何在Linux下使用串口。


一、Linux下的TTY体系介绍

在Linux系统中,tty(Teletypewriter)是指一种终端设备,它提供了用户与操作系统之间的交互界面。在较早的计算机系统中,tty是以打字机作为输入输出设备的终端系统,而现代的Linux系统中,tty则对应着虚拟终端。

Linux下的tty体系是由多个虚拟终端组成的,每个虚拟终端都对应着一个tty设备文件。在Linux系统中,tty设备文件位于/dev目录下,以tty开头,后面跟随一个数字,如tty1、tty2等。

对于用户来说,tty设备提供了一个字符设备节点,用于输入和输出字符数据。用户可以通过tty设备读取输入的字符,并将输出字符发送到tty设备。这些字符可以是用户输入的命令、系统的输出信息等。

用户可以在Linux系统中使用特殊的快捷键(如Ctrl+Alt+F1至Ctrl+Alt+F6)在各个终端之间切换,并在各个tty设备上独立地登录和执行命令。TTY设备为用户提供了与操作系统进行交互的接口,用户可以在不同的虚拟终端上同时工作,执行不同的任务。

在终端下通过ls命令可以查看到非常多的tty设备节点,这些节点都分别对应了不同的功能。

这里引用百问网的一张图片:

二、行规层

行规层(Line Discipline)是指在Unix-like操作系统中提供的一种机制,用于处理终端设备或网络连接中的数据流。它位于终端设备驱动程序和用户进程之间,负责对输入和输出的字符数据进行处理和转换。

行规层在传统的串行终端设备上起着重要的作用。它可以执行以下功能:

输入处理:行规层可以处理终端设备上的输入字符流。它可以执行字符的缓冲、编辑和回显等操作。例如,当用户在终端上输入字符时,行规层可以提供行缓冲,使得用户可以逐行输入,并在按下回车键后将整行字符发送给用户进程。

输出处理:行规层可以对输出字符进行处理。它可以执行转义序列的解释和字符的替换等操作。例如,当用户进程向终端设备发送输出字符流时,行规层可以将特定字符序列转换为控制终端的命令,以调整光标位置、修改显示属性等。

规范模式和原始模式:行规层支持规范模式和原始模式的切换。在规范模式下,行规层提供行缓冲、按行输入和回显等功能。这是默认的终端模式,适用于大多数交互式应用。在原始模式下,行规层关闭缓冲和编辑功能,字符逐个传递给用户进程,适用于特定的应用需求,如串口通信等。

控制字符处理:行规层负责处理特殊控制字符的输入和输出。例如,CTRL+C用于中断正在运行的程序,CTRL+D用于表示输入的结束等。行规层将检测和处理这些特殊字符,并执行相应的操作。

行规层的具体实现在不同的操作系统中会有所不同。在Linux系统中,行规层是通过终端设备驱动程序和TTY子系统来实现的。Linux提供了多个行规层的实现,如原始(line discipline)、终端(line discipline)、网络(line discipline)等。用户可以根据需要选择不同的行规层来完成特定的终端和网络通信任务。

总结来说,行规层是位于终端设备驱动程序和用户进程之间的一层软件机制,用于处理终端设备和网络连接中的数据流。它提供了输入处理、输出处理、规范模式和原始模式的切换以及控制字符处理等功能。通过行规层,用户可以更加灵活地操控和控制终端设备和网络连接。


三、Linux串口编程步骤

在ARM Linux系统上进行串口编程可以按照以下步骤进行:

1.打开串口设备:

首先,需要打开要使用的串口设备文件,一般在Linux系统中,串口设备文件位于/dev/ttySx或/dev/ttyUSBx,其中x是串口号或USB串口号。可以使用系统调用open()函数以读写方式打开串口设备文件。

2.配置串口参数:

在打开串口设备后,需要配置串口的通信参数,如波特率、数据位、停止位、校验位等。可以使用termios数据结构和相关的函数来进行设置。可以使用函数tcgetattr()获取当前串口参数,然后修改相关参数,最后通过tcsetattr()函数将修改后的参数应用到串口设备。

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_cc[NCCS];    /* control characters */
  cc_t c_line;      /* line discipline (== c_cc[19]) */
  speed_t c_ispeed;   /* input speed */
  speed_t c_ospeed;   /* output speed */
};

各个参数介绍:

c_iflag(输入模式标志位):这个字段包含一系列用于控制输入模式的标志位。例如,可以使用IGNBRK来忽略BREAK键输入,使用INPCK来启用奇偶校验等。

c_oflag(输出模式标志位):这个字段包含一系列用于控制输出模式的标志位。例如,可以使用OPOST来启用输出处理(执行输出转义字符的功能),使用ONLCR来将输出中的换行符转换为回车换行等。

c_cflag(控制模式标志位):这个字段包含一系列用于控制串口硬件特性的标志位。例如,可以使用CBAUD来设置波特率,使用CS8来设置数据位为8位等。

c_lflag(本地模式标志位):这个字段包含一系列用于控制本地模式(终端行为)的标志位。例如,可以使用ICANON来启用规范模式,使用ECHO来启用回显等。

c_cc(控制字符数组):这个数组存储了一些特殊控制字符的值,如终端驱动程序使用的信号字符,输入和输出的起始字符等。

c_line(线路规程):这个字段存储了用于处理数据的线路规程(line discipline)的标识符。一般情况下,可以将其设置为0。

c_ispeed(输入波特率):这个字段指定输入的波特率,表示从串口接收数据的速度。

c_ospeed(输出波特率):这个字段指定输出的波特率,表示向串口发送数据的速度。

3.读取和写入数据:

一旦串口打开且参数配置完成,就可以进行数据的读取和写入。可以使用read()函数从串口设备读取数据,使用write()函数将数据写入串口设备。

4.清理并关闭串口:

在使用完串口后,需要进行清理和关闭。可以使用tcsetattr()函数将串口参数恢复到默认值,并使用close()函数关闭串口设备文件。


四、代码编写

代码的话也是使用的是百问网提供的代码:

#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 ) );
  newtio.c_cflag |= CLOCAL | CREAD; 
  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;
  newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
  newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: 
                           * 比如VMIN设为10表示至少读到10个数据才返回,
                           * 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
                           * 假设VTIME=1,表示: 
                           *    10秒内一个数据都没有的话就返回
                           *    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
                           */
  tcflush(fd,TCIFLUSH);
  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;
  //fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
  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;
  /* 1. open */
  /* 2. setup 
   * 115200,8N1
   * RAW mode
   * return data immediately
   */
  /* 3. write and read */
  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;
}

代码测试:

这里输入a后不会马上返回数据只有按下回车后才会打印出数据。

这是因为有行规层的限制,输入a后行规层并不会唤醒scanf,只有当输入回车后行规层才会将scanf唤醒。


总结

本篇文章就讲解到这里。

相关文章
|
3月前
|
Shell Linux
Linux shell编程学习笔记30:打造彩色的选项菜单
Linux shell编程学习笔记30:打造彩色的选项菜单
|
1月前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
3月前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
3月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
120 6
|
3月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
148 3
|
3月前
|
Shell Linux Python
python执行linux系统命令的几种方法(python3经典编程案例)
文章介绍了多种使用Python执行Linux系统命令的方法,包括使用os模块的不同函数以及subprocess模块来调用shell命令并处理其输出。
53 0
|
4月前
|
项目管理 敏捷开发 开发框架
敏捷与瀑布的对决:解析Xamarin项目管理中如何运用敏捷方法提升开发效率并应对市场变化
【8月更文挑战第31天】在数字化时代,项目管理对软件开发至关重要,尤其是在跨平台框架 Xamarin 中。本文《Xamarin 项目管理:敏捷方法的应用》通过对比传统瀑布方法与敏捷方法,揭示敏捷在 Xamarin 项目中的优势。瀑布方法按线性顺序推进,适用于需求固定的小型项目;而敏捷方法如 Scrum 则强调迭代和增量开发,更适合需求多变、竞争激烈的环境。通过详细分析两种方法在 Xamarin 项目中的实际应用,本文展示了敏捷方法如何提高灵活性、适应性和开发效率,使其成为 Xamarin 项目成功的利器。
53 1
|
4月前
|
安全 Linux 开发工具
探索Linux操作系统:从命令行到脚本编程
【8月更文挑战第31天】在这篇文章中,我们将一起潜入Linux操作系统的海洋,从最基础的命令行操作开始,逐步深入到编写实用的脚本。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和实用技能。我们将通过实际代码示例,展示如何在日常工作中利用Linux的强大功能来简化任务和提高效率。准备好了吗?让我们一起开启这段旅程,探索Linux的奥秘吧!
|
4月前
|
Linux
揭秘Linux心脏:那些让你的编程事半功倍的主要系统调用
【8月更文挑战第31天】Linux中的系统调用是操作系统提供给应用程序的接口,用于请求内核服务,如文件操作、进程控制等。本文列举了22种主要系统调用,包括fork()、exec()、exit()、wait()、open()、close()、read()、write()等,并通过示例代码展示了如何使用fork()创建新进程及使用open()、write()、close()操作文件。这些系统调用是Linux中最基本的接口,帮助应用程序与内核交互。
62 1
|
4月前
|
Linux 程序员 开发者
源社区的兴起:从“代码隐士”到Linux引领的“全球编程嘉年华”
在编程的古老森林中,曾有“代码隐士”默默耕耘,惧怕智慧外泄。直到“开源”春风拂过,源社区如全球编程嘉年华盛开!开源文化颠覆了“独门秘籍”的传统,像“武林秘籍共享”般在网络上公开,鼓励知识传播与智慧碰撞。程序员组队开发,分享代码,提升科技实力。Linux则从“首席大厨”变身为“总导演”,以强大内核调制出诱人应用,引领潮流并推动技术创新。加入这场没有血腥厮杀,只有知识盛宴的“编程版《饥饿游戏》”吧!与全球开发者共享编程的乐趣与成就感!别忘了带上你的“独门秘籍”,可能下一个改变世界的创意就在其中!
70 7