14. 串口通讯与终端设备

本章主要讲解串口和终端设备的基本使用。

14.1. 终端设备

Teletype是最早出现的一种终端设备,类似于电传打字机,tty是Teletype的缩写。 最初tty是指连接到Unix系统上的物理或者虚拟终端。但是随着时间的推移,tty也用于串口设备,如ttyn、ttySACn等, Linux系统对终端设备的支持非常强大,本章通过Linux的终端设备文件进行串口通讯。

14.1.1. 终端设备文件

在Linux下终端的设备文件都位于/dev/目录下,以tty*开头的字符命名, 在开发板的终端上执行“ls /dev/tty*”命令,如下图所示。

未找到图片07|

可以看到有个名为“ttymxc0”的设备,“ttymxc0”就是开发板的串口1,它已被默认被用在命令行的终端上。

14.1.2. stty命令

Linux下有一个专门的stty命令可以查看或设置终端的参数。

在开发板的终端执行如下命令:

1
2
3
4
5
#在开发板的终端执行如下命令
#它会输出当前终端的参数
stty
#查看ttymxc0设备参数
stty -F /dev/ttymxc0
未找到图片08|

从上图中命令的执行结果可看到,ttymxc0的通讯速率“speed”为115200, 这就是串口通讯的波特率,这些是在驱动中设置的默认值。若用户想修改tty设备的配置,可以使用如下命令:

1
2
3
4
5
#在开发板的终端执行如下命令
#查看设备参数
stty -F /dev/ttymxc0
#设置通讯速率,其中ispeed为输入速率,ospeed为输出速率
stty -F /dev/ttymxc0 ispeed 9600 ospeed 9600

命令中的ispeed和ospeed分别表示要设置的输入速率和输出速率,并不是所有设备 都支持不同的输入输出速率,所以最好把它们设置成一样。

14.2. 串口通讯实验(Shell)

本实验使用开发板上的串口3进行实验,对应的设备文件为/dev/ttymxc2,这是因为驱动是从0开始编号的。 对tty的设备文件直接读写就可以控制设备通过串口接收或发送数据,下面我们使用开发板配合Windows下的串口调试助手或Linux下的minicom进行测试。

14.2.1. 使能串口3

开发板串口3默认没有使能,打开/boot/uEnv.txt文件启用串口3设备树插件后并重启开发板

如下图:

未找到图片

14.2.2. 查询串口3的通信参数

串口3外设使能后,在/dev目录下生成ttymxc2设备文件,用stty工具查询其通信参数

1
2
#在开发板的终端执行如下命令
stty -F /dev/ttymxc2

如下图:

未找到图片

14.2.3. 关闭回显

默认串口是开启回显的 可以使用以下命令关闭回显

1
stty -F /dev/ttymxc2 -echo

14.2.4. 连接串口线及跳线帽

实验前需要使用串口线或USB转串口线把它与开发板与电脑连接起来,并且使用跳线帽连接 排针“UART3_TXD<—->T2IN”、“UART3_RXD<—->R2OUT”,如下图所示。

未找到图片10|

14.2.5. 与Windows主机通讯

14.2.5.1. 串口通讯实验

配置好串口调试助手后,尝试使用如下命令测试发送数据:

1
2
3
4
5
#在开发板上的终端执行如下指令
#使用echo命令向终端设备文件写入字符串"board"、"embedfire"
echo board > /dev/ttymxc2
echo embedfire > /dev/ttymxc2
#Windows上的串口调试助手会接收到内容

如下图:

未找到图片13|

可以看到,往/dev/ttymxc2设备文件写入的内容会直接通过串口线发送至Winodws的主机。

而读取设备文件则可接收Winodws主机发往开发板的内容,可以使用cat命令来读取:

1
2
3
4
5
6
7
#在开发板上的终端执行如下指令
#使用cat命令读取终端设备文件
cat /dev/ttymxc2
#cat命令会等待
#使用串口调试助手发送字符串
#字符串最后必须加回车!
#开发板的终端会输出接收到的内容

如下图:

未找到图片14|

14.3. 串口通讯实验(系统调用)

终端设备通过termios进行配置。同样串口终端也是通过termios配置,

14.3.1. termios结构体

源码/include/uapi/asm-generic/termbits.h 头文件中声明了串口终端相关数据结构。

termio结构体如下所示。

1
2
3
4
5
6
7
8
9
 #define NCCS 19
 struct termio {
         unsigned short c_iflag;         /* input mode flags */
         unsigned short c_oflag;         /* output mode flags */
         unsigned short c_cflag;         /* control mode flags */
         unsigned short c_lflag;         /* local mode flags */
         unsigned char c_line;           /* line discipline */
         unsigned char c_cc[NCC];        /* control characters */
 };

termios编程相关函数如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 #include <termios.h>
 #include <unistd.h>

 //读取当前串口配置参数
 int tcgetattr(int fd, struct termios *termios_p);

 //设置串口配置参数
 int tcsetattr(int fd, int optional_actions,const struct termios *termios_p);

 //获取输入波特率
 speed_t cfgetispeed(const struct termios *termios_p);

 //获取输出波特率
 speed_t cfgetospeed(const struct termios *termios_p);

 //设置输入波特率
 int cfsetispeed(struct termios *termios_p, speed_t speed);

 //设置输出波特率
 int cfsetospeed(struct termios *termios_p, speed_t speed);

 //同时设置输入输出波特率
 int cfsetspeed(struct termios *termios_p, speed_t speed);

 //清空buffer数据
 int tcflush(int fd, int queue_selector);

 //等待所有输出都被发送
 int tcdrain(int fd);

 //在一个指定的时间区内发送连续的0位流
 int tcsendbreak(int fd, int duration);

 //用于对输入和输出流控制进行控制
 int tcflow(int fd, int action);

 //将终端设置为原始模式
 void cfmakeraw(struct termios *termios_p);

