嵌入式linux串口应用编程(上)

时间:2021-08-22 22:45:03

一般我们了解到的数据通信,可能接触的多的就是串行通信和并行通信了,这两种通信,各怀绝技,在生活中有广泛的应用,今天在这里,讲的是串口的通信。

定义:串行通信是指利用一条传输线将数据以比特单位顺序传送,其特点,通信简单,成本低,适用于传输距离比较远且速度较慢的通信。

准备知识:说到串口通信,就不得不先普及一下串口设置的常用参数,因为在串口通信中,设置参数是一步非常关键的事。当然在这里只需要有个概念就好,不影响你进行编程有兴趣的读者,可以到网上百科一下详细的资料

            1,波特率,一个衡量符号传输速率的参数。它表示每秒钟传送的符号的个数,可以把它理解为速度,串口之间的通信,事先都会约定好,大家会在同一个速度下,进行数据的传输,如果不在同一个波特率下,传输是不被允许进行的,这点要切记。

           2,数据位,这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。一般我们选择的8位,一个字节刚刚好,有些读者可能会问,为什么要选八位呢。理由很简单,有几辆货车,装载容量大小不一样,送货的价钱却一样,你有一批大货要运送,你会选择容量大的还是小的呢,答案非常明显,数据位的选取也是同样的道理。

          3,停止位,用于表示单个包的最后一位。典型的值为1和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。

         4,奇偶校验位,在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。

串口的设置

说完一些串口参数,接下来就是设置的问题,串口设置是有一个强大的结构体支配的,通过引用里面的成员,修改参数,可以达到一个非常强大的功能,下面我们来揭开这个结构体的面纱

#include<termios.h>

struct termios

{

        unsigned short c_iflag;        /*输入模式标志*/

        unsigned short c_oflag;        /*输出模式标志*/

        unsigned short c_cflag;        /*控制模式标志*/

        unsigned short c_lflag;        /*本地模式标志*/

        unsigned short c_line;        /*线路规程*/

        unsigned short c_cc[NCC];        /*控制特性*/

        speed_t             c_ispeed ;          /*输入速度*/

        speed_t             c_ospeed;          /*输出速度*/

}

termios是POSIX规范中定义的标准接口,串口三一种通信设备,一般通过终端的编程接口对其进行配置和编程。

在这里列一下结构体成员支持的常量有哪些,方便接下来编程的查阅

c_iflag参数表
键值说明
IGNBRK 忽略BREAK键输入
BRKINT 如果设置了IGNBRK,BREAK键的输入将被忽略,如果设置了BRKINT ,将产生SIGINT中断
IGNPAR 忽略奇偶校验错误
PARMRK 标识奇偶校验错误
INPCK 允许输入奇偶校验
ISTRIP 去除字符的第8个比特
INLCR 将输入的NL(换行)转换成CR(回车)
IGNCR 忽略输入的回车
ICRNL 将输入的回车转化成换行(如果IGNCR未设置的情况下)
IUCLC 将输入的大写字符转换成小写字符(非POSIX)
IXON 允许输入时对XON/XOFF流进行控制
IXANY 输入任何字符将重启停止的输出
IXOFF 允许输入时对XON/XOFF流进行控制
IMAXBEL 当输入队列满的时候开始响铃,Linux在使用该参数而是认为该参数总是已经设置


c_oflag参数
键值说明
OPOST 处理后输出
OLCUC 将输入的小写字符转换成大写字符(非POSIX)
ONLCR 将输入的NL(换行)转换成CR(回车)及NL(换行)
OCRNL 将输入的CR(回车)转换成NL(换行)
ONOCR 第一行不输出回车符
ONLRET 不输出回车符
OFILL 发送填充字符以延迟终端输出
OFDEL 以ASCII码的DEL作为填充字符,如果未设置该参数,填充字符将是NUL(‘/0’)(非POSIX)
NLDLY 换行输出延时,可以取NL0(不延迟)或NL1(延迟0.1s)
CRDLY 回车延迟,取值范围为:CR0、CR1、CR2和 CR3
TABDLY 水平制表符输出延迟,取值范围为:TAB0、TAB1、TAB2和TAB3
BSDLY 空格输出延迟,可以取BS0或BS1
VTDLY 垂直制表符输出延迟,可以取VT0或VT1
FFDLY 换页延迟,可以取FF0或FF1


