树莓派裸核程序开发 —— 从汇编到第一个C语言程序

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的外设寄存器,完成各个外设的驱动开发。

你可能感兴趣的:(树莓派)