6. 串口通讯

本章配套视频介绍:

../../../_images/video.png

《30-在鲁班猫上使用UART串口》

https://www.bilibili.com/video/BV1us4y1v7fC/

本章主要讲解LubanCat系列板卡的40pin引脚中串口的基本使用。

6.1. 串口引脚关系

其中串口的引脚关系如下表所示:

串口

引脚

功能

TXD

8

发送信号线

RXD

10

接受信号线

对应实物的40pin接口如下:

未找到图片
  • LubanCat-Zero系列使用的是uart8。

  • LubanCat-1系列和LubanCat-2系列使用的是uart3。

虽然接口名字不一样,但是操作方式是一样的。

6.2. 使能串口接口

串口在默认情况是关闭状态的,需要使能才能使用。

6.2.1. 方法一

1
2
3
4
5
#进入工具配置
sudo fire-config

#移动光标到下图的位置
#按确认键进入配置
未找到图片

打开串口:

  1. 使用方向键移动光标到 UART

  2. “空格键” 选中UART(出现 “*” ),如下图。

  3. “确认键” 进行设置。

  4. “Esc键” 退出到终端,运行 sudo reboot 进行重启应用。

未找到图片

6.2.2. 方法二

可以通过打开 /boot/uEnv/uEnv.txt ,查看是否启用了uart相关设备设备树插件。

编辑文件,将带有 uart (以uart3为例) 的那一行的注释符号去掉,如下图:

未找到图片

配置完成后重启激活配置。

注解

如果是直接拔电源的方式重启,会有可能出现文件没能做出修改 (原因:文件未能及时从内存同步到存储设备中,解决方法,在终端上输入 “sync” 再拔电关机)。

6.3. 检查串口设备

查看串口有没有成功使能

1
2
#执行命令查看终端设备
ls /dev/tty*
  • LubanCat-Zero系列使用的是ttyS8。

  • LubanCat-1系列和LubanCat-2系列使用的是ttyS3。

如下图,此处为LubanCat-1对应ttyS3:

未找到图片

6.4. 串口通讯实验(Shell)

本次实验以LubanCat-1板卡讲解,其他板卡的操作和本实验类似,这里就不过多赘述了。 使用板卡上的串口3进行实验,对应的设备文件为/dev/ttyS3。 对tty的设备文件直接读写就可以控制设备通过串口接收或发送数据,下面我们使用板卡配合Windows下的串口调试助手或Linux下的minicom进行测试。

6.4.1. 连接串口

实验前需要使用串口线或USB转串口线把它与板卡与电脑连接起来。

1
2
3
4
板卡 -  电脑
TXD --- RXD
RXD --- TXD
GND --- GND

实物连接如下图:

未找到图片

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

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

1
2
#在板卡的终端执行如下命令
stty -F /dev/ttyS3

如下图:

未找到图片

6.4.3. 修改串口波特率

以修改波特率为115200为例:

1
2
 #设置通讯速率,其中ispeed为输入速率,ospeed为输出速率
 stty -F /dev/ttyS3 ispeed 115200 ospeed 115200

如下图:

未找到图片

6.4.4. 关闭回显

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

1
stty -F /dev/ttyS3 -echo

6.5. 与Windows主机通讯

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

1
2
3
4
5
#在板卡上的终端执行以下指令
#使用echo命令向终端设备文件写入字符串"Hello!"、"I'm lubancat"
echo Hello! > /dev/ttyS3
echo "I'm lubancat" > /dev/ttyS3
#Windows上的串口调试助手会接收到内容

如下图:

未找到图片13|

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

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

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

如下图:

未找到图片14|

6.6. 原始模式与规范模式

默认是需要发送换行符cat命令才能接收到数据的,如果要修改不需要换行符也能接收数据可使用以下命令:

1
2
3
4
5
6
7
8
9
#切换为原始模式,关闭行缓冲,实时传输
stty -F /dev/ttyS3 raw

#更详细的命令,配置串口ttyS8 raw模式、9600波特率、8数据位、1停止位、无奇偶校验
stty -F /dev/ttyS3 raw speed 9600 cs8 -cstopb -parenb


#如需恢复,切换为规范模式
stty -F /dev/ttyS3 sane

Linux把串口当作“终端设备”来管理,默认启用规范模式,这个模式的核心特点是:

  • 终端驱动会在底层缓冲输入的数据,不会立刻把数据传给cat这类读取程序。

  • 只有当检测到换行符(n)、回车符(r)、EOF(结束符)等 “行终止符” 时,才会把缓冲的一整行数据一次性交给cat,此时才能在终端看到打印结果。

