引言

我是一个学生,想在不使用示波器的情况下粗略地测量ARM Cortex-M系列芯片的中断延迟.然而,我遇到了一个非常奇怪的问题,让我非常困惑.

首先,让我简要介绍一下我的测量方法.我try 使用MCU的内置定时器粗略地测量中断延迟.我的基本 idea 如下:在进入中断服务 routine (ISR)之前,我会将计时器重置为零.然后,我将生成中断请求并进入ISR.一旦进入ISR,我就会立即读取计时器的值.此时,计时器值可以作为中断延迟的一个非常粗略的估计.

由于某些原因,我需要用汇编语言编写前面提到的ISR函数.在Main函数中,我使用SWIER寄存器生成一个软件中断,它允许我进入我已经写入的ISR.

在这个过程中,我遇到了一个奇怪的问题.当用汇编语言编写ISR时,习惯上在进入函数时将几个寄存器压入堆栈,例如R4到R11,并在退出函数时将它们弹出.因此,in theory, if I don't push the registers onto the stack upon entering the ISR (assuming I don't use those registers within the ISR), the rough measurement of interrupt latency should be slightly smaller compared to when I do push the registers onto the stack upon entering the ISR. However, through my experiments, I found that the results were exactly the opposite. If I push the registers onto the stack upon entering the ISR, the measured interrupt latency is actually slightly smaller. I使用不同的IDE(对应不同的编译器)进行了实验这使我感到十分困惑.

实验装置

item config
CPU intel i5 8400
OS Windows 10
IDE Keil uVision 5 V5.39.0.0 & IAR For ARM version 8.32.1.18631
C/C++ C99&C++11, optimization: O0
STM32CUBEMX version 6.10.0
ARM ARM Cortex M3
MCUs STM32F103ZET6 & STM32F103C8T6
Timer Timer2, Prescaler: 0, ARR: 65535, Counter mode: up, auto-reload preload: enable
EXI EXIT0, PA0, Priority Group: 4 bits, Preemption Priority: 1, Sub Priority: 0

实验结果

在下表中,时钟列表示SYSCLK(HCLK)的频率.R4-R11表示在进入ISR时将寄存器R4至R11压入堆栈时从读取定时器获得的定时器计数值.类似地,R4表示在进入ISR时仅将寄存器R4压入堆栈时获得的定时器计数值."未堆叠"表示在进入ISR时没有寄存器被压入堆栈时获得的计时器计数值.表中的定时器计数值是从多次实验中获得的.

从上面的实验结果可以看出,在测量过程中,如果只将寄存器R4压入堆栈,则获得的定时器计数值比将寄存器R4至R11压入堆栈时要小(这与理论和直觉一致).然而,当我们 Select 不将寄存器压入堆栈时,奇怪的事情发生了:获得的计时器计数值更大,这与我们的直觉相矛盾.

Exp1

[MCU]:STM32F103C8T6

[IDE]:Keil uVision 5 V5.39.0.0

CLOCK R4~R11 R4 unstacked
72HMZ 40~42 34~36 78~80
40MHZ 38~40 34~36 70~72
20MHZ 38 32 62~64

exp2

[MCU]:STM32F103ZET6

[IDE]:Keil uVision 5 V5.39.0.0

CLOCK R4~R11 R4 unstacked
72HMZ 40 34 78
40MHZ 38~40 32 68~70
20MHZ 38 30~32 62~64

Exp3

[MCU]:STM32F103ZET6

[IDE]:ARM版本8.32.1.18631的IAR

CLOCK R4~R11 R4 unstacked
72HMZ 40~42 34~36 78~80
40MHZ 38~40 32 66
20MHZ 38~40 32 66

从上面的实验结果可以看出,在测量过程中,如果只将寄存器R4压入堆栈,则获得的定时器计数值比将寄存器R4至R11压入堆栈时要小(这与理论和直觉一致).However, when we choose not to push the registers onto the stack, something strange happens: the timer count value obtained is larger, which contradicts our intuition.

实验代码

以下是一些核心代码.

//main.c 
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        TIM2->CNT = 0;      //set timer2 count=0
        EXTI->SWIER |= 1; //call EXIT0 ISR
        for(;i!=0;i--){}    //delay
        printf("[%5d]:TIM time t is :%d\n",line,t);
        printf("***********[%5d]:*******\n",line);
        line++;
        i = COUNT_NUM;
        HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
;ISR.s
    AREA ISR,CODE,READONLY
    EXPORT EXTI0_IRQHandler
    ;EXTI0_IRQHandler is a global function name that is an ISR written in the Assembly language.
    ;To enable EXTI0_IRQHandler, the automatically generated EXTI0_IRQHandler function needs to be commented out in the stm32f1xx_it.c file.
    IMPORT t
    ;t is a global variable used to store the value of the timer read in the ISR.
    IMPORT exit_pr_addr
    ;exit_pr_addr is the global variable used to store the address of the EXTI_PR register. 
    ;This address is used in the ISR in order to clear the interrupt request flag
    IMPORT tim_cnt_addr
    ;tim_cnt_addr is a global variable used to store the address of the count register in the timer.
    ;This address is used in the ISR in order to read the count value of the timer

