STM32裸机开发(10) — 复制data段和清除BSS段
一、什么是BSS段(ZI段)
bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。
bss是英文Block Started by Symbol的简称。
bss段属于静态内存分配。
二、为什么要复制data段
这是因为对于在STM32F103这类资源紧缺的单片机芯片中,数据段只是暂时先保存在Flash上,在使用前被复制到内存里,而复制到内存这个过程是需要我们自己实现的。
三、未处理的结果
修改main函数如下所示:
#include "uart.h"
#include "led.h"
int mydata = 0x32315;
const int myconst = 0x22315;
int myzero = 0;
int my;
int main(void)
{
uart_init();
led_init();
putstring("stm32f103zet6\r\n");
putstring("mydata\t:");
puthex((unsigned int)&mydata);
puthex((unsigned int)mydata);
putstring("\r\nmyconst\t:");
puthex((unsigned int)&myconst);
puthex((unsigned int)myconst);
putstring("\r\nmyzero\t:");
puthex((unsigned int)&myzero);
puthex((unsigned int)myzero);
putstring("\r\nmy\t:");
puthex((unsigned int)&my);
puthex((unsigned int)my);
putstring("\r\n");
while(1)
{
putstring("led on\r\n");
led_on();
delay(1000000);
putstring("led off\r\n");
led_off();
delay(1000000);
}
}
编译烧录运行,可以看到,除了存在Flash的只读全局变量的值正确,其他几个变量的值都是错误的
四、修改链接文件
既然要复制data段和清除BSS段,那么就需要知道如下几个数据
1、数据段的加载地址,即数据段存放在Flash的哪个位置
2、数据段的链接地址,即数据段应该复制到在RAM的哪个位置
3、数据段的长度,应该复制多长的数据从Flash到RAM
4、BSS段的链接地址,即BSS段应该从何处开始清除
5、ZI段的长度,应该清除多长的BSS段
我们将链接文件修改为如下所示,得到data段的起始加载地址、起始链接地址和结束链接地址,以及bss段的起始链接地址和结束链接地址。其中>RAM AT > FLASH
表示存放时使用RAM地址,存放时使用FLASH地址。
/* 指定内存区域 */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}
SECTIONS {
.text :
{
. = ALIGN(4);
*start.o (.text)
*main.o (.text)
*(.text)
} > FLASH
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
_sidata = LOADADDR(.data); /* 获取data段的起始加载地址 */
.data :
{
. = ALIGN(4);
_sdata = .; /* 获取data段的起始链接地址 */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* 获取data段的结束链接地址 */
} >RAM AT > FLASH
. = ALIGN(4);
.bss :
{
_sbss = .; /* 获取bss段的起始链接地址 */
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* 获取bss段的结束链接地址 */
} >RAM
}
五、修改汇编文件
在start.s中加入如下代码复制data段和清除bss段的代码,其中data段的起始加载地址_sidata
、起始链接地址_sdata
和结束链接地址_edata
,以及bss段的起始链接地址_sbss
和结束链接地址_ebss
,都是从链接脚本中获取的。
.syntax unified /* 指明当前汇编文件的指令是ARM和THUMB通用格式 */
.cpu cortex-m3 /* 指明cpu核为cortex-m3 */
.fpu softvfp /* 软浮点 */
.thumb /* thumb指令 */
.global _start /* .global表示Reset_Handler是一个全局符号 */
.word 0x00000000 /* 当前地址写入一个字(32bit)数据,值为0x00000000,实际上应为栈顶地址 */
.word _start+1 /* 当前地址写入一个字(32bit)数据, 值为_reset标号代表的地址+1,即程序入口地址*/
_start: /* 标签_start,汇编程序的默认入口是_start */
/* 1、设置栈 */
LDR SP, =(0x20000000+0x400)
/* 2、复制data段 */
movs r1, #0 /* r1寄存器用来计数 */
b LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata /* 将data段的起始加载地址存入r3寄存器 */
ldr r3, [r3, r1] /* 从Flash取出data段的值存入r3寄存器 */
str r3, [r0, r1] /* 将r3寄存器的值存入RAM中 */
adds r1, r1, #4 /* 每次复制4个字节的data段数据 */
LoopCopyDataInit:
ldr r0, =_sdata /* 将data段的起始链接地址存入r0寄存器 */
ldr r3, =_edata /* 将data段的结束链接地址存入r0寄存器 */
adds r2, r0, r1 /* 起始链接地址加上计数,即当前复制地址存入r2寄存器 */
cmp r2, r3 /* 当前复制地址和结束链接地址相比较 */
bcc CopyDataInit /* 如果r2小于等于r3跳转到CopyDataInit标签处,如果大于则往下执行 */
/* 3、清除bss段 */
ldr r2, =_sbss /* 将bss段的起始链接地址存入r2寄存器 */
b LoopFillZerobss
FillZerobss:
movs r3, #0 /* 将0存入r3寄存器 */
str r3, [r2], #4 /* 将r3中的值存到r2中的值所指向的地址中, 同时r2中的值加4 */
LoopFillZerobss:
ldr r3, = _ebss /* 将bss段的结束链接地址存入r3寄存器 */
cmp r2, r3 /* 比较r2和r3内的值,即当前清除地址和结束链接地址相比较 */
bcc FillZerobss /* 如果r2小于等于r3跳转到FillZerobss标签处,如果大于则往下执行 */
/* 4、跳转到led函数 */
BL main
/* 5、原地循环 */
B .
然后编译运行烧录,这样我们就可以看到变量的值正常了
六、附录
上一篇:STM32裸机开发(9) — 使用链接脚本链接代码
下一篇:
代码存放:https://gitee.com/william_william/stm32f103_noos/tree/master/make-gcc/03_links