通常情况下,我们可以直接使用芯片厂商提供的.S启动文件来启动我们的CPU,但在实际产品开发中,我们需要设计一个系统架构能够兼容不同芯片厂商的芯片,而CPU的启动代码则不同,因此如果有个毕竟好的架构能够实现这功能那对于产品的开发帮助是极大的,幸好ARM公司的cortex-m内核有着良好的兼容性,对于这个,我们可以这样实现,以下以STM32单片机为例,其他的芯片类型,都是可以使用这种方式来启动芯片,而不用考虑.S文件。
1、首先要知道CPU最简单的启动代码一般包括堆栈初始化,全局变量初始化,向量表初始化,最后跳转到用户主程序main执行,而这边的main函数可以自己定义,但学过C语言的都知道,通常我们认为main函数是入口函数,那么这边我们就暂时认为它就是入口函数,其实真正意义来说,这个只能算是应用程序的入口函数;
2.CPU启动的时候cortex-m3的会执行如下过程,
1),从地址0x0000 0000取出4bytes的数据作为MSP的值;
2),从地址0x0000 0004取出4bytes的数据作为复位的PC值;
3),PC跳到复位向量处开始往下执行程序代码。
ARM公司规定的是那样,不过ST公司的启动位置在0x0800 0000地址,看到这边是不是感觉,诶,这个怎么办,其实这边是因为ST公司自动做了地址映射处理,将0x0800 0000地址映射到0x0000 0000地址,这样就符合ARM corte-m内核的规范了,
3,用C代码编写启动代码。
cortex-m3规定好,从0x00000004存放系统异常,接下来就是IC的外部中断(具体布局跟不同厂商设计有关)。
由此我们可以定义个一维数组,按中断布局顺序对应存放不同的向量值。
下面是对应STM32中断布局定义的一维数组,相信有一点C基础的朋友很容易看懂。
typedef void (* const __ISR_FUNC)(); /*向量的数据类型,对应c编译器就是一个函数指针*/
__attribute__ ((section(".isr_vector"))) /*特别指定表格的section名称是isr_vector,连接器定位用到,下面第4点会介绍*/
__ISR_FUNC g_pfnVectors[] =
{
(__ISR_FUNC)(&_eusrstack), /*0x0000_0000 主堆栈的数组*/
Reset_Handler, /*0x0000_0004 复位向量*/
NMI_Handler, /*0x0000_0008*/
HardFault_Handler, /*0x0000_000C*/
MemManage_Handler, /*0x0000_0010*/
BusFault_Handler, /*0x0000_0014*/
UsageFault_Handler, /*0x0000_0018*/
(__ISR_FUNC)0, /*0x0000_001C*/
(__ISR_FUNC)0, /*0x0000_0020*/
(__ISR_FUNC)0, /*0x0000_0024*/
(__ISR_FUNC)0, /*0x0000_0028*//* Reserved */
SVC_Handler, /*0x0000_002C*/
DebugMon_Handler, /*0x0000_0030*/
(__ISR_FUNC)0, /*0x0000_0034*/ /* Reserved */
PendSV_Handler, /*0x0000_0038*/
SysTick_Handler, /*0x0000_003C*/
/*以上16个位系统异常*/
/*下面的就是IC的外部中断(具体布局跟不同厂商设计有关)*/
WWDG_IRQHandler, /*0x0000_0040*/
PVD_IRQHandler,
TAMPER_IRQHandler,
RTC_IRQHandler,
FLASH_IRQHandler,
RCC_IRQHandler,
EXTI0_IRQHandler,
EXTI1_IRQHandler,
EXTI2_IRQHandler,
EXTI3_IRQHandler,
EXTI4_IRQHandler,
DMA1_Channel1_IRQHandler,
DMA1_Channel2_IRQHandler,
DMA1_Channel3_IRQHandler,
DMA1_Channel4_IRQHandler,
DMA1_Channel5_IRQHandler,
DMA1_Channel6_IRQHandler,
DMA1_Channel7_IRQHandler,
ADC1_2_IRQHandler,
USB_HP_CAN_TX_IRQHandler,
USB_LP_CAN_RX0_IRQHandler,
CAN_RX1_IRQHandler,
CAN_SCE_IRQHandler,
EXTI9_5_IRQHandler,
TIM1_BRK_IRQHandler,
TIM1_UP_IRQHandler,
TIM1_TRG_COM_IRQHandler,
TIM1_CC_IRQHandler,
TIM2_IRQHandler,
TIM3_IRQHandler,
TIM4_IRQHandler,
I2C1_EV_IRQHandler,
I2C1_ER_IRQHandler,
I2C2_EV_IRQHandler,
I2C2_ER_IRQHandler,
SPI1_IRQHandler,
SPI2_IRQHandler,
USART1_IRQHandler,
USART2_IRQHandler,
USART3_IRQHandler,
EXTI15_10_IRQHandler,
RTCAlarm_IRQHandler,
USBWakeUp_IRQHandler,
(__ISR_FUNC)0,
(__ISR_FUNC)0,
(__ISR_FUNC)0,
(__ISR_FUNC)0,
(__ISR_FUNC)0,
(__ISR_FUNC)0,
(__ISR_FUNC)0,
(__ISR_FUNC)0xF108F85F //this is a workaround for boot in RAM mode.
};
4.写好中断向量表的数据,如何在0x0000 0000起始处存放以上的数据表格呢?这就需要用到连接器ld来定位了,
接下来就简单介绍下如何定位中断向量表。
/*"以下是stm32的memery布局,flash是从0x0800 0000开始,其实0x0800 0000映射到0x00000 000处的"*/
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K /* also change _estack below */
FLASH (rx) : ORIGIN =0x08000000, LENGTH = 512K
}
/*stm32的复位时向量表数据就放在0x0800 0000开始*/
SECTIONS
{
.isr_vector :
{
. = ALIGN(4); /*flash section内的定位符‘.’从0x0800 0000算起*/
KEEP(*(.isr_vector)) /*把输入section isr_vector 放在0x0800 0000开始,就是我们的向量表格了*/
. = ALIGN(4);
} >FLASH
}
这里就不做多介绍ld的知识了,有兴趣的朋友可以研究以下,个人认为使用gccb编译器,会令你更深入了解C代码是如何生存的,如何对应指令集和内存的。
我只了解下皮毛,由于个人工作原因,没更多的时间深入研究学习。不过本人仍然保持这份兴趣。
5.以下再简单介绍下全都变量初始化,看下面代码:
void Reset_Handler(void) /*这个常量函数指针就是放在0x0000_0004处的*/
{
unsigned long *pulSrc, *pulDest;
/***********************************************************************
初始化有初始值的变量
_sidata标识符就是初始化变量的rom镜像的起始地址,这是链接文件上定义的。
_sdata标识符就是全局变量在ram中的起始地址,_edata标识符就是ram中的结束地址
************************************************************************/
pulSrc = &_sidata;
for(pulDest = &_sdata; pulDest < &_edata; )
{
*(pulDest++) = *(pulSrc++);
}
/*同理下面就是无初始值的变量,全清零*/
for(pulDest = &_sbss; pulDest < &_ebss; )
{
*(pulDest++) = 0;
}
main();/*跳到用户主程序*/
}
以上就完成了简单的启动代码。写cortex的代码完全不用汇编代码,这得益于cortex复位启动时根据一个向量表来启动的。
有兴趣的可以研究学习下gun工具链,这对想搞liunx内核的非常有帮助。