• 454查看
  • 0回复

[芯片硬件] S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定...

[复制链接]


该用户从未签到

发表于 9-3-2024 21:43:24 | 显示全部楼层 |阅读模式

汽车零部件采购、销售通信录       填写你的培训需求,我们帮你找      招募汽车专业培训老师


内容提要
引言1. ARM Cortex-M内核的SysTick Timer1.1 SysTick Timer控制和状态寄存器(SYST_CSR)1.2 SysTick Timer重载值寄存器(SYST_RVR)1.3 SysTick Timer当前计数器值寄存器(SYST_CVR)1.4 SysTick Timer时钟漂移校准寄存器(SYST_CALIB)2. SysTick Timer的中断向量和中断使能以及中断优先级配置2.1 SysTick Timer的中断向量2.2 SysTick Timer的中断使能配置2.3 SysTick Timer的中断优先级和激活状态位3. S32 SDK的OSIF_TimeDelay() API函数延迟实现3.1 基于BareMetal的OSIF_TimeDelay() API函数延迟实现3.2 基于Free RTOS的OSIF_TimeDelay() API函数延迟实现4. S32 SDK的OSIF_TimeDelay() API函数使用FAQ和Tips4.1 不能在内核异常和外设中断服务函数(ISR)及其回调函数(CallBack)中调用OSIF_TimeDelay() API函数和外设驱动的阻塞(blocking) API函数4.2 bootloader跳转到APP之前必须手动关闭OSIF_TimeDelay() API函数的时基定时器4.3 在MCU进入低功耗模式之前,必须关闭OSIF_TimeDelay() API函数的时基定时器4.4 使用FreeRTOS的S32 SDK应用工程,调用OSIF_TimeDelay() API函数之前必须启动Free RTOS内核任务调度器总结
引言
NXP提供的S32 SDK(包括S32K1xx系列MCU的S32K1xx SDK和Qorivva MPC57xx系列MCU以及S32R2/3xx系列MCU的S32PA SDK,也称作c55 SDK))的操作系统接口组件(OSIF--Operation System Interface)提供了延迟服务供用户应用层代码和PAL和PD层外设驱动以及中间件(middleware)软件使用。
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w1.jpg

osif组件提供了延迟(OSIF_TimeDelay())、互斥信号量(OSIF_MutexLock()、OSIF_MutexUnlock()、OSIF_MutexCreate()和OSIF_MutexDestroy())和多值信号量(OSIF_SemaCreate()、OSIF_SemaDestroy()、OSIF_SemaPost()和OSIF_SemaWait())以及获取系统ms级时间(OSIF_GetMilliseconds())等服务:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w2.jpg

其中,OSIF_TimeDelay() API函数被所有SDK的阻塞(blocking)外设驱动API调用,以实现延迟功能。用户开发应用程序时也可以调用该API函数实现ms级的延时。
因此,该API函数被广泛使用。然而,很多用户对其实现原理并不清楚,从而在实际使用时出现不合理的调用,导致应用软件功能异常。

