[RISC-V MCU 应用开发] RISC-V (CH32V103)与Cortex-M3内核(CH32F103)单片机中断延时测评

[复制链接]
 楼主| RISCVLAR 发表于 2020-10-27 09:57 | 显示全部楼层 |阅读模式
【来源:CSDN

最近在学习RISC-V架构的MCU,特地学习了一下“中断处理机制”,对比之前使用过的Cortex-M3内核单片机,研究它们在中断执行和处理上的差异和效率。

样品选择

CH32V103C8T6 和 CH32F103C8T6

  • CH32V103C8T6,沁恒微电子的RSIC-V架构32位通用型MCU,支持IMAC指令集,内置PFIC中断控制器。PFIC是该公司自研设计的结构,所以用法上有独自的特色,部分功能还需搭配软件平台(推荐MRS IDE)实现。
  • CH32F103C8T6,Cortex-M3内核32位通用型MCU,内置NVIC中断控制器。资源及软件工具兼容市场上主流的设备(如Keil、Link等)。测试方法

    中断延迟(响应)时间,即从中断触发条件产生到执行中断服务(用户代码)的时间。
    方法:使用芯片自带的定时器资源,通过定时器具有的自动重加载功能,无需软件代码参与。设置一个1000周期并且计数递减的定时器,当定时器当前计数值由1变为0时,会触发硬件中断同时定时器本身也会重加载计数器值为1000继续递减工作,MCU内核系统在一系列操作后最终执行定时器的中断服务程序。在此服务程序开头进行当前定时器计数值的保存,然后关闭定时器,输出保持的数值,清除中断标志。通过换算与1000的差值,得到的就是大概的中断响应时间(其实这个时间应该再减去对计数值保存的指令代码,后面会提到)。
    为了单纯测试内核设计,排除其他因素影响,我们采用较低的频率,以时钟周期为单位进行测试计算。因为在此测试过程中,会涉及MCU内部的内核中断处理逻辑、flash读取(代码指令)、数据操作(SRAM)等几个大方面,其中flash有等待周期,可能不同的硬件平台结果不一样,所以使用较低的主频,可以降低非内核处理的其他因素影响结果

    测试硬件平台CH32F103开发板,下载最新EVT包,进行代码修改。使用Keil编译器

    C代码,如下:

    1. #include "debug.h"

    2. /*******************************************************************************
    3. * Function Name  : TIM1Init
    4. * Description    :
    5. * Input          : None
    6. * Return         : None
    7. *******************************************************************************/
    8. void TIM1Init(void)
    9. {
    10.     TIM_TimeBaseInitTypeDef  TimeBase_InitStructure;

    11.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_TIM1, ENABLE );

    12.     TimeBase_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    13.     TimeBase_InitStructure.TIM_CounterMode = TIM_CounterMode_Down;
    14.     TimeBase_InitStructure.TIM_Period = 1000;
    15.     TimeBase_InitStructure.TIM_Prescaler = 0;
    16.     TIM1->CNT = 1000;
    17.     TIM_TimeBaseInit( TIM1, &TimeBase_InitStructure );
    18.     TIM_ClearITPendingBit( TIM1, 0X0FF);

    19.     TIM_ITConfig( TIM1, TIM_IT_Update, ENABLE );
    20.         NVIC_EnableIRQ( TIM1_UP_IRQn );
    21.     TIM_Cmd( TIM1, ENABLE );
    22. }
    23. /*******************************************************************************
    24. * Function Name  : main
    25. * Description    : Main program.
    26. * Input          : None
    27. * Return         : None
    28. *******************************************************************************/
    29. unsigned short cnt;

    30. int main(void)
    31. {
    32.     RCC_ClocksTypeDef  Clocks_InitStructure;
    33.    
    34.     Delay_Init();
    35.     USART_Printf_Init(115200);
    36.     RCC_GetClocksFreq( &Clocks_InitStructure );
    37.     printf("HCLK:%d\r\n",Clocks_InitStructure.SYSCLK_Frequency);
    38.     printf("APB2Clk:%d\r\n",Clocks_InitStructure.PCLK2_Frequency);
    39.     printf("APB1Clk:%d\r\n",Clocks_InitStructure.PCLK1_Frequency);
    40.     Delay_Ms( 200 );
    41.    
    42.     TIM1Init();
    43.       
    44.     while(1)
    45.     {        
    46.         ;
    47.     }
    48. }
    49. void TIM1_UP_IRQHandler(void)
    50. {
    51.     cnt = TIM1->CNT;
    52.     TIM_Cmd( TIM1, DISABLE );
    53.     printf("#%d\n",cnt);   
    54.    
    55.     if(TIM_GetFlagStatus( TIM1, TIM_FLAG_Update )!=RESET)
    56.     {
    57.         TIM_ClearITPendingBit( TIM1, TIM_IT_Update );     /* Clear Flag */
    58.     }
    59. }
    中断部分进行反汇编,如下:
    1.                  TIM1_UP_IRQHandler PROC
    2. ;;;63     void TIM1_UP_IRQHandler(void)
    3. 00008e  b570              PUSH     {r4-r6,lr}
    4. ;;;64     {
    5. ;;;65         cnt = TIM1->CNT;
    6. 000090  480d              LDR      r0,|L1.200|
    7. 000092  8800              LDRH     r0,[r0,#0]
    8. 000094  4c19              LDR      r4,|L1.252|
    9. ;;;66         TIM_Cmd( TIM1, DISABLE );
    10. 000096  4d0c              LDR      r5,|L1.200|
    11. 000098  2100              MOVS     r1,#0
    12. 00009a  8020              STRH     r0,[r4,#0]            ;65
    13. 00009c  3d24              SUBS     r5,r5,#0x24
    14. 00009e  4628              MOV      r0,r5
    15. 0000a0  f7fffffe          BL       TIM_Cmd
    16. ;;;67         printf("#%d\n",cnt);   
    17. 0000a4  8821              LDRH     r1,[r4,#0]  ; cnt
    18. 0000a6  a016              ADR      r0,|L1.256|
    19. 0000a8  f7fffffe          BL       __2printf
    20. ;;;68         
    21. ;;;69         if(TIM_GetFlagStatus( TIM1, TIM_FLAG_Update )!=RESET)
    22. 0000ac  2101              MOVS     r1,#1
    23. 0000ae  4628              MOV      r0,r5
    24. 0000b0  f7fffffe          BL       TIM_GetFlagStatus
    25. 0000b4  2800              CMP      r0,#0
    26. 0000b6  d005              BEQ      |L1.196|
    27. ;;;70         {
    28. ;;;71             TIM_ClearITPendingBit( TIM1, TIM_IT_Update );     /* Clear Flag */
    29. 0000b8  4628              MOV      r0,r5
    30. 0000ba  e8bd4070          POP      {r4-r6,lr}
    31. 0000be  2101              MOVS     r1,#1
    32. 0000c0  f7ffbffe          B.W      TIM_ClearITPendingBit
    33.                   |L1.196|
    34. ;;;72         }
    35. ;;;73     }
    36. 0000c4  bd70              POP      {r4-r6,pc}
    37. ;;;74     
    38.                           ENDP

代码中大部分使用的驱动库编写方式,但在定时中断服务函数中获取当前定时器值采用寄存器访问方式,为了减少代码指令,快速访问到。还需注意,使用“TIM_TimeBaseInit()”函数体,完成后会立刻产生中断标志,所以在开启中断使能前要清除中断标志,否则测试的值不对。

运行结果

20201026141013548.png



从串口打印结果上可以看到,输出当前计数值为980,那么可以预估在中断服务中获取到计数器的差值时间为20个系统周期。看下汇编代码(下图),已提取了有效信息并做解释。



2020102614125290.png

从图上的解释可以看到,在‘5’时,将“TIM1->CNT”寄存器的值保存到全局变量的“cnt”中,但是在步骤‘3’时,已经拿到了“TIM1->CNT”寄存器的值。所以最终差值结果的20个周期时间是中断延迟时间+汇编“1-3”指令执行时间。
我没有在相关手册内部找到关于M3指令执行时间的具体说明,按一般设计,这种汇编指令执行需要2-3个周期,所以我推断上述中断服务的汇编指令大约占用了8个周期,那么实际的中断延迟大约占用了12个周期。

硬件平台CH32V103开发板,下载最新EVT包,进行代码修改。

此颗MCU是RISC-V架构,所以选择了MounRiver Studio IDE环境进行编译,源代码部分和CH32F103一样可以保持不变。沁恒为代码兼容做了处理,大部分都可以使用F103上的函数名称和写法。

因为更换了硬件和软件平台,中断的写法有稍微区别,书写一个中断服务函数时,需要为其声明中断属性,即在函数名前添加__attribute__((interrupt()))(原生工具链),这样IDE在进行编译时,会将此函数体识别为中断服务函数,主动添加“压栈出栈”处理及中断返回指令。

C代码,如下:

  1. #include "debug.h"
  2. /*******************************************************************************
  3. * Function Name  : TIM1Init
  4. * Description    :
  5. * Input          : None
  6. * Return         : None
  7. *******************************************************************************/
  8. void TIM1Init(void)
  9. {
  10.     TIM_TimeBaseInitTypeDef  TimeBase_InitStructure;

  11.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_TIM1, ENABLE );

  12.     TimeBase_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  13.     TimeBase_InitStructure.TIM_CounterMode = TIM_CounterMode_Down;
  14.     TimeBase_InitStructure.TIM_Period = 1000;
  15.     TimeBase_InitStructure.TIM_Prescaler = 0;
  16.     TIM1->CNT = 1000;
  17.     TIM_TimeBaseInit( TIM1, &TimeBase_InitStructure );
  18.     TIM_ClearITPendingBit( TIM1, 0X0FF);

  19.     TIM_ITConfig( TIM1, TIM_IT_Update, ENABLE );
  20.         NVIC_EnableIRQ( TIM1_UP_IRQn );
  21.     TIM_Cmd( TIM1, ENABLE );
  22. }
  23. /*******************************************************************************
  24. * Function Name  : main
  25. * Description    : Main program.
  26. * Input          : None
  27. * Return         : None
  28. *******************************************************************************/
  29. unsigned short cnt;

  30. int main(void)
  31. {
  32.     RCC_ClocksTypeDef  Clocks_InitStructure;

  33.     Delay_Init();
  34.     USART_Printf_Init(115200);
  35.     RCC_GetClocksFreq( &Clocks_InitStructure );
  36.     printf("HCLK:%d\r\n",Clocks_InitStructure.SYSCLK_Frequency);
  37.     printf("APB2Clk:%d\r\n",Clocks_InitStructure.PCLK2_Frequency);
  38.     printf("APB1Clk:%d\r\n",Clocks_InitStructure.PCLK1_Frequency);
  39.     Delay_Ms( 200 );

  40.     TIM1Init();
  41. //    NVIC_SetFastIRQ( TIM1_UP_IRQHandler, TIM1_UP_IRQn, 0);
  42.     while(1)
  43.     {
  44.         ;
  45.     }
  46. }
  47. __attribute__((interrupt()))
  48. void TIM1_UP_IRQHandler(void)
  49. {
  50.     cnt = TIM1->CNT;
  51.     TIM_Cmd( TIM1, DISABLE );
  52.     printf("#%d\n",cnt);

  53.     if(TIM_GetFlagStatus( TIM1, TIM_FLAG_Update )!=RESET)
  54.     {
  55.         TIM_ClearITPendingBit( TIM1, TIM_IT_Update );     /* Clear Flag */
  56.     }
  57. }

中断部分进行反汇编,如下:

  1. 00000286 <TIM1_UP_IRQHandler>:
  2.      286:        715d                        addi        sp,sp,-80
  3.      288:        de22                        sw        s0,60(sp)
  4.      28a:        40013437                  lui        s0,0x40013
  5.      28e:        d03e                        sw        a5,32(sp)
  6.      290:        c2445783                  lhu        a5,-988(s0) # 40012c24 <_eusrstack+0x2000dc24>
  7.      294:        c0040413                  addi        s0,s0,-1024
  8.      298:        dc26                        sw        s1,56(sp)
  9.      29a:        da2a                        sw        a0,52(sp)
  10.      29c:        d82e                        sw        a1,48(sp)
  11.      29e:        4581                        li        a1,0
  12.      2a0:        8522                        mv        a0,s0
  13.      2a2:        c686                        sw        ra,76(sp)
  14.      2a4:        c496                        sw        t0,72(sp)
  15.      2a6:        c29a                        sw        t1,68(sp)
  16.      2a8:        c09e                        sw        t2,64(sp)
  17.      2aa:        d632                        sw        a2,44(sp)
  18.      2ac:        d436                        sw        a3,40(sp)
  19.      2ae:        d23a                        sw        a4,36(sp)
  20.      2b0:        ce42                        sw        a6,28(sp)
  21.      2b2:        cc46                        sw        a7,24(sp)
  22.      2b4:        ca72                        sw        t3,20(sp)
  23.      2b6:        c876                        sw        t4,16(sp)
  24.      2b8:        c67a                        sw        t5,12(sp)
  25.      2ba:        c47e                        sw        t6,8(sp)
  26.      2bc:        82f19023                  sh        a5,-2016(gp) # 20000090 <cnt>
  27.      2c0:        2e79                        jal        65e <TIM_Cmd>
  28.      2c2:        8201d583                  lhu        a1,-2016(gp) # 20000090 <cnt>
  29.      2c6:        00002537                  lui        a0,0x2
  30.      2ca:        a0850513                  addi        a0,a0,-1528 # 1a08 <_sbrk+0x20>
  31.      2ce:        23cd                        jal        8b0 <iprintf>
  32.      2d0:        4585                        li        a1,1
  33.      2d2:        8522                        mv        a0,s0
  34.      2d4:        2e75                        jal        690 <TIM_GetFlagStatus>
  35.      2d6:        c501                        beqz        a0,2de <TIM1_UP_IRQHandler+0x58>
  36.      2d8:        4585                        li        a1,1
  37.      2da:        8522                        mv        a0,s0
  38.      2dc:        26c1                        jal        69c <TIM_ClearITPendingBit>
  39.      2de:        5472                        lw        s0,60(sp)
  40.      2e0:        40b6                        lw        ra,76(sp)
  41.      2e2:        42a6                        lw        t0,72(sp)
  42.      2e4:        4316                        lw        t1,68(sp)
  43.      2e6:        4386                        lw        t2,64(sp)
  44.      2e8:        54e2                        lw        s1,56(sp)
  45.      2ea:        5552                        lw        a0,52(sp)
  46.      2ec:        55c2                        lw        a1,48(sp)
  47.      2ee:        5632                        lw        a2,44(sp)
  48.      2f0:        56a2                        lw        a3,40(sp)
  49.      2f2:        5712                        lw        a4,36(sp)
  50.      2f4:        5782                        lw        a5,32(sp)
  51.      2f6:        4872                        lw        a6,28(sp)
  52.      2f8:        48e2                        lw        a7,24(sp)
  53.      2fa:        4e52                        lw        t3,20(sp)
  54.      2fc:        4ec2                        lw        t4,16(sp)
  55.      2fe:        4f32                        lw        t5,12(sp)
  56.      300:        4fa2                        lw        t6,8(sp)
  57.      302:        6161                        addi        sp,sp,80
  58.      304:        30200073                  mret

根据沁恒微电子对其芯片的描述,提供了“中断硬件级的压栈和出栈处理”,所以上段描述的编译软件自动添加“压栈出栈”指令就显得多余了。所以该公司修改了原生工具链(GCC提供的RISC-V工具链),通过在中断服务函数前添加__attribute__((interrupt("WCH-Interrupt-fast"))),取消软件添加压栈出栈指令。

  1. //__attribute__((interrupt()))
  2. __attribute__((interrupt("WCH-Interrupt-fast")))
  3. void TIM1_UP_IRQHandler(void)
  4. {
  5.     cnt = TIM1->CNT;
  6.     TIM_Cmd( TIM1, DISABLE );
  7.     printf("#%d\n",cnt);

  8.     if(TIM_GetFlagStatus( TIM1, TIM_FLAG_Update )!=RESET)
  9.     {
  10.         TIM_ClearITPendingBit( TIM1, TIM_IT_Update );     /* Clear Flag */
  11.     }
  12. }
  1. 00000286 <TIM1_UP_IRQHandler>:
  2.      286:        1141                        addi        sp,sp,-16
  3.      288:        c622                        sw        s0,12(sp)
  4.      28a:        40013437                  lui        s0,0x40013
  5.      28e:        c2445783                  lhu        a5,-988(s0) # 40012c24 <_eusrstack+0x2000dc24>
  6.      292:        c0040413                  addi        s0,s0,-1024
  7.      296:        c426                        sw        s1,8(sp)
  8.      298:        4581                        li        a1,0
  9.      29a:        8522                        mv        a0,s0
  10.      29c:        82f19023                  sh        a5,-2016(gp) # 20000090 <cnt>
  11.      2a0:        2ebd                        jal        61e <TIM_Cmd>
  12.      2a2:        8201d583                  lhu        a1,-2016(gp) # 20000090 <cnt>
  13.      2a6:        00002537                  lui        a0,0x2
  14.      2aa:        9c850513                  addi        a0,a0,-1592 # 19c8 <_sbrk+0x20>
  15.      2ae:        23c9                        jal        870 <iprintf>
  16.      2b0:        4585                        li        a1,1
  17.      2b2:        8522                        mv        a0,s0
  18.      2b4:        2e71                        jal        650 <TIM_GetFlagStatus>
  19.      2b6:        c501                        beqz        a0,2be <TIM1_UP_IRQHandler+0x38>
  20.      2b8:        4585                        li        a1,1
  21.      2ba:        8522                        mv        a0,s0
  22.      2bc:        2645                        jal        65c <TIM_ClearITPendingBit>
  23.      2be:        4432                        lw        s0,12(sp)
  24.      2c0:        44a2                        lw        s1,8(sp)
  25.      2c2:        0141                        addi        sp,sp,16
  26.      2c4:        30200073                  mret

运行结果,以__attribute__((interrupt("WCH-Interrupt-fast")))结果如下:

20201026142446881.png

从串口打印结果上可以看到,输出当前计数值为986,预估中断服务中获取到计数器的差值时间为14个系统周期。看下汇编代码(下图),已提取了有效信息并做解释。

20201026142602142.png

从上图的反汇编过程,我们可以看到屏蔽了软件压栈后RISC-V结构的中断处理过程。首次通过2条指令(1-2)处理sp堆栈保存。当前函数使用的Callee属性寄存器越多,调用“sw”保存的越多。然后获取外设寄存器基地址到内部s0寄存器中(3),然后通过“lhu”Load指令将定时器当前计数值寄存器的值保存到a5寄存器中,直到图上5处才写入到全局变量cnt中。所以最终结果的14个周期差值时间应该是中断延迟时间+汇编“1-4”指令执行时间。按照沁恒给出的设计指标,2和4执行各占用2周期,其他占用1个周期,那么汇编执行占用约6个周期,中断延迟时间约为8个周期。

CH32V103还提供了4路快速中断通道,即中断触发不从向量表取指令,而是直接到中断服务入口,少一次指令跳转。软件代码上在初始化配置上,增加“NVIC_SetFastIRQ(TIM1_UP_IRQHandler,TIM1_UP_IRQn,0)”,将定时器1的中断服务入口配置到快速通道0上。(注意,这种方法没有限制中断服务的位置,可以在SRAM区也可以在Code区,如果为了提供更快的指令执行速度,可以将中断服务放入SRAM区,再配置给快速通道,而不用修改中断向量表基地址),如下运行结果:

20201026142921993.png

从串口打印结果上可以看到,输出当前计数值为988,快速通道方法比前一种情况速度又快了2个周期,仅需12个系统周期。


总结

通过Cortex-M3指南描述,当CM3开始响应一个中断时,会同步进行3项工作。1.入栈,将8个内部寄存器通过硬件自动保存到SRAM中;2.取向量,从向量表中找到对应的服务程序入口地址;3.选择堆栈指针,更新堆栈指针sp、LR寄存器、PC计数器。其中应该属于第一项工作花费时间最长,需要多个周期才能完成数据保存,但是由于采用不同的总线,所以几项工作可以同步执行。所以文档中描述“中断延迟”为:若存储器系统够快,且总线系统允许入栈与取指同时进行,同时该中断可以立即响应,则中断延迟是雷打不动的12周期(满足硬实时所要求的确定性)
而沁恒的RISC-V架构中,一般的中断方式,我们实验测得“中断延迟”约为8个周期,据其设计描述,入栈采用的硬件存储方式,仅需要1个周期就可以完成,剩下的时间包括内核中断挂起、取向量表指令、跳转等时间需要5-6个周期。
2种内核架构使用的是不同的中断处理结构,在时间上沁恒RISC-V中断延迟处理更优,没有入栈存储器的动作也意味着没有存储器出栈的动作,可以完成中断服务内容后更快的返回。以上是单独针对“内核中断延迟”性能方面的比较,但是实际应用中,需要从存储器中取指,如果存储器太慢而引入等待周期或者还有其他因素,则会引入额外的延时,导致整体效果变慢。此外,测试单位是以系统时钟计算,如果单位时间足够的快,貌似几个周期的差值时间似乎没有那么明显,但是高的系统频率会带来存储器的等待延时周期加大或者其他因素,反而降低代码效率,所以不能以单个方面的好评估,具体还是要看应用场景和需求选择合适的MCU产品。





pattywu 发表于 2022-2-13 18:06 | 显示全部楼层
赞一个。
这种研究精神,值得赞一个。
Dick Hou 发表于 2022-2-15 17:07 | 显示全部楼层
所以,如果不打开硬件中断,实际上risc-v的中断延迟比arm大得多。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

133

主题

296

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部

133

主题

296

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部