地址空间划分
对于空间划分是人为定义的。
bootloader编写
启动文件start.s:
PRESERVE8 ; instruct is aligned by 8 bytes 指令集8字节对齐
THUMB ; use Thumb instruction set 使用thumb指令集
AREA RESET, DATA, READONLY ;DATA定义数据段,READONLY只读
EXPORT __Vectors
__Vectors DCD 0 ; 分配内存空间,用0初始化,CPU自动将该处的值设置给sp,Top of Stack
DCD Reset_Handler ; 分配内存空间,用Reset Handler初始化
AREA |.text|, CODE, READONLY ; CODE表示定义代码段,READONLY只读
; Reset handler
Reset_Handler PROC ; 子程序开始标志
EXPORT Reset_Handler [WEAK] ; 导出Reset_Handler全局可见以及弱定义
IMPORT main ; 导入main,类似C语言的extern main,声明main由外部定义
LDR sp, = (0x20000000+0x10000) ; 手动设置栈,只有设置了栈才能跳到C语言的世界执行
BL main ; 跳到C语言世界执行
ENDP ; 子程序结束标志
END ;汇编文件结束
最简单的启动,暂时不使用中断向量。
如果是通过MDK设置链接地址,不勾选use memory layout from Target Dialog
(不使用Target对话框的内存布局)
遇到的问题:必须到导出中断向量全局可见,不然程序无法启动:EXPORT __Vectors
链接地址通过散列文件指定: 这是创建工程默认创建的散列文件
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00080000 { ; load region size_region
ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00010000 { ; RW data
.ANY (+RW +ZI)
}
}
usart.c: 只使用串口打印简单的信息
#include "usart.h"
#include "stdio.h"
typedef unsigned int uint32_t;
typedef struct
{
volatile uint32_t SR; /*!< USART Status register, Address offset: 0x00 */
volatile uint32_t DR; /*!< USART Data register, Address offset: 0x04 */
volatile uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x08 */
volatile uint32_t CR1; /*!< USART Control register 1, Address offset: 0x0C */
volatile uint32_t CR2; /*!< USART Control register 2, Address offset: 0x10 */
volatile uint32_t CR3; /*!< USART Control register 3, Address offset: 0x14 */
volatile uint32_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;
void uart_init(void)
{
USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
volatile unsigned int *pReg;
/* 使能GPIOA/USART1模块 */
/* RCC_APB2ENR */
pReg = (volatile unsigned int *)(0x40021000 + 0x18);
*pReg |= (1<<2) | (1<<14);
/* 配置引脚功能: PA9(USART1_TX), PA10(USART1_RX)
* GPIOA_CRH = 0x40010800 + 0x04
*/
pReg = (volatile unsigned int *)(0x40010800 + 0x04);
/* PA9(USART1_TX) */
*pReg &= ~((3<<4) | (3<<6));
*pReg |= (1<<4) | (2<<6); /* Output mode, max speed 10 MHz; Alternate function output Push-pull */
/* PA10(USART1_RX) */
*pReg &= ~((3<<8) | (3<<10));
*pReg |= (0<<8) | (1<<10); /* Input mode (reset state); Floating input (reset state) */
/* 设置波特率
* 115200 = 8000000/16/USARTDIV
* USARTDIV = 4.34
* DIV_Mantissa = 4
* DIV_Fraction / 16 = 0.34
* DIV_Fraction = 16*0.34 = 5
* 真实波特率:
* DIV_Fraction / 16 = 5/16=0.3125
* USARTDIV = DIV_Mantissa + DIV_Fraction / 16 = 4.3125
* baudrate = 8000000/16/4.3125 = 115942
*/
#define DIV_Mantissa 4
#define DIV_Fraction 5
usart1->BRR = (DIV_Mantissa<<4) | (DIV_Fraction);
/* 设置数据格式: 8n1 */
usart1->CR1 = (1<<13) | (0<<12) | (0<<10) | (1<<3) | (1<<2);
usart1->CR2 &= ~(3<<12);
/* 使能USART1 */
}
int mygetchar(void)
{
USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
while ((usart1->SR & (1<<5)) == 0);
return usart1->DR;
}
int myputchar(char c)
{
USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
while ((usart1->SR & (1<<7)) == 0);
usart1->DR = c;
return c;
}
void myputstr(char *str)
{
while (*str)
{
putchar(*str);
str++;
}
}
#if 1
//#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
while ((usart1->SR & (1<<7)) == 0);
usart1->DR = ch;
return ch;
}
#endif
main.c:
#include "usart.h"
#include "stdio.h"
#define BOOTLOADER_VERSION "1.0"
#define APP_ADDR 0x800B001
typedef void (*app_func)(void);
void uart_init(void);
int main(void)
{
app_func app;
unsigned int *p = (unsigned int *)0x800B000;
unsigned int addr = *(p+1);
uart_init();
myputstr("Bootloader: ");
myputstr(BOOTLOADER_VERSION);
printf("\r\n");
myputstr(__DATE__);
printf(" %s\r\n", __TIME__);
app = (app_func)(APP_ADDR);
app(); /* 跳转执行app */
}
烧录程序上电运行,打印信息:
Bootloader: 1.0
Jul 5 2022 16:34:32
bootloader正常运行。
#define APP_ADDR 0x800B001
app = (app_func)(APP_ADDR);
app(); /* 跳转执行app */
从自定义划分的地址看,APP开始的地址是0x800B000
这里为什么跳转使用的是0x800B001
呢?
原因是STM32是Cortex-M3内核,M3内核只支持Thumb指令集
,当跳转地址最后一个bit是1表示后续执行的程序使用的是Thumb指令集
,若为0表示使用的是ARM指令集
。所以跳转地址如果是0x800B000
则无法成功。
APP编写
启动文件和usart驱动内容和bootloader一样。
main.c : 简单打印一些内容
#include "stdio.h"
#include "usart.h"
void uart_init(void);
void delay(int times)
{
while (--times);
}
char buf[100] = {"guangjieMVP"};
int main(void)
{
int cnt = 0;
uart_init();
//printf("%s\r\n", buf);
myputstr("App Start\r\n");
myputstr(buf);
while (1)
{
//printf("app %d\r\n", cnt++);
myputstr("App runing\r\n");
delay(1000000);
}
}
这个APP无法跳转执行
跳转APP无法执行
当我们使用main作为C程序入口地址时,MDK 编译器自动会在main之前添加一段程序
RESET
__Vectors
0x0800b000: 00000000 .... DCD 0
0x0800b004: 0800b0ad .... DCD 134262957
$t
!!!main
__main
0x0800b008: f000f802 .... BL __scatterload ; 0x800b010
0x0800b00c: f000f83f ..?. BL __rt_entry ; 0x800b08e
!!!scatter
__scatterload
__scatterload_rt2
__scatterload_rt2_thumb_only
....
__scatterload_null
....
$d
0x0800b03c: 00000418 .... DCD 1048
0x0800b040: 00000438 8... DCD 1080
$t
!!handler_copy
__scatterload_copy
...
!!handler_zi
__scatterload_zeroinit
....
,查看反汇编文件可知天降__main这段程序,包含两部分__scatterload 和__rt_entry 两部分程序
MDK __main()代码执行分析_TS_up的博客-CSDN博客___main
https://blog.csdn.net/tianshi_1988/article/details/51084516
APP程序跳转无法执行的原因是添加了__main,至于深层原因暂时搞不懂。。。
解决APP跳转无法执行
1、修改APP的main函数名字
将main函数名字改为mymain或者其他,只要不是main这个名字。
; Reset handler
Reset_Handler PROC ; 子程序开始标志
EXPORT Reset_Handler [WEAK] ; 导出Reset_Handler全局可见以及弱定义
IMPORT mymain ; 导入main,类似C语言的extern main,声明main由外部定义
LDR sp, = (0x20000000+0x10000) ; 设置栈,只有设置了栈才能跳到C语言的世界执行
BL mymain ; 跳到C语言世界执行
ENDP ; 子程序结束标志
int mymain(void)
{
...
...
}
当修改名字后,编译器不会再自动添加一大段__main代码。
编译下载程序,复位执行程序不成功要可以查看反汇编文件看链接地址是否成功
上电执行,打印信息:
Bootloader: 1.0
Jul 6 2022 09:51:12
App Start
?
x-h@$@App runing
App runing
myputstr(buf); 输出的是乱码,buf是放在数据段中
LR_IROM1 0x0800B000 0x00075000 { ; load region size_region 75000
ER_IROM1 0x0800B000 0x00075000 { ; load address = execution address 75000
*.o (RESET, +First)
; *(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00010000 { ; RW data
.ANY (+RW +ZI)
}
}
buf是属于RW段,链接地址是在0x20000000,这是是RAM开始地址,myputstr(buf)调用使用全局变量buf使用的是绝对地址,也就是链接地址,由于没有重定位,buf的链接地址处是乱码,所以输出这里输出了乱码
2、修改bootloader
将APP程序的入口函数名字改回main,修改bootloader程序为:
#include "usart.h"
#include "stdio.h"
#define BOOTLOADER_VERSION "1.1"
#define APP_ADDR 0x800B001
typedef void (*app_func)(void);
void uart_init(void);
int main(void)
{
app_func app;
unsigned int *p = (unsigned int *)0x800B000;
unsigned int addr = *(p+1);
uart_init();
myputstr("Bootloader: ");
myputstr(BOOTLOADER_VERSION);
printf("\r\n");
myputstr(__DATE__);
printf(" %s\r\n", __TIME__);
printf("Jump addr %x\r\n", addr);
app = (app_func)(addr);
app(); /* 跳转执行app */
}
-
unsigned int *p = (unsigned int *)0x800B000;
0x800B000地址存放的是栈顶地址
-
unsigned int addr = *(p+1);
指针+1,此时指针值为0x800B004,该地址存放的是Reset_Handler 的地址,指针接引用就获得了Reset_Handler 地址,
-
app = (app_func)(addr);
强制将整数值转换为函数指针
-
app();
跳转执行, 由于跳转地址直接是APP程序的Reset_Handler ,跳过了MDK编译给程序添加的__main程序
,所以APP程序成功跳转执行
bootloader使用汇编进行跳转
使用汇编跳转要大概知道ARM编程的知识,主要是ARM汇编以及ARM架构过程调用标准AAPCS
ARM架构过程调用标准AAPCS
规定了函数传参返回值等规则。
简单的说就是C程序调用汇编函数是通过分别通过R0-R3来传递第1到第4个形参,超过4个的部分采用栈传递参数。
汇编跳转实现:
main.c:
#include "usart.h"
#include "stdio.h"
#define BOOTLOADER_VERSION "1.2"
#define APP_ADDR 0x800B000 /* APP起始地址 */
void boot_app(unsigned int sp, unsigned int pc);
int main(void)
{
unsigned int *s_addr = (unsigned int *)APP_ADDR;
unsigned int sp = *s_addr;
unsigned int pc = *(s_addr+1);
uart_init();
myputstr("Bootloader: ");
myputstr(BOOTLOADER_VERSION);
printf("\r\n");
myputstr(__DATE__);
printf(" %s\r\n", __TIME__);
boot_app(sp, pc);
}
start.s:
PRESERVE8 ; instruct is aligned by 8 bytes 指令集8字节对齐
THUMB ; use Thumb instruction set 使用thumb指令集
AREA RESET, DATA, READONLY ;DATA定义数据段,READONLY只读
EXPORT __Vectors
__Vectors DCD 0 ; CPU自动将该处的值设置给sp,Top of Stack
DCD Reset_Handler ; Reset Handler, 指令地址,CPU首先执行此句
AREA |.text|, CODE, READONLY ; CODE表示定义代码段,READONLY只读
; Reset handler
Reset_Handler PROC ; 子程序开始标志
EXPORT Reset_Handler [WEAK] ; 导出Reset_Handler全局可见以及弱定义
IMPORT main ; 导入main,类似C语言的extern main,声明main由外部定义
LDR sp, = (0x20000000+0x10000) ; 手动设置栈,只有设置了栈才能跳到C语言的世界执行
BL main ; 跳到C语言世界执行
ENDP ; 子程序结束标志
boot_app PROC ; 汇编标号即函数名或者说函数地址
EXPORT boot_app
; LDR sp, = R0 ;
mov sp, R0
BLX R1 ; 跳去执行APP
ENDP
END ;汇编文件结束
- 采用BL R1 ; 跳去执行APP,编译会报错。需要使用BLX或BX
- BL: 带链接的跳转。 首先将当前指令的下一条指令地址保存在LR寄存器,然后跳转的label。通常用于调用子程序,可通过在子程序的尾部添加mov pc, lr 返回.
- BX: 带状态切换的跳转。最低位为1时,切换到Thumb指令执行,为0时,解释为ARM指令执行
- BLX: 带链接和状态切换的跳转。结合了BX与BL功能
重定位中断向量表
修改boot_app实现重定位Vector :
boot_app PROC ; 汇编标号即函数名或者说函数地址
EXPORT boot_app
STR R0, [R1] ; 向中断向量寄存器写入程序链接地址0x800B000
LDR sp, [R0] ; 取0x800B000地址处的值写入sp即设置栈
LDR R2, [R0, #4] ; 0x800B000 + 4 = 0x800B004,取出0x800B004地址处的值赋值给R2
BX R2 ; 跳去执行APP
ENDP
main.c:
#include "usart.h"
#include "stdio.h"
#define BOOTLOADER_VERSION "1.3"
#define APP_ADDR 0x800B000 /* APP起始地址 */
#define VECTOR_REG_ADDR 0xE000ED08
void boot_app(unsigned int start_addr, unsigned int vector_reg);
int main(void)
{
unsigned int s_addr = APP_ADDR;
unsigned int vector_addr = VECTOR_REG_ADDR;
...
...
boot_app(s_addr, vector_addr);
}
// 寄存器间接寻址。把r0中的数写入到r1中的数为地址的内存中去
str r0, [r1]
//将r1的值赋给r0,ARM是RISC结构,数据从内存到CPU之间的移动只能通过LDR/STR指令来完成。
// 但想把数据从内存中某处读取到寄存器,只能使用ldr ,
//mov只能在寄存器之间移动数据,或者把立即数移动到寄存器中,如 MOV r0,#0是将立即数0放到r0中
ldr r0,[r1]