而原始模式特点是:

  • 终端驱动不做任何缓冲、不解析任何字符,比如换行符、回车符都只是普通字节。

  • 只要串口收到 1 个字节的数据,终端驱动就立刻把这个字节传给cat,cat读取后马上打印,实现 “实时监控”。

6.7. 使用minicom通讯

1
2
3
4
5
#安装minicom软件包
sudo apt install minicom

#设置串口
sudo minicom -s

如下图:

未找到图片

进入设置,修改串口或者波特率, 按键盘上的字母 进入各自要设置的东西,enter键确认。 如按下键盘的“A”,即可跳转到“A - Serial Device”配置行。

未找到图片

设置完成后,可以选择“Save setup as dfl”一行保存配置,保存后,以后再打开不用进行设置。

未找到图片

然后按exit键进入minicom的终端。

未找到图片

如果输入字母,屏幕上没反应,可以通过打开回显来显示, 先按“ctrl + A” 再按’z’键进入菜单。

未找到图片

按下’e’,回显就打开成功了(左下角会有提示,打开或者关闭),可以按下按键测试一下,观察是否有回显。 我们还可以先按“ctrl + A” 再按’z’键,再按’c’键来清除屏幕, 将板卡和电脑用串口线连起来,同时设置为115200, 在板卡上,输入”Hello! i’m lubancat!’’

未找到图片

注意

使用minicom时,不能使用退格键把发出去的内容删掉, minicom是以单个字符的方式发送的。

在pc端发送文字,在板卡端能接收到相应信息。

未找到图片
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
minicom的菜单有很多功能,
s ---- 发送文件
p ---- 设置通讯参数,包括一些预设的波特率,数据格式,数据位等
l ---- 就可以将log信息保存到一个文件中了,方便查看
t ---- 设置终端参数,以及键位设置
w ---- 超出一行的数据后自动换行
r ---- 接收文件
a ---- 换行发送时会增加时间戳
n ---- 增加时间戳
c ---- 清除屏幕
o ---- 设置minicom,相当于sudo minicom -s
j ---- 休眠状态
x ---- 退出的同时复位
q ---- 退出

也可以不用通过按 "ctrl + a" + 'z' + '?'的方式设置
而是直接使用"ctrl + a"  + '?'

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

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

6.8.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);

6.8.2. 编写应用程序

6.8.2.1. 规范模式

根据上面介绍termios相关数据结构及函数,编写应用程序如下,以下为规范模式,需接收换行符才可打印数据:

代码位置
1
quick_start/uart/uart.c

代码较长复制粘贴容易乱序,可以下载我们提供的源码 uart.c

quick_start/uart/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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <string.h>
#include <sys/ioctl.h>

//第一部分代码/
//根据具体的设备修改
const char default_path[] = "/dev/ttyS3";

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;

    //获取串口设备描述符
    printf("This is tty/usart demo.\n");
    fd = open(path, O_RDWR);
    if (fd < 0) {
        printf("Fail to Open %s device\n", path);
        return 0;
    }


    //第三部分代码/
    struct termios opt;
    //清空串口接收缓冲区
    tcflush(fd, TCIOFLUSH);
    // 获取串口参数opt
    tcgetattr(fd, &opt);
    //设置串口输出波特率
    cfsetospeed(&opt, B9600);
    //设置串口输入波特率
    cfsetispeed(&opt, B9600);
    
    //设置数据位数
    opt.c_cflag &= ~CSIZE;
    opt.c_cflag |= CS8;
    //校验位
    opt.c_cflag &= ~PARENB;
    opt.c_iflag &= ~INPCK;
    //设置停止位
    opt.c_cflag &= ~CSTOPB;
    //更新配置
    tcsetattr(fd, TCSANOW, &opt);
    printf("Device %s is set to 9600bps,8N1\n",path);

    //第四部分代码/
    do {
        //发送字符串
        write(fd, buf, strlen(buf));
        //接收字符串
        res = read(fd, buf, 1024);
        if (res >0 )
        //给接收到的字符串加结束符
        buf[res] = '\0';
        printf("Receive res = %d bytes data: %s\n",res, buf);
    } while (res >= 0);

    printf("read error,res = %d",res);
    close(fd);
    return 0;
}

最后使用直接使用gcc进行编译即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#在板卡使用gcc编译
gcc -o uart uart.c

#运行
sudo ./uart

