1.前言
本人采用的树莓派是Raspberry 1B+型, CPU核心为BCM2835, ARM1176JZF-S内核,平常大家可能习惯了使用树莓派搞一些DIY,有意思的电子设计,本人使用树莓派的初衷并不是这样,我准备将它作为我学习嵌入式驱动程序开发的平台,至于选择这个的原因只有一个:便宜。市面上形形色色的开发板,价格高昂不说,配套的硬件资源其实就那么几样,按键、led、蜂鸣器、液晶屏、网口、串口、emmc及usb等,软件资源更不用说了,真正有价值的学习内容基本不会再配套的光盘中找到,而树莓派基本的硬件资源都有,价格还低,并且github、论坛上有很多开源的代码,所以树莓派当然是最佳选择。
2.树莓派启动
树莓派当启动时,ARM1176JZF-S的CPU会处于复位状态,由 GPU核心负责启动系统。启动的第一阶段, 从系统芯片中加载第一阶段的启动程序, 这个启动程序负责加载存放在SD卡中的第二启动程序(bootcode.bin)。bootcode.bin在 GPU上执行并加载第三阶段的启动器start.elf。 start.elf读取存放系统配置的文件config.txt。
当config.txt文件被加载解析之后, start.elf会读取cmdline.txt和kernel.img. cmdline.txt包涵内核运行的参数,而kernel.img将会被加载到处理器分配的共享内存中,当内核加载成功,处理器将结束复位状态,内核开始正式运行,系统启动正式开始。(以上来自于网络)
3.裸核编程思路
根据上一节叙述的内容,树莓派启动的最后阶段会将kernel.img 加载到内存中运行,我们的目标就是进行一次偷天换日,将自己编译出bin格式的kernel.img代替树莓派Linux启动内存卡原本运行的Linux 内核。编译出的裸核应用程序kernel.img跟传统的arm平台裸核应用开发还是有些不同的,不用初始化PLL,不用初始化DDR动态内存(想初始化也没可能,这部分控制器在芯片手册中根本找不到)。我们编译出的kernel.img主要完成以下几个方面:
- 关闭中断、MMU及cache
- 配置CPU工作在SVC模式下
- 配置CPU运行C程序的堆栈
- 运行第一个C应用程序(不能使用C库)
4.实现步骤
汇编语言:
.equ Mode_USR, 0x10
.equ Mode_FIQ, 0x11
.equ Mode_IRQ, 0x12
.equ Mode_SVC, 0x13
.equ Mode_ABT, 0x17
.equ Mode_UND, 0x1B
.equ Mode_SYS, 0x1F
.equ I_Bit, 0x80 @ when I bit is set, IRQ is disabled
.equ F_Bit, 0x40 @ when F bit is set, FIQ is disabled
.text
.global _start
_start:
bl set_svc @设置CPU工作在SVC模式,关闭中断
bl disable_mmu @设置CPU关闭MMU,dcache,icache
bl stack_setup @设置svc模式运行堆栈
bl clean_bss @清空bss段
ldr pc, =main @进入C程序运行阶段
set_svc:
mrs r0, cpsr @操作CPSR寄存器
bic r0, r0,#0x1f
orr r0, r0,#0xd3
msr cpsr, r0
mov pc, lr
disable_mmu:
mcr p15,0,r0,c7,c7,0
mrc p15,0,r0,c1,c0,0
bic r0, r0, #0x00000007
mcr p15,0,r0,c1,c0,0
mov pc, lr
stack_setup:
mov r0, #(Mode_SVC |F_Bit | I_Bit) @建立堆栈
msr cpsr_c, r0
@ Set the startup stack for svc
mov sp, #0x6000000 @设置堆栈指针
bx lr
stop_mmu:
mrc p15,0,r2,c1,c0,0 @设置协处理器C1
bic r2,#0x1000 @关闭icache
bic r2,#0x0004 @关闭dcache
bic r2,#0x0001 @关闭mmu
mcr p15,0,r2,c1,c0,0
bx lr
clean_bss:
ldr r0, =bss_start @bss_start变量取自于链接文件,数据段起始
ldr r1, =bss_end @bss_end变量取自于链接文件,数据段结束
cmp r0, r1
moveq pc, lr
clean_loop:
mov r2, #0 @数据段全部赋值为0
str r2, [r0], #4
cmp r0, r1
bne clean_loop
mov pc, lr
第一个C程序
int main()
{
while(1);
}
链接文件
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS {
. = 0x8000;
. = ALIGN(4);
.text :
{
src/start.o (.text*)
*(.text)
}
. = ALIGN(4);
.data :
{
*(.data)
}
. = ALIGN(4);
bss_start = .;
.bss :
{
*(.bss)
}
bss_end = .;
}
将上述汇编文件与c语言文件编译后的目标码,通过链接文件xxxx.ld,链接成我们最后的目标文件kernel.elf
通过objcopy 将kernel.elf文件转换成kernel.img
将kernel.img放入SD卡替换linux内核,大工告成
5.总结
其实上面生成的kernel.img并没有什么明显的现象,因为c语言程序在无限循环当中。上述程序的作用只是一个裸核程序编程的框架,接下来可以向C语言中添加串口打印的功能、gpio功能等等。
接下来,通过最基础的C语言直接操作BCM2835的外设寄存器,完成各个外设的驱动开发。