[STM32F4]STM32F4深入底层学习


深入底层学习

1 STM32启动过程分析

1.0 预备知识

1、RAM、FLASH

  • ram即随机存储器,掉电即丢失信息,常用于存放寄存器和运行中的数据。
  • flash是和rom类似的存储器,掉电不会丢失信息,用于存放代码。
  • 芯片手册可以查到STM32F407ZGT6的Flash大小为1024Kbyte,SRAM的大小为192k

2、三种启动方式

打开STM32F407中文手册,找到2.4 自举配置

STM32有三种启动方式,分别是从FLASH(闪存)SRAM系统存储器启动,由BOOT0BOOT1设置

  • Flash启动,将Flash地址0x08000000映射到0x00000000,一般我们所写的程序都放在STM32的内部Flash中,是我们最常用的模式
  • SRAM启动,将SRAM地址0x20000000映射到0x00000000,一般用于调试(PS:遇到过flash被锁定,则可通过SRAM解锁Flash,参考【解锁FLASH】如何给STM32芯片解锁)
  • 系统存储器启动,根据中文手册2.4 自举配置表4可得系统存储器即存储器地址0x1FFFF000起到0x1FFF77FF的这一段地址。再往上看,解释这段空间存放着一段嵌入式自举程序,这个程序出厂就烧录在里面。可以理解为电脑的bios程序,这个程序提供了串口下载的功能,也就是串口下载要切换boot的原因。

3、编译完后的信息和map文件分析

在keil中编译完程序之后

  • Code:是程序中代码所占字节大小;

  • RO-data:程序只读(readonly)的变量,也就是带const的,和已初始化的字符串等;

  • RW-data:已初始化的可读写(readwrite)全局/静态变量;

  • ZI-data:未初始化(Zero-initialized)的可读写全局/静态变量;

程序所占Flash空间大小=Code+RO data+RW-data=生成的bin文件大小。

程序固定占用RAM大小=RW data+ZI data

在生成文件中可以找到一个map文件,这个文件记录了编译链接以及内存分配等信息。keil可以设置map的输出信息。

keilProject -> Options for Target -> Listing主要包含配置:

  • Memory Map:内存映射
  • Callgraph:图像映射
  • Symbols:符号
  • Cross Reference:交叉引用
  • Size Info:大小信息
  • Totals Info:统计信息
  • Unused Section Info:未调用模块信息
  • Veneers Info:装饰信息

map文件主要结构

map文件里面内容大致分为五大类(按照map文件分类的顺序)
1.Section Cross References:模块、段(入口)交叉引用
2.Removing Unused input sections from the image:移除未使用的模块
3.Image Symbol Table:映射符号表
4.Memory Map of the image:内存(映射)分布
5.Image component sizes:存储组成大小