#如果板卡上没有安装gcc,可以使用以下命令安装
sudo apt install gcc

#也可以用交叉编译的方式编译出的文件拷贝到板卡上执行即可

6.8.2.2. 原始模式

根据上面介绍termios相关数据结构及函数,编写应用程序如下,以下为原始模式,无需接收换行符即可打印数据:

代码位置
1
quick_start/uart/uart_raw.c

代码较长复制粘贴容易乱序,可以下载我们提供的源码 uart_raw.c

quick_start/uart/uart_raw.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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <string.h>
#include <sys/ioctl.h>

//第一部分代码/
//根据具体的设备修改
const char default_path[] = "/dev/ttyS3";

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;

    //获取串口设备描述符
    printf("This is tty/usart demo (Raw Mode).\n");
    fd = open(path, O_RDWR | O_NOCTTY);  // O_NOCTTY:不将串口设为控制终端
    if (fd < 0) {
        printf("Fail to Open %s device\n", path);
        return -1;
    }


    //第三部分代码/
    struct termios opt;
    //清空串口接收缓冲区
    tcflush(fd, TCIOFLUSH);
    //获取串口参数opt
    if (tcgetattr(fd, &opt) != 0) {
        printf("Failed to get serial attributes\n");
        close(fd);
        return -1;
    }

    // -------------------------- 原始模式核心配置 --------------------------
    // 关闭规范模式,取消行缓冲、回显、信号处理
    // ICANON:关闭规范模式;ECHO/ECHOE:关闭回显;ISIG:关闭信号,如Ctrl+C
    opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    
    // 关闭软件流控、回车/换行转换等输入处理
    // IXON/IXOFF/IXANY:关闭软件流控;ICRNL/INLCR/IGNCR:取消回车<->换行转换
    opt.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL | INLCR | IGNCR);
    
    // 关闭输出处理,原始输出,不修改任何字符
    opt.c_oflag &= ~OPOST;
    
    // 设置读取规则:VMIN=1,至少读1个字符才返回,VTIME=0,无超时,阻塞等待
    opt.c_cc[VMIN] = 1;   // 最小读取字符数:有1个字符就返回
    opt.c_cc[VTIME] = 0;  // 超时时间:0表示无限阻塞,直到有数据
    // ---------------------------------------------------------------------

    //设置串口输出波特率
    cfsetospeed(&opt, B9600);
    //设置串口输入波特率
    cfsetispeed(&opt, B9600);
    
    // 设置数据位数:8位
    opt.c_cflag &= ~CSIZE;
    opt.c_cflag |= CS8;
    // 校验位:无校验
    opt.c_cflag &= ~PARENB;
    opt.c_iflag &= ~INPCK;
    // 停止位:1位
    opt.c_cflag &= ~CSTOPB;
    // 启用接收器,忽略调制解调器状态线
    opt.c_cflag |= CREAD | CLOCAL;

    // 更新配置,TCSANOW:立即生效
    if (tcsetattr(fd, TCSANOW, &opt) != 0) {
        printf("Failed to set serial attributes\n");
        close(fd);
        return -1;
    }
    printf("Device %s is set to 9600bps,8N1 (Raw Mode)\n", path);

    //第四部分代码/
    do {
        // 发送字符串
        write(fd, buf, strlen(buf));
        // 接收字符串,原始模式下,有1个字符就会返回
        res = read(fd, buf, sizeof(buf)-1);  // 留1位给结束符,避免越界
        if (res > 0) {
            // 给接收到的字符串加结束符
            buf[res] = '\0';
            printf("Receive res = %d bytes data: %s\n", res, buf);
        } else if (res < 0) {
            perror("Read error");
            break;
        }
        usleep(100000);
    } while (res >= 0);

    printf("Exit, res = %d\n", res);
    close(fd);
    return 0;
}

最后使用直接使用gcc进行编译即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#在板卡使用gcc编译
gcc -o uart_raw uart_raw.c

#运行
sudo ./uart_raw

#如果板卡上没有安装gcc,可以使用以下命令安装
sudo apt install gcc

#也可以用交叉编译的方式编译出的文件拷贝到板卡上执行即可

6.9. 更多

更多关于uart的使用以及资料可以查阅: rk开源手册/Common/UART 。

6.10. 参考资料

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

《imx6ull : 串口通讯与终端设备》

《LubanCat RK系列 : 串口通讯》

《stty工具命令》

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

《设备树修改基础知识》

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

rk开源资料(github): 《开源手册》