STM32F746函数执行速度依赖Flash存储位置的问题排查
我在基于ARM Cortex-M7的STM32F746NGH微控制器上开发时,发现一个异常现象:线程函数的执行时间与其在Flash ROM中的存储位置直接相关。开发环境为STM32CubeIDE(ARM GCC编译器)。
测试过程
步骤1:双线程对比测试
初始化SysTick计数器,以1ms固定周期触发中断,在中断处理函数中实现简易RTOS线程切换,仅包含Thread1和Thread2两个线程,每个线程仅执行变量自增操作。
代码如下:uint32_t ctr1, ctr2; void thread1(void) { while(1) { ctr1++; } } void thread2(void) { while(1) { ctr2++; } }监测发现ctr2自增速度远快于ctr1,此时thread1地址为
0x08000418,thread2地址为0x0800042C。步骤2:插入新线程后的测试
在thread1前新增thread0函数,代码如下:uint32_t ctr0, ctr1, ctr2; void thread0(void) { while(1) { ctr0++; } } void thread1(void) { while(1) { ctr1++; } } void thread2(void) { while(1) { ctr2++; } }此时thread0地址为
0x08000418(原thread1位置),thread1地址为0x0800042C(原thread2位置),thread2地址为0x08000440。监测结果显示ctr1和ctr2自增速度一致,ctr0速度远慢于二者。步骤3:20线程批量测试
扩展至20个线程,每个线程均为变量自增操作,观察到线程自增速率分为两种:speed1(较慢)和speed2(较快),对应关系如下表:线程 地址 速率 Thread0 0x08000418 speed1 Thread1 0x0800042C speed2 Thread2 0x08000440 speed2 Thread3 0x08000454 speed1 Thread4 0x08000468 speed2 Thread5 0x0800047C speed2 Thread6 0x08000490 speed2 Thread7 0x080004A4 speed2 Thread8 0x080004B8 speed1 Thread9 0x080004CC speed2 Thread10 0x080004E0 speed2 Thread11 0x080004F4 speed1 Thread12 0x08000508 speed2 Thread13 0x0800051C speed2 Thread14 0x08000530 speed2 Thread15 0x08000544 speed2 Thread16 0x08000558 speed1 Thread17 0x0800056C speed2 Thread18 0x08000580 speed2 Thread19 0x08000594 speed1 已确认所有线程的汇编代码完全一致:均为10条16位指令,代码大小20字节,线程地址间隔为
0x14(20字节),排除代码本身差异的影响。某一线程的汇编代码示例:task0: 08000418: push {r7} 0800041a: add r7, sp, #0 21 task0_ctr += 1; 0800041c: ldr r3, [pc, #8] ; (0x8000428 <task0+16>) 0800041e: ldr r3, [r3, #0] 08000420: adds r3, #1 08000422: ldr r2, [pc, #4] ; (0x8000428 <task0+16>) 08000424: str r3, [r2, #0] 08000426: b.n 0x800041c <task0+4> 08000428: movs r4, r3 0800042a: movs r0, #0从表格可见速率存在明显规律:1个speed1线程、2个speed2线程、1个speed1线程、4个speed2线程,以此循环。
已排查的方向与初步结论
查阅Cortex-M7参考手册中Flash相关章节:
指令预取
每次Flash读取操作提供256位数据,对应8条32位指令或16条16位指令。对于顺序代码,执行已读取的指令行至少需要8个CPU周期。ITCM总线上的预取功能可在CPU请求当前指令行时,提前读取下一行指令,该功能可通过设置FLASH_ACR寄存器的PRFTEN位启用,在Flash访问需要至少1个等待状态时有效。当代码非顺序(分支)时,指令可能不在当前或预取的指令行中,此时会产生缺失,周期损失至少等于等待状态数。
但经检查,无论函数完全处于256位块内还是跨块,均可能出现speed1或speed2速率,排除此直接关联。
补充信息
1. 线程调度器代码
__attribute__((naked)) void SysTick_Handler(void) { __asm("CPSID I"); // 禁用全局中断,等效于__disable_irq(); /* 保存当前线程上下文:保存R4-R11(xPSR、PC、LR、R12、R3-R0由处理器自动压栈) */ __asm("PUSH {R4-R11}"); /* OS_Tick += 1 */ __asm("LDR R0, =OS_Tick"); // R0 = &OS_Tick __asm("LDR R1, [R0]"); // R1 = OS_Tick __asm("ADD R1, #1"); // R1 += 1 __asm("STR R1, [R0]"); // OS_Tick = R1; /* Systick_Tick += 1 */ __asm("LDR R0, =Systick_Tick"); // R0 = &Systick_Tick __asm("LDR R1, [R0]"); // R1 = Systick_Tick __asm("ADD R1, #1"); // R1 += 1 __asm("STR R1, [R0]"); // Systick_Tick = R1; /* 调度器:切换线程 */ __asm("LDR R0, =os_kernel_threads_list"); // R0 = &os_kernel_threads_list __asm("LDR R1, [R0]"); // R1 = current_thread __asm("STR SP, [R1,#4]"); // stack_ptr = SP __asm("LDR R2, [R1]"); // R2 = next_tcb __asm("STR R2, [R0]"); // current_thread = next_tcb(新线程) __asm("LDR SP, [R2,#4]"); // SP = stack_ptr(新线程) __asm("POP {R4-R11}"); // 恢复新线程上下文 __asm("CPSIE I"); // 启用全局中断,等效于__enable_irq(); /* 中断返回 */ __asm("BX LR"); }
OS_Tick和Systick_Tick为uint32_t类型变量,os_kernel_threads_list为tcb_list类型变量,定义如下:
/* * 线程控制块(TCB)结构 */ typedef struct tcb_ { struct tcb_ *next_tcb; // 链表指针,指向下一个线程 int32_t *stack_ptr; // 线程栈顶指针(下一个弹出项/最后压栈值) int32_t stack[THREAD_STACK_SIZE]; // 线程栈 } tcb_struct; /* * 循环链表结构 */ typedef struct { tcb_struct *current_thread; // 当前运行线程指针 tcb_struct threads[N_MAX_THREADS]; // 线程数组 int n_threads; // 创建的线程数量 } tcb_list;
线程存储于数组中,以循环链表形式连接。
2. 时钟配置
- PLL源:25MHz晶振
- SYSCLK = PLL_CLK = 216MHz
- Flash等待状态:7WS(符合STM32 datasheet推荐值)
请求分析此现象的根本原因并给出排查方向。
内容的提问来源于stack exchange,提问作者TRElec