c_oflag参数
键值说明
CBAUD 波特率(4+1位)(非POSIX)
CBAUDEX 附加波特率(1位)(非POSIX)
CSIZE 字符长度,取值范围为CS5、CS6、CS7或CS8
CSTOPB 设置两个停止位
CREAD 使用接收器
PARENB 使用奇偶校验
PARODD 对输入使用奇偶校验,对输出使用偶校验
HUPCL 关闭设备时挂起
CLOCAL 忽略调制解调器线路状态
CRTSCTS 使用RTS/CTS流控制


c_lflag参数
键值说明
ISIG 当输入INTR、QUIT、SUSP或DSUSP时,产生相应的信号
ICANON 使用标准输入模式
XCASE 在ICANON和XCASE同时设置的情况下,终端只使用大写。如果只设置了XCASE,则输入字符将被转换为小写字符,除非字符使用了转义字符(非POSIX,且Linux不支持该参数)
ECHO 显示输入字符
ECHOE 如果ICANON同时设置,ERASE将删除输入的字符,WERASE将删除输入的单词
ECHOK 如果ICANON同时设置,KILL将删除当前行
ECHONL 如果ICANON同时设置,即使ECHO没有设置依然显示换行符
ECHOPRT 如果ECHO和ICANON同时设置,将删除打印出的字符(非POSIX)
TOSTOP 向后台输出发送SIGTTOU信号


好了,讲这么多,终于开始进入正题。

串口的设置流程。

1.保存原先的串口配置。

int fd;

struct termios new_cfg ,old_cfg;

tcgetattr(fd,&old_cfg)   //调用这个函数,可以保存原先串口的设置,fd,是指打开串口成功返回的值。函数成功调用成功返回0,否则返回-1


2,设置波特率

设置波特率有专门的函数,cfsetispeed()和cfsetospeed(),函数的记忆也比较容易,把这函数分为三部分看,第一cf,第二设置set,第三输入速度,ispeed;

下面看一下函数的使用

cfsetispeed(&old_cfg,B115200);

cfsetospeed(&old_cfg,B115200)  //B后面接的数字是波特率


3,设置字符大小和数据位

设置字符大小不像波特率那样简单,没有专用的函数。。需要位掩码来掩,一般首先去除数据位数据位中的位掩码。

new_cfg.c_cflag &= ~CSIZE;

new_cfg.cflag |= CS8;因为规定不能对c_cflag进行初始化,所以只能通过,与,或操作使用其中的功能选项

学过寄存器的朋友们,就很好理解位操作,在termios.h头文件中,可以看到有下面一行

#define CSIZE   0000060//字符位宽度屏蔽码   6就是功能位,对于它的激活操作,我们就用与,或来操作,下面有许多例子都是通过位操作的


4,设置奇偶校验位

设置奇偶校验,需要用到两个结构体变量,c_cflag和c_lfag。首先得激活c_cflag的使能标志PARENB,参数可以再上文查询,同时还需要激活c_iflag中对输入进行校验使能IPNCK.

下面的举个例子

奇校验:

new_cfg.c_cflag |= (PARENB | PARODD);

new_cfg.c_iflag |= INPCK;


偶校验:

new_cfg.c_cflag |= PARENB;

new_cfg.c_cflag &=~PARODD;

new_cfg.c_iflag |=INPCK;


5,设置停止位

设置停止位三通过激活c_cflag中的CSTOPB实现的,若停止位一个,则清除CSTOPB,两个停止为则激活

