一般我们了解到的数据通信,可能接触的多的就是串行通信和并行通信了,这两种通信,各怀绝技,在生活中有广泛的应用,今天在这里,讲的是串口的通信。
定义:串行通信是指利用一条传输线将数据以比特单位顺序传送,其特点,通信简单,成本低,适用于传输距离比较远且速度较慢的通信。
准备知识:说到串口通信,就不得不先普及一下串口设置的常用参数,因为在串口通信中,设置参数是一步非常关键的事。当然在这里只需要有个概念就好,不影响你进行编程有兴趣的读者,可以到网上百科一下详细的资料
1,波特率,一个衡量符号传输速率的参数。它表示每秒钟传送的符号的个数,可以把它理解为速度,串口之间的通信,事先都会约定好,大家会在同一个速度下,进行数据的传输,如果不在同一个波特率下,传输是不被允许进行的,这点要切记。
2,数据位,这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。一般我们选择的8位,一个字节刚刚好,有些读者可能会问,为什么要选八位呢。理由很简单,有几辆货车,装载容量大小不一样,送货的价钱却一样,你有一批大货要运送,你会选择容量大的还是小的呢,答案非常明显,数据位的选取也是同样的道理。
3,停止位,用于表示单个包的最后一位。典型的值为1和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。
4,奇偶校验位,在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。
串口的设置
说完一些串口参数,接下来就是设置的问题,串口设置是有一个强大的结构体支配的,通过引用里面的成员,修改参数,可以达到一个非常强大的功能,下面我们来揭开这个结构体的面纱
#include
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流控制
串口的设置流程。
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,总结
经过冗长而麻烦的设置之后呢,串口通信的设置这一步总算完成了,这个可以保存起来,给以后的通信可以用,下面给出源代码,让大家感受一下
大家会发现上面的程序除了一些串口的设置之外,还有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:
感谢观看,由于作者也是新手,如有不对和疑惑的地方,恳请您不要吝惜的您的谏言,在以下评论,一起学习,在此非常感谢