文件名词
段(section):描述映像文件的代码和数据块
RO:Read-Only的缩写,包括RO-data(只读数据)和RO-code(代码)
RW:Read-Write的缩写,主要是RW-data,RW-data由程序初始化初始值
ZI:Zero-initialized的缩写,主要是ZI-data,由编译器初始化为0。
.text:与RO-code同义
.constdata:与RO-data同义
.bss:与ZI-data同义
.data:与RW-data同义

  • Section Cross References模块、段(入口)交叉引用(需勾上Cross Reference)
    • 表达不同文件中函数的调用关系
    • 例如:main.o(.text) refers to delay.o(.text) for delay_init表达main文件里的一段语句,调用了delay文件delay_init函数

  • Removing Unused input sections from the image 移除未使用的模块(需勾上Unuaed Sections Info)
    • 最后一句统计信息23 unused section(s) (total 104 bytes) removed from the image.表示总共23段没有调用,没有调用的大小为104字节

  • Image Symbol Table 映射符号表(需勾上Symbols)
    • 映射符号表,也就是各个段所存储对应地址的表(这一项比较重要)
    • Symbols分为两大类1.Local Symbols局部、2.Global Symbols全局
    • 1、Symbol Name:符号名
    • 2、Value:存储对应的地址;(0x08开头表示存储在Flash中,0x2开头表示存储在SRAM中)
    • 3、Ov Type:符号对应的类型符号类型大概有几种:NumberSectionThumb CodeData等;(全局、静态变量等位于0x2000xxxx的内存RAM中)
    • 4、Size:存储大小(怀疑内存溢出,可以查看代码存储大小来分析)
    • 5、.Object(Section):段目标(这里一般指所在模块(所在源文件))

  • Memory Map of the image:内存(映射)分布(需勾上Memory Map)
    • 1、Base Addr:存储地址(0x0800xxxxFLASH地址和0x2000xxxx内存RAM地址)
    • 2、Size:存储大小
    • 3、Type:类型(Data:数据类型Code:代码类型 Zero:未初始化变量类型 PAD:“补充类型”。)
    • 4、Attr:属性(RO:存储与ROM中的段 RW:存储与RAM中的段)
    • 5、Section Name:段名这里也可以说为入口分类名,与第一章节“Section Cross References”指的模块、段一样。大概包含:RESET、.ARM、 .text、 i、 .data、 .bss、 HEAP、 STACK等。
    • 6、Object:目标模块

  • Image component sizes:存储组成大小(需勾上Size Info)
    • 对信息进行汇总,和开头分析的那个信息是一样的。

1.1 启动过程分析

1 启动文件解读

野火哥的启动文件视频讲解,超详细

  1. 初始化堆和栈

第一段定义了一个栈空间(用于存放局部变量,函数)

  • Stack_Size EQU 0x00000400;伪指令Stack_Size=1KB
  • AREA STACK, NOIIT, READWRITE, ALIGN=3;STACK新的栈段,NOIIT表示存放到SRAM,READWRITE可读写ALIGN=32^3字节即8字节对齐
  • Stack_Mem SPACE Stack_Size;分配空间
  • __initial_sp 表示栈的结束地址,即栈顶地址;栈是由高向低生长的

第二段定义了一个堆空间(动态内存分配)

  • Heap_Size EQU 0x00000200;Heap_Size=512字节
  • AREA HEAP, NOINIT, READWRITE, ALIGN=3同上
  • __heap_base;堆的起始地址
  • Heap_Mem SPACE Heap_Size;分配空间
  • __heap_limit;堆的结束地址;堆是由低向高生长的,和栈相反
  • PRESERVE8;指定当前文件的堆栈按照8 字节对齐。
  • THUMB;表示后面指令兼容THUMB 指令。THUBM是ARM以前的指令集
  1. 初始化向量表

  • AREA RESET, DATA, READONLY;定义一个数据段,名字为RESET,存放在flash,只读。

  • 声明 __Vectors__Vectors_End__Vectors_Size 这三个标号具有全局属性,可供外部的文件调用。

    • EXPORT __Vectors
    • EXPORT __Vectors_End
    • EXPORT __Vectors_Size
    • EXPORT:声明一个标号可被外部的文件使用,使标号具有全局属性。如果是IAR 编译器,则使用的是GLOBAL 这个指令。
  • DCD __initial_sp ;

    • DCD 简单来说就是给后面的标号分配了一个地址,并存放在该位置
    • 第一个存放栈顶指针SP,第二个存放PC指针,指向复位RESET中断子服务函数
  1. 复位中断子服务函数及其他中断子服务函数

  • EXPORT Reset_Handler [WEAK];WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
  • IMPORT SystemInit;IMPORT:表示该标号来自外部文件,跟C 语言中的EXTERN 关键字类似。
  • 然后下面两个分别进入外部的SystemInit函数和__main函数(__main集成在MDK自带的库里面了,主要的功能是软件设置SP、加载.data.bss并初始化栈区)

下面是一些其他的中子服务函数,并且具有弱WEAK性,即如果外部有,优先采用外部的,不会产生冲突

  1. 用户堆栈初始化

给用户自己定义堆栈


2 内部Flash启动过程

启动过程主要为:
1、初始化堆栈指针SP=_initial_sp和PC指针=Reset_Handler
2、配置系统时钟,调用C 库函数_main 初始化用户堆栈调用main

