【嵌入式Linux】基于OrangePi的系统移植(编译香橙派SDK,交叉编译wiringOP库、工程代码,u-boot和内核)


之前使用香橙派, 都是直接在香橙派上进行代码编译, 但在实际的项目开发过程中,更多的还是使用交叉编译环境进行代码的编译。再编译完成之后再把代码放到香橙派等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电脑或者虚拟机上运行的。

  1. 确认自己的虚拟机或者Ubuntu实体机是不是22.04的方法如下:
lsb_release -a
  1. 如果不是,可从ubuntu官网重新下载ubuntu 22.04 x64镜像,重新更新下虚拟机,ubuntu 22.04X64镜像下载地址:(也可以从清华源网站下载,速度会快点)
http://releases.ubuntu.com/22.04/ubuntu-22.04.3-desktop-amd64.iso
  1. 编译出来的完整SDK大概有16 G大小,因此建议在创建虚拟机时,至少分配50G的存储给虚拟机使
    用。

1.2 获取Linux SDK

方法一:github 下载

  1. 从 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
  1. orangepi-build 下载完后会包含下面的文件和文件夹
    a. build.sh: 编译启动脚本, 我们可以通过build.sh编译uboot、内核、根文件系统甚至完整的img
    b. external: 包含编译镜像需要用的配置文件、特定的脚本以及部分程序的源码等
    c. LICENSE: GPL 2 许可证文件
    d. README.md: orangepi-build 说明文件
    e. scripts: 编译 linux 镜像的通用脚本

  2. 解压完后,需要去修改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下载过程中出现下载失败导致最后编译出来的系统可能是异常的)

  1. 从下面的百度网盘链接下载提前编译好的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
  1. 这个时候可以用如下的命令进行合并解压:
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脚本 )

  1. 运行 build.sh 脚本,记得加 sudo 权限
sudo ./build.sh
  1. 选择Full OS image for flashing 进行完整镜像的编译
    在这里插入图片描述
  2. 选择不修改配置
    在这里插入图片描述
  3. 根据实际的香橙派派开发版的型号, 选orangepizero2

在这里插入图片描述

  1. 选择根文件系统类型, 这边选择ubuntu 22.04 的根文件系统, 也就是jammy

在这里插入图片描述

  1. 选择带桌面环境,即接入HDMI显示器后,是有桌面显示的

在这里插入图片描述

  1. 最后选择桌面环境,这边选择xfce

在这里插入图片描述
在这里插入图片描述

  1. 这里的软件主要是一些额外第三方软件包的安装, 都不选择,直接ok开始编译

在这里插入图片描述

  1. 如果是第一次运行 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: 存放编译脚本需要用到的配置文件。

  1. 经过漫长等待编译完成后, 会在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条件判断有下面几种:

  1. 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
  1. 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库。

  1. 修改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
  1. 修改devLib/Makefile、gpio/Makefile、wiringPiD/Makefile
将所有Makefile中的CC := gcc 改成 CC := aarch64-none-linux-gnu-gcc
  1. 修改devLib/Makefile、gpio/Makefile、wiringPiD/Makefile 、wiringPi/Makefile
修改DESTDIR?=/usr 替换为DESTDIR?= $(shell pwd)/../_INSTALL/usr
  1. 修改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
  1. 修改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
  1. 修改devLib/Makefile
INCLUDE = -I. 修改为INCLUDE = -I. -I $(DESTDIR)$(PREFIX)/include
  1. 然后执行
./build
  1. 然后输入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
  1. 后面我们就可以把_INSTALL里的内容拷贝到香橙派的根目录下,然后执行
scp _INSTALL orangepi@192.168.0.10:/home/orangepi
sudo ldconfig
  1. 就可以使用该库了,可以使用
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系统的组成

在这里插入图片描述

  1. BIOS和UEFI的作用
    a. 进行硬件自检,检测内存,CPU,显卡,硬盘等设备的状态和配置。
    b. 设置启动顺序,选择从哪个设备加载引导程序,如硬盘,U盘等。
    c. 加载引导程序,如bootmgr,grub2等,然后由引导程序加载操作系统,如Windows,Linux等。
    d. UEFI是BIOS的一种升级替代方案。UEFI本身已经相当于一个微型操作系统。
  2. grub2和bootmgr
    a. grub2是GNU项目开发的一种通用的引导加载器,它可以引导多种不同的操作系统,包括Linux,Windows,FreeBSD等。
    b. bootmgr是Windows版本的引导加载器,它只能引导Windows系统或者其他使用MBR分区表的
    系统。
    b. grub2和bootmgr都可以通过chainloader命令来加载对方的引导文件,从而实现多重引导。
    c. 加载引导程序,如bootmgr,grub2等,然后由引导程序加载操作系统,如Windows,Linux等。
  3. 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大小的说明。