为了帮助大家更好地理解和使用OSIF_TimeDelay() API函数提供的延迟功能,本文将深入介绍其实现原理(包括ARM Cortex-M内核的SysTick定时器配置和中断配置以及基于硬件定时器的OSIF_TimeDelay() BareMetal和FreeRTOS实现方式)和使用注意事项,希望对大家有所帮助和启发。
1. ARM Cortex-M内核的SysTick Timer
所有的ARM Cortex-M系列CPU内核都必须实现一个24-bit的系统心跳定时器(SysTick Timer),其具有以下功能特性:


    灵活的工作时钟配置,可选择内核时钟(core clock)作为其工作时钟;

    计数器寄存器读值清零;

    递减计数,到零自动重新加载初始值,并可配置产生内核异常中断(固定为#15内核异常);

    可校准;

SysTick Timer有4个寄存器:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w3.jpg

1.1 SysTick Timer控制和状态寄存器(SYST_CSR)
SYST_CSR寄存器提供了SysTick Timer的计数器使能(ENABLE)、异常中断使能(TICKINT)和时钟源选择(CLKSOURCE)配置位以及计数器递减至0标志位(COUNTFLAG):
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w4.jpg

1.2 SysTick Timer重载值寄存器(SYST_RVR)
SYST_CSR寄存器提供了SysTick Timer的计数器递减至0后的重载值(RELOAD, 24-bit)配置:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w5.jpg

1.3 SysTick Timer当前计数器值寄存器(SYST_CVR)
SYST_CVR寄存器提供了SysTick Timer的当前时刻的计数器值,仅24-bit LSB有效,高位为0:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w6.jpg

1.4 SysTick Timer时钟漂移校准寄存器(SYST_CALIB)
SYST_CALIB寄存器提供了SysTick Timer的计数器校准配置,提供10ms的重载值:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w7.jpg

2. SysTick Timer的中断向量和中断使能以及中断优先级配置
2.1 SysTick Timer的中断向量
SysTick Timer中断属于ARM Cortex-M系列CPU内核的#15号内核异常,使用中断向量表的15号中断向量(地址偏移为15 * 4 = 0x3C),在CMSIS和S32K1xx SDK中,其默认的中断ISR函数名为SysTick_Handler():
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w8.jpg

2.2 SysTick Timer的中断使能配置
SysTick Timer中断属于内核异常,不受NVIC管理,因此其没有NVIC的中断使能配置,只需要配置SysTick模块的SYST_CSR寄存器的计数器使能(ENABLE)和中断使能(TICKINT)位即可使能。
2.3 SysTick Timer的中断优先级和激活状态位
SysTick Timer中断的优先级配置和激活状态位由挂在Cortex-M内核私有外设总线(PPB)的系统控制块(SCB)模块提供:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w9.jpg

其中,SCB->SHPR3寄存器的PRI_15提供SysTick Timer中断的优先级配置,默认值为0,即内核可配置的最高中断优先级:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w10.jpg

Tips:SysTick Timer的中断优先级可配置值由具体的内核和MCU实现决定(数字越小优先级越高):在NXP的S32K11x系列MCU中,使用Cortex-M0+内核,可配置优先级为0~3;S32K14x系列MCU中,使用Cortex-M4F内核,可配置优先级为0~15。
若当前Cortex-M内核正在响应SysTick Timer中断(执行其中断ISR函数),则SCB->SHCSR寄存器的SYSTICKACT的bit(中断激活)将被置1:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w11.jpg

Tips:以上介绍的SysTick Timer的配置寄存器和中断优先级配置寄存器需要在ARM Cortex-M的特权(Privilege)模式下才有写访问权限,否则会触发内核异常(BusFault或者HardFault)。
3. S32 SDK的OSIF_TimeDelay() API函数延迟实现
S32 SDK的OSIF_TimeDelay() API函数提供了BareMetal(osif_baremetal.c)和FreeRTOS(osif_freertos.c)两个不同的实现。
S32K1xx SDK的osif组件实现代码:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w12.jpg

S32PA SDK的osif组件实现代码:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w13.jpg

在ARM Cortex-A/R/M系列内核平台的S32 SDK,比如S32K1xx SDK和S32V SDK中,无论是BareMetal还是FreeRTOS都是基于ARM Cortex内核的24-bit系统心跳定时器(SysTick Timer)实现的。
而在Power e200系列内核平台的S32 SDK,比如S32PA SDK(目标MCU为Qorivva MPC57xx系列汽车MCU)和S32R SDK(目标MCU为S32R26/7、S32R294和S32R37系列汽车雷达传感器MCU),则是使用PIT定时器的某个通道(通常为MCU PIT定时器模块的最大通道,若多核MCU的,使用每个CPU内核的Core-ID进行区分,从而使用不同的PIT定时器通道作为各自CPU内核OSIF_TimeDelay() API函数的时基):/* application is allowed to change the pit channel used by osif * but care must be taken in multicore environments to not use  * the same channel for multiple cores */#ifndef OSIF_PIT_CHAN_ID#define OSIF_PIT_CHAN_ID (OSIF_PIT_CHAN_ID_MAX - (uint32_t)GET_CORE_ID())#endif /* OSIF_PIT_CHAN_ID */
3.1 基于BareMetal的OSIF_TimeDelay() API函数延迟实现
在使用BareMetal方式时,SysTick Timer和PIT定时器通道的定时中断周期都是固定为1ms的,即OSIF_TimeDelay() API函数提供的系统延迟服务是毫秒(ms)级的。
在osif_baremetal.c中,可以看到OSIF_TimeDelay() API函数的BareMetal实现代码如下:void OSIF_TimeDelay(const uint32_t delay){    osif_UpdateTickConfig();    uint32_t start = osif_GetCurrentTickCount();    uint32_t crt_ticks = osif_GetCurrentTickCount();    uint32_t delta = crt_ticks - start;    uint32_t delay_ticks = MSEC_TO_TICK(delay);    while (delta < delay_ticks)    {        crt_ticks = osif_GetCurrentTickCount();        delta = crt_ticks - start;    }}其中,调用了osif_UpdateTickConfig()实现SysTick Timer的1ms中断配置和使能以及系统心跳ms计数值(s_osif_tick_cnt:static inline void osif_UpdateTickConfig(void){    uint32_t core_freq = 0u;    /* Get the correct name of the core clock */    status_t clk_status = CLOCK_SYS_GetFreq(OSIF_CLK, &core_freq);    DEV_ASSERT(clk_status == STATUS_SUCCESS);    DEV_ASSERT(core_freq > 0u);    (void)clk_status;
    /* For Cortex-M0 devices the systick counter is initialized with an undefined     value, so make sure to initialize it to 0 before starting */    S32_SysTick->CSR = S32_SysTick_CSR_ENABLE(0u);    S32_SysTick->RVR = S32_SysTick_RVR_RELOAD(core_freq / 1000u);    S32_SysTick->CVR = S32_SysTick_CVR_CURRENT(0U);    S32_SysTick->CSR = S32_SysTick_CSR_ENABLE(1u) | S32_SysTick_CSR_TICKINT(1u) | S32_SysTick_CSR_CLKSOURCE(1u);}然后,调用osif_GetCurrentTickCount()获取当前的系统心跳ms计数值(s_osif_tick_cnt),该软件计数值在SysTick Timer的中断服务函数(SysTick_Handler(),通过覆盖默认中断向量表定义的弱符号ISR名实现中断向量安装)中累加,从而获取当前系统参考时间:static volatile uint32_t s_osif_tick_cnt = 0u;
static inline void osif_Tick(void){    s_osif_tick_cnt++;}
static inline uint32_t osif_GetCurrentTickCount(void){    return s_osif_tick_cnt;}void SysTick_Handler(void){    osif_Tick();}S32PA SDK中使用PIT定时器通道实现OSIF_TimeDelay() API函数延时功能与S32K1xx SDK类似:
相应的osif_UpdateTickConfig()和以及PIT定时器通道中断ISR函数实现如下:void OSIF_PIT_IRQHandler(void);
void OSIF_PIT_IRQHandler(void){    OSIF_PIT->TIMER[OSIF_PIT_CHAN_ID].TFLG = PIT_TFLG_TIF(1u);
    osif_Tick();}
static inline void osif_UpdateTickConfig(void){#if defined(DEV_ERROR_DETECT) || defined(CUSTOM_DEVASSERT)    static bool first_init = true;#endif    uint32_t pit_chan_id = OSIF_PIT_CHAN_ID;    uint32_t tick_freq = 0u;    PIT_Type * base = OSIF_PIT;
    /* get the clock frequency for the timer and compute ticks for 1 ms */    status_t clk_status = CLOCK_SYS_GetFreq(OSIF_CLK, &tick_freq);    DEV_ASSERT(clk_status == STATUS_SUCCESS);    DEV_ASSERT(tick_freq > 0u);    (void)clk_status;    uint32_t tick_1ms = tick_freq / 1000u;
#if defined(DEV_ERROR_DETECT) || defined(CUSTOM_DEVASSERT)    if (first_init)    {        /* check if the timer is already running. If it is, we cannot use it.         * This only needs to be checked for the first initialization of osif.         */        DEV_ASSERT((base->TIMER[pit_chan_id].TCTRL & PIT_TCTRL_TEN_MASK) == 0u);        first_init = false;    }#endif    /* setup timer and enable interrupt */#if FEATURE_OSIF_PIT_FRZ_IN_DEBUG  base->MCR |= PIT_MCR_FRZ(1u); /* stop the timer in debug */#endif /* FEATURE_OSIF_PIT_FRZ_IN_DEBUG */    base->MCR &= ~PIT_MCR_MDIS(1u); /* make sure module is started */    base->TIMER[pit_chan_id].LDVAL = tick_1ms;    base->TIMER[pit_chan_id].TCTRL |= PIT_TCTRL_TEN(1u) | PIT_TCTRL_TIE(1u);    static const IRQn_Type pitIrqId[PIT_INSTANCE_COUNT][PIT_IRQS_CH_COUNT] = PIT_IRQS;
    INT_SYS_InstallHandler(pitIrqId[0U][pit_chan_id], OSIF_PIT_IRQHandler, NULL);    INT_SYS_EnableIRQ(pitIrqId[0U][pit_chan_id]);}Tips:PIT定时器通道的中断向量注册和系统中断使能,需要调用S32 SDK的interrupt_manager组件的INT_SYS_InstallHandler()和INT_SYS_EnableIRQ() API函数,SysTick Timer的中断配置则不需要。
Tips:从以上实现可知,OSIF_TimeDelay() API函数延迟时是基于硬件SysTick或者PIT定时器实现的,而非软件延迟。其延迟实现依赖于相应定时器中断ISR函数中的软件计数器值(s_osif_tick_cnt)的更新,若有更高优先级的内核异常或者外设中断发生,抢占了SysTick或者PIT定时器中断ISR的执行,将导致出现延迟误差。为此,需要将SysTick或者PIT定时器中断设置为MCU的最高可配置中断优先级,且保证内核异常和与SysTick或者PIT定时器中断相同优先级的外设中断ISR函数执行时间足够短。
以上OSIF_TimeDelay() API函数代码并未设置SysTick或者PIT定时器通道的中断优先级,因此使用默认优先级(0--最高可配置中断优先级),因此,OSIF_TimeDelay() API函数的延时还是非常精准的。
3.2 基于Free RTOS的OSIF_TimeDelay() API函数延迟实现
使用FreeRTOS方式时, SysTick Timer和PIT定时器通道的定时中断将被FreeRTOS内核作为系统心跳时钟使用,其中断周期由FreeRTOS的configTICK_RATE_HZ配置决定,该配置默认值为1000(即每秒钟产生1000次FreeRTOS内核心跳中断),因此,FreeRTOS方式下OSIF_TimeDelay() API函数提供的系统延迟服务默认也是毫秒(ms)级的:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w14.jpg

当然,用户可以通过FreeRTOS组件的以下Processor Expert配置选项进行内核心跳时钟频率配置:
S32K SDK使用详解之OSIF_TimeDelay()实现与使用注意事项(含Cortex-M内核SysTick定时器详解)w15.jpg

使用S32 SDK的应用工程,添加FreeRTOS组件后,将自动添加osif_freertos.c,其中包含OSIF_TimeDelay() API函数的实现代码如下:void OSIF_TimeDelay(uint32_t delay){    /* One dependency for FreeRTOS config file */    /* INCLUDE_vTaskDelay */    vTaskDelay(MSEC_TO_TICK(delay));}可以看到,其直接调用了FreeRTOS的内核延迟API函数--vTaskDelay(),从而是实现与FreeRTOS的无缝对接。
Tips:相较于BareMetal的实现方式,OSIF_TimeDelay() API函数的FreeRTOS实现,依赖于FreeRTOS的内核延迟服务,并不包含硬件时基定时器(SysTick或者PIT定时器)的配置和中断处理。因此,调用之前必须先启动内核任务调度器,即调用FreeRTOS的API函数--vTaskStartScheduler()。
4. S32 SDK的OSIF_TimeDelay() API函数使用FAQ和Tips
基于以上介绍,本小节将S32 SDK的OSIF_TimeDelay()API函数使用FAQ和注意事项总结如下,供大家参考:
4.1 不能在内核异常和外设中断服务函数(ISR)及其回调函数(CallBack)中调用OSIF_TimeDelay() API函数和外设驱动的阻塞(blocking) API函数
不能在内核异常和外设中断服务函数(ISR)及其回调函数(CallBack)中调用OSIF_TimeDelay() API函数以及会调用OSIF_TimeDelay() API函数的所有外设阻塞(locking) API函数。原因如下:
首先,基于MCU的嵌入式系统最为看重的就是系统的实时性,所以必须保证内核异常和外设中断服务函数执行时间尽量短;
其次,虽然OSIF_TimeDelay() API函数时间延迟依赖的SysTick Timer和PIT定时器通道的中断优先级默认配置为MCU可配置的最高优先级,但外设中断的中断优先级默认也是MCU可配置的最高优先级,因此,无法中断嵌套,OSIF_TimeDelay() API函数时间延迟计数器无法计数,从而卡死,无法退出外设中断,因而导致整个MCU应用功能软件无法运行。
4.2 bootloader跳转到APP之前必须手动关闭OSIF_TimeDelay() API函数的时基定时器
在bootloader跳转到APP之前必须手动关闭OSIF_TimeDelay() API函数的时基定时器,也就是SysTick Timer和PIT定时器及其中断。否则可能会导致跳转失败(在bootloader和APP程序的内核堆栈配置和系统异常/中断向量表切换的过程中,发生中断)。
4.3 在MCU进入低功耗模式之前,必须关闭OSIF_TimeDelay() API函数的时基定时器
在MCU进入低功耗模式之前,必须关闭OSIF_TimeDelay() API函数的时基定时器,也就是SysTick Timer和PIT定时器及其中断,否则可能会导致CPU内核进入低功耗模式失败(在CPU内核进入低功耗的过程中,发生中断),甚至导致CPU内核异常卡死(lockup/stuck)。
4.4 使用FreeRTOS的S32 SDK应用工程,调用OSIF_TimeDelay() API函数之前必须启动Free RTOS内核任务调度器
相较于BareMetal的实现方式,OSIF_TimeDelay() API函数的FreeRTOS实现,依赖于FreeRTOS的内核延迟服务,因此,在使用FreeRTOS的S32 SDK应用工程中,调用OSIF_TimeDelay() API函数之前必须先启动内核任务调度器,即调用FreeRTOS的API函数--vTaskStartScheduler()。
Note:在使用FreeRTOS的S32 SDK应用工程中,SDK PD和PAL层外设初始化API函数(PD/PALxxx_Init())以及SDK包含的中间件软件初始化函数都会调用osif组件的互斥信号量(Mutex)和信号量(Semaphore)创建API函数,外设驱动和中间件软件的阻塞操作API函数(xxx_Blockingxxx())会调用osif组件的OSIF_TimeDelay() API函数,都依赖FreeRTOS内核服务,因此,调用前也必须先启动FreeRTOS内核。
总结
本文详细介绍了ARM Cortex-M系列CPU内核的SysTick Timer定时器的工作原理和中断配置以及基于该定时器的S32 SDKOSIF_TimeDelay() API函数延时功能BareMetal和FreeRTOS方式实现,最后,还对OSIF_TimeDelay() API函数的使用FAQ和注意事项进行了归纳总结,以方便大家学习参考。
以下是与本文介绍知识点相关的推荐阅读文章(点击文章标题即可直接跳转阅读):《S32K1xx系列MCU的常见内核异常(Fault Exception)及处理详解(以S32K144为例介绍)》;《深入浅出谈嵌入式MCU 内核之ARM Cortex-M系列CPU内核特权模式定义与切换方法详解》;《S32K1xx系列MCU使用Tips之SDK软件架构和使用详解》;《S32K SDK使用详解之interrupt_manager组件配置与使用详解》;

快速发帖

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|手机版|小黑屋|Archiver|汽车工程师之家 ( 渝ICP备18012993号-1 )

GMT+8, 3-3-2025 12:46 , Processed in 0.423131 second(s), 34 queries .

Powered by Discuz! X3.5

© 2001-2013 Comsenz Inc.