EXTI0_IRQHandler
    ;push {r4-r11}
    ;push {r4}
    ;;;t = TIM2->CNT
    ldr r1,=tim_cnt_addr;read global var addr
    ldr r2,[r1]         ;read tim_cnt_addr value
    ldr r2,[r2]         ;read cnt value
    ldr r0,=t           ;get global var t addr
    str r2,[r0]         ;set t = TIM2->CNT
    ;;;clear interupt request flg
    ldr r1,=exit_pr_addr ;read global var addr
    ldr r1,[r1]     ;read exit_pr_addr value
    mov r3,#(1<<0)  ;
    str r3,[r1];
    ;pop {r4}
    ;pop {r4-r11}
    bx lr
    END

使用Keil uVision5 V5.39.0.0进行编译的一些设置.

pic3

pic4

依附

我已经在STM32F103C8T6上打包了上面提到的项目代码,它在Keil uVision5 V5.39.0.0下编译成功.在IAR平台下,项目代码几乎相同,除了用汇编语言编写的ISR格式有一些不同外,其余代码保持不变.这部分代码在我的附件中的isr_for_iar.s文件中给出.上面提到的代码被作为attachment上传到Google Drive.

有谁能解释我发现的这个奇怪的问题吗?

回答

根据我的实验验证,这个问题的原因与@wek提供的答案完全相同.

我的实验方法如下:我使用一个全局变量cnt,它被初始化为0.每次输入ISR时,我都设置为cnt = cnt + 1.在退出ISR(在主功能中)后,我重置cnt = 0.

//main.c 
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        TIM2->CNT = 0;      //set timer2 count=0
        EXTI->SWIER |= 1; //call EXIT0 ISR
        for(;i!=0;i--){}    //delay
        printf("[%5d]:TIM time t is :%d\n",line,t);
        printf("[%5d]: cnt is :%d\n",line,cnt);
        printf("***********[%5d]:*******\n",line);
        line++;
        cnt=0;            //cnt is global var
        i = COUNT_NUM;
        HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
;ISR.s
    AREA ISR,CODE,READONLY
    EXPORT EXTI0_IRQHandler
    IMPORT t
    IMPORT exit_pr_addr
    IMPORT tim_cnt_addr
    IMPORT cnt
    ;cnt is a global variable used to store the number of times the ISR is entered.

EXTI0_IRQHandler
    ;push {r4-r11}
    ;push {r4}

    ;;;t = TIM2->CNT
    ldr r1,=tim_cnt_addr;read global var addr
    ldr r2,[r1]         ;read tim_cnt_addr value
    ldr r2,[r2]         ;read cnt value
    ldr r0,=t           ;get global var t addr
    str r2,[r0]         ;set t = TIM2->CNT
    
    ;;;set  cnt = cnt + 1
    ldr r0, =cnt;
    ldr r1,[r0]
    add r1,r1,#1;
    str r1,[r0]
    
    ;;;clear interupt request flag
    ldr r1,=exit_pr_addr ;read global var addr
    ldr r1,[r1]     ;read exit_pr_addr value
    mov r3,#(1<<0)  ;
    str r3,[r1];
    
    ;pop {r4}
    ;pop {r4-r11}
    bx lr
    END

根据我的实验,当我 Select 将寄存器压入堆栈时,打印的cnt的值是1.然而,当我 Select 不将寄存器压入堆栈时,打印的cnt的值是2.这意味着如果未将寄存器推送到ISR中的堆栈上,ISR将在退出时重新进入,导致总共有2个条目进入ISR.

推荐答案

在没有推送/弹出的情况下,由于延迟清除导致中断的标志,您可能是running the ISR twice岁.当两个ISR背靠背运行时,Main打印第二次运行中存储的值(因此大致是整个ISR的持续时间,加上第二个ISR的"一半",减go 尾部链接的校正).

您可以通过在清除中断标志之前移动POP,或通过添加计数计数器,或通过在进入ISR时判断中断标志,或通过清除ISR中的TIMx_CNT,或通过将TIMx_CNT设置为ISR中的某个显著值,来确认或拒绝这一点,或者...可能有很多种方法.

JW

PS.示波器,或者至少是一个便宜的逻辑分析仪(LA)是微控制器编程必不可少的工具,我建议买一些;判断中断延迟/持续时间的方法之一是切换引脚和测量-您自己也提到过这一点.

C++相关问答推荐

从C函数调用asm函数时生成错误的BLX指令(STM32H753上的gcc)

C strlen on char array

va_copy的使用是未定义的行为吗?

使用NameSurname扫描到两个单独的字符串

当多个线程在C中写入相同的文件描述符时,如何防止争用情况?

如何使用低级C++写出数值

字符是否必须转换为无符号字符,然后才能与getc家族的返回值进行比较?

_泛型控制表达式涉及数组碰撞警告的L值转换错误?

为静态库做准备中的奇怪行为

在为hashmap创建加载器时,我的存储桶指向它自己

struct -未知大小

对于C中给定数组中的每个查询,如何正确编码以输出给定索引范围(1到N)中所有数字的总和?

理解C版宏(看起来像未声明的变量?)

判断X宏的空性

如何按顺序将所有CSV文件数据读入 struct 数组?

类型定义 struct 与简单的类型定义 struct

运行时错误:在索引数组时加载类型为';char';`的空指针

如何在不更改格式说明符的情况下同时支持双精度和长双精度?

C/C++编译器可以在编译过程中通过按引用传递来优化按值传递吗?

是什么阻止编译器优化手写的 memcmp()?