交叉开发
1、交叉环境
编译,就是把一种语言(如C语言) 翻译成 另一种语言(如机器语言)
之前我们的编译环境 和 运行环境是相同的。
交叉编译:
也是编译,也是把一种语言翻译成另一种语言
但是,在嵌入领域中, 很多时候程序的运行环境 不适合 去编写和编译程序。
所以,我们把常规的 编写 编译 执行 的这几个步骤分开:
在PC机上编写程序
在PC机上编译程序
最后通过一些特殊的工具,把生成的目标代码(如可执行文件)传输到目标设备(如GEC6818开发板)上去执行。
简单的说,交叉编译 就是在一种环境下 去编译 另一种环境的程序。
2、交叉编译器
一般来说,交叉编译器由开发板厂商提供,也当然也可以自己去网上下载,如:
http://releases.linaro.org/components/toolchain
https://www.evryarm.com
安装交叉编译器:
*1、下载
从官网下载
从云盘下载
从飞秋下载…
gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabi.tar.xz
可以使用指令uname -a查看系统版本:
结果显示中包含 x86_64,就是64位的系统,简称x64
结果显示中包含 i686,就是32位的系统,简称x86
下载完成后,把压缩包放到Ubuntu系统中去(共享目录也行)
2、解压
把下载的压缩包解压到一个合适的目录(不能解压到共享目录!),如/usr/local/ 目录
具体过程:
1、进入压缩包所在的目录,并能查看到它
cd /mnt/hgfs/Tools/交叉开发
ls
2、解压
sudo tar xvf gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabi.tar.xz -C /usr/local/
解压完成后,会在/usr/local目录下生成一个 gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabi 目录
自动生成的目录略微过长,使用不便,所以,改成一个短一点的名字,如 arm-linux
改名指令:
sudo mv gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabi arm-linux
3、给交叉编译器创建软链接
进入交叉编译工具链所在目录
cd /usr/local/arm-linux/bin
把创建软链接的脚本拷贝到当前目录
sudo cp /mnt/hgfs/Tools/交叉开发/myln-x64.sh ./
执行脚本
sudo ./myln-x64.sh
4、配置环境变量
为了能够在任意目录下都能使用交叉编译器,我们需要把交叉编译器所在的路径添加到环境变量PATH中去
临时添加指令:
PATH=/usr/local/arm-linux/bin:$PATH
请确保该指令是正确的!(=两边不能有空格,分配符是英文的冒号,PATH必须大写)
如果需要永久生效,需要把上述指令写入系统的启动配置文件中,并把PATH导出为环境变量
可以写入这些文件的最后面(已有内容不要修改,只是在后面追加),使得配置永久生效:
/etc/profile 对所有的普通用户有效,只要登录系统,系统会自动读取该文件中的配置信息
~/.bashrc 对当前用户用效,只要登录系统,自动读取。
具体指令:
打开配置文本:
sudo vim /etc/profile
按快捷键shift+g(把光标定位到文件的最后一行)
按i键,进入插入模式,在最后一行的后面,写入配置指令
配置指令:
export PATH=/usr/local/arm-linux/bin:$PATH
写入完成后,保存退出(按esc键,再:wq),重启系统。
5、测试是否配置成功
打开终端,输入指令, 查看交叉编译器的版本信息:
arm-linux-gcc -v
3、熟悉使用开发板
开发板名称: GEC6818
标准配置:
底板
LCD液晶屏
电源适配器
串口线
USB-Serial线
最简单基本的使用:
接上电源,接好串口线,开机
开机能看到登录界面 或 能看到粤嵌的LOGO图标
PC机与开发板的连接,需要一些专门的软件,用于操作开发板上的系统,如:
SecureCRT (我们选择这个)
Xshell
putty
…
在第一次使用USB-Serial线连接开发板时,可能需要安装这根线的驱动!
如何确认呢?
1、打开设备管理器
如果一切正常应该能看到 端口选项(USB-SERIAL CH340(COMX))
如果接好线,也没有看到这个选项,看下有没有 《其它设备》这个选项,如果有,那么可能需要安装驱动(推荐使用驱动精灵安装)
如果上面两个选项都没,那么请先关闭虚拟机,拔掉USB转串口线,重新接入。
如果还不行,请检查线是否接好
如果还不行,换一根确定是好的线再试一试。
…
2、打开SecureCRT
菜单-> 快速连接
协议:serial
端口:请查看设备管理器
波特率:115200
数据位:8 (默认就是,不要修改)
奇偶校验: None(默认就是,不要修改)
停止位:1(默认就是,不要修改)
流控:都不要(去掉中间的那默认的勾)
设置完成后,点击连接按钮即可。
如果是先接线,再启动开发板,那么在SecureCRT界面,会看到系统的启动信息,启动时间6秒左右。
启动成功后,按下回车,或输入其它的指令,以确认一切正常。
3、第一次操作开发板
创建自己的工作目录(类似PC端的家目录)
mkdir /home/china -p
以后每次开机,请先进入你的工作目录。
以后尽量不要去随意修改(如创建/删除)工作目录之外的文件。
4、通过串口传输文件到开发板
rx(默认就有)
常规使用步骤:
1、rx 文件名
2、回车
3、菜单->传输->发送Xmodem
4、在弹出的文件对话框中选择你需要传送的文件,即可
5、稍等几秒钟,就能看到数据的传输过程
6、如果要传输的文件是可执行文件,那么在执行之前需要添加可执行文件权限,指令如下:
chmod +x 文件名
7、按常规方式执行程序即可。
rz(需要自己配置)
1、进行自己的工作目录
cd /home/china
2、rx arm-rz.tar.xz
3、发送完成后,解压
tar xvf arm-rz.tar.xz
4、进入解压生成的arm-rz目录
5、执行该目录下的脚本
7、使用rz时,原则上,输入rz后,直接回车就能弹出文件对话框
如果要发送的文件在开发板上已经存在了,那么
要么把已存在的文件删除
要么rz -y
5、从开发板传输文件到PC
sz 文件名
6、使用tftp网络服务把文件从Ubuntu中传输到开发板
tftp是一个简单文件传输协议,是一个基于Client/Server的网络应用程序
我们这里是把ubuntu18.04做为服务器,开发板是做为客户端的。
客户端只需要网络畅通就行,而服务端需要做一些安装配置
a.服务端的安装配置:
1、先创建一个tftp服务目录,如:
mkdir -p /home/china/tftpboot
chmod 777 tftpboot
2、下载安装
sudo apt install tftpd-hpa
3、配置
打开配置文件:
sudo vi /etc/default/tftpd-hpa
修改该文件中的配置为:
/etc/default/tftpd-hpa
TFTP_USERNAME=“tftp”
TFTP_DIRECTORY="/home/china/tftpboot" # 设置自己的服务目录
TFTP_ADDRESS=“0.0.0.0:69” # 设置IP地址,为本机的任意IP
TFTP_OPTIONS="-l -c -s" # 设置上传/下载的权限
配置完成后,保存退出即可。
4、重启tftp服务
sudo /etc/init.d/tftpd-hpa restart
注:
可以对tftp进行如下操作:
start 启动
restart 重启
stop 停止
status 查看状态
b.客户端配置
客户端需要配置IP地址,配置IP有手动和自动之分
使用指令 udhcpc 来自动获取IP地址,获取成功后,可使用ifconfig查看
还可以使用 ping 网关IP ,来确认网络畅通
使用tftp服务:
1、把需要发送的文件 放到 tftp的服务目录,也就是 /home/china/tftpboot
2、查看服务端的IP地址,也就是ubuntu的IP,使用指令ifconfig
3、切换到SecureCRT, 进入工作目录
4、执行下载指令:
tftp -g -r 文件名 服务端的IP
例:
tftp -gr shui.mp3 192.168.2.157
注:
在使用tftp的过程中,可能会碰到一些常见的错误,如
错误1: tftp: server error: (1) File not found
解决方案:查看文件名是否正确,查看服务目录是否有你需要下载的文件
错误2:tftp: timeout
解决方案:检查服务端IP是否正确,检查网络是否畅通(ping 服务端IP), 网线有没有接好等
错误3:tftp: sendto: Network is unreachable
解决方案:请正确配置开发板的IP地址(在局域网内,推荐使用udhcpc)
4、动态库和静态库
- 库 library
库 是一种二进制的封装形式,简单的说,库中封装了一堆的目标文件(.o)
当库封装好之后,就可以在其他(文件/程序)地方去使用它,但是用户是无法看到它的内部实现的。
库的这种做法有利于 代码模块化,只要函数接口设计合理,改变库的内部实现,不会影响用户级别的代码使用。
根据库的封装和链接格式,可以把库 分成 静态库 和 共享库(动态库)。
- 静态库
静态库的特点:
当把目标文件封装成静态库之后,如果有其它程序需要用到库中的功能,编译器会把相关代码直接拷贝到你的程序中去
当程序编译完成后,运行时不再依赖静态库
但是如果静态库更新了,你的程序必须再次编译
创建静态库:
1、编写源代码
vi sum.h
vi sum.c
2、把源代码编译成目标文件
gcc sum.c -c
3、把目标文件封装成库
ar -rc lib库名.a 目标文件
例:
ar -rc libmath.a sum.o
说明:
库的前级建议以 lib开头,静态库以.a结尾
-rc中r 表示插入目标文件到静态库中去,c表示创建指定的静态库。
使用静态库:
用户在编译使用了库的代码时,需要指定头文件和库文件所在路径 及 库的 名字,格式如下:
gcc 源文件/目标文件 -I 头文件所在路径 -L 库文件所在路径 -l库名
编译完成后,程序运行不再需要这个静态库。
-
共享库(动态库)
动态库的特点:
当把目标文件封装成共享库之后,如果有其它程序需要用到库中的功能,编译器不会去拷贝相关的代码,而仅仅是做一个标记
当程序执行时,根据这个标记去查找相关的代码,也就是说,程序执行时依赖于共享库。
所谓依赖于某个库,指这个库必须存在,且编译器知道它的路径。
当共享库更新后,只要接口不变,用户程序一般不需要做任何改变。 -
创建共享库:
1、编写源代码
vi sum.h
vi sum.c
2、把源代码编译成目标文件
gcc sum.c -c -fPIC
3、把目标文件封装成库
gcc -shared -fPIC -o lib共享库名.so 目标文件
例:
gcc -shared -fPIC -o libmath.so sum.o sub.o
注:
-shared 指要创建一个共享库
-fPIC 表示要生成与位置无关的代码
-o 要创建的库的名字,一般约定共享库的名字格式为:
lib库名.so -
使用共享库:
用户在编译使用了库的代码时,需要指定头文件和库文件所在路径 及 库的 名字,格式如下:
gcc 源文件/目标文件 -I 头文件所在路径 -L 库文件所在路径 -l库名
编译完成后,程序运行还需要这个共享库。
用以下方式指定运行时库的路径:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:共享库所在路径
5、make
-
为什么需要make
在我们的实际开发中,如果仅使用gcc来编译程序,效率较低:
程序往往由多个模块(.h/.c)组成,源文件个数越来越多,那么GCC命令行就会越来越长,而且编译规则 还会增加命令行的复杂度。
在编码工作中,调试时间往往大于编码时间,在调试的时候,每次可能 只会修改部分源文件,但是使用GCC命令行编译程序时,那些没有修改过的源文件也会被编译,会大大的影响编译效率。
所以,我们需要一个工具能够自动检查文件的更新情况、自动进行编译。
这个工具就叫做make
make是由GNU项目提供的一个命令行的项目管理工具。make是一个工具,它能自动检查文件的更新情况,并能自动进行编译,那么它是怎么知道我们的项目中有哪些文件的呢?以及这些文件应该怎么编译的?
-
Makefile
make工具有一个专门的配置文件,我们只需要把项目相关的信息写入这个配置文件,make会自动的去读取这个配置文件,并根据配置文件中的规则 进行工作。
这个配置文件,就叫做makefile
Makefile怎么写?
Makefile可理解为一门小型的编程语言,它有变量、流程控制、函数等,可以很复杂。
但不管怎么复杂,它的核心规则 是一样的:
需要生成的目标文件(target)
生成目标文件所需要的依赖文件(dependency)
生成目标文件的编译规则(command)
以上三个核心规则以如下形式进行组织:
target: dependency
command
说明:
编译规则command前必须加一个tab键
一个Makefile文件中目标可以有多个,可分两类:
真正的目标:
指需要生成一个文件的这种目标,如 可执行文件等
make自动执行第一个目标,所以一般我们把真正的目标写在最前面
如果需要执行其它目标,那么必须在make时明确说明
make
make 目标名
伪目标:
指不需要生成某个文件的目标,只是想执行某个操作
最常用的伪目标: clean 和 install
伪目标必须在make时明确指出,而且通常出现在真正的目标的后面
make clean # 一般用于清除中间及结果文件
make install # 一般用于拷贝/移动结果文件到指定的目录
- makefile中的变量
makefile中的变量可分成多种,如自定义变量、内置变量、自动变量等
makefile中的变量是没有类型的,或者说只有字符串类型
定义一个变量,只需要起一个名字,然后给它一个值即可。
引用变量时,不能直接使用变量名,而需要在变量名前加一个$符号,如果变量名由多个字符组成,还需要用括号把变量名括起来。
例:
-
直接赋值
变量名 := 值 -
递归赋值
变量名 = 值
A = $B
B = hello -
追加赋值
变量名 += 值
A = hello
A += world -
条件赋值
变量名 ?= 值
如果变量没有定义过,则定义,否则 什么都不做。
A = hello
A ?= world
内置变量:
CC 表示C语言编译器,默认值为 cc
CXX 表示C++编译器,默认为g++
…
自动变量:
@ 在编译规则中表示目标的名字
< 表示第一个依赖文件的名字
^ 表示所有的依赖文件的名字
- makefile中的函数
makefile中有很多内置函数,可供用户直接使用。
调用规则:
$(函数名 参数列表)
函数wildcard:
调用方式: $(wildcard PATTERN)
功能: 获取匹配模式为PATTERN的文件名
返回:匹配模式为PATTERN的文件名
例:
SRC := $(wildcard *.c)
如果当前目录下有a.c, b.c等,那么变量SRC的值就是 a.c b.c
函数patsubst:
调用方式: $(patsubst PATTERN, REPLACE, TEXT)
功能: 按照PATTERN指定的模式从TEXT搜索子字符串,并将它们替换成REPLACE
返回: 返回替换之后的新字符串
例:
DEPENDENCY := $(patsubst %.c, %.o, $(SRC))
如果SRC的值为a.c b.c 那么,DEPENDENCY的值为a.o b.o
6、实践
-
下载源码
官网
云盘
飞秋
…
下载完成后,把源码包拷贝到linux系统 -
解压
进入源码压缩包所在目录(如我的共享目录)
cd /mnt/hgfs/20197/20200702-0704-交叉开发/
解压到指定目录(指定目录必须存在)
tar xvf libjpeg-turbo-1.5.1.tar.gz -C /home/china/opensrclib/
进入解压后生成的源码目录
cd /home/china/opensrclib/libjpeg-turbo-1.5.1 -
配置(如果一切正常,会生成Makefile)
./configure --prefix=/home/china/opensrclib/libjpeg-arm --host=arm-linux
说明:
–prefix: 指定安装目录,该目录可以不存在,安装时会自动创建
–host: 目标平台
-
编译
make -
安装(把生成的结果,分类存放到prefix指定的目录中去,正常情况会生成四个目录:bin/ include/ lib/ share/)
make install -
打包移植
我们需要把生成的那个lib目录中所有的文件移植到开发板上去
进入安装目录:
cd /home/china/opensrclib/libjpeg-arm
压缩打包:
tar Jcvf jpeglib.tar.xz lib/
把生成的压缩包,传输(如rx/rz/tftp等)到开发板的工作目录下 -
配置开发板上库的路径
进入开发板的工作目录,解压上一步传输过来的压缩包
cd /home/china
tar xvf jpeglib.tar.xz
解压生成一个lib目录,该lib目录的绝对路径为: /home/china/lib/
把lib的绝对路径添加到环境变量LD_LIBRARY_PATH中去
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/china/lib/
确保上述指令正确后,可以把该指令写入/etc/profile文件的末尾,以使其永久生效!