×
嵌入式 > 技术百科 > 详情

STM8 IAP升级程序设计详解 - IAR环境

发布时间:2024-05-03 发布时间:
|

1.STM8内存空间分配

首先我们在STM8L15x的官方手册中查看一下CPU的内存空间分配:

除了系统预留的部分我们实际用到的内存空间并不多,下面简单说明主要部分:
--RAM0x00 0000 - 0x00 0FFF(最大 4KB, 包含堆栈区,栈区最大513bytes):
--DataEEPROM0x00 1000 - 0x00 17FF(最大2KB):
STM8定义的专门用于保存掉电数据一块区域,操作方法与内部Flash大致相同。只是可以不用擦除就能直接写。
--Option bytes0x00 4800 - 0x00 48FF
--GPIO and Perpheral registers0x00 5000 - 0x00 57FF
GPIO 和 外设寄存器的的地址
--Boot ROM0x006000 - 0x67FF
--CPU/SWIM/Debug/ITC Register0x00 7F00 - 0x00 7FFF
--Flash program memory0x00 8000 - 0x01 7FFF (最大64KB)

小结:

在我们制作升级程序的时候需要将生成的 bootloader 和 app 的 bin 文件烧写到 Flash program memory 这块地址中去,其中 0x8000 - 0x807F 这块区域是中断向量表的地址,当发生中断时会强制 pc 指针指向该地址。对于我们烧写的 bin 文件,可以通过分析 .map 文件来了解其中的具体的内容。 对于每个完整的 bin 文件都应该由以下的段组成:
bin文件:中断向量表 + rodata段(const常量) + 系统、堆栈等的初始化代码 + 用户代码 + 初始值不为零的全局变量
当 Flash中只有一个 bin 文件时,Flash 中的内容和 bin 文件一致,但是 Flash 中可以存放多个 bin 文件。


顺便我们也简单说明一下程序运行时 Ram 中包含的内容:
RAM:初始值不为零的全局/静态变量(由flash重定位) + 初始值零的全局/静态变量 + 堆区 + 栈区 (降序栈,栈顶地址从sram的最高地址开始)


注意:
1. map 文件中,全局变量 / 静态变量 /常量 的地址是指程序运行时的地址,每个变量的地址在链接时规定好,所以虽然 Flash 中也有全局变量,静态变量等地址但是 map 文件中显示的并不是 Flash 中的地址。所以在汇编文件中看到的读写某个变量的值时,实际上是读写某个地址内的内容。
2. 程序含有(反汇编文件):代码段 + rodata段(const常量) + .data(数据段) + .bss(初始值零或者为初始化的全局/静态变量) + comment段(注释)
最后两项不包含在bin文件中


补充说明一下C语言程序的内存分区:
栈区:编译器自动分配释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。


堆区:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。


全局区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。


文字常量区:常量字符串就是放在这里的。程序结束后由系统释放。
程序代码区:存放函数体的二进制代码


2.IAP升级程序设计流程

IAP原理非常简单,首先在 bootloader 程序中接收(串口、IIC、SPI等)第二个程序的代码,并写入Flash中,然后跳转到第二个程序首地址,开始运行第二个程序,也就是说我们需要写两个程序:
1.BootLoader程序
2. 用户APP


当Flash中存在两个bin文件时,程序是怎样运行的呢?我们又是如何在一个程序运行结束之后跳转到另一个程序中去呢?想要知道这些原理,首先我们需要先了解一下单片机的中断机制和启动流程:

2.1 STM8中断机制

在官方手册的第6章给出了 STM815xL 的中断向量表的定义:

在参考手册中一般都会列出单片机系统所有的中断向量及其对应的地址, 每个中断向量都存放着4个字节的数据(8位的跳转指令 + 24位的跳转地址),在中断发生时,会强制PC指针指向该中断向量的地址,然后取出该地址中的指令执行。


例如:此时来了 USART2 的中断,通过上表我们知道 USART2 的中断向量存放在 0x00 8054 地址,此硬件会把PC指针强制 = 0x00 8054 也就是从这个地址里取指令执行,而这个地址中的内容是 0x82 + OFFSET_ADDR(16位), 0x82 是内部指令,意思是跳转到后面的地址执行,OFFSET_ADDR,就是 USART2 的中断服务函数的入口地址, 这样最终就跳转到了 USART2 的中断服务函数中去执行。


当然中断发生时还会有一些入栈操作,保存程序当前运行的地址,一些变量的值到栈中,当中断服务程序执行完成后会从栈中恢复到执行中断前程序的运行状态,从而保证主程序的正常运行。


注意:在 STM8 中,0x82 后面会跟着24位的地址(PCE + PCH + PCL),CPU 最大寻址 2^24 = 16M空间

