中断重映射

最近一直在做无线程序在线升级功能,功能都实现了,做下总结。具体实现过程可以参考

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 地址处存储器重映射的例子,简而言之,地址重映射就是可以通过软件配

置来改变一块存储器物理地址的一种机制或方法。

当一段程序对运行自己的存储器进行重映射的时候,需要特别注意保证程序

执行流程在重映射前后的承接关系。下面是一种典型的存储器地址重映射情况:

中断重映射_第1张图片

系统上电后的缺省状态是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 方式,如下图:

中断重映射_第2张图片

原来RAM 和ROM 各有自己的地址,进行重映射以后RAM 和ROM 的地址

都发生了变化,这种情况下,可以采用以下的方案:

(1) 上电后,从0 地址的ROM 开始往下执行。

(2) 根据映射前的地址,对RAM 进行必要的代码和数据拷贝。

(3) 拷贝完成后,进行remap 操作。

(4) 因为RAM 在remap 前准备好了内容,使得PC 指针能继续在RAM 里取

到正确的指令。

不同的系统可能会有多种灵活的remap 方案,根据上面提到的两个例子,可以总结出最根本的考虑是:要使程序指针在remap 以后能继续往下得到正确的指令。

二、关于Cortex M0系列MCU中断向量表重映射问题

最近使用了一款Cortex-M0内核的芯片STM32F030CC,发现它中断向量表的重映射方法与STM32F10x系列的有所区别,在这里记录与分享一下。

 
     由于需要通过IAP进行固件升级,所以芯片的FLASH里面要烧录两份代码:一个Boot loader, 一个用户应用程序。理所当然的,在用户应用程序中,必须得重新映射中断向量表。
     可是在ST提供的固件库里,我却没有发现类似于stm32f10x固件库中的voidNVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)接口。
     浏览了一下Cortex-M0的Programmingmanual,原来M0并没有SCB->VTOR这个寄存器,难怪ST的库里没有提供NVIC_SetVectorTable这个接口。
     这下要怎么办?在网络上搜索了一下,受到网友findaway123这篇文章的启发,我在STM32F030CC的Reference manual中找到以下说明:
     Physicalremap
  Once the boot mode is selected, the application software canmodify the memory accessible in the code area.This modification isperformed by programming the MEM_MODE bits in the SYSCFGconfiguration register 1 (SYSCFG_CFGR1). Unlike Cortex? M3 and M4,the M0 CPU does not support the vector table relocation. Forapplication code which is located in a different address than0x0800 0000, some additional code must be added in order to be ableto serve the application interrupts. A solution will be to relocateby software the vector table to the internal SRAM:
  ? Copy the vector table from the Flash (mapped at the base of theapplication load address) to the base address of the SRAM at 0x20000000.
  ? Remap SRAM at address 0x0000 0000, using SYSCFG configurationregister 1.
  ? Then once an interrupt occurs, the Cortex?-M0 processor willfetch the interrupt handler start address from the relocated vectortable in SRAM, then it will jump to execute the interrupt handlerlocated in the Flash.
  This operation should be done at the initialization phase of theapplication. Please refer to AN4065 and attached IAP codefrom www.st.com for more details.
  OK,解决方法找到了!

 

  在用户应用程序中,按照以上方法,添加以下两行代码:

    memcpy((void*)0x20000000, (void*)0x08004000, VECTOR_SIZE); SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);

   其中,0x2000 0000是SRAM的起始地址,这个不需要改动。
   而之后的两个参数需要根据实际情况作出修改。0x08004000是应用程序的起址地址,从这里开始的VECTOR_SIZE字节,存放是的应用程序的中断向量表。VECTOR_SIZE是指中断向量表的大小,具体多大可以在startup.s文件里计算得到。以下以startup_stm32f030.s为例作说明:
中断重映射_第3张图片
 
  我们只需关注其中的一小部分。从29行开始,直到75行,每一个DCD都代表一个中断向量(所谓中断向量,说得明白点,其实就是某个中断服务程序的入口地址)。例如第74行的:

  DCD     USART1_IRQHandler              ; USART1

  这里的“USART1_IRQHandler"其实就是UART1中断服务程序USART1_IRQHandler这个函数,同时,它也代表这个函数的入口地址。
  以上代码即定义了这样一张表,这张表包括45个元素,每个元素是一个长度为4字节的地址。除了第一个地址是SP(堆栈指针)外,其它的地址都是某个中断服务程序的入口地址。
  那么,回到我们要解决的问题上来,之前memcpy函数中的第三个参数VECTOR_SIZE,针对本例,就应该是45*4=180(0xB4)个字节。

 

  在执行完以上两行代码后,若发生中断,CPU就会去SRAM(即0x2000 0000处)取中断向量了,所以,以0x20000000作为起始地址之后的VECTOR_SIZE个字节就不能被改动了。为了达到这VECTOR_SIZE个字节不被修改的目的,如下两种方法可以实现。
  ?在工程文件内修改SRAM的起始地址及长度,如下图
  中断重映射_第4张图片
  ?如果使用了分散加载文件,则在分散加载文件中修改SRAM的起始地址及长度也能达到目的。

 

  至此,STM32F0系列Cortex-M0内核芯片中断向量表重映射的问题已解决。

