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汇编头文件中要做的事
                ; 定义各个硬件模块的地址
                ; 定义所有中断号

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

  • 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		; 文件尾

  • 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,否则编译时从第一条语句就会报错,而且只会提示指令不支持

  • 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		; 文件尾

  • config.inc:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; @brief	程序的共用宏定义配置
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CONFIG_STACK_K_SIZE	EQU	0x04	; 4K栈空间
CONFIG_HEAP_K_SIZE	EQU	0x00	; 堆空间无

	END	; 文件尾

  • main.c:
/******************************************************************************
 * \brief	程序除boot外执行的第一个C语言函数
 * \details	main函数里什么都没有,这个工程主要是自己用汇编写了M3的boot代码,
 *			并成功用汇编引导了C语言main函数
 * \author	将狼才鲸
 * \date	2022-11-08
 * \note	File format: UTF-8
 ******************************************************************************/

/************************************ 头文件 **********************************/
#include <stdio.h>

/************************************ 宏定义 **********************************/
#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;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要在Keil进行字符串的打印输出,可以使用Keil提供的库函数来实现。 首先,在Keil中,我们可以通过sprintf()函数将需要打印的字符串格式化为一个字符数组。此函数的原型为: ``` int sprintf (char *buffer, const char *format [, arguments ... ]); ``` 其中,buffer表示待填充的字符数组,format表示格式化字符串。 例如: ```c #include <stdio.h> #include <string.h> int main() { char buffer[100]; char name[] = "Alice"; int age = 20; sprintf(buffer, "My name is %s and I am %d years old.", name, age); printf("%s\n", buffer); return 0; } ``` 上述示例中,通过sprintf()函数将字符串格式化后,结果存储在buffer中,最后通过printf()函数将buffer中的内容打印输出。 除了sprintf()函数,Keil还提供了其他一些用于字符串处理和打印输出的函数,比如puts()、putchar()等等。根据需求选择合适的函数即可实现字符串的打印输出。 ### 回答2: 要在Keil打印输出字符串,可以使用C语言中的printf()函数。下面是实现的步骤: 1. 在Keil中创建一个C源文件,例如main.c。 2. 在该源文件的顶部,添加包含标准输入/输出库的头文件。 ```c #include <stdio.h> ``` 3. 在main()函数中,使用printf()函数打印输出字符串。可以将要打印的字符串作为参数传递给printf()函数。 ```c int main() { printf("Hello, World!"); return 0; } ``` 4. 在Keil进行编译并链接程序。 5. 将编译后的程序下载到目标设备上进行运行。 当程序运行时,字符串"Hello, World!"将被打印到控制台或终端上,作为输出结果。 这是一个简单的例子,演示了如何在Keil中使用printf()函数打印输出字符串。根据具体的应用场景,你可以根据需要打印不同的字符串,并使用printf()函数的格式化功能来输出更详细的信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值