最近在学习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代码,如下:

#include "debug.h"/*******************************************************************************
* Function Name  : TIM1Init
* Description    :
* Input          : None
* Return         : None
*******************************************************************************/
void TIM1Init(void)
{TIM_TimeBaseInitTypeDef  TimeBase_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_TIM1, ENABLE );TimeBase_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TimeBase_InitStructure.TIM_CounterMode = TIM_CounterMode_Down;TimeBase_InitStructure.TIM_Period = 1000;TimeBase_InitStructure.TIM_Prescaler = 0;TIM1->CNT = 1000;TIM_TimeBaseInit( TIM1, &TimeBase_InitStructure );TIM_ClearITPendingBit( TIM1, 0X0FF);TIM_ITConfig( TIM1, TIM_IT_Update, ENABLE );NVIC_EnableIRQ( TIM1_UP_IRQn );TIM_Cmd( TIM1, ENABLE );
}
/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Return         : None
*******************************************************************************/
unsigned short cnt;int main(void)
{RCC_ClocksTypeDef  Clocks_InitStructure;Delay_Init();USART_Printf_Init(115200);RCC_GetClocksFreq( &Clocks_InitStructure );printf("HCLK:%d\r\n",Clocks_InitStructure.SYSCLK_Frequency);printf("APB2Clk:%d\r\n",Clocks_InitStructure.PCLK2_Frequency);printf("APB1Clk:%d\r\n",Clocks_InitStructure.PCLK1_Frequency);Delay_Ms( 200 );TIM1Init();while(1){   ;}
}
void TIM1_UP_IRQHandler(void)
{cnt = TIM1->CNT;TIM_Cmd( TIM1, DISABLE );printf("#%d\n",cnt);    if(TIM_GetFlagStatus( TIM1, TIM_FLAG_Update )!=RESET){TIM_ClearITPendingBit( TIM1, TIM_IT_Update );     /* Clear Flag */}
}

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

                 TIM1_UP_IRQHandler PROC
;;;63     void TIM1_UP_IRQHandler(void)
00008e  b570              PUSH     {r4-r6,lr}
;;;64     {;;;65         cnt = TIM1->CNT;
000090  480d              LDR      r0,|L1.200|
000092  8800              LDRH     r0,[r0,#0]
000094  4c19              LDR      r4,|L1.252|
;;;66         TIM_Cmd( TIM1, DISABLE );
000096  4d0c              LDR      r5,|L1.200|
000098  2100              MOVS     r1,#0
00009a  8020              STRH     r0,[r4,#0]            ;65
00009c  3d24              SUBS     r5,r5,#0x24
00009e  4628              MOV      r0,r5
0000a0  f7fffffe          BL       TIM_Cmd
;;;67         printf("#%d\n",cnt);
0000a4  8821              LDRH     r1,[r4,#0]  ; cnt
0000a6  a016              ADR      r0,|L1.256|
0000a8  f7fffffe          BL       __2printf
;;;68
;;;69         if(TIM_GetFlagStatus( TIM1, TIM_FLAG_Update )!=RESET)
0000ac  2101              MOVS     r1,#1
0000ae  4628              MOV      r0,r5
0000b0  f7fffffe          BL       TIM_GetFlagStatus
0000b4  2800              CMP      r0,#0
0000b6  d005              BEQ      |L1.196|
;;;70         {;;;71             TIM_ClearITPendingBit( TIM1, TIM_IT_Update );     /* Clear Flag */
0000b8  4628              MOV      r0,r5
0000ba  e8bd4070          POP      {r4-r6,lr}
0000be  2101              MOVS     r1,#1
0000c0  f7ffbffe          B.W      TIM_ClearITPendingBit|L1.196|
;;;72         }
;;;73     }
0000c4  bd70              POP      {r4-r6,pc}
;;;74     ENDP

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

运行结果

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


从图上的解释可以看到,在‘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代码,如下:

#include "debug.h"
/*******************************************************************************
* Function Name  : TIM1Init
* Description    :
* Input          : None
* Return         : None
*******************************************************************************/
void TIM1Init(void)
{TIM_TimeBaseInitTypeDef  TimeBase_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_TIM1, ENABLE );TimeBase_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TimeBase_InitStructure.TIM_CounterMode = TIM_CounterMode_Down;TimeBase_InitStructure.TIM_Period = 1000;TimeBase_InitStructure.TIM_Prescaler = 0;TIM1->CNT = 1000;TIM_TimeBaseInit( TIM1, &TimeBase_InitStructure );TIM_ClearITPendingBit( TIM1, 0X0FF);TIM_ITConfig( TIM1, TIM_IT_Update, ENABLE );NVIC_EnableIRQ( TIM1_UP_IRQn );TIM_Cmd( TIM1, ENABLE );
}
/*******************************************************************************
* Function Name  : main
* Description    : Main program.
* Input          : None
* Return         : None
*******************************************************************************/
unsigned short cnt;int main(void)
{RCC_ClocksTypeDef  Clocks_InitStructure;Delay_Init();USART_Printf_Init(115200);RCC_GetClocksFreq( &Clocks_InitStructure );printf("HCLK:%d\r\n",Clocks_InitStructure.SYSCLK_Frequency);printf("APB2Clk:%d\r\n",Clocks_InitStructure.PCLK2_Frequency);printf("APB1Clk:%d\r\n",Clocks_InitStructure.PCLK1_Frequency);Delay_Ms( 200 );TIM1Init();
//    NVIC_SetFastIRQ( TIM1_UP_IRQHandler, TIM1_UP_IRQn, 0);while(1){;}
}
__attribute__((interrupt()))
void TIM1_UP_IRQHandler(void)
{cnt = TIM1->CNT;TIM_Cmd( TIM1, DISABLE );printf("#%d\n",cnt);if(TIM_GetFlagStatus( TIM1, TIM_FLAG_Update )!=RESET){TIM_ClearITPendingBit( TIM1, TIM_IT_Update );     /* Clear Flag */}
}

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

00000286 <TIM1_UP_IRQHandler>:286: 715d                    addi    sp,sp,-80288:   de22                    sw  s0,60(sp)28a:   40013437            lui s0,0x4001328e:  d03e                    sw  a5,32(sp)290:   c2445783            lhu a5,-988(s0) # 40012c24 <_eusrstack+0x2000dc24>294:   c0040413            addi    s0,s0,-1024298: dc26                    sw  s1,56(sp)29a:   da2a                    sw  a0,52(sp)29c:   d82e                    sw  a1,48(sp)29e:   4581                    li  a1,02a0:    8522                    mv  a0,s02a2:   c686                    sw  ra,76(sp)2a4:   c496                    sw  t0,72(sp)2a6:   c29a                    sw  t1,68(sp)2a8:   c09e                    sw  t2,64(sp)2aa:   d632                    sw  a2,44(sp)2ac:   d436                    sw  a3,40(sp)2ae:   d23a                    sw  a4,36(sp)2b0:   ce42                    sw  a6,28(sp)2b2:   cc46                    sw  a7,24(sp)2b4:   ca72                    sw  t3,20(sp)2b6:   c876                    sw  t4,16(sp)2b8:   c67a                    sw  t5,12(sp)2ba:   c47e                    sw  t6,8(sp)2bc:    82f19023            sh  a5,-2016(gp) # 20000090 <cnt>2c0: 2e79                    jal 65e <TIM_Cmd>2c2: 8201d583            lhu a1,-2016(gp) # 20000090 <cnt>2c6: 00002537            lui a0,0x22ca:  a0850513            addi    a0,a0,-1528 # 1a08 <_sbrk+0x20>2ce:  23cd                    jal 8b0 <iprintf>2d0: 4585                    li  a1,12d2:    8522                    mv  a0,s02d4:   2e75                    jal 690 <TIM_GetFlagStatus>2d6:   c501                    beqz    a0,2de <TIM1_UP_IRQHandler+0x58>2d8: 4585                    li  a1,12da:    8522                    mv  a0,s02dc:   26c1                    jal 69c <TIM_ClearITPendingBit>2de:   5472                    lw  s0,60(sp)2e0:   40b6                    lw  ra,76(sp)2e2:   42a6                    lw  t0,72(sp)2e4:   4316                    lw  t1,68(sp)2e6:   4386                    lw  t2,64(sp)2e8:   54e2                    lw  s1,56(sp)2ea:   5552                    lw  a0,52(sp)2ec:   55c2                    lw  a1,48(sp)2ee:   5632                    lw  a2,44(sp)2f0:   56a2                    lw  a3,40(sp)2f2:   5712                    lw  a4,36(sp)2f4:   5782                    lw  a5,32(sp)2f6:   4872                    lw  a6,28(sp)2f8:   48e2                    lw  a7,24(sp)2fa:   4e52                    lw  t3,20(sp)2fc:   4ec2                    lw  t4,16(sp)2fe:   4f32                    lw  t5,12(sp)300:   4fa2                    lw  t6,8(sp)302:    6161                    addi    sp,sp,80304:    30200073            mret

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

//__attribute__((interrupt()))
__attribute__((interrupt("WCH-Interrupt-fast")))
void TIM1_UP_IRQHandler(void)
{cnt = TIM1->CNT;TIM_Cmd( TIM1, DISABLE );printf("#%d\n",cnt);if(TIM_GetFlagStatus( TIM1, TIM_FLAG_Update )!=RESET){TIM_ClearITPendingBit( TIM1, TIM_IT_Update );     /* Clear Flag */}
}
00000286 <TIM1_UP_IRQHandler>:286: 1141                    addi    sp,sp,-16288:   c622                    sw  s0,12(sp)28a:   40013437            lui s0,0x4001328e:  c2445783            lhu a5,-988(s0) # 40012c24 <_eusrstack+0x2000dc24>292:   c0040413            addi    s0,s0,-1024296: c426                    sw  s1,8(sp)298:    4581                    li  a1,029a:    8522                    mv  a0,s029c:   82f19023            sh  a5,-2016(gp) # 20000090 <cnt>2a0: 2ebd                    jal 61e <TIM_Cmd>2a2: 8201d583            lhu a1,-2016(gp) # 20000090 <cnt>2a6: 00002537            lui a0,0x22aa:  9c850513            addi    a0,a0,-1592 # 19c8 <_sbrk+0x20>2ae:  23c9                    jal 870 <iprintf>2b0: 4585                    li  a1,12b2:    8522                    mv  a0,s02b4:   2e71                    jal 650 <TIM_GetFlagStatus>2b6:   c501                    beqz    a0,2be <TIM1_UP_IRQHandler+0x38>2b8: 4585                    li  a1,12ba:    8522                    mv  a0,s02bc:   2645                    jal 65c <TIM_ClearITPendingBit>2be:   4432                    lw  s0,12(sp)2c0:   44a2                    lw  s1,8(sp)2c2:    0141                    addi    sp,sp,162c4:    30200073            mret

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

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

从上图的反汇编过程,我们可以看到屏蔽了软件压栈后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区,再配置给快速通道,而不用修改中断向量表基地址),如下运行结果:


从串口打印结果上可以看到,输出当前计数值为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产品。

RISC-V (CH32V103)与Cortex-M3内核(CH32F103)单片机中断延时测评相关推荐

  1. Cortex M3内核架构

    CortexM3内核架构 宗旨:技术的学习是有限的,分享的精神是无限的. 1.ARMCortex-M3处理器 Cortex-M3处理器内核是单片机的中央处理单元( CPU). 完整的基于CM3的MCU ...

  2. Cortex‐M3的Faults异常究竟是什么?

    关注+星标公众号,不错过精彩内容 作者 | strongerHuang 微信公众号 | strongerHuang 有许多朋友在学习,或者开发STM32时都遇到过HardFault_Handler的情 ...

  3. cortex M内核优先级设置

    Cortex M内核中 每个中断都有一个8位的优先级设置寄存器 这个8位的寄存器可以分为抢占优先级和子优先级两个部分(通过设置优先级组设置) 抢占优先级和子优先级有什么用? 举例说明: 有两个中断A. ...

  4. Cortex、ARMv8、arm架构、ARM指令集、soc?Cortex A8、A9都是ARMv7a 架构;Cortex M3、M4是ARMv7m架构;前者是处理器(内核)后者是指令集的架构(架构)

    架构组成元素的指令集状态或者语法thumb指令集与arm指令集的区别例如thumb指令集是什么_thumb指令集与arm指令集的区别以及thumb-2的关系在下一文中介绍,本文暂时不讨论 有粉丝问我到 ...

  5. ARM 架构、ARM7、ARM9、STM32、Cortex M3 M4 、51、AVR 有啥区别

    ARM架构.ARM7.ARM9.STM32.Cortex M3 M4.51.AVR之间有什么区别和联系? ARM架构:由英国ARM公司设计的一系列32位的RISC微处理器架构总称,现有ARMv1~AR ...

  6. ARM 架构、ARM7、ARM9、STM32、Cortex M3 M4 、51、AVR 之间有什么区别和联系?

    本文转自嵌入式资讯精选公众号,特别鸣谢, 编者按:初学习ARM单片机的同学们可能会对ARM的架构定义并不是很明确,形形色色的名词背后到底代表什么含义呢?请听听这位嵌入式工程师的经验总结. ARM架构: ...

  7. ARM 架构 ARM7 ARM9 STM32 Cortex M3 M4 51 AVR 有啥区别

    ARM架构.ARM7.ARM9.STM32.Cortex M3 M4.51.AVR之间有什么区别和联系? ARM架构:由英国ARM公司设计的一系列32位的RISC微处理器架构总称,现有ARMv1~AR ...

  8. Cortex M3 Bit-banding简介

    http://blog.csdn.net/shevsten/article/details/7676397 Cortex M3 Bit-banding简介 分类: ARM MCU2012-06-19 ...

  9. ARM® Cortex®-M内核单片机STM32家族介绍,覆盖STM32F、STM32H、STM32L全系列

     STM32是ARM®Cortex®-M内核单片机.目前提供10大产品线(F0, F1, F2, F3, F4, F7, H7, L0, L1, L4),超过700个型号.STM32产品广泛应用于 ...

最新文章

  1. NanoPi NEO Air使用十三:使用自带的fbtft驱动点亮SPI接口TFT屏幕,ST7789V,模块加载的方式
  2. UML 2.0(装载)
  3. it oracle 培训,Oracle数据库技术培训_OracleDBA数据库工程师_Oracle数据库在线学习视频教程_IT职业培训-51CTO学院_精培学院...
  4. linux权限sudo和su,Linux su和sudo命令的区别,并获得root权限
  5. BUG_ON()、panic()、dump_stack()几种内核调试手段
  6. 天然气门站监控摄像头如何布置_监控摄像头布置原则
  7. Linux使用I/O复用函数的超时机制的定时器
  8. python数学实验与建模_Python数学
  9. ios怎么ftp上传文件到服务器,Mac OS通过 FTP工具上传文件的方法
  10. 一、初学计算机——认识键盘布局及快捷键使用
  11. Windows 禁用U盘
  12. 第六节:通信之WLAN(SSID)
  13. win7系统怎样添加wifi连接到服务器,win7如何连接无线wifi设置详细教程
  14. JAVA-输出一个三角形(详解)
  15. Androd Camera Yuv Jepg bmp
  16. 张学友-歌神同行.叁(国语篇)2019【SACD-ISO】
  17. CSS水平垂直居中: flex方式
  18. c语言 点运算符,C语言点运算符和箭头运算符
  19. 企业选择SOP作业指导书系统的目的和意义
  20. 常规调幅系统matlab结果,基于MATLAB的单边带调幅系统的建模仿真.doc

热门文章

  1. 谈谈Java与C#的区别
  2. 理财(二):国债逆回购与通货膨胀
  3. 企业租用家庭屋顶投资光伏电站存四大争议
  4. tp5之学习时长排行榜
  5. sakila mysql样例数据库解析_Sakila MySQL 样例数据库解析
  6. R基础2——数据形式
  7. 企业级-Shell案例7——监控多台服务器磁盘利用率脚本
  8. 浅谈数据可视化及大屏设计方法
  9. 什么在阻挡互联网应用“称王”
  10. 2014北邮网研机试