三、问题总结

1、在写STM32G070的boot时候,没有找到void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset)
这个函数,但是找到了如下函数:
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,原因如下:

中断重映射_第5张图片

跳转到APP后,会导致看门狗复位,查看软件地址,我修改了,询问ST的FAE,说M0+有VTOR寄存器,M0的没有。
最后找到原因,我使用了自己的sct文件,修改MDK文件里的地址不管用,修改sct文件后,再测试,OK。
2、无线升级一定做冗余设计,单次通信升级失败率很高。
 
 

EXTI

EXTI (External interrupt) 就是指外部中断,通过 GPIO 检测输入脉冲,引起中断事件,打断原来的代码执行流程,进入到中断服务函数中进行处理,处理完后,再返回到中断之前的代码中执行。

STM32 的所有 GPIO 都可以用作外部中断源的输入端,利用这个特性,我们可以把按键轮询检测 改为由中断 来处理,大大提高软件执行的效率。

Cortex 内核具有强大的异常响应系统,它把能够打断当前代码执行流程的事件分为异常(exception)和中断(interrupt),并把它们用一个表管理起来,编号为 0~15 的称为内核异常,而 16 以上的则称为外部中断(外,相对内核而言),这个表就称为中断向量表。

中断重映射_第6张图片
中断重映射_第7张图片
中断重映射_第8张图片

而 STM32 对这个表重新进行了编排,把编号从-3 至 6 的中断向量定义为系统异常,编号为负 的内核异常不能被设置优先级,如复位(Reset)、不可屏蔽中断 (NMI)、硬错误(Hardfault)。从编号 7 开始的为外部中断,这些中断的优先级都是可以自行设置的。详细的 STM32 中断向量表见图 ,STM32 中断向量表STM32 的中断如此之多,配置起来并不容易,因此,我们需要一个强大而方便的中断控制器 NVIC (Nested Vectored Interrupt Controller)。NVIC 是属于Cortex 内核的器件,不可屏蔽中断 (NMI)和外部中断都由它来处理,而SYSTICK 不是由 NVIC 来控制的。

中断重映射_第9张图片

NVIC

当我们要使用 NVIC 来配置中断时,自然想到 ST 库肯定也已经把它封装成库函数了。查找库帮助文档,发现在 Modules->STM32F10x_StdPeriph_Driver->misc 查找到一个 NVIC_Init() 函数,对 NVIC 初始化,首先要定义并填充一个NVIC_InitTypeDef 类型的结构体。
这个结构体有四个成员
中断重映射_第10张图片

前面两个结构体成员都很好理解,首先要用 NVIC_IRQChannel 参数来选择将要配置的中断向量,用 NVIC_IRQChannelCmd 参数来进行使能(ENABLE)或关闭(DISABLE)该中断。在NVIC_IRQChannelPreemptionPriority 成员要配置中断向量的抢占优先级,在 NVIC_IRQChannelSubPriority 需要配置中断向量的响应优先级。对于中断的配置,最重要的便是配置其优先级,但 STM32 的同一个中断向量为什么需要设置两种优先级?这两种优先级有什么区别?

抢占优先级和响应优先级

STM32 的中断向量具有两个属性,一个为抢占属性,另一个为响应属性,其属性编号越小,表明它的优先级别越高。抢占,是指打断其它中断的属性,即因为具有这个属性,会出现嵌套中断(在执行中断服务函数 A 的过程中被中断 B 打断,执行完中断服务函数 B 再继续执行中断服务函数 A),抢占属性由 NVIC_IRQChannelPreemptionPriority 的参数配置。而响应属性则应用在抢占属性相同的情况下,当两个中断向量的抢占优先级相同时,如果两个中断同时到达,则先处理响应优先级高的中断,响应属性由 NVIC_IRQChannelSubPriority 的参数配置。
例如,现在有三个中断向量
中断重映射_第11张图片
若内核正在执行 C 的中断服务函数,则它能被抢占优先级更高的中断 A 打断,由于 B 和 C 的抢占优先级相同,所以 C 不能被 B 打断。但如果 B 和 C 中断是同时到达的,内核就会首先响应响应优先级别更高的 B 中断

NVIC 的优先级组

在配置优先级的时候,还要注意一个很重要的问题,中断种类的数量。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 个外部中断/事件线上

EXTI 外部中断

STM32 的所有 GPIO 都引入到 EXTI 外部中断线上,使得所有的 GPIO 都能作为外部中断的输入源。GPIO 与 EXTI 的连接方式见图
中断重映射_第12张图片

观察这个图知道,PA0~PG0 连接到 EXTI0 、PA1~PG1 连接到EXTI1、 ……、 PA15~PG15 连接到 EXTI15。这里大家要注意的是:PAx~PGx端口的中断事件都连接到了 EXTIx,即同一时刻 EXTx 只能相应一个端口的事件触发,不能够同一时间响应所有 GPIO 端口的事件,但可以分时复用。它可以配置为上升沿触发,下降沿触发或双边沿触发。EXTI 最普通的应用就是接上一个按键,设置为下降沿触发,用中断来检测按键。

你可能感兴趣的:(单片机)