韦东山老师的开发板在讲解存储管理技术的时候大致意思如下,片内ram只有4K,可执行程序大小超过4K怎么办,在这里韦东山老师的办法是在可执行代码的首部,运行自身拷贝,把自己拷贝到内存地址足够存放存放程序的地址处,然后直接跳转过去执行,这种技术在stm32里面叫做程序自举,这种技术也叫作程序动态加载,既可以用于软件自举,操作系统引导加载,还可以用于嵌入式系统不停机运行升级。
这种技术本身跟操作系统内核引导加载是同一种技术,linux下我们使用uBoot加载操引导操作系统内核,内核再引导加载整个操作系统,都是用了这种技术。韦东山老师使用汇编编写的引导加载代码,并且在链接的时候指定了加载地址。我决定用C语言实现,汇编代码做尽可能少的工作。实在是汇编代码编写难度太大,只能回到熟悉的C了,用另一种思路实现,也算是对自己学习的考验吧。闲话休提,总计使用了两个C文件一个汇编文件,上代码。
//这个是systm.c文件,用于初始化硬件
#define WATCH_DOG (*(volatile unsigned long *)0x53000000)
#define SDRAM_BASE (volatile unsigned long *)0x30000000
#define MEM_CTL_BASE (volatile unsigned long *)0x48000000
const unsigned long memcfgval[]={
0x22011110, 0x00000700, 0x00000700, 0x00000700,
0x00000700, 0x00000700, 0x00000700, 0x00018005,
0x00018005, 0x008C07A3, 0x000000B1, 0x00000030,
0x00000030
};
void disable_watch_dog(void) //禁用看门狗
{
WATCH_DOG = 0;
}
void mem_setup(void) //配置内存存储设置
{
int i;
volatile unsigned long* ptr;
ptr = MEM_CTL_BASE;
for(i = 0; i < sizeof(memcfgval)/sizeof(memcfgval[0]); i++)
{
*ptr = memcfgval[i];
ptr++;
}
}
void executable_code_jmp(void) //内存拷贝,实现自身从0地址拷贝到指定地址
{
int i;
volatile unsigned long* src;
volatile unsigned long* dest;
src=0;
dest = SDRAM_BASE;
for(i = 0; i < 1024; i++)
{
*dest = *src;
dest++;
src++;
}
}
这个是汇编启动代码文件statrup.S,用于设置C使用环境
.text
.global _start
_start:
ldr sp,=1<<12
bl disable_watch_dog
bl mem_setup
bl executable_code_jmp
ldr sp,=0x34000000
ldr pc,=(0x30000000 + main)
halt_loop:
b halt_loop
这个是main.c文件,常规C程序,使用韦东山的点灯
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPF4_out (1<<(4*2))
#define GPF5_out (1<<(5*2))
#define GPF6_out (1<<(6*2))
extern void disable_watch_dog(void);
extern void mem_setup(void);
extern void executable_code_jmp(void);
void wait(volatile unsigned long dly)
{
for(; dly > 0; dly--);
}
int main(void)
{
unsigned long i = 0;
GPFCON = GPF4_out|GPF5_out|GPF6_out; // 将LED1,2,4对应的GPF4/5/6三个引脚设为输出
while(1){
wait(30000);
GPFDAT = (~(i<<4)); // 根据i的值,点亮LED1,2,4
if(++i == 8)
i = 0;
}
return 0;
}
一下是makefile文件
led.bin:startup.S systm.c main.c
arm-linux-gcc -c -o startup.o startup.S
arm-linux-gcc -c -o systm.o systm.c
arm-linux-gcc -c -o main.o main.c
arm-linux-ld -Ttext 0x00000000 -g startup.o systm.o main.o -o led_elf
arm-linux-objcopy -O binary -S led_elf led.bin
arm-linux-objdump -D -m arm led_elf > led.dis
汇编启动代码还是按照常规,关键代码在bl executable_code_jmp
ldr sp,=0x34000000
ldr pc,=(0x30000000 + main)
executable_code_jmp 实现了代码自动拷贝到内存指定地址处,然后设置堆栈,直接跳转main函数入口地址+0x30000000处。反汇编结果如下所示
c: eb000024 bl a4
10: e3a0d30d mov sp, #872415232 ; 0x34000000
14: e59ff000 ldr pc, [pc, #0] ; 1c <.text+0x1c>00000018 :
18: eafffffe b 18
1c: 30000154 andcc r0, r0, r4, asr r1
然后下载运行,跟韦东山的代码执行效果一模一样。在这里我使用 ldr pc,=(0x30000000 + main)语句跳转到main函数地址,韦东山的代码通过链接时候指定偏移地址0x30000000,然后bl main实现。可见,只要思路行得通,软件的实现方法是多种多样的。