You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32F746函数执行速度依赖Flash存储位置的问题排查

Flash存储位置影响Cortex-M7线程执行速率的问题分析排查

我在基于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(较快),对应关系如下表:

    线程地址速率
    Thread00x08000418speed1
    Thread10x0800042Cspeed2
    Thread20x08000440speed2
    Thread30x08000454speed1
    Thread40x08000468speed2
    Thread50x0800047Cspeed2
    Thread60x08000490speed2
    Thread70x080004A4speed2
    Thread80x080004B8speed1
    Thread90x080004CCspeed2
    Thread100x080004E0speed2
    Thread110x080004F4speed1
    Thread120x08000508speed2
    Thread130x0800051Cspeed2
    Thread140x08000530speed2
    Thread150x08000544speed2
    Thread160x08000558speed1
    Thread170x0800056Cspeed2
    Thread180x08000580speed2
    Thread190x08000594speed1

    已确认所有线程的汇编代码完全一致:均为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 20:58:09