1、初始化堆栈指针SP=_initial_spPC指针=Reset_Handler、初始化中断向量表

上电后默认将0x0000 0000的位置读出为SP指针,将0x0000 0004的位置数据读出为PC指针

根据前面,我们知道以flash启动时0x0000 0000被映射到0x0800 0000的位置,所以根据启动文件可以得知,向量表也被映射到了0x0800 0000,由此可以读出栈顶地址复位中断地址

这里用软件STM32 ST-LINK Utility 下载地址1(官网) 下载地址2

打开hex文件,可以查到0x0800 0000起头两个的数据,第一个0x20000758是分配给SP的地址(栈顶地址),第二个0x080004A1是分配给PC指针的地址(复位中断地址)

0x080004A14字节对齐0x080004A0,在.map文件中找到地址0x080004A0对应得模块(复位中断函数就放在这个启动文件里)

PC指针跳转到复位中断子服务函数Reset_Handler

2、配置系统时钟,调用C 库函数_main 初始化用户堆栈调用main

复位中断子服务函数里完成这些事


参考资料


[应用]IAP、ISP

见专栏IAP

2 SysTick系统定时器

系统定时器非常重要,在非操作系统里,系统定时器用来做延时使用,在操作系统里,系统定时器用来产生任务调度的定时。

SysTick系统定时器是Cortex M4架构里都有的外设,所以我们查看Cortex M4内核编程手册

打开Cortex M4内核编程手册,找到4 Core peripherals(内核特性)-4.5 SysTick Timer

它告诉我们SysTick是个24位的系统定时器,从预装载值向下计数到0,相关寄存器有控制状态寄存器STK_CTRL装载值寄存器STK_LOAD当前值寄存器STK_VAL校准数值寄存器STK_CALIB

  • 控制状态寄存器STK_CTRL

    • COUNTFLAG:当定时器数值为0时,此位为1
    • CLKSOURCE:选择时钟源,0:AHB/8 1:AHB
    • TICKINT:1使能定时器异常请求,即定时器回到0不会重装载
    • ENABLE:使能定时器重装载,同时受TICKINT控制
  • 装载值寄存器STK_LOAD

    • RELOAD:重装值为真实值N-1
  • 当前值寄存器STK_VAL

    • CURRENT:可手动清0

固件库中的Systick相关函数

  • SysTick_CLKSourceConfig() Systick时钟源选择(在misc.c文件中)

  • SysTick_Config(uint32_t ticks) 初始化systick,时钟为HCLK,并开启中断 (在core_cm3.h中)

  • void SysTick_Handler(void); SysTick中断函数

  • SysTick_CLKSourceConfig() 选择时钟源,0:AHB/8 1:AHB

void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}
  • SysTick_Config(uint32_t ticks)
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk)  return (1);      /* Reload value impossible */

  SysTick->LOAD  = ticks - 1;                                  /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}


  • if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1); 预装载值不能超过最大值
  • SysTick->LOAD = ticks - 1; 设置重装载寄存器的值
  • NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);设置中断寄存器
  • SysTick->VAL = 0; 当前值为0
  • SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;设置控制寄存器

应用:延时delay函数

  • 初始化延迟函数delay_init
//初始化延迟函数
//当使用OS的时候,此函数会初始化OS的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{

 	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); 
	fac_us=SYSCLK/8;						//不论是否使用OS,fac_us都需要使用
	fac_ms=(u16)fac_us*1000;				//非OS下,代表每个ms需要的systick时钟数   

}			

指定时钟源为AHB/8

  • us延时delay_us

void delay_us(u32 nus)
{		
	u32 temp;
	SysTick->LOAD=nus*fac_us; 				//时间加载
	SysTick->VAL=0x00;        				//清空计数器
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 	 
	do
	{
		temp=SysTick->CTRL;
	}while((temp&0x01)&&!(temp&(1<<16)));	//等待时间到达   
	SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
	SysTick->VAL =0X00;       				//清空计数器 
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM