看本文请参考《都江堰操作系统与嵌入式系统设计》第15章,该书可在www.djyos.com下载。
做了许多功课以后,终于开始写代码了。
移植操作系统,首先修改的永远是启动代码,启动代码是为后续程序准备最起码的运行环境的,每个OS都要有自己独特的要求,像IAR、MDK之类的工具自带的启动代码一般是不能用了,你把它翻译到gcc汇编也没用。Gcc自备的启动代码crt0.s呢,一般来说也没有用了,启动代码必须自己写,这点是不能偷懒的。
djyos过去的版本中,总有洋洋数百行的启动代码initcpu.s,令人望而生畏!不过,cm3是单片机级别的cpu,需要用汇编设置的东西比较少,initcpu.s只有了了几行。initcpu.s的内容如下:
.extern handler_msp @ld文件中定义的主栈指针初始化值
.extern thread_psp @ld文件中定义的线程栈指针初始化值
.extern cpu_init @c写的cpu初始化函数
.text @代码段开始
.global _start @把符号_start输出给ld文件使用,以便在elf文件
@中把_start标识为程序入口地址
.code 16 @thumb2汇编
.syntax unified @使用cm3的16位和32位统一汇编架构
.section reset_vect,"x" @初始化代码段
_start:
.word handler_msp
.word reset_start
.word rst_fault_handler @nmi_fault_handler
.word rst_fault_handler @hard_fault_handler
rst_fault_handler:
b .
reset_start:
CPSID I @PRIMASK=1,关中断
CPSID F @FAULTMASK=1,关异常
ldr r0,=thread_psp
msr psp,r0 @初始化线程栈指针,主栈指针是自动初始化的
mov r0,#0x20000000 @下面初始化ram中的中断向量表中系统异常部分。
ldr r1,=handler_msp
str r1,[r0]
ldr r1,=reset_start
str r1,[r0,#0x044]
ldr r1,=rst_fault_handler
str r1,[r0,#0x08]
str r1,[r0,#0x0c]
str r1,[r0,#0x10]
str r1,[r0,#0x14]
str r1,[r0,#0x18]
str r1,[r0,#0x1c]
str r1,[r0,#0x20]
str r1,[r0,#0x24]
str r1,[r0,#0x28]
str r1,[r0,#0x2c]
str r1,[r0,#0x30]
str r1,[r0,#0x34]
str r1,[r0,#0x38]
str r1,[r0,#0x3c]
ldr r1,=0xe000ed08 //NVIC.SCB.VTOR地址
str r0,[r1]
bl cpu_init @ 用C实现的cpu初始化部分
bl load_preload @开始操作系统预加载
前几句跟gcc汇编的语法相关,注释很详细,这里就不多说明了。
接下来三行放置了主栈指针地址,复位向量、nmi 和hardfault向量,这也是cortex-m3要求的,为的是能捕获启动时发生的nmi和hard异常。我们看到,nmi上只有一条死循环指令,在系统初始化完成之前,先给它搭一个临时避难所。cm3在复位后,nmi和hard异常是出于允许状态的,其异常向量必须在启动前准备好,否则如果在启动时发生nmi和hard异常的话,cpu将无所适从。至于除这两个异常以外的其他异常,我找了许多资料,都没写复位后的使能状态,依经验,暂且当他们是禁止的,等程序运行起来后再测试其使能状态,若使能,则必须向nmi和hard异常一样,要事先为他们准备好异常向量表。
复位后,cpu实际上市从reset_start处开始执行指令的,首先关闭了所有中断和硬fault。接下来两句这里初始化了线程栈指针psp,djyos的异常(包括中断)用msp,线程用psp,就是在这里初始化的。接下来初始化了一个异常向量表,把所有系统异常塞进前面说说的临时避难所中。为什么要这样做呢?这个跟djyos的启动过程是有关的,djyos允许用在最紧急的控制系统中,在系统启动前,就加载了中断系统,并且允许用户在OS系统启动过程中用中断执行最紧急的控制。这使得用户代码可以在系统复位后几个微秒内获得关键控制权。这是djyos对用户的特别关怀,任何系统都可能因bug而崩溃,崩溃导致重新启动时,系统会短暂失效,用户肯定希望关键功能失效时间越短越好,djyos的危急代码加载和实时中断机制就是为这个量身定做的。初始化中断系统会修改PRIMASK和FAULTMASK寄存器,系统异常将被使能,如果系统从灾难中恢复,有些系统异常可能出于使能状态,而此时OS还没启动,异常处理的代码也还没有加载,只能先给他们临时安置在紧急避难所中。
接下来就进入C语言环境继续初始化工作,完成后便开始系统加载了。cpu_init的代码如下:
void cpu_init(void)
{
switch(tg_scb_reg.CPUID)
{
case cn_revision_r0p0:break; //市场没有版本0的芯片
case cn_revision_r1p0:
tg_scb_reg.CCR |= bm_scb_ccr_stkalign;
break;
case cn_revision_r1p1:
tg_scb_reg.CCR |= bm_scb_ccr_stkalign;
break;
case cn_revision_r2p0:break; //好像没什么要做的
}
if((tg_rcc_reg.CR & cn_cr_check_mask) == cn_cr_check) //检查时钟状态
{
if((tg_rcc_reg.CFGR & cn_cfgr_check_mask) == cn_cfgr_check)
return ; //时钟已经初始化好
}
//开始初始化时钟
//step1:复位时钟控制寄存器
tg_rcc_reg.CR |= (uint32_t)0x00000001;
// 复位 SW[1:0], HPRE[3:0], PPRE1[2:0], PPRE2[2:0], ADCPRE[1:0] MCO[2:0] 位
tg_rcc_reg.CFGR &= (uint32_t)0xF8FF0000;
// 复位 HSEON, CSSON and PLLON 位
tg_rcc_reg.CR &= (uint32_t)0xFEF6FFFF;
// 复位 HSEBYP 位
tg_rcc_reg.CR &= (uint32_t)0xFFFBFFFF;
// 复位 PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE 位
tg_rcc_reg.CFGR &= (uint32_t)0xFF80FFFF;
// 禁止所有中断
tg_rcc_reg.CIR = 0x00000000;
//step2:设置各时钟控制位以及倍频、分频值
tg_rcc_reg.CFGR = cn_cfgr_set+(111<<24); // set clock configuration register
tg_rcc_reg.CR = cn_cr_set; // set clock control register
while(bb_rcc_cr_hserdy ==0);
while(bb_rcc_cr_pllrdy ==0);
}
这里首先检查cpu版本,根据cpu版本做了相应的设置,代码很简单。
接下来是时钟初始化,单片机经常用在实时控制中,这种系统要求快速启动,尤其是系统灾难恢复时,更加要求缩短系统失效时间。有硬件经验的人都知道,晶体起振和PLL稳定是需要比较长的时间的,在灾难恢复中,灾难事件往往没有破坏系统的时钟部分,这种情况就无需重新初始化时钟,以节约时间。所以在代码的开头,我们首先判断时钟状态,也就是判断RCC寄存器组的CR和CFGR两个寄存器中跟主时钟相关的成员的值是否等于我们的设定值。如果正常的话,则直接返回,否则把他们复位后再重新初始化。 |
|