目录
之前使用香橙派, 都是直接在香橙派上进行代码编译, 但在实际的项目开发过程中,更多的还是使用交叉编译环境进行代码的编译。再编译完成之后再把代码放到香橙派等ARM开发板上运行。因此这章节专门讲解下交叉编译环境的搭建、Uboot移植和内核移植内容
1. OrangePi Zero2 SDK说明
SDK 全称 Software Development Kit,即软件开发工具包。一般包括了一些工具(如交叉编译工具链)、库、文档和示例代码。香橙派的Linux SDK其实指的就是 orangepi-build 这套代码集,orangepibuild 在脚本和配置文件中会指定 u-boot、Linux内核和交叉编译工具链的地址,运行 orangepi-build时,当其发现本地没有这些东西,会自动去相应的地方下载的。使用 orangepi-build 可以编译出多个版本的 Linux 镜像。
1.1 使用环境要求(使用虚拟机运行Ubuntu22.04版本镜像)
新版本的orangepi-build对编译主机(也就是搭建的vmware 虚拟机)的要求:新版本的orangepibuild是在Ubuntu22.04的x64电脑或者虚拟机上运行的。
- 确认自己的虚拟机或者Ubuntu实体机是不是22.04的方法如下:
lsb_release -a
- 如果不是,可从ubuntu官网重新下载ubuntu 22.04 x64镜像,重新更新下虚拟机,ubuntu 22.04X64镜像下载地址:(也可以从清华源网站下载,速度会快点)
http://releases.ubuntu.com/22.04/ubuntu-22.04.3-desktop-amd64.iso
- 编译出来的完整SDK大概有16 G大小,因此建议在创建虚拟机时,至少分配50G的存储给虚拟机使
用。
1.2 获取Linux SDK
方法一:github 下载
- 从 github 下载 orangepi-build(要求网络要好,因此当前不是特别推荐)搭建完虚拟机后, 从github下载香橙派Linux SDK:
sudo apt update
sudo apt install git
git clone https://github.com/orangepi-xunlong/orangepi-build.git -b next
当然,如果git clone失败, 也可以直接访问该网址, 把orangepi-build的压缩包下载下来后,再放到编译主机里。
然后解压下载下来的压缩包:
unzip orangepi-build-next.zip
-
orangepi-build 下载完后会包含下面的文件和文件夹
a. build.sh: 编译启动脚本, 我们可以通过build.sh编译uboot、内核、根文件系统甚至完整的img
b. external: 包含编译镜像需要用的配置文件、特定的脚本以及部分程序的源码等
c. LICENSE: GPL 2 许可证文件
d. README.md: orangepi-build 说明文件
e. scripts: 编译 linux 镜像的通用脚本 -
解压完后,需要去修改orangepizero2的配置脚本
vi ./external/config/sources/families/sun50iw9.conf
修改nex)分支里的内核配置版本(默认是6.1.y这里改成5.16.y版本),不然6.1.y默认编译出来的内核默认没有无线网卡、I2C也不支持。
## For Linux5.16.y
KERNELBRANCH="branch:orange-pi-5.16-sunxi64"
LINUXCONFIG="linux-5.16-sun50iw9-current"
## For Linu6.1.y
#KERNELBRANCH="branch:orange-pi-6.1-sun50iw9"
#LINUXCONFIG="linux-6.1-sun50iw9-next"
方法二:从百度网盘下载(推荐使用该方法, 就不用考虑github下载过程中出现下载失败导致最后编译出来的系统可能是异常的)
- 从下面的百度网盘链接下载提前编译好的oragepi-build SDK包:
链接:https://pan.baidu.com/s/1y3db5GZFPZSnyYOCrKQcmQ
提取码:92gt
下载下来后是几个拆分好的压缩包, 如下所示:
ls
orangepi-build-ok.tar.gz_00 orangepi-build-ok.tar.gz_01 orangepi-buildok.tar.gz_02
- 这个时候可以用如下的命令进行合并解压:
cat orangepi-build-ok.tar.gz_0* > orangepi-build-ok.tar.gz
tar -xvf orangepi-build-ok.tar.gz
1.3 首次编译完整SDK
下载完源码后, 即可用build.sh进行首次编译.(注:如果是使用方法二:从百度网盘下载后, 由于已经缓存了交叉编译工具链、uboot和内核源码, 可以不运行build.sh脚本 )
- 运行 build.sh 脚本,记得加 sudo 权限
sudo ./build.sh
- 选择Full OS image for flashing 进行完整镜像的编译
- 选择不修改配置
- 根据实际的香橙派派开发版的型号, 选orangepizero2
- 选择根文件系统类型, 这边选择ubuntu 22.04 的根文件系统, 也就是jammy
- 选择带桌面环境,即接入HDMI显示器后,是有桌面显示的
- 最后选择桌面环境,这边选择xfce
- 这里的软件主要是一些额外第三方软件包的安装, 都不选择,直接ok开始编译
- 如果是第一次运行 orangepi-build 中的 build.sh 脚本时会自动下载交叉编译工具链、u-boot 和
linux 内核源码,成功编译完一次 linux 镜像后在 orangepi-build 中可以看到 的文件和文件夹有:
a. build.sh: 编译启动脚本
b. external: 包含编译镜像需要用的配置文件、特定功能的脚本以及部分程序 的源码,编译镜像过程中缓存的 rootfs 压缩包也存放在 external 中
c. kernel: 存放 linux 内核的源码,内核源码的文件夹的名字请不要手动修改,如果修改了,编译系统运行时会重新下载内核源码
d. LICENSE: GPL 2 许可证文件
e. README.md: orangepi-build 说明文件
f. output: 存放编译生成的 u-boot、linux 等 deb 包、编译日志以及编译生成的镜像等文件
g. scripts: 编译 linux 镜像的通用脚本
h. toolchains: 存放交叉编译工具链
i. u-boot: 存放 u-boot 的源码,u-boot 源码的文件夹的名字请不要手动修改,如果修改了,编译系
统运行时会重新下载 u-boot 源码
j. userpatches: 存放编译脚本需要用到的配置文件。
- 经过漫长等待编译完成后, 会在orangepi-build/output/images/Orangepizero2_3.1.0_ubuntu_jammy_desktop_xfce_linux5.16.17/下Orangepizero2_3.1.0_ubuntu_jammy_desktop_xfce_linux5.16.17.img镜像。 可以直接拿这个img 烧入到SD卡中运行
2.交叉编译工具链配置
2.1 关于编译
编译是指将源代码文件(如C/C++文件)经过预处理、编译、汇编和链接等步骤,转换为可执行文件的过程。将源代码转换成机器代码的过程称为编译(Compile),编译的工作需要编译(Complier)来完成。
2.2 本地编译(X86平台在这里是指我们个人电脑PC机上虚拟机中运行的Ubuntu镜像,ARM平台在这里指的是我们的orangepi)
本地编译是指在当前的编译平台上,生成能在当前平台上运行的可执行文件。例如,在x86平台上,使用x86平台上的工具,开发针对x86平台本身的可执行程序,这个编译过程称为本地编译。
以一个简单的例子来说明本地编译,假设有一个hello.c文件,它包含以下内容:
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
我们想要在x86平台上进行本地编译,并在x86平台上运行这个程序。可以使用以下命令:
gcc -o hello hello.c
运行:
./hello
输出结果为:
Hello, world!
2.3 交叉编译
交叉编译是指在当前的编译平台上,生成能在体系结构不同的另一种目标平台上运行的可执行文件。例如,在x86平台上,使用针对ARM平台的工具,开发针对ARM平台的可执行程序,这个编译过程称为交叉编译。
以一个简单的例子来说明本地编译,假设有一个hello.c文件,它包含以下内容:
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
想要在x86平台上进行交叉编译,并在ARM平台上运行这个程序。首先需要在家目录下的.bashrc最后配置添加交叉编译工具链
vi .bashrc
最后面添加以下内容,用来将ARM架构下的GCC编译工具(交叉编译工具)的环境变量导入
export PATH=$PATH:/home/$(whoami)/orangepi-build/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin
然后断开重连桌重新登陆后, 执行export指令,即可看到最新导入的PATH环境变量
export
declare -x DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
declare -x DISPLAY="localhost:10.0"
declare -x HOME="/home/pg"
declare -x LANG="en_US.UTF-8"
declare -x LC_ADDRESS="zh_CN.UTF-8"
declare -x LC_IDENTIFICATION="zh_CN.UTF-8"
declare -x LC_MEASUREMENT="zh_CN.UTF-8"
declare -x LC_MONETARY="zh_CN.UTF-8"
declare -x LC_NAME="zh_CN.UTF-8"
declare -x LC_NUMERIC="zh_CN.UTF-8"
declare -x LC_PAPER="zh_CN.UTF-8"
declare -x LC_TELEPHONE="zh_CN.UTF-8"
declare -x LC_TIME="zh_CN.UTF-8"
declare -x LESSCLOSE="/usr/bin/lesspipe %s %s"
declare -x LESSOPEN="| /usr/bin/lesspipe %s"
declare -x LOGNAME="pg"
declare -x MOTD_SHOWN="pam"
declare -x OLDPWD="/home/pg/orangepi-build/toolchains/gcc-arm-9.2-2019.12-x86_64-
aarch64-none-linux-gnu/bin"
declare -x
PATH="/home/pg/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin
:/bin:/usr/games:/usr/local/games:/snap/bin:/home/pg/orangepibuild/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linuxgnu/bin:/home/pg/orangepi-build/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-
none-linux-gnu/bin"
同时执 aarch64-none-linux-gnu-gcc --version 可以看到对应的版本号
aarch64-none-linux-gnu-gcc --version
aarch64-none-linux-gnu-gcc (GNU Toolchain for the A-profile Architecture 9.2-
2019.12 (arm-9.10)) 9.2.1 20191025Copyright (C) 2019 Free Software Foundation,
Inc.This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
在x86平台上进行交叉编译,可以使用以下命令
aarch64-none-linux-gnu-gcc -o hello hello.c
利用file命令可以看到编译出来的程序是ARM aarch64的二进制程序
file hello
这时候需要将该文件拷贝到比如香橙派等ARM开发板上运行, 在X86宿主机上是无法正常运行的
scp hello orangepi@192.168.1.28:/home/orangepi
3. Makefile的入门
3.1 编译工具及构建工具介绍
在之前的课程,都是直接使用gcc对代码进行编译,这对简单的工程是可以的,但当我们遇到复杂的工程时,每次用gcc等编译工具去操作就会显得很低效。因此make工具就出现了, make的出现是为了解决手动编译和链接大型工程的问题,它可以避免重复的工作,提高效率,保证正确性。make工具就根据makefile中的命令进行编译和链接的。但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,因此更高级的一些构建系统或者工具工具像cmake、qmake、ninja和auto make就出现了,它们可以根据一些配置文件来自动化编译和链接软件项目。
cmake是一个跨平台的构建系统,它可以根据CMakeLists.txt中的指令来生成不同平台和工具的工程文件,例如Makefile、Visual Studio解决方案、Ninja文件等。cmake可以支持多种语言和多种架构,它还提供了一些高级功能,如测试、打包、安装等。
qmake是一个用于Qt项目的构建系统,它可以根据.pro或.pri中的指令来生成Makefile或其他形式的工程文件。
ninja是一个小巧而快速的构建工具,它可以根据ninja.build中的规则来执行编译和链接命令。ninja主要关注于性能和效率,它可以利用多核处理器和并行处理来加速构建过程。ninja通常不需要用户直接编写配置文件,而是由其他构建系统(如cmake)来生成。
auto make是一个用于生成Makefile.in文件的工具,Makefile.in是一种用于auto conf的配置文件格式,auto conf是一个用于生成configure脚本的工具。configure脚本是一个用于检测系统环境并生成最终的Makefile文件的脚本Makefile.am是一种用于auto make的配置文件格式,它包含了一些指令和变量,用于定义程序或库的源文件、目标文件、依赖关系和编译选项等。
make是一个经典而通用的构建工具,它可以根据Makefile中的规则来执行编译和链接命令。make可以支持多种平台和工具,它还提供了一些高级功能,如条件判断、函数调用、模式匹配。
3.2 Makefile的简单讲解
3.2.1 编译的四个阶段
回顾下编译的四个过程:预处理(Pre-Processing)、编译(Compiling)、汇编 (Assembling)、链接(Linking)
3.2.2 Makefile的规则
a. 基本规则
target ... : prerequisites ...
<tab缩进>command
<tab缩进>...
<tab缩进>...
target 也就是一个目标文件,可以是 Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的讲“伪目标”中会有叙述。prerequisites 就是,要生成那个 target 所需要的文件或是目标。command 也就是 make 需要执行的任意shell命令。
Makefile一个示例:
debug:
@echo "hello world"
如果我们要编译下面这个最简单的例子:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("hello world!\n");
return 0
}
Makefile修改如下:
debug:
@echo "hello world"
test:
gcc -o test test.c
执行命令make test 可以生成 test文件, 执行make debug可以输出“hello world”:
test@test:~/makefiletest$ make debug
hello world
test@test:~/makefiletest$ ls
Makefile test.c
test@test:~/makefiletest$ make test
gcc -o test test.c
test@test:~/makefiletest$ ls
Makefile test test.c
b. 伪目标
如果一个目标和一个实际文件同名,那么make会认为该目标已经是最新的,不需要重新生成,也不会执行其命令。通过将目标声明为伪目标,可以避免这种情况,强制执行其命令。
debug:
@echo "hello world"
test:
gcc -o test test.c
.PHONY: debug
c. 变量赋值和预定义变量
Makefile中的变量赋值运算符有四种,分别是=、:=、?=和+=, $符号表示取变量的值,当变量名多于一个字符时,使用"( )"。
= 表示延迟展开赋值,即变量的值是在使用时才确定,可能会受到后面的赋值影响。例如,VAR_A = A,VAR_B = $(VAR_A) B,VAR_A = AA,那么最后VAR_B的值是AAB,而不是AB。
:= 表示直接赋值,即变量的值是在定义时就确定,不会受到后面的赋值影响。例如,VAR_A := A,
VAR_B := $(VAR_A) B,VAR_A := AA,那么最后VAR_B的值是AB,而不是AAB。
?=表示条件赋值,即只有当变量没有被赋值时,才使用等号后面的值作为变量的值。例如,VAR ?=
new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,否则保持原来的值不变。
+= 表示追加赋值,即将等号后面的值追加到变量原来的值之后,形成一个新的值。例如,VAR +=
new_value,如果VAR在之前没有被赋值,那么VAR的值就为new_value,如果VAR在之前被赋值为
old_value,那么VAR的值就为old_value new_value
$符的其他用法:
$^ 表示所有的依赖文件
$@ 表示生成的目标文件
$< 代表第一个依赖文件
d. 注释和换行符
采用#进行一行注释
采用\作为续行符
e. 变量的替换引用
语法格式是:
$(var:a=b)或${var:a=b}
表示把变量var的值中的a后缀替换成b后缀。例如:
src := a.c b.c c.c
obj := $(src:c=o)
把变量src的值中的.c后缀替换成.o后缀,赋值给变量obj,结果是:
obj := a.o b.o c.o
举例:
# 这是一个Makefile的注释
TARGET = hello #TARGET延迟赋值hello
CC := gcc #CC立即赋值gcc
CC += -g #CC追加赋值-g, gcc -g表示添加调试信息,可用于gdb的调试
SRC = hello.c
OBJ = $(SRC:.c=.o) #变量的替换引用,把hello.c的.c替换成.o
debug :
@echo "hello world"
echo $(SRC)
echo $(OBJ)
$(TARGET): $(SRC)
$(CC) -o $@ $<
# $(CC) -o ${TARGET} hello.c
compile: $(TARGET)
clean:
@rm hello hello.o -r
.PHONY: clean compile
f. 常见函数
Makefile函数的基本格式是:$(functionname param1,param2...)
或者是${functionname param1,param2...}
,其中,functionname是函数名,param是函数的参数,参数之间要用逗号分隔开,参数和函数名之间使用空格分开。调用函数的时候要使用字符“$”,后面可以跟小括号或者大括号。
1) wildcard 通配符
Makefile中的wildcard 是一个函数,用于扩展通配符,返回与通配符匹配的文件列表。通配符是一种特殊的字符,可以表示多个文件名或目录名,常见的通配符有 * 和 ?,分别表示任意长度的任意字符和单个任意字符。格式如下:
$(wildcard argments)
比如*.c 表示所有以 .c 结尾的文件名,a?.txt 表示所有以 a 开头,中间有一个任意字符,以 .txt 结尾的文件名。例如:
SRC = $(wildcard src/*.c)
表示查找并返回src目录下所有的.c文件, *表示通配符, 匹配一个或者多个任意字符。
2)shell
$(shell <cmd> <args>)
cmd: 执行命令名称
args:参数列表
返回值: 返回命令执行结果
例如:
SRC = $(shell find . -name *.c)
表示查找当前目录及子目录下的所有.c文件结尾的代码源文件
3) patsubst替换函数
$(patsubst pattern,replacement,text)
pattern: 是一个包含通配符 % 的模式,表示匹配任意长度的任意字符
replacement: 是一个替换字符串,也可以包含 %,表示用 pattern 中匹配的字符替换
text: 是一个要处理的文本,可以包含多个以空格分隔的单词
返回值:patsubst 函数会在 text 中找到所有符合 pattern 的单词,并用 replacement 替换它们,然后
返回替换后的文本
例如,如果有一个变量 src,它的值是:
src = a.c b.c c.c
想把它的值中的所有 .c 后缀替换成 .o 后缀,可以这样写:
obj = $(patsubst %.c,%.o,$(src))
这样,obj 的值就是:
obj = a.o b.o c.o
4) subst替换函数
$(subst from,to,text)
from: 是要被替换的字符或单词
to: 是替换后的字符或单词
text: 是要处理的字符串
返回值:subst 函数会在 text 中找到所有的 from,并用 to 替换它们,然后返回替换后的字符串
例如:
$(subst ee,EE,feet on the street)
返回:
fEEt on the strEEt
综合举例,测试工程代码目录:
test@test:~/makefiletest$ tree
.
├── Makefile
└── src
└── test.c
Makefile内容:
CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))
debug:
@echo "hello world"
echo $(SRC)
echo $(TARGET)
$(TARGET): $(SRC)
mkdir -p obj
$(CC) -o $@ $<
compile: $(TARGET)
clean:
@rm obj -r
.PHONY: clean compile
test.c内容:
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
执行:
make compile
生成obj/test:
pg@pg-Default-string:~/makefiletest$ tree -a
.
├── Makefile
├── obj
│ └── test
└── src
└── test.c
5)dir函数
$(dir NAMES...)
dir 函数是一个用于从文件名序列中提取目录部分的函数
优化Makefile内容:
CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))
debug:
@echo "hello world"
echo $(SRC)
echo $(TARGET)
$(TARGET): $(SRC)
mkdir -p $(dir $(TARGET))
$(CC) -o $@ $<
compile: $(TARGET)
clean:
@rm $(dir $(TARGET)) -r
.PHONY: clean compile
6)suffix函数
$(suffix <names...>)
功能:从文件名序列中取出各个文件名的后缀。
返回值:返回文件名序列的后缀序列,如果文件没有后缀,则返回空字串。
例如:
$(suffix src/foo.c src-1.0/bar.c hacks)
返回:
.c .c
7)basename函数
$(basename <names...>)
功能:从文件名序列中取出各个文件名的前缀部分。
返回值:返回文件名序列的前缀序列,如果文件没有前缀,则返回空字串。
例如:
$(basename src/foo.c src-1.0/bar.c hacks)
返回:
src/foo src-1.0/bar hacks
8) addsuffix函数
$(addsuffix <suffix>,<names...>)
功能:把后缀加到中的每个单词后面。
返回:返回加过后缀的文件名序列。
例如:
$(addsuffix .c,foo bar)
返回值:
foo.c bar.c
9)addprefix函数
$(addsuffix <prefix>,<names...>)
功能:把前缀加到中的每个单词后面。
返回值:返回加过前缀的文件名序列。
例如:
$(addprefix src/,foo bar)
返回值:
src/foo src/bar
10)foreach函数
$(foreach <var>,<list>,<text>)
功能:把list中使用空格分割的单词依次取出并赋值给变量var, 然后执行text表达式。
例如:
files := foo bar baz
files-with-c := $(foreach file,$(files),$(file).c)
11)条件判断语言
Makefile条件判断有下面几种:
- ifeq/ifneq语句
ifeq语句 : 判断参数 是否相等,相等为 true,否则是 false。
ifeq (arg1, arg2)
#arg1 arg2 相等执行这里的语句
else
#arg1 arg2 不相等执行这里的语句
endif
ifneq语句:判断参数 是否不等,不等为 true, 否则为 false。
ifneq (arg1, arg2)
#arg1 arg2 不相等执行这里的语句
else
#arg1 arg2 相等执行这里的语句
endif
- ifdef/ifndef语句
ifdef 语句: 判断参数 是否有值 ,有值为 true, 否则是 false。
ifndef : 判断参数 是否没有值 ,没有值为 true, 否则为 false。
ifdef:
ifdef var
#如果定义了var,执行这里的内容
else
#如果没定义var,执行这里的内容
endif
ifndef:
infdef var
#如果没定义var,执行这里的内容
else
#如果定义var,执行这里的内容
endif
4. 交叉编译wiringOP库
以前我们是直接在香橙派里直接编译的wiringOP库,这一节讲解利用交叉编译工具链配置出来的交叉编译工具编译wiringOP库。
- 修改build.sh脚本,在echo “WiringPi Library” 之前添加:
mkdir $PWD/_INSTALL/usr/local/bin -p
mkdir $PWD/_INSTALL/usr/local/include -p
mkdir $PWD/_INSTALL/usr/local/lib -p
- 修改devLib/Makefile、gpio/Makefile、wiringPiD/Makefile
将所有Makefile中的CC := gcc 改成 CC := aarch64-none-linux-gnu-gcc
- 修改devLib/Makefile、gpio/Makefile、wiringPiD/Makefile 、wiringPi/Makefile
修改DESTDIR?=/usr 替换为DESTDIR?= $(shell pwd)/../_INSTALL/usr
- 修改wiringPi/Makefile
$Q ln -sf $(DESTDIR)$(PREFIX)/lib/libwiringPi.so.$(VERSION)
$(DESTDIR)/lib/libwiringPi.so
修改为:
$Q ln -sf $(DESTDIR)$(PREFIX)/lib/libwiringPi.so.$(VERSION)
$(DESTDIR)$(PREFIX)/lib/libwiringPi.so
- 修改devLib/Makefile
$Q ln -sf $(DESTDIR)$(PREFIX)/lib/libwiringPiDev.so.$(VERSION)
$(DESTDIR)/lib/libwiringPiDev.so
修改为:
$Q ln -sf $(DESTDIR)$(PREFIX)/lib/libwiringPiDev.so.$(VERSION)
$(DESTDIR)$(PREFIX)/lib/libwiringPiDev.so
- 修改devLib/Makefile
INCLUDE = -I. 修改为INCLUDE = -I. -I $(DESTDIR)$(PREFIX)/include
- 然后执行
./build
- 然后输入26(选择板子为修改DESTDIR?=/usr 替换为orangepizero2),这时候,就会wiringOP-master下生成_INSTALL目录,里面有完整编译出来的库文件和头文件
tree -a
.
└── usr
└── local
├── bin
│ └── gpio
├── include
│ ├── ads1115.h
│ ├── bmp180.h
│ ├── drcNet.h
│ ├── drcSerial.h
│ ├── ds1302.h
│ ├── ds18b20.h
│ ├── font.h
│ ├── gertboard.h
│ ├── htu21d.h
│ ├── lcd128x64.h
│ ├── lcd.h
│ ├── max31855.h
│ ├── max5322.h
│ ├── maxdetect.h
│ ├── mcp23008.h
│ ├── mcp23016.h
│ ├── mcp23016reg.h
│ ├── mcp23017.h
│ ├── mcp23s08.h
│ ├── mcp23s17.h
│ ├── mcp23x0817.h
│ ├── mcp23x08.h
│ ├── mcp3002.h
│ ├── mcp3004.h
│ ├── mcp3422.h
│ ├── mcp4802.h
│ ├── oled.h
│ ├── OrangePi.h
│ ├── pcf8574.h
│ ├── pcf8591.h
│ ├── piFace.h
│ ├── piGlow.h
│ ├── piNes.h
│ ├── pseudoPins.h
│ ├── rht03.h
│ ├── scrollPhat.h
│ ├── sn3218.h
│ ├── softPwm.h
│ ├── softServo.h
│ ├── softTone.h
│ ├── sr595.h
│ ├── w25q64.h
│ ├── wiringPi.h
│ ├── wiringPiI2C.h
│ ├── wiringPiSPI.h
│ ├── wiringSerial.h
│ ├── wiringShift.h
│ └── wpiExtensions.h
├── lib
│ ├── libwiringPiDev.so -> /home/pg/test/wiringOPmaster/devLib/../_INSTALL/usr/local/lib/libwiringPiDev.so.2.46
│ ├── libwiringPiDev.so.2.46
│ ├── libwiringPi.so -> /home/pg/test/wiringOPmaster/wiringPi/../_INSTALL/usr/local/lib/libwiringPi.so.2.46
│ └── libwiringPi.so.2.46
└── share
└── man
└── man1
└── gpio.1
- 后面我们就可以把_INSTALL里的内容拷贝到香橙派的根目录下,然后执行
scp _INSTALL orangepi@192.168.0.10:/home/orangepi
sudo ldconfig
- 就可以使用该库了,可以使用
sudo gpio readall
测试库是否链接正常。
5. 交叉编译智能垃圾分类系统工程代码
原有项目的目录结构为:
test@test:~/garbage$ tree
.
├── garbage.c
├── garbage.h
├── garbage.py
├── main.c
├── myoled.c
├── myoled.h
├── pwm.c
├── pwm.h
── socket.c
├── socket.h
├── uartTool.c
└── uartTool.h
调整目录结构为:
test@test:~/garbage$ tree -a
.
├── inc
│ ├── garbage.h
│ ├── myoled.h
│ ├── pwm.h
│ ├── socket.h
│ └── uartTool.h
├── src
│ ├── garbage.c
│ ├── garbage.py
│ ├── main.c
│ ├── myoled.c
│ ├── pwm.c
│ ├── socket.c
│ └── uartTool.c
增加3rd目录,用于存放wiringOP和python3.10等第三方依赖库和头文件。需增加如下几个依赖库.
首先,从香橙派上利用apt download下载依赖包的头文件和库文件,并拷贝到宿主机里:
apt download zlib1g zlib1g-dev libpython3.10 libpython3.10-dev libexpat1 libexpat1-dev libcrypt1 libcrypt-dev libffi8 libffi-dev
scp *.deb test@192.168.1.10:/home/test #test为宿主机用户名, 192.168.1.10为宿主机ip
然后,利用dpkg -x命令解压deb文件到garbage/3rd目录下:
dpkg -x libpython3.10_3.10.12-1~22.04.2_arm64.deb garbage/3rd
dpkg -x libpython3.10-dev_3.10.12-1~22.04.2_arm64.deb garbage/3rd/
dpkg -x libcrypt1_1%3a4.4.27-1_arm64.deb garbage/3rd/
dpkg -x libexpat1_2.4.7-1ubuntu0.2_arm64.deb garbage/3rd/
dpkg -x libpython3.10-dev_3.10.12-1~22.04.2_arm64.deb garbage/3rd/
dpkg -x libpython3.10_3.10.12-1~22.04.2_arm64.deb garbage/3rd
dpkg -x libexpat1_2.4.7-1ubuntu0.2_arm64.deb garbage/3rd/
dpkg -x libexpat1-dev_2.4.7-1ubuntu0.2_arm64.deb garbage/3rd/
dpkg -x libcrypt1_1%3a4.4.27-1_arm64.deb garbage/3rd/
dpkg -x zlib1g_1%3a1.2.11.dfsg-2ubuntu9.2_arm64.deb garbage/3rd/
dpkg -x libcrypt-dev_1%3a4.4.27-1_arm64.deb garbage/3rd/
dpkg -x zlib1g-dev_1%3a1.2.11.dfsg-2ubuntu9.2_arm64.deb garbage/3rd/
因为默认提供的aarch64-none-linux-gnu-gcc 9.2.0的版本编译是, 如果去加载上面的依赖库是, 会出现库版本的依赖问题, 因此。 宿主机安装aarch64-linux-gnu-gcc 11.2版本并使用该交叉编译工具:
sudo apt install gcc-aarch64-linux-gnu
Makefile 文件内容如下:
CC := aarch64-linux-gnu-gcc
SRC := $(shell find src -name "*.c")
INC := ./inc \
./3rd/usr/local/include \
./3rd/usr/include \
./3rd/usr/include/python3.10 \
./3rd/usr/include/aarch64-linux-gnu/python3.10 \
./3rd/usr/include/aarch64-linux-gnu
OBJ := $(subst src/,obj/,$(SRC:.c=.o))
TARGET=obj/garbage
CFLAGS := $(foreach item, $(INC),-I$(item)) # -I./inc -I./3rd/usr/local/include
LIBS_PATH := ./3rd/usr/local/lib \
./3rd/lib/aarch64-linux-gnu \
./3rd/usr/lib/aarch64-linux-gnu \
./3rd/usr/lib/python3.10
LDFLAGS := $(foreach item, $(LIBS_PATH),-L$(item)) # -L./3rd/usr/local/libs
LIBS := -lwiringPi -lpython3.10 -pthread -lexpat -lz -lcrypt
obj/%.o:src/%.c
mkdir -p obj
$(CC) -o $@ -c $< $(CFLAGS)
$(TARGET) :$(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS)
compile : $(TARGET)
clean:
rm $(TARGET) obj $(OBJ) -rf
debug:
echo $(CC)
echo $(SRC)
echo $(INC)
echo $(OBJ)
echo $(TARGET)
echo $(CFLAGS)
echo $(LDFLAGS)
echo $(LIBS)
.PHONY: clean compile debug
最后在工程里执行:
make
就会在obj目录下生成garbage文件, 将该文件拷贝到香橙派里即可执行。
6. 嵌入式Linux系统的组成
- BIOS和UEFI的作用
a. 进行硬件自检,检测内存,CPU,显卡,硬盘等设备的状态和配置。
b. 设置启动顺序,选择从哪个设备加载引导程序,如硬盘,U盘等。
c. 加载引导程序,如bootmgr,grub2等,然后由引导程序加载操作系统,如Windows,Linux等。
d. UEFI是BIOS的一种升级替代方案。UEFI本身已经相当于一个微型操作系统。 - grub2和bootmgr
a. grub2是GNU项目开发的一种通用的引导加载器,它可以引导多种不同的操作系统,包括Linux,Windows,FreeBSD等。
b. bootmgr是Windows版本的引导加载器,它只能引导Windows系统或者其他使用MBR分区表的
系统。
b. grub2和bootmgr都可以通过chainloader命令来加载对方的引导文件,从而实现多重引导。
c. 加载引导程序,如bootmgr,grub2等,然后由引导程序加载操作系统,如Windows,Linux等。 - uboot
a.u-boot是一种用于嵌入式系统的引导加载器,它可以支持多种硬件平台和架构,如ARM,MIPS,PowerPC等。
b. u-boot可以提供BIOS和grub2的功能,它可以初始化硬件设备,设置启动顺序,加载引导文件,启动操作系统,或者进入命令行模式。
7. 编译u-boot
7.1 u-boot简介
uboot是一种通用的引导加载程序,它可以用于多种嵌入式系统,支持多种操作系统,如Linux, Android,NetBSD等。uboot的主要作用是将操作系统内核从存储设备(如Flash, SD卡等)加载到内存中,并执行内核代码。
7.2 XIP设备
XIP设备是指一种可以直接在存储器中执行程序代码的设备,而不需要将代码复制到内存中。XIP的全称是Execute In Place,即芯片内执行。像片内的SRAM, NOR Flash, BROM等。
7.3 为什么需要u-boot
因为嵌入式系统的硬件资源有限,CPU上电后只能执行一小段内置的代码(BROM System),这段代码不足以完成内存初始化,文件系统访问,网络通信等复杂的任务。因此,需要一个中间层的程序,来完成这些工作,并引导操作系统启动。
7.4 u-boot启动流程
开机Uboot的运行流程一般是这样的:
以下官网有对U-Boot SPL大小限制的说明:
https://linux-sunxi.org/BROM
另《Allwinner_H616_Datasheet_v1.0.pdf》(3.1 Memory Mapping)章节对BROM大小的说明。
执行顺序:
- 当H616芯片上电或复位后,brom会自动执行,它会根据芯片的引脚电平或寄存器设置,确定启动模式,如从nand flash,spi flash,sd卡,usb等设备中启动。
- brom会根据启动模式,选择相应的设备驱动,初始化SD卡设备控制器,设置设备参数,如时钟频率,总线宽度,电压等级等。
- brom会从启动设备的特定扇区中,读取第一级引导程序,如spl将其加载到芯片的内部sram中,并跳转到其入口点执行。
- spl会继续初始化一些硬件设备,如ddr,pll,gpio等,然后从启动设备的特定分区中,读取第二级引导程序,如uboot proper,将其加载到ddr中,并跳转到其入口点执行。
- u-boot会继续初始化一些硬件设备,如网卡,lcd,从SD卡中读取内核文件,启动操作系统。
7.5 u-boot编译流程
根据官方的编译方式,详见(《OrangePi_Zero2_H616用户手册v4.0》 6.3 编译u-boot
- 运行 build.sh 脚本, 记得加 sudo 权限
test@test:~/orangepi-build$ sudo ./build.sh
- 选择 U-boot package, 然后回车
- 接着选择开发板的型号
- 重复编译 u-boot 时, 使用下面的命令无需通过图形界面选择, 可以直接开始编译 u-boot
sudo ./build.sh BOARD=orangepizero2 BRANCH=next BUILD_OPT=u-boot
- 查看编译生成的 u-boot deb 包
test@test:~/orangepi-build$ ls output/debs/u-boot/linux-u-boot-currentorangepizero2_3.1.0_arm64.deb
- 然后登录到H616开发板, 卸载已安装的 u-boot 的 deb 包
dpkg -l | grep linux-u-boot //查看u-boot包名称
sudo apt purge -y linux-u-boot-orangepizero2-current //卸载上面查找到的u-boot包名称,有可能是linux-u-boot-orangepizero2-next
- 再安装刚才上传的新的 u-boot 的 deb 包
sudo dpkg -i linux-u-boot-next-orangepizero2_3.1.0_arm64.deb
sudo nand-sata-install //一路回车
sudo reoobt -f
上述官方编译u-boot的方法可以用以下步骤代替:
- 清理u-boot
sudo chown test:test v2021.10-sunxi -R //修改所属用户为当前用户(根据实际用户名修改),保证当前用户下权限没有问题
cd v2021.10-sunxi
make distclean
- 配置u-boot,生成.config配置文件
make -j6 orangepi_zero2_defconfig CROSS_COMPILE="aarch64-none-linux-gnu-"
- 编译uboot, 生成u-boot-sunxi-with-spl.bin
make -j6 CROSS_COMPILE="aarch64-none-linux-gnu-"
- 将生成的u-boot-sunxi-with-spl.bin 拷贝到开发板上,参考官网对BROM的启动及SD卡数据组成的说明,官网地址如下:
https://linux-sunxi.org/Bootable_SD_card#SD_Card_Layout
然后执行如下命令:
dd if=/dev/zero of=/dev/mmcblk1 bs=1k count=1023 seek=1 status=noxfer //格式化1k到1M为止的数据
dd if=u-boot-sunxi-with-spl.bin of=/dev/mmcblk1 bs=1k seek=8 conv=fsync
8. 编译内核
8.1 Linux内核的主要功能
Linux操作系统框架如下:
Linux内核的主要功能:进程管理、内存管理、驱动、系统调用。
Linux的目录结构如下:
test@test:~/orangepi-build/kernel/orange-pi-5.16-sunxi64$ ls
arch include mm scripts
block init modules.builtin security
certs ipc modules.builtin.modinfo sound
COPYING Kbuild modules-only.symvers System.map
CREDITS Kconfig modules.order tools
crypto kernel Module.symvers usr
Documentation lib net virt
drivers LICENSES README vmlinux
export.txt MAINTAINERS README.md vmlinux.o
fs Makefile samples vmlinux.symvers.
- arch/:这个目录包含了不同架构处理器的代码,如x86,arm,mips等。每个架构都有自己的子目
录,如arch/x86/,arch/arm/等。在每个架构的子目录中,又有一些子目录和文件,如boot/,lib/,mm/,include/等,分别包含了特定平台的启动代码,库函数,内存管理,头文件。 - block/:这个目录包含了块设备的代码,如硬盘,光驱等。block/目录中主要包含了块设备的基本框架和I/O调度算法,以及一些通用的块设备驱动。
- crypto/:这个目录包含了加密算法的代码,如AES,SHA1,MD5等。
- drivers/:这个目录包含了设备驱动程序的代码,如键盘,鼠标,网卡,声卡,摄像头等。drivers/目录中的代码按照设备的类别进行分类,如char/,block/,input/,i2c/,spi/,pci/,usb/等。
- fs/:这个目录包含了文件系统的代码,如ext4,fat,ntfs,nfs,cifs等。fs/目录中的代码按照文件系统的类型进行分类,如ext4/,fat/,ntfs/等。在fs/目录中,还有一些通用的文件和子目录,如mount.h,dentry.c,proc/,sysfs/等,它们用于实现文件系统的基本功能和接口。
- include/:这个目录包含了内核所需的头文件,如linux/,asm/,uapi/等。头文件是用于声明变量,函数,结构,宏,常量等的文件,它们可以被其他的源文件引用,以便共享和重用代码。include/目录中的头文件按照不同的层次和用途进行分类,如linux/目录中的头文件是与平台无关的,asm/目录中的头文件是与平台相关的,uapi/目录中的头文件是用于内核和用户空间的API的。
- init/:这个目录包含了内核初始化的代码,如main.c,version.c,do_mounts.c等。init/目录中的代码是内核的入口和核心,它们负责调用其他子系统和模块的初始化函数。
- ipc/:这个目录包含了进程间通信的代码,如sem.c,msg.c,shm.c等。进程间通信是指在不同的进程之间传递数据和信号的方法,它们可以让进程之间实现协作和同步。ipc/目录中的代码实现了一些常用的进程间通信机制,如信号量,消息队列,共享内存等。
- kernel/:这个目录包含了内核的核心代码,如sched/,irq/,time/,fork.c,exit.c,signal.c等。
kernel/目录中的代码实现了一些内核的基本功能和服务,如进程调度,中断处理,时间管理,进程创
建,进程终止,信号处理等。 - lib/:这个目录包含了内核需要引用的一些库函数的代码,如string.c,vsprintf.c,crc32.c等。
- mm/:这个目录包含了内存管理的代码。
- net/:这个目录包含了网络协议的代码,和网卡驱动不相关代码。
- scripts/:这个目录包含了内核编译所需的一些脚本,如Makefile,Kconfig,checkpatch.pl等。
- tools/:这个目录包含了一些和内核交互的工具,如perf/,ftrace/,cpupower/,objtool/等。工具是用于分析和调试内核的程序,它们可以让内核的性能和稳定性更加优化和提高。
8.2 Linux内核编译流程
根据官方的编译方式,详见(《OrangePi_Zero2_H616用户手册v4.0》 6.4 编译Linux内核
- 运行 build.sh 脚本, 记得加 sudo 权限
test@test:~/orangepi-build$ sudo ./build.sh
- 选择 Kernel package, 然后回车
- 然后会提示是否需要显示内核配置界面, 如果不需要修改内核配置, 则选择第一个即可, 如果需要修改内核配置, 则选择第二个
-
接着选择开发板的型号
-
查看编译生成的内核相关的 deb 包
a. linux-dtb-next-sun50iw9_3.1.0_arm64.deb 包含有内核使用的 dtb 文件
b. linux-headers-next-sun50iw9_3.1.0_arm64.deb 包含内核头文件
c. linux-image-next-sun50iw9_3.0.1_arm64.deb 包含内核镜像和内核模块 -
重复编译 kernel 时, 使用下面的命令无需通过图形界面选择, 可以直接开始编译kernel
sudo ./build.sh BOARD=orangepizero2 BRANCH=next BUILD_OPT=kernel KERNEL_CONFIGURE=no
- 如果对内核做了修改, 可以使用下面的方法来更新开发板 linux 系统的内核和内核模块
scp linux-image-next-sun50iw9_3.1.0_arm64.deb test@192.168.1.28:/home/test/
sudo apt purge -y linux-image-next-sun50iw9 //也有可能是linux-image-next-sun50iw9
sudo dpkg -i linux-image-next-sun50iw9_3.1.0_arm64.deb
sudo reboot -f
上述官方编译kernel的方法可以用以下步骤代替:
- 清理旧配置文件及生成的文件
sudo apt-get install ccache //安装ccahe,用于编译加速
export PATH=$PATH:/home/$(whoami)/orangepi-build/toolchains/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin //导入教程编译环境
make ARCH=arm64 distclean
- 配置内核, 更新内核依赖属性和新属性, 生成.config
cp ../../external/config/kernel/linux-5.16-sun50iw9-current.config .config //拷贝默认配置
make ARCH=arm64 CROSS_COMPILE="aarch64-none-linux-gnu-" olddefconfig //这里执行make menuconfig 然后exit也是可以的
a. make olddefconfig 的作用是根据已有的 .config 文件生成一个新的 .config 文件,同时更新内核的依赖属性和新属性。它会使用旧的 .config 文件中的参数作为默认参数,不会询问用户的选择。它会将新添加的内核选项设置为默认值,也不会提醒用户。它会将旧的 .config 文件重命名为 .config.old 文件,以备后用。
b. make olddefconfig 的作用和 make oldconfig 类似,但是 make oldconfig 会以交互方式询问用户对
新配置的选择,而 make olddefconfig 不会。
c. make menuconfig 是基于 Ncurses 图形界面去配置 .config 文件,它可以让用户在一个菜单式的界面中选择和修改内核的各种选项,也可以查看选项的帮助信息,它是一种比较方便和直观的配置方式。
- 编译内核
make -j6 ARCH=arm64 'CROSS_COMPILE=ccache aarch64-none-linux-gnu-' LOCALVERSION=-sun50iw9 Image modules dtbs
- 接下来运行
make modules_install INSTALL_MOD_STRIP=1 INSTALL_MOD_PATH=$PWD/_install ARCH=arm64 'CROSS_COMPILE=ccache aarch64-none-linux-gnu-' //安装驱动
make install INSTALL_PATH=$PWD/_install/boot //安装内核
或者直接打包成deb包
cp external/patch/misc/headers-debian-byteshift.patch /tmp
make -j6 bindeb-pkg KDEB_PKGVERSION=3.1.0 KDEB_COMPRESS=xz BRANCH=next LOCALVERSION=-sun50iw9 KBUILD_DEBARCH=arm64 ARCH=arm64 DEBFULLNAME="Orange Pi" CROSS_COMPILE="ccache aarch64-none-linux-gnu-”
- bindeb-pkg 是指定 make 命令使用 Debian 的格式来打包内核,生成一个 .deb 文件,这样可以方便地在 Debian 系的系统中安装内核 。
- KDEB_PKGVERSION=3.1.0 是指定打包的内核的版本号为 3.1.0,这个版本号可以自定义,也可以使用内核的默认版本号 。
- KDEB_COMPRESS=xz 是指定打包的内核使用 xz 的压缩格式,这样可以减少打包文件的大小,也可以使用其他的压缩格式,如 gzip, bzip2 等 。
- BRANCH=next 是指定编译的内核的分支为 next。
- LOCALVERSION=-sun50iw9 是指定编译的内核的本地版本为 -sun50iw9。
- KBUILD_DEBARCH=arm64 是指定打包的内核的架构为 arm64,这个架构是用来表示 64 位的 ARM处理器。
- ARCH=arm64 是指定编译的内核的架构为 arm64 。
- DEBFULLNAME=Orange Pi 是指定打包的姓名为Orange Pi。
9 Linux根文件系统
根文件系统也叫roofs,Linux 根文件系统是指整个文件系统的最顶层,以 “/” 来表示。它是内核启动时所挂载的第一个文件系统,包含了系统运行所必需的目录和文件:
- /bin目录下存放着系统需要的可执行文件比如ls、mv、cp等命令,现在新根文件系统的像ubuntu等debian系的rootfs, 基本都是软链接到/usr/bin目录下。
- /dev目录下面存放着的文件都与设备有关,此目录下的文件都是设备文件。
- /etc目录存放Linux下所必须的库文件。
- /mnt目录, 临时挂载目录,可以在从目录下创建空的子目录。
- /proc、sys 目录, Linux虚拟文件系统,由内核生成各类节点。
- /sbin 一般软链到/usr/sbin下,一般存放一些root权限才能执行的命令。
- /lib 软链接到/usr/lib目录,用于存放库文件。
- /usr/ 存放lib bin sbin目录, 另外的share目录里面存放的是共享、只读的程序和数据。
- /tmp/存放临时文件或目录。
- /root目录 系统管理员(root)的主文件夹,即是根用户的目录,与此对应,普通用户的目录是/home下的某个子目录。
- /var目录、与/usr目录相反,/var目录中存放可变的数据,比如log文件\临时文件等。
- /home目录,系统默认的用户文件夹,它是可选的,对于每个普通用户,在/home目录下都有一个以用户名命名的子目录,里面存放用户相关的配置文件。
根文件系统的编译详见(《OrangePi_Zero2_H616用户手册v4.0》 6.5. 编译 rootfs