14.3.2. 编写应用程序

根据上面介绍termios相关数据结构及函数,编写应用程序如下

ebf_6ull_quick_start_code/Source/tty_uart/tty_uart.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>

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

 const char default_path[] = "/dev/ttymxc2";

 /*
 *串口配置成功返回0,失败返回-1;
 */
 int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop);

 int main(int argc,char *argv[])
 {
     int fd;
     int res;
     char *path;
     char buf[1024] = "Embedfire tty send test.\n";

     if(argc > 1)
     {
         path = argv[1];
     }
     else
     {
         path = (char *)default_path;
     }

     fd = open(path,O_RDWR);
     if(fd < 0)
     {
         perror(path);
         exit(-1);
     }

     if( set_uart(fd,115200,8,'n',1) )
     {
         printf("set uart error\n");
     }

     while(1)
     {
         write(fd, buf, strlen(buf));
         //printf("send res = %d bytes data: %s",strlen(buf), buf);
         memset(buf,0,1024);
         res = read(fd, buf, 1024);
         if(res > 0)
         {
             printf("Receive res = %d bytes data: %s",res, buf);
         }
     }

     close(fd);

     return 0;
 }

 int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop)
 {

     struct termios opt;

     //清空串口接收缓冲区
     tcflush(fd, TCIOFLUSH);

     //获取串口配置参数
     tcgetattr(fd, &opt);

     opt.c_cflag &= (~CBAUD);    //清除数据位设置
     opt.c_cflag &= (~PARENB);   //清除校验位设置

     //opt.c_iflag |= IGNCR;       //忽略接收数据中的'\r'字符,在windows中换行为'\r\n'
     opt.c_iflag &= (~ICRNL);    //不将'\r'转换为'\n'

     opt.c_lflag &= (~ECHO);     //不使用回显

     //设置波特率
     switch(nSpeed)
     {
         case 2400:
             cfsetspeed(&opt,B2400);
             break;

         case 4800:
             cfsetspeed(&opt,B4800);
             break;

         case 9600:
             cfsetspeed(&opt,B9600);
             break;

         case 38400:
             cfsetspeed(&opt,B38400);
             break;

         case 115200:
             cfsetspeed(&opt,B115200);
             break;

         default:
             return -1;
     }

     //设置数据位
     switch(nBits)
     {
         case 7:
             opt.c_cflag |= CS7;
             break;

         case 8:
             opt.c_cflag |= CS8;
             break;

         default:
             return -1;
     }

     //设置校验位
     switch(nEvent)
     {
         //无奇偶校验
         case 'n':
         case 'N':
             opt.c_cflag &= (~PARENB);
             break;

         //奇校验
         case 'o':
         case 'O':
             opt.c_cflag |= PARODD;
             break;

         //偶校验
         case 'e':
         case 'E':
             opt.c_cflag |= PARENB;
             opt.c_cflag &= (~PARODD);
             break;

         default:
             return -1;
     }

     //设置停止位
     switch(nStop)
     {
         case 1:
             opt.c_cflag &= ~CSTOPB;
             break;
         case 2:
             opt.c_cflag |= CSTOPB;
             break;
         default:
             return -1;
     }
     //设置串口
     tcsetattr(fd,TCSANOW,&opt);

     return 0;
 }

最后使用直接使用arm-linux-gnueabihf-gcc进行编译即可。

1
arm-linux-gnueabihf-gcc -o  tty_uart tty_uart.c

14.4. 串口设备树插件修改

野火imx6ull提供了很多的设备树插件源码,若想要添加或修改不同的引脚作为串口, 可参考:

仓库中提供了串口2-串口8的设备树插件, 其中串口3的设备树插件文件为 imx-fire-uart3-overlay.dts ,源码如下所示

/arch/arm/boot/dts/overlays/ebf/imx-fire-uart3-overlay.dts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/dts-v1/;
/plugin/;
#include "imx6ul-pinfunc.h"
#include "dt-bindings/gpio/gpio.h"

/ {
   fragment@0 {
      target=<&uart3>;
      __overlay__ {
         pinctrl-names = "default";
         pinctrl-0 = <&pinctrl_uart3>;
         status = "okay";
      };
   };
   fragment@1 {
      target=<&iomuxc>;
      __overlay__ {
         pinctrl_uart3:uart3grp {
            fsl,pins = <
               MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0x1b0b1
               MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0x1b0b1
            >;
         };
      };
   };
};

串口设备树插件修改相对简单,若想修改相对应的引脚,只需修改代码第20-21行即可。

14.5. 参考资料

本文档主要提供有一定经验的使用者快速入门使用,对于初学者可查看以下文档:

《串口通讯与终端设备》

《stty工具命令》

设备树相关知识点请阅读下面章节的内容:

《设备树修改基础知识》

《设备树插件修改方法指导》