new_cfg.c_cflag &= ~CSTOPB;  /*设置一个停止位*/

new_cfg.c_cflag |=  CSTOPB;  /*设置两个个停止位*/


6,设置最少字符和等等待时间

在对接受字符和等待时间没有特别要求的情况下,可以将其设置为0,则在任何请款下read()函数可以立即返回

new_cdg.c_cc[VTIME] =0;

new_cdg.c_cc[VMIN] =0;


7,清楚串口的缓存

在串口重新设置之后,需要对当前的串口设备进行处理,调用的函数如下;

int  tcdrain (int   fd);  /*使程序阻塞,直到输出缓冲区的数据全部发送完毕*/

int  tcflow(int  fd, int action); /*用于暂停或重新开始输出*/

int  tcflush(int fd,int queue_selector); /*用于清空输入,输出缓冲区*/

在这里使用tcflush()函数

其中第二个参数的取值有以下几种

TCIFLUSH :对接受到而未被读取到的数据进行清空处理

TCSADRAIN;对尚未传送成功的输出数据进行清空的处理

TCIOFLUSH:包括以上良种功能,即对未尚未处理的数据进行清空


8,激活配置

在完成所有的配置之后,需要激活生效,用到的函数tcsetattr();

函数原型:tcsetattr(int fd ,int optional_acctions,const struct termios *termios *termios_p)

其中第二,三个变量看起来很麻烦,但下面给个例子,大家马上就会知道了,在给出例子前。说一下第二个参数

取值如下

TCSANOW:配置的 修改马上生效

TCSADARIN: 配置的修改在所有的写入fd的输出都传输完毕之后生效

TCSAFLUSH:所有已接受但为读入的输入都将在修改之前被丢弃。

下面看函数,调用成功返回0,否则为-1

if(  (   tcsetattr (fd,TCSANOW,&new_cfg)   )   ==-1  )

{

              perror("failed!!!");

              exit(-1);

}

9,总结

经过冗长而麻烦的设置之后呢,串口通信的设置这一步总算完成了,这个可以保存起来,给以后的通信可以用,下面给出源代码,让大家感受一下

嵌入式linux串口应用编程(上)

嵌入式linux串口应用编程(上)


嵌入式linux串口应用编程(上)


嵌入式linux串口应用编程(上)


嵌入式linux串口应用编程(上)


嵌入式linux串口应用编程(上)



大家会发现上面的程序除了一些串口的设置之外,还有open()函数,那open()函数的作用究竟是什么呢?

打开串口

当你配置完串口的相关属性后,就可以对串口进行打开和都写操作了,首先我们先来讲一下串口的打开,跟普通文件的操作一样,对照上面的例子,带着疑问读这段话,会让记忆更加深刻。

1)fd=open("/dev/ttyUSB0",O_RDWR|O_NOCTTY|O_NDELAY);

在这里讲的的,文件打开函数的标志,第一个O_RDWR自然是可读可写,第二个和第三个就要和大家详细分析。

O_NOCTTY

用于通知linux系统,该参数不会使打开的文件成为这个进程的控制终端,也就是说,当你在串口通信里敲下ctrl+c也不会影响到实际的用户进程

O_NDELAY

用于通知linux系统,指定了这个标志之后,进程会一直处在睡眠的状态,直到DCD信号线被激活

2)打开之后,我们需要将串口的状态为阻塞状态,用于等待串口数据的写入,fcntl()函数的实现

         fcntl(fd,F_SETFL,0);

3)再接着可以测试一下打开文件描述符是否连接到一个终端设备,以进一步确认串口是否正确打开

         isatty(STDIN_FILENO);

这时。一个串口就被我们打开的,接下来就可以对串口进行读和写的操作了,

至于读和写以及如何在开发板上编译通信,将会在下篇逐一介绍。


PS:

感谢观看,由于作者也是新手,如有不对和疑惑的地方,恳请您不要吝惜的您的谏言,在以下评论,一起学习,在此非常感谢