2.2 单片机启动流程

2.1.1 内核初始化

在单片机上电后首先会进行一系列内核的初始化,关于这部分工作我们只需要了解即可,在内核初始化的过程中主要做了以下几件事情:
1.内核复位和 NVIC 寄存器部分清零
2.内核设置堆栈: 内核从向量表0地址读出堆栈地址,并设置主堆栈指针(SP_main)
3.设置PC和LR寄存器
a. LR设置未初始复位值0xffff ffff
b. STM32F4 的内部硬件机制亦会自动将 PC 指针定位到“中断向量表”处,把复位中断 Reset_Handler 的地址赋值给PC指针

2.1.2 复位中断函数 Reset_Handler
可以看到在内核复位的最后一步,将PC指针指向了复位中断向量,而复位中断服务函数中的内容才是我们真正需要关心的内容。
我们可以在 STM32F4 的 .s 汇编启动文件中看到以下内容:
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit //加载 SystemInit的地址到寄存器 R0 中
BLX R0 //跳转 R0 中的地址执行 (执行 SystemInit 函数)
LDR R0, =__main //加载 SystemInit的地址到寄存器 R0 中
BX R0 //跳转 R0 中的地址执行 (执行 __main 函数)
ENDP
上面的代码就是 Reset_Handler 的中断服务函数,可以看到在服务中断函数中先使用 IMPORT声明了两个函数 __main,SystemInit。
然后再跳转执行 SystemInit 和 __main函数。下面我们再来了解一下这两个函数具体干了些什么事情:
a. SystemInit 函数:
在 system_stm32f4xx.c 文件中我们可以看到该函数的定义,该函数主要干了以下两件事情:
1.初始化时钟(SYSCLK, HCLK, PCLK2 and PCLK1 prescalers)
2.配置中断向量表(中断向量表的定位是在 Flash 还是SRAM,是否需要偏移)
注意:可以通过 system_stm32f4xx 文件中的宏定义修改系统时钟频率(通过设置锁相环的相关系数),中断向量表的地址(位于SRAM还是Flsah,是否偏移,偏移地址多少等参数)
b. __main()函数(在IAR中是 __iar_program_start ):
该函数被封装进了编译器的库中,所以不同的IDE该函数的名称可以有所区别,但所实现的功能大致类似:
1.完成全局变量/静态变量/常量的初始化和重定位工作.
跳转进入__scatterload_rt2函数:通过设置四个寄存器来配置待copy内容(静态变量、全局变量、常量)的的加载域和运行域,设置待copy内容的大小,为后续__scatterload_cpy()函数服务。
跳转进入__scatterload_cpy函数,完成静态变量、全局变量、常量的从flash到SRAM的重定位。
跳转进入__scatterload_zeroinit函数,完成未初始化的全局变量的初始化,
2.初始化堆栈(这里指程序栈)和库函数
跳转进入 __user_steup_stackheap 函数,实现用户的堆栈的配置
__user_steup_stackheap 完成了以下调用:
a. __user_libspac__user_libspace 为C库保持了静态数据。这是一个96字节,0初始化的数据块,该块由C库创建。在C库初始化期间可以用来当做临时栈。
b. __user_initial_stackheap 用户的初始化堆栈函数
_fp_init和__rt_fp_status_addr(C库函数)两个函数调用实现浮点运算的支持
3.程序的跳转,进入main()函数。
跳转进入用户的main函数

注意:
1.未初始化和初始值为零全局变量/静态变量 一般在RAM中,初始化值不为零的 全局变量/静态变量 一般在FLASH中。
2. 因为 Flash不能随机写(只能写0,不能写1),所以一般会在程序运行之前将初始值重定位到RAM中
3. 全局变量和常量的地址在编译时都已经被分配好了(所以能够在.map文件中看到), 而局部变量则是程序运行时在栈中创建的,栈空间大小可以在 IDE 中设置。
4. 单片机启动时,不需要用将代码从ROM搬移到RAM,而 ARM 则需要。我们先看看单片机程序执行的过程,单片机执行分三个步骤,取执行->分析指令->执行指令。取指令的任务是:根据 PC 的值从程序存储器读出指令,送到指令寄存器。然后分析执行执行。这样单片机就从内部程序存储器去代码指令,从RAM存取相关数据。要知道RAM取数的速度是远高于ROM的,但是单片机因为本身运行频率不高,所以从ROM取指令慢并不影响。而 ARM 不同,CPU 运行的频率高,远大于从ROM读写的速度,所以一般有操作系统,都需要将代码部分拷贝到RAM中再执行。

[1] [2]
STM8IAP升级程序IAR环境

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
Semtech的LoRa技术携手Chipsafer将牧场连接至云端