最近一直在做无线程序在线升级功能,功能都实现了,做下总结。具体实现过程可以参考
https://blog.csdn.net/wuhenyouyuyouyu/article/details/102851287
0.什么是Remap
我的理解是:在ROM从0x0用几句指令引导系统之后,把RAM映射到0x0就是Remap。
1.Remap的作用
当ARM处理器上电或者Reset之后,处理器从0x0取指。因此,必须保证系统上电时,0x0处有指令可以执行。所以,上电的时候,0x0地址处必定是ROM或者Flash(NOR)。
但是,为了加快启动的速度,也方便可以更改异常向量表,加快中断响应速度,往往把异常向量表映射到更快、更宽(32bit/16bit)的RAM中。但是异常向量表的开始地址是由ARM架构决定的,必须位于0x0处,因此,必须把RAM映射到0x0。
2.Remap的配置
Remap的实现和ARM处理器的实现相关。
1)如果处理器有专门的寄存器可以完成Remap。那么Remap是通过Remap寄存器的相应bit置1完成的。如Atmel AT91xx
2)如果处理器没有专门的寄存器,但是memory的bank控制寄存器可以用来配置bank的起始地址,那么只要把RAM的起始地址编程为0x0,也可以完成 remap。如samsung s3c4510
3)如果上面两种机制都没有,那么Remap就不要做了。因为处理器实现决定了SDRAM对应的bank地址是不能改变的。如Samsung S3c2410.
3.Remap配置前后要做的工作
Remap前后,不同之处就是RAM的位置变了。为了达到Remap的目的,就是加快启动的速度和异常处理速度,一定要初始化异常堆栈和建立异常向量表的。
4.如果象2410那样不能Remap的话怎么办?
2410不是不能Remap吗?为了加快启动速度,可以这样做
1)使用它的NAND boot模式。为什么NAND boot会比较快,那是因为2410里面有块小石头——“SteppingStone”,一块4KB SRAM,它是映射在0x0的。启动程序会自动被copy到这个石头里面。自然异常向量的入口放到这个地方,一样可以达到比NOR boot快的启动、异常响应速度。
2)如果你对NOR Boot情有独衷,那么你只好把你的异常向量的入口copy到SDRAM里面,实现所谓的High Vector
存储器地址重映射是当前很多先进控制器所具有的功能。在上一节中已经提
到了0 地址处存储器重映射的例子,简而言之,地址重映射就是可以通过软件配
置来改变一块存储器物理地址的一种机制或方法。
当一段程序对运行自己的存储器进行重映射的时候,需要特别注意保证程序
执行流程在重映射前后的承接关系。下面是一种典型的存储器地址重映射情况:
系统上电后的缺省状态是0 地址上放有ROM,这块ROM 有两个地址:从0
起始和从0x10000 起始,里面存储了初始化代码。当进行地址remap 以后,从0
起始的地址被定向到了RAM 上,ROM 则只保留有唯一的从0x10000 起始的地
址了。
如果存储在ROM 里的Reset_Handler 一直在0 – 0x4000 的地址上运行,则
当执行完remap 以后,下面的指令将从RAM 里预取,必然会导致程序执行流程
的中断。根据系统特点,可以用下面的办法来解决这个问题:
(1) 上电后系统从0 地址开始自动执行,设计跳转指令在remap 发生前使PC指针指向0x10000 开始的ROM 地址中去,因为不同地址指向的是同一块ROM,所以程序能够顺利执行。
(2) 这时候0 - 0x4000 的地址空间空闲,不被程序引用,执行remap 后把RAM
引进。因为程序一直在0x10000 起始的ROM 空间里运行,remap 对运行流程没有任何影响。
(3) 通过在ROM 里运行的程序,对RAM 进行相应的代码和数据拷贝,完成应用程序运行的初始化。
下面是一段实现上述步骤的例程:
ENTRY
;启动时,从0 开始,设法跳转到“真”的ROM 地址(0x10000 开始的空间里)
LDR pc, =start
;insert vector table here
…
Start ;Begin of Reset_Handler
; 进行remap 设置
LDR r1, =Ctrl_reg ;假定控制remap 的寄存器
LDR r0, [r1]
ORR r0, r0, #Remap_bit ;假定对控制寄存器进行remap 设置
STR r0, [r1]
;接下去可以进行从ROM 到RAM 的代码和数据拷贝
除此之外,还有另外一种常见的remap 方式,如下图:
原来RAM 和ROM 各有自己的地址,进行重映射以后RAM 和ROM 的地址
都发生了变化,这种情况下,可以采用以下的方案:
(1) 上电后,从0 地址的ROM 开始往下执行。
(2) 根据映射前的地址,对RAM 进行必要的代码和数据拷贝。
(3) 拷贝完成后,进行remap 操作。
(4) 因为RAM 在remap 前准备好了内容,使得PC 指针能继续在RAM 里取
到正确的指令。
不同的系统可能会有多种灵活的remap 方案,根据上面提到的两个例子,可以总结出最根本的考虑是:要使程序指针在remap 以后能继续往下得到正确的指令。
最近使用了一款Cortex-M0内核的芯片STM32F030CC,发现它中断向量表的重映射方法与STM32F10x系列的有所区别,在这里记录与分享一下。
memcpy((void*)0x20000000, (void*)0x08004000, VECTOR_SIZE); SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
DCD USART1_IRQHandler ; USART1
void SystemInit(void)
{
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
按着修改:
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)
{
/* Check the parameters */
//SCB->VTOR = NVIC_VectTab | (Offset & (uint32_t)0x1FFFFF80);
SCB->VTOR = NVIC_VectTab | (Offset & (uint32_t)0x1FFFFF80); /* Vector Table Relocation in Internal FLASH */
/*!< Vector Table base offset field.
This value must be a multiple of 0x100. */
}
至于为什么与0x1FFFFF80,原因如下:
EXTI (External interrupt) 就是指外部中断,通过 GPIO 检测输入脉冲,引起中断事件,打断原来的代码执行流程,进入到中断服务函数中进行处理,处理完后,再返回到中断之前的代码中执行。
STM32 的所有 GPIO 都可以用作外部中断源的输入端,利用这个特性,我们可以把按键轮询检测 改为由中断 来处理,大大提高软件执行的效率。
Cortex 内核具有强大的异常响应系统,它把能够打断当前代码执行流程的事件分为异常(exception)和中断(interrupt),并把它们用一个表管理起来,编号为 0~15 的称为内核异常,而 16 以上的则称为外部中断(外,相对内核而言),这个表就称为中断向量表。
而 STM32 对这个表重新进行了编排,把编号从-3 至 6 的中断向量定义为系统异常,编号为负 的内核异常不能被设置优先级,如复位(Reset)、不可屏蔽中断 (NMI)、硬错误(Hardfault)。从编号 7 开始的为外部中断,这些中断的优先级都是可以自行设置的。详细的 STM32 中断向量表见图 ,STM32 中断向量表STM32 的中断如此之多,配置起来并不容易,因此,我们需要一个强大而方便的中断控制器 NVIC (Nested Vectored Interrupt Controller)。NVIC 是属于Cortex 内核的器件,不可屏蔽中断 (NMI)和外部中断都由它来处理,而SYSTICK 不是由 NVIC 来控制的。
当我们要使用 NVIC 来配置中断时,自然想到 ST 库肯定也已经把它封装成库函数了。查找库帮助文档,发现在 Modules->STM32F10x_StdPeriph_Driver->misc 查找到一个 NVIC_Init() 函数,对 NVIC 初始化,首先要定义并填充一个NVIC_InitTypeDef 类型的结构体。
这个结构体有四个成员
前面两个结构体成员都很好理解,首先要用 NVIC_IRQChannel 参数来选择将要配置的中断向量,用 NVIC_IRQChannelCmd 参数来进行使能(ENABLE)或关闭(DISABLE)该中断。在NVIC_IRQChannelPreemptionPriority 成员要配置中断向量的抢占优先级,在 NVIC_IRQChannelSubPriority 需要配置中断向量的响应优先级。对于中断的配置,最重要的便是配置其优先级,但 STM32 的同一个中断向量为什么需要设置两种优先级?这两种优先级有什么区别?
STM32 的中断向量具有两个属性,一个为抢占属性,另一个为响应属性,其属性编号越小,表明它的优先级别越高。抢占,是指打断其它中断的属性,即因为具有这个属性,会出现嵌套中断(在执行中断服务函数 A 的过程中被中断 B 打断,执行完中断服务函数 B 再继续执行中断服务函数 A),抢占属性由 NVIC_IRQChannelPreemptionPriority 的参数配置。而响应属性则应用在抢占属性相同的情况下,当两个中断向量的抢占优先级相同时,如果两个中断同时到达,则先处理响应优先级高的中断,响应属性由 NVIC_IRQChannelSubPriority 的参数配置。
例如,现在有三个中断向量
若内核正在执行 C 的中断服务函数,则它能被抢占优先级更高的中断 A 打断,由于 B 和 C 的抢占优先级相同,所以 C 不能被 B 打断。但如果 B 和 C 中断是同时到达的,内核就会首先响应响应优先级别更高的 B 中断
在配置优先级的时候,还要注意一个很重要的问题,中断种类的数量。NVIC 只可以配置 16 种 中断向量的优先级,也就是说,抢占优先级和响应优先级的数量由一个 4 位的数字来决定,把这个 4 位数字的位数 分配成抢占优先级部分和响应优先级部分。有 5 组分配方式:
第 0 组:
所有 4 位用来配置抢占优先级,即 NVIC 配置的 2 4 =16 种中断向量都是只有抢占属性,没有响应属性。
第 1 组:
最高 1 位用来配置抢占优先级,低 3 位用来配置响应优先级。表示有 21=2 种级别的抢占优先级(0 级,1 级),有 23=8 种响应优先级,即在 16种中断向量之中,有 8 种中断,其抢占优先级都为 0 级,而它们的响应优先级分别为 0~7,其余 8 种中断向量的抢占优先级则都为 1 级,响应优先级别分别为 0~7。
第 2 组:
2 位用来配置抢占优先级,2 位用来配置响应优先级。即 22=4 种抢占优先级,22=4 种响应优先级。
第 3 组:
高 3 位用来配置抢占优先级,最低 1 位用来配置响应优先级。即有 8 种抢占优先级,2 种响应 2 优先级。
第 4 组:
所有 4 位用来配置响应优先级。即 16 种中断向量具有都不相同的响应优先级。
要配置这些优先级组,可以采用库函数 NVIC_PriorityGroupConfig(),可输入的参数为NVIC_PriorityGroup_0 ~ NVIC_PriorityGroup_4,分别为以上介绍的 5 种分配组。
于是,有人觉得疑惑了,如此强大的 STM32,所有 GPIO 都能够配置成外部中断,USART、ADC 等外设也有中断,而 NVIC 只能配置 16 种中断向量,那在某个工程中使用了超过 16 个的中断怎么办呢?注意 NVIC 能配置的是 16种 中断向量,而不是 16 个,当工程之中有超过 16 个中断向量时,必然有 2 个以上的中断向量是使用相同的中断种类,而具有相同中断种类的中断向量不能互相嵌套。
STM2 单片机的所有 I/O 端口都可以配置为 EXTI 中断模式,用来捕捉外部信号,可以配置为下降沿中断,上升沿中断和上升下降沿中断这三种模式。它们以下图的方式连接到 16 个外部中断/事件线上
STM32 的所有 GPIO 都引入到 EXTI 外部中断线上,使得所有的 GPIO 都能作为外部中断的输入源。GPIO 与 EXTI 的连接方式见图
观察这个图知道,PA0~PG0 连接到 EXTI0 、PA1~PG1 连接到EXTI1、 ……、 PA15~PG15 连接到 EXTI15。这里大家要注意的是:PAx~PGx端口的中断事件都连接到了 EXTIx,即同一时刻 EXTx 只能相应一个端口的事件触发,不能够同一时间响应所有 GPIO 端口的事件,但可以分时复用。它可以配置为上升沿触发,下降沿触发或双边沿触发。EXTI 最普通的应用就是接上一个按键,设置为下降沿触发,用中断来检测按键。