执行顺序:

  1. 当H616芯片上电或复位后,brom会自动执行,它会根据芯片的引脚电平或寄存器设置,确定启动模式,如从nand flash,spi flash,sd卡,usb等设备中启动。
  2. brom会根据启动模式,选择相应的设备驱动,初始化SD卡设备控制器,设置设备参数,如时钟频率,总线宽度,电压等级等。
  3. brom会从启动设备的特定扇区中,读取第一级引导程序,如spl将其加载到芯片的内部sram中,并跳转到其入口点执行。
  4. spl会继续初始化一些硬件设备,如ddr,pll,gpio等,然后从启动设备的特定分区中,读取第二级引导程序,如uboot proper,将其加载到ddr中,并跳转到其入口点执行。
  5. u-boot会继续初始化一些硬件设备,如网卡,lcd,从SD卡中读取内核文件,启动操作系统。

7.5 u-boot编译流程

根据官方的编译方式,详见(《OrangePi_Zero2_H616用户手册v4.0》 6.3 编译u-boot

  1. 运行 build.sh 脚本, 记得加 sudo 权限
test@test:~/orangepi-build$ sudo ./build.sh
  1. 选择 U-boot package, 然后回车
    在这里插入图片描述
  2. 接着选择开发板的型号
    在这里插入图片描述
  3. 重复编译 u-boot 时, 使用下面的命令无需通过图形界面选择, 可以直接开始编译 u-boot
sudo ./build.sh BOARD=orangepizero2 BRANCH=next BUILD_OPT=u-boot
  1. 查看编译生成的 u-boot deb 包
test@test:~/orangepi-build$ ls output/debs/u-boot/linux-u-boot-currentorangepizero2_3.1.0_arm64.deb
  1. 然后登录到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
  1. 再安装刚才上传的新的 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的方法可以用以下步骤代替:

  1. 清理u-boot
sudo chown test:test v2021.10-sunxi -R //修改所属用户为当前用户(根据实际用户名修改),保证当前用户下权限没有问题
cd v2021.10-sunxi
make distclean
  1. 配置u-boot,生成.config配置文件
make -j6 orangepi_zero2_defconfig CROSS_COMPILE="aarch64-none-linux-gnu-"
  1. 编译uboot, 生成u-boot-sunxi-with-spl.bin
make -j6 CROSS_COMPILE="aarch64-none-linux-gnu-"
  1. 将生成的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.
  1. arch/:这个目录包含了不同架构处理器的代码,如x86,arm,mips等。每个架构都有自己的子目
    录,如arch/x86/,arch/arm/等。在每个架构的子目录中,又有一些子目录和文件,如boot/,lib/,mm/,include/等,分别包含了特定平台的启动代码,库函数,内存管理,头文件。
  2. block/:这个目录包含了块设备的代码,如硬盘,光驱等。block/目录中主要包含了块设备的基本框架和I/O调度算法,以及一些通用的块设备驱动。
  3. crypto/:这个目录包含了加密算法的代码,如AES,SHA1,MD5等。
  4. drivers/:这个目录包含了设备驱动程序的代码,如键盘,鼠标,网卡,声卡,摄像头等。drivers/目录中的代码按照设备的类别进行分类,如char/,block/,input/,i2c/,spi/,pci/,usb/等。
  5. fs/:这个目录包含了文件系统的代码,如ext4,fat,ntfs,nfs,cifs等。fs/目录中的代码按照文件系统的类型进行分类,如ext4/,fat/,ntfs/等。在fs/目录中,还有一些通用的文件和子目录,如mount.h,dentry.c,proc/,sysfs/等,它们用于实现文件系统的基本功能和接口。
  6. include/:这个目录包含了内核所需的头文件,如linux/,asm/,uapi/等。头文件是用于声明变量,函数,结构,宏,常量等的文件,它们可以被其他的源文件引用,以便共享和重用代码。include/目录中的头文件按照不同的层次和用途进行分类,如linux/目录中的头文件是与平台无关的,asm/目录中的头文件是与平台相关的,uapi/目录中的头文件是用于内核和用户空间的API的。
  7. init/:这个目录包含了内核初始化的代码,如main.c,version.c,do_mounts.c等。init/目录中的代码是内核的入口和核心,它们负责调用其他子系统和模块的初始化函数。
  8. ipc/:这个目录包含了进程间通信的代码,如sem.c,msg.c,shm.c等。进程间通信是指在不同的进程之间传递数据和信号的方法,它们可以让进程之间实现协作和同步。ipc/目录中的代码实现了一些常用的进程间通信机制,如信号量,消息队列,共享内存等。
  9. kernel/:这个目录包含了内核的核心代码,如sched/,irq/,time/,fork.c,exit.c,signal.c等。
    kernel/目录中的代码实现了一些内核的基本功能和服务,如进程调度,中断处理,时间管理,进程创
    建,进程终止,信号处理等。
  10. lib/:这个目录包含了内核需要引用的一些库函数的代码,如string.c,vsprintf.c,crc32.c等。
  11. mm/:这个目录包含了内存管理的代码。
  12. net/:这个目录包含了网络协议的代码,和网卡驱动不相关代码。
  13. scripts/:这个目录包含了内核编译所需的一些脚本,如Makefile,Kconfig,checkpatch.pl等。
  14. tools/:这个目录包含了一些和内核交互的工具,如perf/,ftrace/,cpupower/,objtool/等。工具是用于分析和调试内核的程序,它们可以让内核的性能和稳定性更加优化和提高。

8.2 Linux内核编译流程

根据官方的编译方式,详见(《OrangePi_Zero2_H616用户手册v4.0》 6.4 编译Linux内核

  1. 运行 build.sh 脚本, 记得加 sudo 权限
test@test:~/orangepi-build$ sudo ./build.sh
  1. 选择 Kernel package, 然后回车
    在这里插入图片描述
  2. 然后会提示是否需要显示内核配置界面, 如果不需要修改内核配置, 则选择第一个即可, 如果需要修改内核配置, 则选择第二个

在这里插入图片描述

  1. 接着选择开发板的型号
    在这里插入图片描述

  2. 查看编译生成的内核相关的 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 包含内核镜像和内核模块

  3. 重复编译 kernel 时, 使用下面的命令无需通过图形界面选择, 可以直接开始编译kernel

sudo ./build.sh BOARD=orangepizero2 BRANCH=next BUILD_OPT=kernel KERNEL_CONFIGURE=no
  1. 如果对内核做了修改, 可以使用下面的方法来更新开发板 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的方法可以用以下步骤代替:

  1. 清理旧配置文件及生成的文件
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
  1. 配置内核, 更新内核依赖属性和新属性, 生成.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 文件,它可以让用户在一个菜单式的界面中选择和修改内核的各种选项,也可以查看选项的帮助信息,它是一种比较方便和直观的配置方式。

  1. 编译内核
make -j6 ARCH=arm64 'CROSS_COMPILE=ccache aarch64-none-linux-gnu-' LOCALVERSION=-sun50iw9 Image modules dtbs
  1. 接下来运行
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-
  1. bindeb-pkg 是指定 make 命令使用 Debian 的格式来打包内核,生成一个 .deb 文件,这样可以方便地在 Debian 系的系统中安装内核 。
  2. KDEB_PKGVERSION=3.1.0 是指定打包的内核的版本号为 3.1.0,这个版本号可以自定义,也可以使用内核的默认版本号 。
  3. KDEB_COMPRESS=xz 是指定打包的内核使用 xz 的压缩格式,这样可以减少打包文件的大小,也可以使用其他的压缩格式,如 gzip, bzip2 等 。
  4. BRANCH=next 是指定编译的内核的分支为 next。
  5. LOCALVERSION=-sun50iw9 是指定编译的内核的本地版本为 -sun50iw9。
  6. KBUILD_DEBARCH=arm64 是指定打包的内核的架构为 arm64,这个架构是用来表示 64 位的 ARM处理器。
  7. ARCH=arm64 是指定编译的内核的架构为 arm64 。
  8. DEBFULLNAME=Orange Pi 是指定打包的姓名为Orange Pi。

9 Linux根文件系统

根文件系统也叫roofs,Linux 根文件系统是指整个文件系统的最顶层,以 “/” 来表示。它是内核启动时所挂载的第一个文件系统,包含了系统运行所必需的目录和文件:

  1. /bin目录下存放着系统需要的可执行文件比如ls、mv、cp等命令,现在新根文件系统的像ubuntu等debian系的rootfs, 基本都是软链接到/usr/bin目录下。
  2. /dev目录下面存放着的文件都与设备有关,此目录下的文件都是设备文件。
  3. /etc目录存放Linux下所必须的库文件。
  4. /mnt目录, 临时挂载目录,可以在从目录下创建空的子目录。
  5. /proc、sys 目录, Linux虚拟文件系统,由内核生成各类节点。
  6. /sbin 一般软链到/usr/sbin下,一般存放一些root权限才能执行的命令。
  7. /lib 软链接到/usr/lib目录,用于存放库文件。
  8. /usr/ 存放lib bin sbin目录, 另外的share目录里面存放的是共享、只读的程序和数据。
  9. /tmp/存放临时文件或目录。
  10. /root目录 系统管理员(root)的主文件夹,即是根用户的目录,与此对应,普通用户的目录是/home下的某个子目录。
  11. /var目录、与/usr目录相反,/var目录中存放可变的数据,比如log文件\临时文件等。
  12. /home目录,系统默认的用户文件夹,它是可选的,对于每个普通用户,在/home目录下都有一个以用户名命名的子目录,里面存放用户相关的配置文件。

根文件系统的编译详见(《OrangePi_Zero2_H616用户手册v4.0》 6.5. 编译 rootfs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT阳晨。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值