stm32单片机中断向量偏移地址设置时的对齐要求

本文深入探讨了STM32中NVIC中断向量表偏移寄存器的设置方法,并针对不同手册定义下的偏移量计算进行了详细分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

void MY_NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset)  
{ 
 assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
 assert_param(IS_NVIC_OFFSET(Offset));    
 SCB->VTOR = NVIC_VectTab|(Offset & (u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器用于标识向量表是在CODE区还是在RAM区
}

在以上的代码中,为什么(Offset & (u32)0x1FFFFF80)  为什么使用0x1FFFFF80,0x1FFFFF80是怎样得来的呢?

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

<<权威指南>>第一百零四页,有这么一段话:
    NVIC 中有一个寄存器,称为“向量表偏移量寄存器”(在地址0xE000_ED08 处),通过修改它的值就能定位向量表。但必须注意的是:向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是2 的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有32 个中断,则共有32+16(系统异常)=48 个向量,向上增大到2 的整次幂后值为64,因此地址地址必须能被64*4=256 整除,从而合法的起始地址可以是:0x0, 0x100, 0x200 等。
    向量表偏移量寄存器,也就是SCB->VTOR.它的第29位,用来标识向量表是在CODE区还是RAM区,从而0X1,就是最高3位不去动,这好理解.   但是低位,根据上面这段话的理解,STM32自己有60个中断,加上CM3的16个,总共有76个中断,扩大到2的整次幂,那就是128,然后再乘以4,得到512,也就是0X200.根据这样计算,合法的偏移地址应该是0X0,0X200,0X400,0X600... 因此,在此处应该&0X1FFF FE00.才对.
    以上是我的理解.实际上确是&0X1FFF FF80;这点,我也有疑问.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

按上面的算法,好像也挺对的,Offset&0X1FFF FF80 
Offset:是偏移量的计算 
也就是说STM32自己有60个中断,加上CM3的16个,总共有76个中断,扩大到2的整次幂,那就是128,然后再乘以4,得到512,也就是0X200.根据这样计算,合法的偏移地址应该是0X0,0X200,0X400,0X600,0x800...... 
29 TBLBASE R/W 0 Table base in Code (0) or RAM (1)  
28:7 TBLOFF R/W 0 Table offset value from Code region or RAM region  

屏蔽前七位0x1FFFFF80和后三位+Offset + VECTTOR就是开始地址

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

我觉得原子大哥的"&0X1FFF FE00"更确切一些。 
原因如下: 
在 "ARMv7-M Architecture Application Level Reference Manual(November 2010)" 的716页给出的VTOR定义为: 
[31:7]——Bits [31:7] of the vector table address 
[6:0]  ——Reserved. 

(这里不是[29:7]的原因在 "Cortex-M3 Technical Reference Manual.r2p1" 的24页"The Vector Table Offset Register located at address 0xE000ED08 has been increased by two bits from 29:7 to 31:7",也就是r2p1版本升级扩展了两位,不过仍然向后兼容:"One or two of the high-order bits of the TBLOFF field can be implemented as  
RAZ/WI, reducing the supported address range. For example, if two bits are implemented as RAZ/WI, then TBLOFF[29:7] defines bits [29:7] of the address") 
也就是说ARM官方r2p0的定义:[31:30]保留,[29]作为TBLBASE判别标准屏蔽,[6:0]保留,如此一来得到&0X1FFF_FF80; 

对于ST官方的定制而言,在"PM0056 Programming manual Rev4"的134页 给出的定制为: 
"[31:30] Reserved, must be kept cleared" 
"TBLOFF[29:9]: Vector table base offset field" 
"Bits 8:0Reserved, must be kept cleared" 
也就是说ST把[8:7]也给保留了,原因就是楼上kenluo所说的。 
这种情况下按ST官方的定制::[31:30]保留,[29]作为TBLBASE判别标准屏蔽,[8:0]保留,如此一来得到&0X1FFF_FE00,就是原子大哥的解释。 

我学STM32的过程中觉得看手册一点很重要,就是区分各个官方手册的等级: 
"ARMv7-M Architecture Application Level Reference Manual(November 2010)" (ARM官方发布) 这是理论框架,涵盖范围最广..........(I) 
"Cortex-M3 Technical Reference Manual.r2p1" (ARM官方发布) 这是技术构架,涵盖范围次之.........................................................(II) 
"PM0056 Programming manual Rev4" & "Reference manual_Rev13"(ST官方发布)这是产品实现,范围最小,定位到特定系列产品.....(III) 

以一个例子为例: 
(I)中理论框架支持多大496个外部中断 
(II)中技术构架240个外部中断 
(III)中产品实现68个外部中断(互联型)

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

转自:http://www.openedv.com/posts/list/392.htm


<think>我们参考用户提供的引用和之前回答的结构,针对STM32F103R8T6单片机实现IAP功能提供代码示例。根据引用[2],STM32F103R8T6具有64KB的Flash空间,我们可以将Flash划分为BootLoader区域和APP区域。 划分建议: - BootLoader: 0x08000000 ~ 0x08003FFF (16KB) - APP: 0x08004000 ~ 0x0800FFFF (48KB) 注意:实际划分可根据需求调整,但必须确保BootLoader大小足够且起始地址对齐。 实现步骤: 1. BootLoader程序:负责通过串口接收APP的bin文件,将其写入APP区域,并跳转执行。 2. APP程序:需要设置中断向量偏移,编译设置程序起始地址为0x08004000。 下面分别给出BootLoader和APP的关键代码示例。 ### BootLoader程序关键代码 #### 初始化部分 ```c #include "stm32f10x.h" #include "usart.h" #include "flash.h" #define APP_ADDRESS 0x08004000 // APP起始地址 // 函数声明 void JumpToApp(void); int main(void) { // 初始化系统钟、串口等 SystemInit(); USART1_Init(115200); // 初始化串口1,波特率115200 // 等待一段间,看是否有升级命令 uint8_t timeout = 0; uint8_t cmd = 0; while(timeout < 100) { if(USART_ReceiveData(USART1, &cmd, 1) == 1) { if(cmd == 0xAA) { // 假设0xAA是升级命令 // 进入升级模式 Upgrade_Handle(); // 升级完成后,跳转到APP JumpToApp(); } } Delay(10); // 延10ms timeout++; } // 没有收到升级命令,直接跳转到APP JumpToApp(); while(1); } ``` #### 升级处理函数 ```c void Upgrade_Handle(void) { // 发送ACK,表示准备接收数据 USART_SendData(USART1, 0x79); // 发送ACK // 接收文件长度(假设前4个字节是文件长度) uint32_t file_size = 0; uint8_t *p = (uint8_t*)&file_size; for(int i=0; i<4; i++) { while(USART_ReceiveData(USART1, p+i, 1) != 1); } // 擦除APP区域(从0x08004000开始,擦除48KB) uint32_t start_addr = APP_ADDRESS; uint32_t end_addr = APP_ADDRESS + file_size; // 计算需要擦除的页数(STM32F103R8T6每页1KB) uint32_t start_page = (start_addr - 0x08000000) / FLASH_PAGE_SIZE; uint32_t end_page = (end_addr - 0x08000000 + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE; FLASH_ErasePages(start_page, end_page - start_page); // 开始接收数据并写入Flash uint8_t buffer[128]; // 假设每次接收128字节 uint32_t addr = APP_ADDRESS; uint32_t received = 0; while(received < file_size) { // 接收一包数据 uint16_t len = sizeof(buffer); if (file_size - received < len) { len = file_size - received; } for(int i=0; i<len; i++) { while(USART_ReceiveData(USART1, &buffer[i], 1) != 1); } // 写入Flash FLASH_WriteBuffer(addr, buffer, len); addr += len; received += len; // 发送ACK,表示本包接收成功 USART_SendData(USART1, 0x79); } } ``` #### 跳转到APP函数 ```c void JumpToApp(void) { // 检查栈顶地址是否合法(在RAM范围内) if (((*(__IO uint32_t*)APP_ADDRESS) & 0x2FFE0000) == 0x20000000) { // 设置堆栈指针 __set_MSP(*(__IO uint32_t*)APP_ADDRESS); // 设置PC指针(复位中断向量) uint32_t reset_handler = *(__IO uint32_t*)(APP_ADDRESS + 4); ((void(*)())(reset_handler))(); } } ``` ### APP程序关键配置 #### 修改中断向量偏移 在APP程序的`system_stm32f10x.c`文件中,修改`SystemInit`函数(或单独在main函数开始处设置): ```c void SystemInit(void) { // ... 其他初始化 // 设置中断向量偏移 SCB->VTOR = 0x08000000 | 0x4000; // 偏移量0x4000 } ``` #### 修改链接脚本 在Keil中,修改APP程序的ROM起始地址为0x08004000,大小为0xC000(48KB)。 ### 注意事项 1. **通信协议**:上述示例中通信协议较为简单,实际应用中应加入校验(如CRC)和重传机制。 2. **Flash操作**:在写入Flash前必须解锁并擦除,写入完成后锁定。注意擦除操作的单位是页(STM32F103每页1KB)。 3. **中断处理**:在BootLoader中,如果使用了中断,跳转前需要禁用中断。APP程序开始后重新设置中断向量表。 ### 总结 以上代码为STM32F103R8T6实现IAP功能的关键部分。BootLoader程序通过串口接收APP数据并写入Flash,然后跳转执行。APP程序需要设置中断向量偏移并修改链接地址。实际应用中还需完善通信协议和错误处理机制[^1][^2]。 §§相关问题§§ 1. 如何为STM32F103的IAP设计带CRC校验的通信协议? 2. 在STM32F103中,如何正确擦除和写入Flash? 3. 如何配置Keil MDK使APP程序从指定地址开始? 4. IAP升级失败后,BootLoader如何实现回滚机制? 5. 如何通过USB或CAN接口实现STM32IAP功能?</think>### STM32F103R8T6 IAP实现代码示例 以下针对STM32F103R8T6(64KB Flash)的IAP实现代码,包含BootLoader和APP程序的关键实现。Flash分配参考引用[2]的建议: ```c /* 文件:bootloader.c */ #include "stm32f10x.h" #include "usart.h" #include "flash_if.h" #define APP_ADDR 0x08004000 // APP起始地址(16KB后) typedef void (*pFunction)(void); // 跳转到APP程序 void JumpToApp(void) { pFunction AppEntry; uint32_t StackTop = *(__IO uint32_t*)APP_ADDR; // 获取栈顶地址 if((StackTop & 0x2FFE0000) == 0x20000000) { // 验证栈地址在RAM范围内 __set_MSP(StackTop); // 设置主堆栈指针 AppEntry = (pFunction)(*(__IO uint32_t*)(APP_ADDR + 4)); // 复位向量地址 AppEntry(); // 跳转到APP } } int main(void) { // 初始化系统 SystemInit(); USART1_Init(115200); // 初始化串口 // 检查升级命令(示例协议) if(USART_ReceiveByte() == 0x7F) { // 收到升级指令 FLASH_If_Erase(APP_ADDR, 48*1024); // 擦除APP区域(48KB) Receive_And_Program_App(); // 接收并烧录APP } JumpToApp(); // 跳转到APP while(1); } ``` ```c /* 文件:flash_if.c (Flash操作关键函数) */ #include "stm32f10x_flash.h" // 擦除指定范围的Flash void FLASH_If_Erase(uint32_t start, uint32_t size) { FLASH_Unlock(); // 解锁Flash uint32_t page; for(uint32_t addr = start; addr < start+size; addr += FLASH_PAGE_SIZE) { page = (addr - FLASH_BASE) / FLASH_PAGE_SIZE; FLASH_ErasePage(FLASH_BASE + (page * FLASH_PAGE_SIZE)); } FLASH_Lock(); // 锁定Flash } // 写入Flash数据 void FLASH_If_Write(uint32_t dest, uint8_t *data, uint32_t len) { FLASH_Unlock(); for(uint32_t i=0; i<len; i+=2) { uint16_t data16 = *(uint16_t*)(data + i); FLASH_ProgramHalfWord(dest + i, data16); } FLASH_Lock(); } ``` ```c /* 文件:app.c (用户应用程序) */ #include "stm32f10x.h" // 设置中断向量偏移 void SystemInit(void) { SCB->VTOR = 0x08000000 | 0x4000; // 设置中断向量偏移[^1] // ...其他初始化代码 } int main(void) { // 用户应用程序主循环 while(1) { // 实现设备功能 } } ``` ### 关键配置说明 1. **Flash分区**(64KB空间): - BootLoader:`0x08000000 - 0x08003FFF` (16KB) - APP程序:`0x08004000 - 0x0800FFFF` (48KB) 2. **编译器设置**(Keil MDK): ```plaintext Target -> IROM1 Start: 0x08004000 Size: 0xC000 (48KB) ``` 3. **中断向量表重映射**: ```c // 在APP的system_stm32f10x.c中修改: VECT_TAB_OFFSET = 0x4000; // 偏移16KB ``` 4. **通信协议示例**: ```c /* 简单升级协议实现 */ void Receive_And_Program_App(void) { uint32_t file_size = USART_ReceiveU32(); // 接收文件长度 uint8_t buffer[256]; for(uint32_t addr = APP_ADDR; addr < APP_ADDR+file_size; ) { USART_ReceiveBuffer(buffer, sizeof(buffer)); FLASH_If_Write(addr, buffer, sizeof(buffer)); addr += sizeof(buffer); USART_SendByte(0x79); // 发送ACK } } ``` ### 实现要点 1. **BootLoader设计**: - 初始化后检查升级指令 - 支持Flash擦除/编程操作 - 实现APP跳转前验证栈指针有效性[^1] 2. **APP程序要求**: - 编译设置正确ROM起始地址 - 启动重映射中断向量表 - 避免使用BootLoader占用的Flash区域 3. **Flash操作注意事项**: - 操作前必须解锁Flash (`FLASH_Unlock()`) - 按半字(16位)写入数据 - STM32F103页大小为1KB[^2] 4. **一键下载实现**(参考引用[3]): ```plaintext 上位机控制DTR/RTS信号: DTR低电平 -> 复位MCU RTS高电平 -> BOOT0=1(进入BootLoader) ``` > 完整工程需包含:串口驱动、Flash操作库、超处理机制和CRC校验。实际应用建议添加数据校验和故障恢复机制[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Veiko

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值