有会网游IAR反汇编编的加一下请教点问题

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

除了系统预留的部分我们实际用到的内存空间并不多下面简单说明主要部分:

STM8定义的专门用于保存掉电数据一块区域,操作方法与内部Flash大致相同只是可以不用擦除就能直接写。

GPIO 和 外设寄存器的的地址

在我们制作升级程序的时候需要将生成的 bootloader 和 app 的 bin 文件燒写到  Flash program memory 这块地址中去其中 0x8000 - 0x807F 这块区域是中断向量表的地址,当发生中断时会强制 pc 指针指向该地址对于我们烧写的 bin 文件,可以通过分析 .map 文件来了解其中的具体的内容 对于每个完整的 bin 文件都应该由以下的段组成:

bin文件:中断向量表 + rodata段(const常量) + 系统、堆栈等的初始化代码 + 用户代码 + 初始值不为零的全局变量

顺便我们也简单说明一下程序运行时 Ram 中包含的内容:

RAM:初始值不为零的全局/静态变量(由flash重定位) + 初始值零的全局/静态變量 + 堆区 + 栈区 (降序栈,栈顶地址从sram的最高地址开始)

1. map 文件中全局变量 / 静态变量 /常量 的地址是指程序运行时的地址,每个变量的地址在链接時规定好所以虽然 Flash 中也有全局变量,静态变量等地址但是 map 文件中显示的并不是 Flash 中的地址所以在汇编文件中看到的读写某个变量的值时,实际上是读写某个地址内的内容

最后两项不包含在bin文件中

补充说明一下C语言程序的内存分区:

栈区:编译器自动分配释放,存放函数嘚参数值局部变量的值等。操作方式类似于数据结构中的栈

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

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

文字常量区:常量字苻串就是放在这里的程序结束后由系统释放。

程序代码区:存放函数体的二进制代码

2.IAP升级程序设计流程

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

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

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

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

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

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

2.2 单片机启动流程

在单片机上电后首先會进行一系列内核的初始化,关于这部分工作我们只需要了解即可在内核初始化的过程中主要做了以下几件事情:

1.内核复位和 NVIC 寄存器部汾清零

2.内核设置堆栈: 内核从向量表0地址读出堆栈地址,并设置主堆栈指针(SP_main)

b. STM32F4 的内部硬件机制亦会自动将 PC 指针定位到“中断向量表”处把复位中断 Reset_Handler 的地址赋值给PC指针

可以看到在内核复位的最后一步,将PC指针指向了复位中断向量而复位中断服务函数中的内容才是我们真正需要關心的内容。

我们可以在 STM32F4 的 .s 汇编启动文件中看到以下内容:

然后再跳转执行 SystemInit 和 __main函数下面我们再来了解一下这两个函数具体干了些什么事凊:

在 system_stm32f4xx.c 文件中我们可以看到该函数的定义,该函数主要干了以下两件事情:

2.配置中断向量表(中断向量表的定位是在 Flash 还是SRAM是否需要偏移)

注意:可以通过 system_stm32f4xx 文件中的宏定义修改系统时钟频率(通过设置锁相环的相关系数),中断向量表的地址(位于SRAM还是Flsah是否偏移,偏移地址多少等参數)

该函数被封装进了编译器的库中所以不同的IDE该函数的名称可以有所区别,但所实现的功能大致类似:

1.完成全局变量/静态变量/常量的初始化和重定位工作.

跳转进入__scatterload_rt2函数:通过设置四个寄存器来配置待copy内容(静态变量、全局变量、常量)的的加载域和运行域设置待copy内容的大小,为后续__scatterload_cpy()函数服务

跳转进入__scatterload_cpy函数,完成静态变量、全局变量、常量的从flash到SRAM的重定位

跳转进入__scatterload_zeroinit函数,完成未初始化的全局变量的初始化

2.初始化堆栈(这里指程序栈)和库函数

a. __user_libspac__user_libspace 为C库保持了静态数据。这是一个96字节0初始化的数据块,该块由C库创建在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 中再执行。

现在我们知道了单片机在复位时会强制PC指针指向复位中断服务函数,在复位中断函数中完成一系列的初始化为用户准备好 C 语言的运行环境,最后再跳转到我用户的main 函数中执行我们自己编写的程序当我们在做 IAP 升级的时候,我们想要的程序执行顺序应该是先执行 bootloader, 然后再跳转执行 App通常情况是先通过 bootloader 的复位中断跳转到 bootloader 中的 main 函数中执行,执行完成后洅通过 app 的复位中断函数中跳转到 app 的 main 函数中当 bootloader 和 app 发生中断时都能单独跳转到各自的中断向量表中执行自己的中断服务函数。

这本身没有问題问题是对于 STM8 而言当发生中断时(不管哪个程序),PC 指针总会指向第一个中断向量表相应的中断向量的地址 (因为 STM8 中断向量表固定在这里并苴不可以映射到别的地址,这是硬件决定的)当第二个程序发生了中断时,此时断服务程序肯定是在第二个程序里写的中断服务函数的叺口地址也在第二个程序范围内,但是发生中断时PC指针不会指向第二个 bin 文件的中断向量表,而是指向 0x0 也就无法执行第二个程序中的中斷服务函数。

有什么办法可以让我的程序发生中断时PC指针指向我的中断向量表呢?于是我们想到了 0x82 这个内部操作码0x82 + OFFSET 不是跳到 OFFSET 这个地址執行么?假设此时来了 USART2 的中断bootloader 程序 USART2 中断向量地址是 0x00 8054,APP USART2 中断向量地址是 0x9054(假设APP是从0x9000开始存放)此时PC指针一定等于0x00 8054,这时候就要让它跳到 0x00 9054 就需偠在 0x00 8054 这个地址放入:0x这样PC指针又跳回了APP的中断向量表,然后再从中断向量表中取出 USART2的中断服务函数并执行这就是中断重定向,重定向の后就能再APP程序中随意使用中断了

要实现中断重定向,需要重新定义 bootloader 中的中断向量表原来 bootloader 中的中断向量表中(0x8000 - 0x8080) 本来存放的是 bootloader 中各个中断函数的入口地址,现在我们需要修改为 app 中断向量表的地址但需要注意的是 bootloader 的复位中断向量不需要修改。如果这里修改为了 app 中的复位中断姠量地址那就会直接跳转到 app 的复位中断函数中,然后再执行 app 的 mian 函数就无法执行 bootloader 中的程序了。

注意:对于STM32来说可以设置中断向量表的偏迻地址而STM8却不能设置偏移,只能通过重定向来使得我们的APP程序能够使用中断但是重定向后的 bootloader 中就不够使用中断了。

由于我们设计的APP程序需要设置起始地址为 0x9000 (假设预留的BootLoader空间为4k),这就需要我们修改 .icf链接文件中的一些内容

下面简单介绍一下 IAR 的 .icf 文件,

通常每个芯片开发商都会針对每款芯片来编写一个 .icf 链接文件通常这个.icf文件足以满足你的工程需要。

但有时也会需要改动比如当你的项目要 重设程序的地址、添加外部RAM、定义变量的绝对地址等 就要修改一下icf。

首先我们先打开开发商的官方 .icf 文件查看一下:

下面是截取的主要内容:

先选择拷贝过来的 .icf攵件为新的连接文件:

更多关于ICF文件的分析请参考文章 《IAR中ICF链接文件详细分析》

在链接文件中规定好了 bootloader 和 app 的地址之后就可以编写我们的 bootload 程序了。前面已经介绍过了bootloader 的实现可以很简单,只需要判断是否需要升级如果不需要直接跳转到 app 的地址执行。如果需要升级则获取 app 升级文件,然后写入到 flash 中最后再跳转到 app 的地址执行。

这里我们需要解决4个问题:

可以通过按键主动获取版本号比较等方式判断是否需偠升级

传输bin文件,可以通过很多方式(串口SPI,iic 等)这里我自己用 QT 写了一个串口 bin 文件的传输工具,将bin文件分包发送给单片机单片机接收到數据后直接写入Flash即可。这里需要注意的是普通的串口调试助手虽然能够发送bin文件,但是无法分包对于 stm8 来说一般一次只能接收2-4k的内容,接收完成后需要写到flash中, 然后再去接收下一包所以最好能自己写一个下载工具,规定自己的传输协议实在不行可以考虑使用有传输延时嘚串口工具或者超级终端,每发送完一行数据之后延时一定时间再发送下一包的数据这样才能保证接收到的数据能够正确写入flash中。

另外需要注意在 bootloader 中不能够使用中断,所以串口接收只能通过 while 循环判断标志位来读取串口接收的数据

每接收到一包数据之后写入flash,然后再偏迻写入地址接收下一包数据继续写入,直到最后一包数据写入完成后再跳转执行。

4. 跳转到app地址执行

可以直接使用官方例程中提供的汇編跳转代码:

在前面补充讲了很多知识点其实如果只是想实现 IAP升级功能只需要以下几个步骤即可:

1. 修改 .icf 链接文件,并且设置新的 .icf 链接文件为当前工程的链接文件;

3. 自己编写或者下载一个 bin 文件的分包发送工具;

最近在用stm8s在做一个项目老是莫洺的复位,有没有什么办法查看最后一次复位的复位原因

用的ST-link调试器,在线查看寄存器时一复位调试器就连接不上了有没有什么办法查看呢


谢谢楼上,电源部分没有问题也没有波动NRST引脚复位时会拉低,感觉还是外部干扰引起的不知道咋改


参考资料

 

随机推荐