前言
我目前使用的是Ubuntu 20.04作为上位机的系统,其他的系统可能会在部分不一致的步骤,比如不同的配置文件位置等等,但是大体上都还是一样的。
串口通信
我使用的是minicom
作为串口通信的软件,这个软件在绝大多数的发行版软件源中都应该已经包含了。
普通用户的权限不足以打开串口,你有三种方法可以解决这个问题:
- 将当前用户添加到
dialout
用户组,即sudo usermod -a -G dialout $USER
; - 每次
sudo
; - 使用
sudo chmod +s /usr/bin/minicom
命令,使得用户始终以/usr/bin/minicom
的拥有者的身份(即root
)运行minicom
。
你只需要选择其中一个方法就可以,我个人的推荐偏好为依次递减。
接下来,你需要配置minicom,使得上位机能够与开发板正确通信,使用命令minicom -s
可以直接打开minicom
的配置界面,或者,你也可以在minicom
的界面里,使用Ctrl + A
然后按O
的组合键打开同样的界面。
在
minicom
的界面里,使用Ctrl + A
然后按Z
的组合键可以打开帮助菜单,帮助菜单内,按对应的按键也能够打开对应的功能。
在配置界面中,选择Serial port setup
进入串口配置页面,敲击每个配置项前的大写字母即可分别修改该配置项。一般而言,你只需要注意以下几个设置项:
- Serial Device:这是你想要连接到的设备,你需要事先到
/dev
目录下寻找设备,一般会是/dev/ttyUSBx
或/dev/ttySx
的形式,其中x
为数字,一般从0开始编号。 - Bps/Par/Bits: 这是串口通信的参数配置,不同的开发板有不同的配置,需要参考手册设定,但是绝大多数情况下,应当设置为
115200 8N1
,其代表波特率为115200bps,数据位8位,没有奇偶校验位,停止位1位。 - Hardware Flow Control:不同的开发板有不同的配置,需要参考手册设定,但是绝大多数情况下,应当设置为No。
- Software Flow Control:不同的开发板有不同的配置,需要参考手册设定,但是绝大多数情况下,应当设置为No。
在完成设置后,返回上级菜单,选择Save setup as dfl
即可将配置保存为默认值。
如果需要临时指定一个不同的设备进行连接,不需要每次更改串口配置,可以使用
minicom -D <设备文件>
的方式打开一个新的minicom
。在
minicom
的界面里,使用Ctrl + A
然后按X
的组合键即可退出。
ZMODEM
选用minicom
的好处是其支持使用ZMODEM直接通过终端传输文件,在2022年5月1日及更新的根文件系统中,都已经包含了ZMODEM的二进制文件sz
和rz
,可以用来发送和接收文件。注意,这里的“发送”与“接收”是从开发板的视角出发的。
事实上,ZMODEM并不局限于
minicom
或者串口,只要有一个支持的终端软件就可以通过终端收发文件,比如使用Xshell通过SSH也可以实现同样的目的。
- 发送文件:使用
sz <文件名>
命令发送,你的终端模拟器应当会处理剩下的工作,比如保存这个文件到上位机,如果你没有特别配置,那么minicom
应该会把文件保存在你运行minicom
时所处的目录。
你可以使用
sz --help
命令查看其支持的更多的配置参数。
- 接收文件:使用
rz
命令接收文件,输入命令后,开发板会进入等待阶段,在minicom
的界面里,使用Ctrl + A
然后按S
的组合键打开文件发送功能,然后选择zmodem
,并在弹出的列表中用Tag
标记你需要发送的文件,再用Okay
将文件发送到开发板。
tftp服务器
tftp服务器在嵌入式调试中很重要,因为许多的关键的文件传输都使用的tftp
,比如在U-boot
中,几乎所有的文件都是使用tftp
从上位机传输到开发板上的。
在Ubuntu 20.04中,我推荐使用tftpd-hpa
软件包实现tftp服务器功能。你需要使用apt
安装tftpd-hpa
和tftp-hpa
两个软件包。
在安装完成软件包后,你需要对上位机进行一些简单的配置:
- 你需要编辑
/etc/hosts.allow
文件,这个文件控制了网络上的哪些主机能够访问本机上的INET服务,为了简单考虑,我们允许所有的主机访问tftp服务:
tftpd:ALL
in.tftpd:ALL
- 你需要检查tftpd服务的配置文件,以Ubuntu系统上的
tftpd-hpa
软件包为例,其配置文件位于/etc/default/tftpd-hpa
,你可以修改配置以满足自己的需要,或者保持不动:
# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/var/lib/tftpboot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
TFTP_DIRECTORY
项目对应的/var/lib/tftpboot
即为tftp服务器工作的根目录,为了方便,你可以考虑将这个目录用软链接的方式链接到常用的目录,比如桌面上。
- 最后,使用
systemd
启动tftpd服务,这样,tftp服务器就配置完成了。
sudo systemctl start tftpd.socket tftpd.service
sudo systemctl enable tftpd.socket
NFS服务器
NFS文件系统需要内核的支持。
NFS是网络文件系统的缩写,在Linux上,你可以将一个NFS target挂在到本地的文件系统上,实现一种“挂载盘”一样的使用体验,你甚至可以将整个NFS target作为一个Linux的根文件系统进行使用。
NFS可以极大程度地降低嵌入式调试的工作量,不再需要反复插拔SD卡或者调用各种FTP、MODEM,你只需要在上位机的文件系统中进行文件操作,嵌入式系统中挂载的NFS便会同步更新。
在Ubuntu 20.04中,你需要安装nfs-kernel-server
软件包来安装NFS服务器。
在安装完成软件包后,你需要对上位机进行一些简单的配置:
- 你需要编辑
/etc/exports
文件,这个文件控制了NFS会将哪些目录以什么样的方式暴露给网络上的哪些主机:
/srv/nfs4 *(rw,sync,no_subtree_check,no_root_squash)
/srv/nfs4/nfsboot_rootfs *(rw,sync,no_subtree_check,no_root_squash)
对于每一行记录,第一部分代表共享的目录,这里的两个目录分别用作NFS文件共享和NFS启动根文件系统。
第二部分的括号前部分代表允许连接的客户端,*号代表允许所有人连接。
括号内的部分代表共享属性配置,rw
指允许读写,sync
指文件更改将实时在所有挂载客户端同步,no_subtree_check
指不检查父目录的权限,no_root_squash
指连接的客户端如果是以超级用户身份连接的,则其对于NFS目录也是超级用户身份。
为了方便,你可以考虑将NFS的工作目录用软链接的方式链接到常用的目录,比如桌面上。
- 最后,使用
systemd
启动nfs-kernel-server
服务,这样,NFS服务器就配置完成了。
U-boot相关
读取
uEnv.txt
需要配置U-boot支持。
U-boot是一个在嵌入式系统中广泛使用的第二阶段Bootloader(SSBL),可以以多种方式引导Linux系统启动。U-boot支持以文本文件的方式读入启动配置,实现自动无干预的启动。下面是当前的嵌入式系统使用的uEnv.txt
。
modeboot=minaduki_sdboot
uenvcmd=run minaduki_sdboot
adi_sdboot=echo Copying Linux from SD to RAM... && fatload mmc 0 0x3000000 ${kernel_image} && fatload mmc 0 0x2A00000 ${devicetree_image} && if fatload mmc 0 0x2000000 ${ramdisk_image}; then bootm 0x3000000 0x2000000 0x2A00000; else bootm 0x3000000 - 0x2A00000; fi
bootargs=console=ttyPS0,115200 root=/dev/mmcblk0p2 rw earlycon rootfstype=ext4 rootwait cpuidle.off=1
localip=192.168.3.27
remoteip=192.168.3.102
gwip=192.168.3.1
mask=255.255.255.0
nfsroot=/srv/nfs/zynq-ubuntu
tftpsetenv=setenv ipaddr ${localip}; setenv gatewayip ${gwip}; setenv netmask ${mask}; setenv serverip ${remoteip};
tftpgetimage=tftpboot 0x3000000 ${kernel_image}; tftpboot 0x2A00000 ${devicetree_image};
nfssetenv=setenv nfsargs nfsaddrs=${localip}:${remoteip}:${gwip}:${mask}
nfssetrootfs=setenv rootfs root=/dev/nfs rw nfsroot=${remoteip}:${nfsroot},vers=3
setbootargs=setenv bootargs console=ttyPS0,115200 $rootfs $nfsargs earlycon rootwait cpuidle.off=1
minaduki_netboot=run tftpsetenv; run tftpgetimage; run nfssetenv; run nfssetrootfs; run setbootargs; bootm 0x3000000 - 0x2A00000;
set_minaduki_sdboot_bootargs=setenv bootargs console=ttyPS0,115200 root=/dev/mmcblk0p3 rw earlycon rootfstype=ext4 rootwait cpuidle.off=1
minaduki_sdboot=run set_minaduki_sdboot_bootargs; run adi_sdboot;
minaduki_rescue=run adi_sdboot
Linux配置文件相关
Linux的配置文件基本位于/etc
目录下,这里仅介绍几个最基础、最essential的文件。想要了解详细的Linux启动与工作的流程可以参考方元老师的书。
初始化表
初始化表位于/etc/inittab
,系统启动程序init
在系统启动时会读取并解释执行文件的内容,下面是一个最简单的初始化表:
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::once:/usr/sbin/telnetd -l /bin/login
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
其中,第一行sysinit
对应的/etc/init.d/rcS
就是系统启动是运行的脚本文件,其他行对应的是在不同的情况下(比如按下Ctrl+Alt+Delete)时执行的命令。
Run Command
Run command即为系统启动时运行的脚本,一般而言位于/etc/init.d/
目录下,随着systemd
的逐渐流行,整个Linux的启动流程也日渐复杂化了,不过,在嵌入式系统上,因为需要运行的服务往往不是很多,所以Run command一般可以放在同一个文件内。
我一般习惯在/etc/rc
文件内撰写Run command脚本,并将其软链接到/etc/init.d/rcS
。下面是一个简单的脚本示例:
#! /bin/sh
hostname NJURadio
mount -a
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
mkdir -p /dev/pts
mkdir -p /dev/i2c
mount -t devpts devpts /dev/pts
cat /etc/motd
Message Of ToDay
今日信息位于/etc/motd
,一般保存一些特定的自定义内容,MOTD一般在终端登陆时显示,用于提示操作者。
下面是处于工作状态和救援状态的NJURadio的不同MOTD,作为示例:
工作状态:
# .88b d88. d888888b d8b db .d8b. d8888b. db db db dD d888888b
# 88'YbdP`88 `88' 888o 88 d8' `8b 88 `8D 88 88 88 ,8P' `88'
# 88 88 88 88 88V8o 88 88ooo88 88 88 88 88 88,8P 88
# 88 88 88 88 88 V8o88 88~~~88 88 88 88 88 88`8b 88
# 88 88 88 .88. 88 V888 88 88 88 .8D 88b d88 88 `88. .88.
# YP YP YP Y888888P VP V8P YP YP Y8888D' ~Y8888P' YP YD Y888888P
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# Buildroot 2021.11.3 rootfs built for NJURadio.
# Powered by MINADUKI Technologies 2022. All rights reserved.
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
救援状态:
# .88b d88. d888888b d8b db .d8b. d8888b. db db db dD d888888b
# 88'YbdP`88 `88' 888o 88 d8' `8b 88 `8D 88 88 88 ,8P' `88'
# 88 88 88 88 88V8o 88 88ooo88 88 88 88 88 88,8P 88
# 88 88 88 88 88 V8o88 88~~~88 88 88 88 88 88`8b 88
# 88 88 88 .88. 88 V888 88 88 88 .8D 88b d88 88 `88. .88.
# YP YP YP Y888888P VP V8P YP YP Y8888D' ~Y8888P' YP YD Y888888P
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# Busybox 1.35.stable rootfs compiled for NJURadio.
# Powered by MINADUKI Technologies 2022. All rights reserved.
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# You are in rescue mode. Think twice before performing any opeations!
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
文件系统表
文件系统表的自动挂载需要
init
程序的支持。
文件系统表位于/etc/fstab
,系统启动时会读取这个文件,并根据文件的内容自动挂载文件系统到对应的挂载点上,省去手动挂载的麻烦。下面是一个文件系统表的示例,文件系统表中可以指定不同类型的文件系统进行挂载。
# <file system> <mount pt> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
mdev /dev tmpfs defaults 0 0
/dev/mmcblk0p1 /boot vfat defaults 0 2
/dev/mmcblk0p2 / ext4 defaults,noatime 0 2
/dev/mmcblk0p4 /nju ext4 defaults,noatime 0 2
192.168.3.102:/srv/nfs /nfs nfs defaults,noauto 0 0
环境文件
环境文件分为系统全局的和用户的,不同shell往往对应了不同的文件。系统全局的环境配置文件一般在/etc
目录下,用户定义的环境配置文件一般在$HOME/
目录下。
用户可以在环境配置文件中输入一些自定义的alias或者source其他的环境文件,实现简化的配置文件,比如,我在自己的.zshrc
中加入了这两句alias,实现快速切换Vivado开发环境。
alias vivado18="source /tools/Xilinx/Vivado/2018.3/settings64.sh"
alias vivado21="source /tools/Xilinx/Vivado/2021.2/settings64.sh"
解释:内核与根文件系统
根文件系统是内核启动时挂载的第一个文件系统,文件系统内提供了运行时必要的可执行文件和挂载点,是用户与OS交互的主要工具。
一个常见的误区是,根文件系统经常被人和内核放在一起提及,可能的原因是,市面上常见的Linux发行版几乎都是将内核与根文件系统同时提供给用户的。然而这两者的关联并不紧密,根文件系统和系统内核可以由完全不同的厂家提供(在嵌入式领域尤为如此),可以具有完全不同的配置方法,同时根文件系统提供的是用户态的可执行程序,从本质上讲和用户自己编写的程序是一样的,区别于系统内核,而且一般来讲,根文件系统的“兼容性”相对更强,只要二进制格式正确,配置正确,同样的根文件系统可以在多种嵌入式平台上正常运行,而这显然是内核做不到的。
由于内核编译与rootfs制作有单独的文章进行解释,在此不再赘述。