• 03 自己写Keil ARM M3汇编的boot,并成功引导main进行打印输出


    作者将狼才鲸
    创建日期2022-11-05

    Gitee工程和源码地址:才鲸嵌入式 / ARM-Cortex-M3从汇编到C_从Boot到应用教程
    CSDN文章阅读地址:ARM Cortex-M3从汇编到C,从Boot到应用的教程
    Bilibili视频讲解地址(待完成):才鲸嵌入式


    工程名称作用
    01_Hello_world使用Keil的模拟器在虚拟终端输出Hello world
    02_Keil_boot_commentsKeil自带汇编boot的注释
    03_Self_assembler_boot自行实现汇编boot
    04_Uart_loopback串口收发回环

    3)03_Self_assembler_boot

    ; 使用Keil自动生成时,也可用纯C写Boot相关的配置
    
                    INCLUDE YOUR_CONFIG.INC ; #include "YOUR_NAME.INC" 包含头文件
    ; 用户自定义宏定义
    YOUR_CONFIG1    EQU 0x01    ; 类似于#define宏定义,用不同的配置选项配置程序
    YOUR_CONFIG2    EQU 0x01
    
                    PRESERVE8   ; 指定当前文件要求堆栈八字节对齐
                    THUMB       ; 使用THUMB指令集,不使用ARM指令集
    
    ; 定义堆,堆是malloc主动分配内存的位置
    Heap_Size       EQU 0       ; Heap_Size是MDK指定的堆空间长度名称;不用malloc分配的堆没什么用,所以长度设置为0
                    IF Heap_Size != 0       ; IF ELSE ENDIF和同名宏定义的含义类似
                    AREA     HEAP, NOINIT, READWRITE, ALIGN=3 ; 申明开辟名为HEAP的内存,不写入值初始化,可读可写,2^3 8字节对齐
                    EXPORT   __heap_base    ; MDK指定的名称,堆起始地址位置
                    EXPORT   __heap_limit   ; MDK指定的名称,堆结束地址位置
    __heap_base
    Heap_Mem        SPACE    Heap_Size      ; 开始分配指定长度的内存
    __heap_limit
                    ENDIF
    
    ; 定义栈,栈是为全局变量自动分配空间的位置
    Stack_Size      EQU      (4096)         ; Stack_Size是MDK指定的栈空间长度名称
    
                    AREA     STACK, NOINIT, READWRITE, ALIGN=3 ; 申明开辟名为STACK的内存,不写入值初始化,可读可写,2^3 8字节对齐
                    EXPORT   __stack_limit  ; MDK指定的名称,栈起始地址位置
                    EXPORT   __initial_sp   ; MDK指定的名称,栈结束地址位置
    
    __stack_limit
    Stack_Mem       SPACE    Stack_Size     ; 开始分配指定长度的一片连续的内存
    __initial_sp
    
                    AREA     RESET, DATA, READONLY      ; 定义数据段,名字为RESET;上电后首先运行的函数地址
                    EXPORT   __Vectors                  ; 输出ARM CMSIS中需要用到的一些标号,__Vectors函数在后续定义
                    EXPORT   __Vectors_End
                    EXPORT   __Vectors_Size
                    EXPORT   Default_Interrupt_Handler  ; 中断入口
                    IMPORT   __initial_sp
    
    ; 申明异常和中断入口
    __Vectors       DCD      __initial_sp               ;     Top of Stack
                    DCD      Reset_Handler              ;     Reset Handler
                    DCD      NMI_Handler                ; -14 NMI Handler
                    DCD      HardFault_Handler          ; -13 Hard Fault Handler
                    DCD      MemManage_Handler          ; -12 MPU Fault Handler
                    DCD      BusFault_Handler           ; -11 Bus Fault Handler
                    DCD      UsageFault_Handler         ; -10 Usage Fault Handler
                    DCD      0                          ;     Reserved
                    DCD      0                          ;     Reserved
                    DCD      0                          ;     Reserved
                    DCD      0                          ;     Reserved
                    DCD      SVC_Handler                ;  -5 SVCall Handler
                    DCD      DebugMon_Handler           ;  -4 Debug Monitor Handler
                    DCD      0                          ;     Reserved
                    DCD      PendSV_Handler             ;  -2 PendSV Handler
                    DCD      SysTick_Handler            ;  -1 SysTick Handler
    
                    ; Interrupts
                    DCD      Interrupt_Handler_0
                    ; …… 省略其它中断 ……
                    DCD      Interrupt_Handler_45       ; BCD:分配存储单元
    __Vectors_End
    __Vectors_Size  EQU      __Vectors_End - __Vectors
    
                    ; 类似于宏定义函数
                    MACRO                               ; 宏定义函数的开始 
                    Set_Default_Handler $Handler_Name   ; 前一个时宏定义函数的名字,后面是要操作的对象
    $Handler_Name   PROC                                ; 定义子程序
                    EXPORT   $Handler_Name [WEAK]       ; 输出函数名;[WEAK]虚函数,可定义可不定义
                    B        .                          ; 死循环
                    ENDP                                ; 子程序定义结束
                    MEND                                ; 宏定义函数结束
    
                    AREA     |.text|, CODE, READONLY    ; 定义.text代码段,只读
    
                    ; 申明默认的异常和中断处理函数
                    Set_Default_Handler  Reset_Handler
                    Set_Default_Handler  NMI_Handler
                    Set_Default_Handler  HardFault_Handler
                    Set_Default_Handler  MemManage_Handler
                    Set_Default_Handler  BusFault_Handler
                    Set_Default_Handler  UsageFault_Handler
                    Set_Default_Handler  SVC_Handler
                    Set_Default_Handler  DebugMon_Handler
                    Set_Default_Handler  PendSV_Handler
                    Set_Default_Handler  SysTick_Handler
                    Set_Default_Handler  Default_Interrupt_Handler
                    Set_Default_Handler  Interrupt_Handler_0
                    ; …… 省略其它中断 ……
                    Set_Default_Handler  Interrupt_Handler_45
     
                   ; 各种程序
                    ; Reset_Handler是板子上电后首先执行的位置,它由异常中断的跳转来实现
    Reset_Handler   PROC                    ; 程序名 PROC 程序内容 ENDP 程序结束
                    EXPORT   Reset_Handler
                    IMPORT	 __hardwareInit ; 自己编写的C程序,在里面初始化各种硬件配置
                    IMPORT   __main         ; main()函数入口
                    BL       __hardwareInit ; 调用初始化硬件的汇编函数
                    BL       __main         ; 跳转到main()函数
                    BL       cpuStall       ; 程序退出后一直死循环
                    ALIGN
                    ENDP
    
    cpuStall        PROC
                    EXPORT   cpuStall
                    B        .              ; 死循环
                    ENDP
                    END                     ; 通知编译器已经到了该源文件的结尾了
    
    __hardwareInit  PROC
                    EXPORT   __hwInitialize
                    PUSH     {R0,R1,R2,R3,LR} ; 压栈
                    ; 配置GPIO输出
                    ; 配置PLL系统主频,将主频从晶振原有的频率提高到实际的工作频率
                    ; 初始化串口
                    ; 初始化其它外设
                    POP      {R0,R1,R2,R3,LR} ; 弹栈
                    BX       LR               ; 跳转到LR寄存器里的地址执行,也就是跳转回被调用的地方
                    ALIGN
                    ENDP
    
                    ; 其它的.inc汇编头文件中要做的事
                    ; 定义各个硬件模块的地址
                    ; 定义所有中断号
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

    • 此处列出工程中的各个文件:

    • startup.asm

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; @brief	程序执行的第一行代码所在处,中断向量表
    ; @note		File format: UTF-8
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    				INCLUDE	config.inc
    
    				PRESERVE8	; 8字节对齐,函数标号所在处一定要4字节对齐才能运行正常
    				THUMB		; 汇编使用thumb指令集
    
    ;;
    ; \brief	中断向量表
    ; \details	从地址0开始的数据段,0地址存放栈顶地址,4地址是复位中断,后面紧接着的是其它中断
    ;;
    				AREA	RESET, DATA, READONLY	; 定义名为‘RESET’的数据段,只读
    
    				; 声明本文件中定义的函数
    				EXPORT	__Vectors		; 声明定义了第一个中断的入口地址
    				EXPORT	__Vectors_End	; 声明定义了最后一个中断的入口地址
    				EXPORT	__Vectors_Size	; 声明中断的地址总量
    				EXPORT	Default_Interrupt_Handler	; 声明没有指定中断函数时的默认中断处理函数
    
    				IMPORT	__Init_Sp		; 声明其它文件中定义的栈顶地址
    
    ;;
    ; \brief	在各个中断入口调用中断处理函数
    ; \details	__Vectors是整个中断入口的初始地址
    ;;
    __Vectors		DCD	__Init_Sp		; 栈顶地址
    				DCD	Reset_Handler	; 复位中断
    				;TODO: 添加其它的所有中断入口
    __Vectors_End
    
    __Vectors_Size  EQU __Vectors_End - __Vectors
    
    ;;
    ; \brief	中断和异常处理的宏定义函数
    ; \details	这里定义的函数里面只有死循环,可以在别处重写这些中断处理函数
    ;;
    				MACRO	; 宏定义函数
    				Set_Default_Handler	$Handler_Name
    $Handler_Name	PROC
    				EXPORT	$Handler_Name [WEAK] ; 该函数可重定义,也可以不定义
    				B		.	; 死循环
    				ENDP
    				MEND
    
    ;;
    ; \brief	声明默认的中断处理函数
    ; \details	该函数没有定义也可以
    ;;
    				AREA     |.text|, CODE, READONLY	; 定义只读代码段
    				Set_Default_Handler  Reset_Handler
    				Set_Default_Handler  Default_Interrupt_Handler
    
    				ALIGN	; 取消8字节对齐
    				END		; 文件尾
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • reset.asm:
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; @brief	程序执行的第二行代码所在处
    ; @author	将狼才鲸
    ; @date		2022-11-06
    ; @note		File format: UTF-8
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    ;/*
    ; * SPDX-License-Identifier: Apache-2.0
    ; *
    ; * Licensed under the Apache License, Version 2.0 (the License); you may
    ; * not use this file except in compliance with the License.
    ; * You may obtain a copy of the License at
    ; *
    ; * www.apache.org/licenses/LICENSE-2.0
    ; *
    ; * Unless required by applicable law or agreed to in writing, software
    ; * distributed under the License is distributed on an AS IS BASIS, WITHOUT
    ; * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    ; * See the License for the specific language governing permissions and
    ; * limitations under the License.
    ; */
    
    				PRESERVE8	; 指定当前文件要求堆栈八字节对齐
    				THUMB		; 使用THUMB指令集,不使用ARM指令集	; 头文件包含,里面只定义了一些宏定义
    
    				AREA		|.text|, CODE, READONLY	; 定义.text代码段,只读
    
    ;;
    ; \brief	复位中断处理函数
    ; \details	程序执行的第一行代码是0地址的复位中断,然后才直接调用到这个函数,
    ;			中断向量表在startup.asm中定义
    ;;
    Reset_Handler	PROC
    				EXPORT	Reset_Handler	; 声明函数,声明后可以在别处调用
    				;IMPORT	__hw_init		; 声明硬件初始化函数
    				;IMPORT	__main			; 声明c语言的main函数,ARM Compiler 5版本的编译器编译时,会在c语言函数前面加上两个下划线
    				IMPORT main				; 当前ARM Compiler 6汇编中直接调用C函数就可以了
    
    				;BL		__hw_init		; 如果有必要,调用硬件初始化的函数,如配置PLL之类的
    				;BL		__main			; 跳转到C语言main()函数
    				BL		main
    				BL		Cpu_Stall		; 如果main函数中不是死循环,函数退出了,则直接在这里死循环
    
    				ALIGN	; 取消8字节对齐,标号地址都是要4字节对齐的,否则程序会出错
    				ENDP	; 函数结束
    
    ;;
    ; \brief	死循环函数
    ;;
    Cpu_Stall		PROC
    				EXPORT	Cpu_Stall
    				B		.	; 跳转到本行地址,也就是死循环
    
    				ENDP
    
    				END			; 编译汇编时,文件尾要先写END,否则编译时从第一条语句就会报错,而且只会提示指令不支持
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • stack.asm:
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; @brief	定义栈地址,类似于宏定义,并不具体执行代码
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    				INCLUDE config.inc
    
    				PRESERVE8
    				THUMB
    
    ;;
    ; \brief	定义栈尺寸,必须8字节对齐
    ;;
    Stack_Size		EQU		(CONFIG_STACK_K_SIZE * 1024)	; 4K
    
    				; 申明开辟名为STACK的内存,不写入值初始化,可读可写,2^3 8字节对齐
    				AREA	STACK, NOINIT, READWRITE, ALIGN=3
    				EXPORT	__Stack_Limit	; 声明栈底地址变量
    				EXPORT	__Init_Sp		; 声明栈顶地址变量
    
    __Stack_Limit
    Stack_Mem		SPACE	Stack_Size		; 开辟4K内存
    __Init_Sp
    
    				ALIGN	; 取消8字节对齐
    				END		; 文件尾
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • config.inc:
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; @brief	程序的共用宏定义配置
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    CONFIG_STACK_K_SIZE	EQU	0x04	; 4K栈空间
    CONFIG_HEAP_K_SIZE	EQU	0x00	; 堆空间无
    
    	END	; 文件尾
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • main.c:
    /******************************************************************************
     * \brief	程序除boot外执行的第一个C语言函数
     * \details	main函数里什么都没有,这个工程主要是自己用汇编写了M3的boot代码,
     *			并成功用汇编引导了C语言main函数
     * \author	将狼才鲸
     * \date	2022-11-08
     * \note	File format: UTF-8
     ******************************************************************************/
    
    /************************************ 头文件 **********************************/
    #include 
    
    /************************************ 宏定义 **********************************/
    #define UNUSED_VARIABLE(X)	((void)(X))
    #define ITM_PORT8(n)		(*(volatile unsigned char *)(0xe0000000 + 4*(n)))
    #define ITM_PORT16(n)		(*(volatile unsigned short *)(0xe0000000 + 4*(n)))
    #define ITM_PORT32(n)		(*(volatile unsigned long *)(0xe0000000 + 4*(n)))
    #define DEMCR				(*(volatile unsigned long *)(0xE000EDFC))
    #define TRCENA				0X01000000
    
    /*********************************** 接口函数 *********************************/
    int fputc(int ch, FILE *f)
    {
    	UNUSED_VARIABLE(f);
    
    	if(DEMCR & TRCENA) {
    		while(ITM_PORT32(0) == 0);
    		ITM_PORT8(0) = (char)ch;
    	}
    	return ch;
    }
    
    int main()
    {
    	printf("Hello world!\n");
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 相关阅读:
    算法小考试(有点难)
    2023届-计算机视觉算法岗实习面经
    代码随想录第46天 | ● 583. 两个字符串的删除操作 ● 72. 编辑距离
    渗透测试-目录遍历漏洞
    【PHP】手术麻醉系统源码
    声明式事务管理案例-转账(xml、注解)
    url跳转打开方式
    mysql核心-innodb与myisam详细解读
    nvm:轻松管理多个 Node 版本 | 开源日报 No.80
    项目可行性方案:人脸识别实现无感考勤的项目技术可行性方案
  • 原文地址:https://blog.csdn.net/qq582880551/article/details/127760642