iTOP-4412全能版采用四核Cortex-A9,主频为1.4GHz-1.6GHz,配备S5M8767 电源管理,集成USB HUB,选用高品质板对板连接器稳定可靠,大厂生产,做工精良。接口一应俱全,开发更简单,搭载全网通4G、支持WIFI、蓝牙、陀螺仪、CAN总线、RS485总线、500万摄像头等模块,稳定运行Android 4.0.3/Android 4.4操作,系统通用Linux-3.0.15+Qt操作系统(QT支持5.7版本),Ubuntu版本:12.04,接口智能分配 方便好用。
第十八章 Linux串口编程
学习过单片机的用户,对串口不会太陌生,在单片机串口编程中,需要用户直接对寄存器以及中断进行控制。
而在 linux 串口编程中,无论是从 linux 官方直接下载的原生态内核,还是任何厂家提供的 linux 内核,都会将串口驱动写好,所以对于所有的驱动工程师来说,是完全不需要自己动手写串口驱动的。
本章介绍的 linux 串口编程,实际上指的是 Linux 串口应用编程,直接使用提供原厂提供的接口,进行初始化配置以及发送和接收。对于从事 linux 编程的程序员来说,这一部分内容必须熟练掌握。
本章配套视频为:
“视频 07_01 串口编程之基本概念以及流程分析”
“视频 07_02 串口编程之开机启动运行”
“视频 07_03 串口编程之打开串口”
“视频 07_04 串口编程之串口初始化”
“视频 07_05 串口编程之串口发送”
“视频 07_06 串口编程之串口接收”
18.1 串口通信简介
串口通信是指一次只传送一个数据位。虽然在通信的时候串口有 8 位或者 9 位等,但是在物理层面传输的时候,它仍然是以单个 bit 的方式传输的。
RS-232 是 EIA(Electronic Industries Association)定义的串行通信的电器接口。RS-232 事实上有三种(A,B 和 C),它们分别采用不同的电压来表示 on 和 off。最被广泛使用的是 RS- 232C,它将 mark(on)比特的电压定义为-3V 到-12V 之间,而将 space(off)的电压定义到+3V 到+12V 之间。虽然 RS-232C 标准说信号最远被传输 8m,但事实上你可以使用它传输更长的距离,直到信号波特率已经小到不行了为止。 RS-232 的连结线中除去用来传入传出数据的电线,还有一些用来提供时序,状态和握手的电线。
RS232 的针脚定义
RS232 有两种标准定义,25 针和 8 针的。不过即使是 8 针,在大多数场合工程师也觉得太多了,绝大多数情况下都是只使用 TX,RX 针脚发送和接收信号,再加上一个 GND 地脚。其它定义大家可以通过网络了解一下。
什么是流控
两个串行接口之间的传输数据流通常需要协调一致才行。这可能是由于用以通信的某个串行接口或者某些存储介质的中间串行通信链路的限制造成的。对于异步数据这里有两个方法做到这一点。
第一种方法通常被叫做“软件”流控制。这种方法采用特殊字符来开始(XON,DC1,八进制数 021)或者结束(XOFF,DC3 或者八进制数 023)数据流。而这些字符都在 ASCII 中定义好了。虽然这些编码对于传输文本信息非常有用,但是它们却不能被用于在特殊程序中的其他类型的信息。
第二种方法叫做“硬件”流控制。这种方法使用 RS-232 标准的 CTS 和 RTS 信号来取代之前提到的特殊字符。当准备就绪时,接受一方会将 CTS 信号设置成为 space 电压,而尚未准备就绪时它会被设置成为 mark 电压。相应得,发送方会在准备就绪的情况下将 RTS 设置成 space 电压。正因为硬件流控制使用了于数据分隔的信号,所以与需要传输特殊字符的软件流控制相比它的速度很快。但是,并不是所有的硬件和操作系统都支持 CTS/RTS 流控制。
18.2 串口编程流程
在 linux 下串口编程流程比较容易理解,如下图所示。
c一个程序能够开机自动启动运行,那么就能不用超级终端的调试串口也能进行程序的测试了。
如下图所示,使用前面最简单的 helloworld 程序。,前面也编译运行通过了,代码如下。
首先确保上面的 helloworld 可执行程序已经在 U 盘中了。开机启动开发板,插入 U 盘, 加载 U 盘。
然后如下图所示,使用拷贝命令“cp -r /mnt/udisk/helloworld /bin/”将其拷贝到/bin 目录下,然后使用 ls 命令确认一下。
使用命令“chmod 777 /bin/helloworld”修改权限。
接着使用命令“vi /etc/init.d/rcS”打开文件系统的启动文件,如下图所示。
接着进入文件的最后一行,添加“/bin/helloworld &”。
保存退出。
为了方便查找打印信息,可以按照前面入门介绍的方法设置一下开机启动 log,我这里的路径是桌面上的 startlog.log 文件。如下图所示,迅为提供的超级终端设置保存 log 的方法为,“控制”→“日志设置”,然后弹出下图所示的日志配置窗口。
然后重启开发板。重启开发板之后,其实超级终端中也是会打印信息的,但是启动信息太多,不容易查找。
如果上图中找不到(步骤对了肯定是有的),打开前面设置的 startlog.log 文件,然后在里面查找关键词“Hello world!”。能够查找到打印的字符,说明开机启动程序正常。
18.4 打开串口
如何确认设备节点
先来学习一下如何打开串口,在几乎所有的 Linux 系统中,在 dev 目录下都会有 tty*的设备节点,如下图所示,启动开发板,在超级终端中,进入 dev 目录,输入查找命令“ls tty*”。
如上图所示,有多种形式的设备节点,在 4412 开发板中,设备节点使用的是 ttySAC*系列,即 ttySAC0,ttySAC1,ttySAC2,ttySAC3。
iTOP-4412 开发板可以支持 4 个串口,如下图所示,方便用户使用的除了控制台(超级终端使用的串口)以外,精英版靠近麦克和耳机的串口,也是可以直接拿来使用的。
在核心板原理图上,搜索“GPS_TXD”,如下图所示,可以看到这个串口对应的是XuRXD3,所以可以确认精英版靠近耳机和麦克的串口对应的设备节点是“ttySAC3”。后面的实验都已这个操作这个串口为例子来讲解。
串口打开和关闭例程
编写简单的 uartopen.c 文件打开串口设备节点。
首先添加头文件,如下图所示,和 io 文件使用的头文件一样。
然后 main 函数如下图所示。
如上图代码所示。
第 13 行,打开“/dev/ttySAC3”设备节点,方法和 led 等字符的方法类似。
第 14 行,打开不成功则发送“open %s is failed!”。
第 17 行,打开成功则发送“open %s is success!”。
第 20 行,使用 close 函数关闭文件。
编译运行测试
在 Ubuntu 系统下,如下图所示,进入前面实验创建的目录“/home/linuxsystemcode/”,使用命令“mkdir uartapp”新建 uartapp 文件夹,将源码uartopen.c 拷贝进去,进入新建的文件夹 uartapp,如下图所示。
使用命令“arm-none-linux-gnueabi-gcc -o uartopen uartopen.c -static”编译生成uartopen 文件,如下图所示,使用命令“ls”可以看到生成了 uartopen 可执行文件。
这里介绍 U 盘拷贝代码的方法,也可以编译进文件系统,具体方法参考 10.3.5 小节。将编译成的可执行文件 uartopen,拷贝到 U 盘,启动开发板,插入 U 盘,加载 U 盘,运行程序。
如下图所示,使用命令“./mnt/udisk/uartopen ”运行程序 uartopen,打印出“open /dev/ttySAC3 is success”说明串口打开成功,并没有被其它程序占用。
18.5 串口初始化
串口编程的最大的难度就是初始化,用的参数非常多。
大家可能查看过网上一些关于串口的资料以及历史,由于串口的设计之初太过于复杂了, 但是到了实际应用中,两线的串口(tx/rx)应用却是最广泛的。在实际应用中几乎很少看到有多线的,即使复杂一点也最多是添加一根流控。
这样会导致一个结果,网上的串口相关教程晦涩难懂,学习的时候不容易抓住重点。
本节内容将直接介绍应用中最多的一套初始化代码。这套代码尽量贴近实际,大家以后应用的时候,要是传输的协议一样,可以直接拿来使用,不一样稍微修改一下参数的配置就能够通信。
串口的初始化结构介绍
如果大家学习过单片机,多半使用过类似如下图所示的串口助手。
如上图所示,在左下角其实就是串口最基本的参数设置,这些配置是最常用的,包括串口号、波特率、数据位、停止位,校验位、流控。
如下图所示,使用 source insight 打开内核源码,串口的初始化最终要将参数传递到内核中的,搜索“termios.h”,如下所示。
如上图所示,打开“arch\arm\include\asm”目录下的“termios.h”头文件。
如下图所示,可以看到这个 termio 结构体的定义。
分析一下上图中几个常用的参数。
成员 tcflag_t c_iflag:输入模式标志
成员 tcflag_t c_oflag:输出模式标志
成员 tcflag_t c_cflag:控制模式标志
成员 tcflag_t c_lflag:本地模式标志
成员 cc_t c_line:line discipline
成员 cc_t c_cc[NCC]:control characters
串口的初始化常用函数介绍
在介绍了上面的结构体之后,接着看一下初始化的几个函数以及用法。
在给串口初始化之前必须读取串口的句柄,也就是先要使用 open 函数,在前面的实验中已经测试过,可以正常返回 fd 了。
函数 tcgetattr
函数 tcgetattr 用于读取当前串口的参数值,在实际应用中,一般用于先确认该串口是否能够配置,做检测用。
需要用到头文件 “#include <termios.h>”和“#include <unistd.h>”。函数原型为 int tcgetattr(int fd, struct termios *termios_p)。
参数 1:fd 是 open 返回的文件句柄。
参数 2:*termios_p 是前面介绍的结构体。
使用这个函数前可以先定义一个 termios 结构体,用于存储旧的参数。
波特率相关的函数
函数 cfsetispeed 和 cfsetospeed 用于修改串口的波特率,函数 cfgetispeed 和cfgetospeed 可以用于获取当前波特率。在实际应用中,这个经常需要用到,例如修改默认的波特率。
波特率相关的函数需要用到头文件 “#include <termios.h>”和“#include<unistd.h>”。
1)先介绍设置波特率的函数。
函数原型 int cfsetispeed(struct termios *termios_p, speed_t speed);
参数 1:*termios_p 是前面介绍的结构体。
参数 2:speed 波特率,常用的 B2400,B4800,B9600,B115200,B460800 等等。
执行成功返回 0,失败返回-1
函数原型 int cfsetospeed(struct termios *termios_p, speed_t speed);
参数 1:*termios_p 是前面介绍的结构体。
参数 2:speed 波特率,常用的 B2400,B4800,B9600,B115200,B460800 等等。
执行成功返回 0,失败返回-1
2)下面介绍获取波特率的函数。
函数原型为 speed_t cfgetispeed(const struct termios *termios_p)。
用于读取当前串口输入的波特率。
参数 1:*termios_p 是前面介绍的结构体。
返回值为 speed_t。
函数 speed_t cfgetospeed(const struct termios *termios_p)。
这个函数用于读取当前输出的波特率。
参数 1:*termios_p 是前面介绍的结构体。
返回值为 speed_t 类型,当前波特率。
函数 tcflush
函数 tcflush 用于清空串口中没有完成的输入或者输出数据。在接收或者发送数据的时候,串口寄存器会缓存数据,这个函数用于清除这些数据。
原型为 int tcflush(int fd, int queue_selector);
参数 1:fd 是 open 返回的文件句柄。
参数 2:控制 tcflush 的操作。有三个常用数值,
TCIFLUSH 清除正收到的数据,且不会读取出来;
TCOFLUSH 清除正写入的数据,且不会发送至终端;
TCIOFLUSH 清除所有正在发生的 I/O 数据。
执行成功返回 0,失败返回-1
函数 tcsetattr
前面介绍了读取串口配置参数的函数,tcsetattr 函数是设置参数的函数。
原型为 int tcsetattr(int fd, int optional_actions,const struct termios *termios_p);
参数 1:fd 是 open 返回的文件句柄。
参数 2:optional_actions 是参数生效的时间。有三个常用的值:
TCSANOW:不等数据传输完毕就立即改变属性;
TCSADRAIN:等待所有数据传输结束才改变属性;
TCSAFLUSH:清空输入输出缓冲区才改变属性。
参数 3:*termios_p 在旧的参数基础上修改的后的参数。
执行成功返回 0,失败返回-1
一般在初始化最后会使用这个函数。
其它函数的使用
在串口中还有其它的参数需要了解,其它的函数需要学习。
大家在 linux 下使用串口的时候,如果有其它的参数需要配置,可以在网上搜索相关的资料,那样针对性更强,有的放矢,效率更高。
前面的参数和设置已经可以对付大部分实际应用场景了。
初始化流程分析
如下图所示,是串口初始化的流程图。
如上图所示,这是一个简单的初始化过程,后面初始化代码也会按照这个过程组织。
例程:串口初始化代码
下面写一个函数,包括串口的基本的配置,函数里面的参数还可以添加,也可以单独拿出来配置。
如下图所示,首先定义一个初始化函数int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)。
然后定义结构体 newtio 和 oldtio。
接着将 newtio 清零和设置标志位 c_cflag。如下图所示。
上面涉及到的点很多,需要大家具体的了解。接着设置波特率,如下图所示,使用 switch 配置波特率,只有部分常用的波特率,如果有需要还可以添加。
接着设置停止位,如下图所示。
最后配置参数,如下图所示。
18.6 串口发送
本节实验演示串口的发送。
这里提醒一下,在学习中用到的超级终端,里面的打印信息,本质上是用于 linux 的调试的,类似在开发环境的 Ubuntu 下的终端。那个串口已经被内核占用了,不能直接用于本章的实验的调用,只能作为一个辅助调试的手段。
如果最终的产品串口不够,可以使用 usb 转串口,或者将调试的控制台屏蔽掉即可。内核经过处理之后才能作为串口使用。
串口发送例程
串口发送类似文件操作,非常简单。使用 write 函数即可,三个参数分别是句柄,传输的buffer 以及,传输的长度。这个函数前面介绍文件 IO 的时候已经介绍过了,这里就不再重复。
源码文件名为“uartwrite.c”。先来看一下代码的头文件,然后引入前面的初始化函数。
将串口参数配置整合为一个函数,主函数就比较简单了,如下图所示。
如上图所示,运行代码之后,可观察到如下现象。控制台打印“itop4412 uart3 writetest start”。
控制台打印“open %s is success\n”。接着打印 i=10 次的"hello world!\n"。
打印的同时控制台会打印“wr_static is %d”。
另外,如果其它打开设备之类的操作出错,会打印对应的错误。
编译运行测试
在 Ubuntu 系统下,如下图所示,进入前面实验创建的目录“/home/linuxsystemcode/uartapp”,将源码 uartwrite.c 拷贝进去,如下图所示。
使用命令“arm-none-linux-gnueabi-gcc -o uartwrite uartwrite.c -static”,编译 uartwrite 文件,如下图所示,使用命令“ls”可以看到生成了 uartwrite 可执行文件。
这里介绍 U 盘拷贝代码的方法,也可以编译进文件系统,具体方法参考 10.3.5 小节。将编译成的可执行文件 uartwrite,拷贝到 U 盘。开发板精英版接两个串口,一个是控制台的超级终端,另一个是接声卡旁边的串口。超级终端,如下图所示。
程序控制的串口,使用另外一个串口助手,如下图所示。
注意上面两个串口号需要和根据实际情况设置串口号,在使用手册的第三章有关于串口软件基础的介绍,如果不清楚可以再学习一下。
启动开发板,插入 U 盘,加载 U 盘,运行程序如下。
如上图所示,另外一个终端会打印出设置好的字符,如下图所示,说明串口调用输出成功。
如果用户的串口线不够,没有关系,可以看一下下一节的测试方法,利用开机启动来测试。
只有一个串口的测试方法
如果用户只有一个串口,那么可以将这个程序设置为开机启动。
启动开发板,在超级终端控制台中,将 uartwrite 拷贝到/bin 目录,然后修改权限,如下图所示。
接着修改开机启动脚本文件,在超级终端中使用命令“ vi /etc/init.d/rcS”,如下图所示。
接着修改启动脚本。
保存退出,然后关掉开发板。因为只有一个串口,那么将唯一的串口接到耳机旁边的串口座子,控制台串口不需要接到 PC 上。串口接好之后,使用如下图所示的串口助手。
接着开启开发板,过一会就会打印 10 行 hello world!,如下图所示。
这里提醒大家一下,串口是不支持热拔插的。如果强行热拔插,容易损坏串口芯片。
18.7 串口接收
串口接收使用 read 函数,在文件 io 中已经介绍过了。
接收例程命名为“uartread.c”。来看一下头文件,如下图所示。
接着看一下 main 主函数。
如上图所示。
第 17 行:定义一个 buffer 接收数据。第 18 行:将缓冲器清零。
第 19 行:打开串口。
第 22 行:初始化串口。
第 23 行:串口输出“please input”字符串,提醒可以输入了。
第 25 行-29:循环接收串口发送过来的数据,并发送出去。
编译运行测试
在 Ubuntu 系统下,如下图所示,进入前面实验创建的目录“/home/linuxsystemcode/uartapp”,将源码 uartread.c 拷贝进去,如下图所示。
使用命令“arm-none-linux-gnueabi-gcc -o uartread uartread.c -static”编译uartread 文件,如下图所示,使用命令“ls”可以看到生成了 uartread 可执行文件。
这里介绍 U 盘拷贝代码的方法,也可以编译进文件系统,具体方法参考 10.3.5 小节。测试方法和串口读函数的例程类似,如果只有一个串口可以参考前面介绍开机启动的方
法。
将编译成的可执行文件 uartread,拷贝到 U 盘,启动开发板,插入 U 盘,加载 U 盘,运行程序如下,后台运行注意加“&”。
如下图所示,运行之后另外一个串口会发送“please input”字符。小提示:使用清除之后,可以计算到底接收发送了
如上图所示,字符串输入框中可以写入任何字符输如。上面写得是“helloread”,然后多打击单击发送几次,可以看到发送的字符被发送回来了。
如上图所示,输入输出会一直相差保持 14,出去启动接收的 please input,表明接收成功。