裸机工程开发调试
嵌入式系统的组成
嵌入式系统设备遵循自底向上的基本结构
嵌入式微处理器
嵌入式操作系统
外围硬件设备
用户应用程序
###s5p6818系统主要资源
s5p6818是三星公司推出的64位RISC微处理器, 其CPU采用的是ARM CortexA53内核, 基于ARMv7和ARMv8指令集架构
八核, 保守主频1.4GHZ, 向下完全兼容4418核心, 可以做到嵌入式产品硬件的无缝升级
s5p6818结构框图如下:
s5p6818试验仪介绍
s5p6818核心板资源介绍:
s5p6818寻址空间介绍
s5p6818寻址空间采用统一编址方式进行管理
寻址空间映射图:
Normal I/O就是我们常说的特殊功能寄存器
GPIO等内容在这里进行配置
iROM
和iRAM
具体分布:
Internal ROM
启动, 是可以由BootMode
相关引脚选择的多种程序加载方式, 即是从内部还是从外部等途径来加载程序(P94
)
P93
和P95
决定了外部程序的加载顺序
RST_CFGn
对应的引脚通过查表和原理图来最终确定程序加载启动流程
裸机开发调试
常用GNU工具介绍
GNU组织不仅给我们带来了许多开源软件工程, 还带来了强大的GNU编译工具
GNU提供的常用工具包括:
-
预处理器 cpp
-
C编译器 gcc
-
C++编译器
-
g++汇编器 as
-
链接器 ld
-
二进制工具集 objcopy、 objdump、 ……
-
ls /usr/local/arm/4.3.2/bin/
下面介绍几个常用的工具
nm
: 符号显示器
显示符号 $nm -n main_elf
显示内容:
-
第一列为 符号地址
-
第二列为 符号所在段
-
第三列为 符号名称
各段含义 :
段 | 描述 |
---|---|
b / B | .bss(b静态 / B非静态)未初始化变量 |
d / D | .data(d静态 / D非静态)已初始化变量 |
r / R | .rodata(r静态 / R非静态)只读数据段 |
t / T | .text(t静态 / T非静态)函数 |
A | 不可改变的绝对值 |
C | .o中未初始化非静态变量 |
N | 调试用的符号 |
U | 表示符号只有声明没有定义 |
W | 一个弱符号,尚未被明确标记为弱对象符 |
nm符号显示器总结 ;
静态变量和非静态的全局变量, 所分配的段只和是否初始化有关, 如果初始化了则被分配 在.data
段中, 否则在.bss
段中
函数无论是静态还是非静态的, 总是被分配在.text
中,小写 t
表示静态, 大写 T
表示非静态
函数内的局部变量由于是分配在栈上的, 所以在 nm
中是看不到他们的
objdump: 显示二进制文件的信息
-
查看所有段信息
$objdump -h main_elf
-
查看文件头信息
$objdump -f main_elf
-
查看反汇编
$objdump -d main_elf
-
查看内嵌反汇编
$objdump -S -d main_elf
objcopy : 段剪辑器
- 去除 elf 格式信息
$objcopy -O binary -S main_elf main.bin
LMA和VMA
从源代码到可执行的代码,一般要经过一下几个过程:
源代码编辑 -> 编译 -> 链接 -> 装载 -> 执行
编译
简单的说就是用编译工具,将你的源码变成可以执行的二进制文件,即目标文件,当然只是对应某一种硬件平台,比如x86,arm
链接
就是将多个目标文件合并为一个目标文件,称作可执行文件。
每个目标文件都包含一连串的section,最常见,最基础的有:
.text: 代码段, 就是cpu要运行的指令代码
.data: 数据段, 程序中的一些数据,如初始化的全局变量,静态变量等都放在这个段里
.bss: 未初始化段,记录了程序里未初始化的全局变量,就相当于只记录对应的名字,留着程序运行前去初始化为0,所以此处不占用具体空间。打个比方,只记录人名,没有人站在这里.
而对应的.text 和.data段,都是既有人名(函数或者变量名),又占用对应的地方(包含具体空间记录到底, 是什么指令代码和数据的数值是多少)
section一般分为loadable和allocatable 通俗点说就是:
loadable,可加载 , 就是,原来目标文件里面包含对应的代码或数据,所以,装载器要把这些内容,load 到对应的地址,以便程序可以运行;
allocatable, 可分配的 ,最简单理解就是上面的**.bss段**,那里记录了人名,到时候,你要给这些人名分配空间给你站的地方,对应着也就是变量所要的具体内存空间。
对于目标文件中的 loadable 或 allocatable的section,其都有两个地址:VMA 和 LMA
LMA(Load Memory Address):内存装载地址
load, 装载
如果想要使你的程序(即由你的源码,通过编译器的编译,链接器的链接,形成的可执行文件), 能做内存里面运行,那么肯定要涉及到一点,就是,把你的程序从常见的存储硬盘里面搬到内存里面,然后才有可能运行。而这里的装载就是这这个意思。就是把程序从硬盘里面装载到内存里。
对应的,放到内存的什么地方?就是LMA。详细点说,就是把其中的.text代码段,.data数据段等内容,搬到,也就是copy到内存的LMA地址处。
内存
程序运行的本质,就是cpu读取到指令,然后执行。
如果想要运行你的程序,首先,你应该把你对应的指令放到合适的位置,cpu才能读到,才能执行。此处合适的地方,有人想到,直接放到硬盘里,cpu过来读取,然后执行不就可以了嘛,还不用这么麻烦将(指令)代码搬来搬去的,多省事。但是实际上,系统就是这么“笨”地搬来搬去,原因在于,从硬盘上直接读取指令,速度比直接从内存要慢很多倍(一般pc上的内存是各种各样的RAM,如ddr,但此处统称为memory/内存),所以系统才会不嫌麻烦,把代码拷贝到内存里面去,然后从内存里面读取指令,再执行,这样速率会高很多。
所以简单的说就是,为了总体效率,对于普通系统,比如pc,程序的执行都是在memory,内存里面执行的。
总结:
代码被装载到内存的某个地方,那个地方的地址,就是LMA
VMA:Virtual Memory Address 虚拟内存地址
简单的说就是,你程序运行时所对应的地址。
此处所谓的虚拟,一般来说,指的是启用了MMU之后,才有了虚拟地址和实地址。此处,我们可以简单的理解为,就是内存的实际地址即可。
程序运行前,要把程序的内容,拷贝到对应的内存地址处,然后才能运行。
总结:
代码运行的时候,此时对应的地址,就是VMA
在多数情况下,LMA和VMA是相等的
理解:
如果是普通pc电脑,也就是上面说的,大多数情况下,LMA和VMA是一样的,也就是,程序被加载到内存的什么地方,也就在什么地方运行。
程序被放到了ROM中,比如设置位只读的Nor Flash中,那么LMA的地址就是Nor Flash的地址。
随便举例为0x1000000,而程序要运行时的地址是内存地址,比如0x30000000,也就是VMA是0x30000000,这时候,就要我们自己保证,在程序运行之前,把自己的程序,从LMA = 0x10000000拷贝到VMA = 0x30000000 处,然后程序才可以正常运行。
有人会问,反正对应ROM来说,CPU也是可以直接从ROM里面读取代码,然后运行。为何还要前面提到的,弄个LMA和VMA不同,搬来搬去呢?因为ROM,顾名思义,是只读的,只能读取,不能写入。
而程序中的代码段,由于只是被读取,不涉及到修改写入,是没问题的。但是对于数据段和bss位初始化段来说,里面的所有的程序的变量,多数都是在运行的时候,不仅要读取,而且要被修改成新的值,然后写入新的值,所以,如果还是放到ROM里面,就没法修改写入。
而且,另一个原因是,cpu从ROM,比如常见的Nor Flash中读取代码的速度,要远远小于从RAM,比如常见的SDRAM中读取的速度,所以,才会牵涉到将代码烧写到ROM里面,然后代码的最开始,将此部分程序reaload,重载,也就是从此处的ROM的地址,即LMA,重新拷贝到SDRAM中去,也就是VMA的地方的地方,然后从那里运行。
关于LMA和VMA
Linker,连接器的作用:
1、将LMA写到(可执行的)二进制文件里面去
2、解析符号。即,把不同的符号,根据符号表中的信息,转换成对应的地址。此处只涉及VMA,即程序运行时的地址。
Loader,装载器的作用:
1、从二进制文件中读出对应的段的信息,比如text,data,bss等段的信息
将内容拷贝到对应的LMA的地址处,所谓,装载(对应内容)到装载地址(LMA)
2、如果发现VMA != LMA, 即 程序运行时候的地址和刚刚把程序内容拷贝到的地址LMA不一样,那么就要把对应的内容,此处主要是data,数据段的内容,从刚刚装载到的位置,LMA处,拷贝到VMA处,这样,程序运行的时候,才能够在执行的时候,找到对应VMA处的变量,才能找到对应的值,程序才能正常运行。
程序的编译链接过程
最后一步是将目标文件交给链接器链接生成可执行文件
gcc -E -o main.i main.c
gcc -S -o main.S main.i
gcc -c -o main.o main.S
gcc -o main_elf main.o
一个标准应用程序通常都由链接器采用默认链接脚本(arm-linux-ld --verbose
)将“ 用户程序” 和“ 库” 共同链接生成可执行程序
假如不采用系统给定的默认链接脚本, 我们应该怎样进行链接 :
ld -o main main.o
结论:
链接过程中需要一个标号( _start
) 作为程序入口
标号(_start
)的作用是: 将用户程序从汇编带到了C语言程序入口, 即main()函数, 从此开始我们的应用程序之旅
_start标号所在的汇编文件在编译工具链下面:
4.3.2/arm-none-linux-gnueabi/libc/usr/lib/crt1.o
我们可以自己实现**_start入口, 或者重新指定一个新的入口**
手动链接并生成可执行文件过程如下:
直接通过参数指定程序入口和段地址:
arm-linux-ld -Ttext=0x30000 -Tdata=0x40000 -e main -o app head.o main.o
通过链接脚本来指定程序入口和段地址:
arm-linux-ld -Tapp.lds -o app head.o main.o
//连接文件app.lds为:
ENTRY(main)
SECTIONS
{ //“*”号指所有目标, 可以指定.o目标文件, 多个用空格隔开
.=0x30000; //“.”指的是当前位置
.text:{*(.text)}
.=0x40000;
.data:{*(.data)}
.bss:{*(.bss)}
}
裸机工程构建及调试
裸机程序完整编译过程:
gcc -c -o main.o main.c
gcc -c -o uart.o uart.c
ld -Tapp.lds -o app_elf main.o uart.o
objcopy -O binary -S app_elf app.bin
裸机工程管理文件
-
各目录
Makefile
-
链接脚本
app.lds
-
规则文件
Rules.mk
最后将app.bin下载到裸板内存中并执行
- 工程实例:
00-pro-demo
- 下载命令:
update app
- 运行命令:
go
- 退出程序:
crtl+c
基础代码中的Makefile文件
# 剪除elf格式信息, 这样才能在裸机状态下执行
app.bin:$(OBJS)
$(LD) -v $(LFLAGS) -o app_elf $(OBJS) $(ARMLIBS)
$(OBJCOPY) -O binary -S app_elf $@
$(OBJDUMP) -D -m arm app_elf > app.dis
$(NM) -v -l app elf > app.map
构建嵌入式开发平台
嵌入式 linux 软件系统由 bootloader、kernel、root filesystem 构成,如下:
Bootloader(一次固化) + 内核(多次更新) + 根文件系统制作
资料拷贝:
资料存放路径如下:
6_平台\linux_resource\3.4.39_tools_
6_平台\3.4.39_tools
将上面路径下用到的文件拷到自己虚拟机中,存放在自建目录下:
例如(推荐):/home/edu/share/linux_platform2.6.35.7/
注意: 以上资料一定要拷贝放入 linux 虚拟机目录下, 且目录有一切权限,一定不能放到 /mnt/hgfs 所映射的磁盘中
安装交叉编译器 arm-linux-gcc-4.3.2
进入虚拟机中找到你上步自建的目录中, 找到 arm-linux-gcc-4.3.2.bz2 文件:
cd /home/edu/share/linux_platform2.6.35.7/
查看自己的虚拟机是否安装交叉编译工具链:
echo $PATH
安装以下交叉编译工具: (4.3.2: 用于交叉编译各种镜像)
# 如果有这个目录就不用建了
mkdir –p /usr/local/arm
sudo tar jxvf arm-linux-gcc-4.3.2.bz2 –C /usr/local/arm
# 或 将 arm-linux-gcc-4.3.2.bz2 拷贝到/usr/local/arm 直接解压
cd /usr/local/arm
sudo tar jxvf arm-linux-gcc-4.3.2.bz2
其中 4.3.2 需要假加入环境变量
sudo vim ~/.bashrc
# 在最后加入下面这行
export PATH=/usr/local/arm/4.3.2/bin:$PATH
# 使环境变量设置立即生效
source ~/.bashrc
配置编译 u-boot
# 在虚拟机中解压 u-boot-2014.07.tar.bz2 文件
tar jxv 7.tar.bz2
# 进入解压后的文件目录中
cd u-boot-2014.07
# 编译前清除旧的配置文件
make distclean
# 生成新的配置文件
make x6818_config
# 编译生成 u-boot 可执行文件
make
# 在当前目录下会生成一个 ubootpak.bin 的文件
# 将 tools 目录下生成的 mkimage 文件拷贝到/bin 目录中, 后面用于制作 u-boot 所需格式的内核
# 这一步也可以不做, 主要用于生成 uImage
cp u-boot-2014.07/tools/mkimage /bin
配置编译内核
# 在虚拟机中解压 kernel6818_3.4.39.tar.gz 文件
tar zxvf kernel6818_3.4.39.tar.gz
# 进入解压后的文件目录
cd kernel-3.4.39
# 清除旧的内核配置文件
make distclean
# 重命名内核配置文件
cp sp_config_2017.03.20 .config
# 配置内核
make menuconfig
# 注意: 如果执行“make menuconfig” 提示找不到“ncurses 库”, 那么执行更新命令:
sudo apt-get install libncurses5-dev
# 如果无法连接到 172.20.220.71 , 将虚拟机设置成了静态 IP, 首先将 IP 设置为自动获取, 然后禁用一下虚拟机网络连接再启动即可
# 由于采用的是配置好的脚本, 因此对配置界面不用做任何修改, 直接保存退出即可。
# 生成内核镜像文件
# 在 arch/arm/boot 下会生成 uImage 文件
make uImage
# 或
make uImage -j2
# 进入内存镜像所在目录
cd arch/arm/boot
# 拷贝 temp.tar.bz2 到内存镜像所在目录 arch/arm/boot 并解压
tar jxvf temp.tar.bz2
# 进入解压出来的 temp 目录
cd temp
# 执行脚本生成 boot.img 下载镜像文件
./mkboot.sh
Ext4 根文件系统的制作
# Ext4 格式的根文件系统是可读可写的文件系统
# 解压根文件系统文件
tar jxvf gtk_rootfs.tar.bz2
# 给 mkyaffs2image 添加可执行权限
chmod +x mkfs_ext4/make_ext4fs
# 拷贝 mkyaffs2image 文件到/bin 下
sudo cp mkfs_ext4/make_ext4fs /bin
# 生成根文件系统镜像
make_ext4fs -s -l 314572800 -a root -L linux gtkfs.img gtk_rootfs
使用 USB 方式进行烧写
先拷贝以下路径的 fastboot 文件夹到本机:
执行步骤:
首先连接上 USB 线,串口线,电源线
然后给开发板上电,并进入 U-boot 命令行模式
在 U-boot 命令行中输入 fastboot 命令后点击回车键
桌面上会自动弹出对话框,提示安装驱动
选择从列表或指定位置安装,单击下一步
选择在搜索中包括这个位置,单击浏览按钮,选择路径
选择完后点击确定,然后点击下一步
弹出此对话框时单击确定即可
安装完成出现下面图片,点击完成即可完成驱动安装
在 fastboot 文件夹中创建文件夹 all_image,把刚才制作的三个镜像文件拷贝到 all_image 中
【注意】红色方框圈起来的 all_image 文件夹中的内容为:
右键编辑上面文件夹中的"sp_linux_image_down.bat"(方框标识的批处理文件),将其内容由
fastboot flash ubootpak ../sp_linux_image/ubootpak.bin
fastboot flash boot ../sp_linux_image/boot.img
fastboot flash gtkfs ../sp_linux_image/gtkfs.img
fastboot flash userdata ../sp_linux_image/userdata.img
fastboot flash cache ../sp_linux_image/cache.img
fastboot flash recovery ../sp_linux_image/recovery.img
# 改为:
fastboot flash ubootpak ./all_image/ubootpak.bin
fastboot flash boot ./all_image/boot.img
fastboot flash gtkfs ./all_image/gtkfs.img
# 其他内容使用 rem 注释掉即可
# 保存关闭
双击批处理文件"sp_linux_image_down.bat",这时会在我们的 u-boot 命令行中由串口打印出相关信息
镜像安装办法
Ubuntu 下制造 TF 卡
第一步: 准备一张不小于 4GB 的 TF 卡, 启动 ubuntu 虚拟机, 插入 TF 卡到 PC机, 然后点击下图中的红框按钮将 TF 卡连接到虚拟机。
# 安装 gparted 命令
sudo apt-get install gparted
# gparted 工具删除里面所有分区
sudo gparted /dev/sdb
选中列表中的磁盘分区, 右键删除, 再点击菜单上的勾, 应用刚才的操作。
第二步: 新建分区。 选中列表中未分配的磁盘, 再点击分区->新建, 在弹出的对话框中, 在文件系统中选择 fat32
, 在之前的空余空间中输入 256, 预留256M 空间, 可用于后面烧写 uboot, 方便后续 TF 卡更新。
第三步: 点击添加, 再点击菜单栏的勾, 稍等之后完成新的分区创建。
第四步: 将 bootpak.bin 拷贝到 Ubuntu 虚拟机下, 执行如下命令制作 TF 卡:
sudo dd if=ubootpak.bin of=/dev/sdb1 bs=512 seek=1 conv=sync
硬件连接
将制作好的 TF 启动卡插入开发板的 TF 卡槽中。
X6818 开发板默认首先从 SD0
通道启动, 如果 SD0 卡槽放有能够启动 6818的 TF 卡, 则从 TF 卡启动, 否则从 EMMC 启动。 当我们将裸机程序烧写到 TF 卡后, 只需要插到 SD0 通道, 即 X6818 开发板右侧的 TF 卡槽, 开机即可运行 U-boot程序, 而不必理会开发板上是否已经烧有映像, 也无需进行任何的跳线设置。
将串口线的一端连接到开发板上, 另一端连接到 PC 机的 USB 口上, 同时准备好 OTG 下载线, 连接好硬件之后就可以进行下一步操作了。
建立超级终端
双击运行超级终端, 或者 Xshell5.exe 等等终端如下所示:
点击新建, 然后设置串口相关信息, 将名称和协议设置好, 协议设置为 SERIAL
设置好之后, 点击左边类别拦中的连接选项, 点击 SERIAL, 设置端口号和波特率
确定之后点击连接, 进入终端界面
进入 U-Boot, 烧写系统镜像文件(USB 线烧模式)
为系统上电后打印的一段信息。 红色框处是自动引导倒计时, 我们设置的默认时间是 3 秒钟, 在 3 秒后系统将会进入自动引导区。 在倒计时完成之前,我们按下 PC 机任意键就可以停止自动引导, 进入 U-Boot
界面。
在 U-Boot 命令行模式下输入“fast” 命令, 并回车;
将预先准备好的 OTG
下载线的一段连接到开发板上, 另一端连接到 PC
机的USB
口上;
找到资料 sp_linux_image_down.bat
(注: 烧写 Linux
镜像时, 选择 fast.bat) , 双击运行批处理文件
烧写过程中会提示一下打印信息, 说明正在正常烧写系统镜像文件
可以观看试验仪的显示屏是会显示烧写进度, 等待系统镜像烧写完成。
烧录(Linux/Android) 双系统步骤:
需要制作 android 和 gnu/linux 双系统,但两者各需要三个分区和一些非必要分区,如果我们用原来的分区方法,可能会出现分区数不够 或 分区空间不够的情况,因此我们需要对设备存储空间进行统一的重新分区。
A53 板子上电启动, 进入 U-boot 界面:
# 查看分区
fdisk 2
七个分区 :
# 查看 fastboot 支持的下载内容
fastboot
要在里面加一个 gtkfs
另外原来的 uboot 不支持我们要的 9 分区数,因此我们需要重新烧写新的 uboot。程序已经改好,我们只需下载新的 uboot 即可,因此,更新
修改下载脚本
fastboot flash ubootpak ./ubotpak.bin
REM fastboot flash boot ./boot.img
REM fastboot flash gtkfs ./gtkfs.img
REM fastboot flash cache ./image6818/android/cache.img
REM fastboot flash userdata ./image6818/android/userdata.img
REM fastboot flash system ./image6818/android/system.img
下载好新的 uboot 后重启设备。
烧写双系统的要求: 都是将文件烧写到 emmc 上, 烧写双系统得重新分区, 所以先分区, 用 fdisk 命令, 命令如下:
fdisk 2 8 100000:4000000 4100000:2f200000 33300000:1ac00000 4e000000:800000 4e900000:1600000 50000000:0xc800000 0x5c900000:0x1f400000 0x7be00000:0x0
# 查看分区
fdisk 2
9 个分区了,足够双系统使用
fastboot
后仍然看不到 gtkfs 项目,因为这些内容并不会随 uboot 而改变,我们需要手动清除,这样 uboot 才会对其进行重新设置。
# 擦写分区
mmc erase 0x400 0x40
之后重启一下。
fastboot
出现了 gtkfs 选项,表示我们能够把这个镜像下载到新的分区中了
上述步骤完成之后, 重启板子, 再次进入 U-boot
界面, 用 fast 命令(USB 线烧模式)进行烧写。
由于我们进行了重新分区,因此除了 uboot 以外的内容很可能都不能使用了,因此我们先烧写内核,也就是 boot.img 镜像。
下载脚本修改 :
REM fastboot flash ubootpak ./ubotpak.bin
fastboot flash boot ./boot.img
REM fastboot flash gtkfs ./gtkfs.img
REM fastboot flash cache ./image6818/android/cache.img
REM fastboot flash userdata ./image6818/android/userdata.img
REM fastboot flash system ./image6818/android/system.img
再下载 gtkfs.img 文件系统镜像即可
下载脚本修改成:
REM fastboot flash ubootpak ./ubotpak.bin
REM fastboot flash boot ./boot.img
fastboot flash gtkfs ./gtkfs.img
REM fastboot flash cache ./image6818/android/cache.img
REM fastboot flash userdata ./image6818/android/userdata.img
REM fastboot flash system ./image6818/android/system.img
下载后重启,成功
现在具有下载并启动双系统的能力。
配置 U-Boot 环境变量
待系统镜像烧写完成后, 先拔去 OTG 下载线, 在给试验仪重新上电, 并进入U-Boot 界面;
如果板子烧写的是( Linux/Android) 双系统, 默认启动的是 Linux
系统, 板子上电之后不用任何操作, 启动起来就是 Linux
系统。 如果要启动 Android 系统,板子上电时按住下方按键从左往右的第四个按键, 就是启动 Android 系统, 在U-boot
界面显示如下: