[随手笔记]STM32启动之探究

File Name : startup_stm32l071xx.s

1. 裸机

启动文件由汇编编写,是系统上电复位后第一个执行的程序。主要做了以下工作:

  1. 初始化堆栈指针 SP(__initial_sp)
  2. 初始化 PC 指针(Reset_Handler)
  3. 初始化中断向量表(__Vectors)
  4. 配置系统时钟(SystemInit)
  5. 调用 C 库函数 _main 初始化用户堆栈,从而最终调用 main 函数去到 C 的世界

1.栈设置

Stack_Size		EQU     0x200

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp

开辟栈的大小为 0X00000400(1KB),名字为 STACK,NOINIT 即不初始化,可读可写,8(2^3)字节对齐

栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部 SRAM 的大小

如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。如果某一天,你写的程序出现了莫名奇怪的错误,并进入了硬 fault 的时候,这时你就要考虑下是不是栈不够大,溢出了

标号 __initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的

2. 堆设置

Heap_Size      EQU     0x200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

开辟堆的大小为 0X00000200(512 字节),名字为 HEAP,NOINIT 即不初始化,可读可写
__heap_base 表示对的起始地址,__heap_limit 表示堆的结束地址。堆是由低向高生长的,跟栈的生长方向相反
堆主要用来动态内存的分配,像 malloc() 函数申请的内存就在堆上面

3. 向量表

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     PendSV_Handler            ; PendSV Handler
                DCD     SysTick_Handler           ; SysTick Handler

                ; External Interrupts
                DCD     WWDG_IRQHandler                ; Window Watchdog
                DCD     PVD_IRQHandler                 ; PVD through EXTI Line detect
                DCD     RTC_IRQHandler                 ; RTC through EXTI Line
                DCD     FLASH_IRQHandler               ; FLASH
                DCD     RCC_IRQHandler                 ; RCC
                DCD     EXTI0_1_IRQHandler             ; EXTI Line 0 and 1
                DCD     EXTI2_3_IRQHandler             ; EXTI Line 2 and 3
                DCD     EXTI4_15_IRQHandler            ; EXTI Line 4 to 15
                DCD     0                              ; Reserved
                DCD     DMA1_Channel1_IRQHandler       ; DMA1 Channel 1
                DCD     DMA1_Channel2_3_IRQHandler     ; DMA1 Channel 2 and Channel 3
                DCD     DMA1_Channel4_5_6_7_IRQHandler ; DMA1 Channel 4, Channel 5, Channel 6 and Channel 7
                DCD     ADC1_COMP_IRQHandler           ; ADC1, COMP1 and COMP2 
                DCD     LPTIM1_IRQHandler              ; LPTIM1
                DCD     USART4_5_IRQHandler            ; USART4 and USART5
                DCD     TIM2_IRQHandler                ; TIM2
                DCD     TIM3_IRQHandler                ; TIM3
                DCD     TIM6_IRQHandler                ; TIM6
                DCD     TIM7_IRQHandler                ; TIM7
                DCD     0                              ; Reserved
                DCD     TIM21_IRQHandler               ; TIM21
                DCD     I2C3_IRQHandler                ; I2C3
                DCD     TIM22_IRQHandler               ; TIM22
                DCD     I2C1_IRQHandler                ; I2C1
                DCD     I2C2_IRQHandler                ; I2C2
                DCD     SPI1_IRQHandler                ; SPI1
                DCD     SPI2_IRQHandler                ; SPI2
                DCD     USART1_IRQHandler              ; USART1
                DCD     USART2_IRQHandler              ; USART2
                DCD     LPUART1_IRQHandler             ; LPUART1
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
   
__Vectors_End

在这里插入图片描述

4. 复位程序

_main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,并在函数的最后调用 main 函数去到 C 的世界

; Reset handler routine
Reset_Handler    PROC
                 EXPORT  Reset_Handler                 [WEAK]
        IMPORT  __main
        IMPORT  SystemInit  
                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

//2. 主函数
int main()
{


}

2.操作系统

//1. 启动文件
; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP
// 2. 主函数之前代码(理解为抢占了进入主函数的先机)
#if defined(__CC_ARM) || defined(__CLANG_ARM)
extern int $Super$$main(void);
/* re-define main function */
int $Sub$$main(void)
{
    rtthread_startup();
    return 0;
}
//3. 真正初始化启动
int rtthread_startup(void)
{
    rt_hw_interrupt_disable();

    /* board level initialization
     * NOTE: please initialize heap inside board initialization.
     */
    rt_hw_board_init();

    /* show RT-Thread version */
    rt_show_version();

    /* timer system initialization */
    rt_system_timer_init();

    /* scheduler system initialization */
    rt_system_scheduler_init();

    /* create init_thread */
    rt_application_init();

    /* timer thread initialization */
    rt_system_timer_thread_init();

    /* idle thread initialization */
    rt_thread_idle_init();

    /* start scheduler */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}
//4. 里面创建main线程
void rt_application_init(void)
{
    rt_thread_t tid;

#ifdef RT_USING_HEAP
    tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                           RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(tid != RT_NULL);
#else
    rt_err_t result;

    tid = &main_thread;
    result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
                            main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(result == RT_EOK);

    /* if not define RT_USING_HEAP, using to eliminate the warning */
    (void)result;
#endif

    rt_thread_startup(tid);
}

//5. 恢复跳到主函数
/* the system main thread */
void main_thread_entry(void *parameter)
{
    extern int main(void);
    extern int $Super$$main(void);

#ifdef RT_USING_COMPONENTS_INIT
    /* RT-Thread components initialization */
    rt_components_init();
#endif
	//恢复跳主函数
    /* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
    $Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
    main();
#endif
}
  • 上面操作系统相当于在使用库函数_main之前作弊插入一个函数
  • 这个函数做了初始化操作系统资源的作用
  • 然后在某一个线程里恢复到主函数main的运行
  • 相当于主函数mian只是操作系统的一个线程

示例:

在主程序执行前插入一段新程序
int $Sub$$main(void)
{
    rt_hw_interrupt_disable();
    rtthread_startup();
    return 0;
}

恢复主函数main
#if defined (__CC_ARM)
    $Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
    main();
#endif

rtthread_startup() 是进入主程序前的系统初始化,因此这两段程序作用就是为了完成主程序运行前的系统初始化工作,其中int Sub main(void) 是在主程序main前插入一段新代码,这段代码可以用来初始化系统,当然也可以做其他事情;
而Super main() 是在主程序main前插入一段已有的代码,比如以前写好的一段系统初始化的程序段 。

使用示例

void $Sub$$main(void)
{
	extern int $Super$$main(void);
	//初始化HAL
	HAL_Init();
	//初始化系统时钟
	SystemClock_Config();
	delay_init(80); 		//初始化延时函数    80M系统时钟
	uart_init(115200);		//初始化串口,波特率为115200
	printf("初始化已完成\n");
	
	//回到真正的main函数里
	$Super$$main();
}

int main(void)
{
    u8 len;
    u16 times = 0;

//    HAL_Init();
//    SystemClock_Config();	//初始化系统时钟为80M
//    delay_init(80); 		//初始化延时函数    80M系统时钟
//    uart_init(115200);	//初始化串口,波特率为115200
	printf("main()\r\n");

    while(1)
    {
	}

通俗一点讲,实际上就相当于在main函数之前先运行了$ Sub $ $ main这个函数
并且在$ Sub $ $ main这个函数里最后一句$ Super $ $ main();又用真正的main函数整体替换这个$ Super $ $main(),

  1. sub
  2. super

以上使用注意点:

void $ Sub$$main(void)和int main(void)可以不放在同一个C文件中,就算放在同一文件中也没有先后顺序之分;

extern int $ Super $ $ main(void);必须要有,且放在 $ Super $ $ main();调用之前,可以放在$ Sub $ $main函数里也可以放在外面。

$ Super $ 和 和和 Sub $ $基本是要成对使用

需要特定编译器才支持该符号,比如MDK
$ Super $ 和 和和 Sub $ $不只用于main函数,也可以用于其他任意函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值