实现NXP S32K3 微秒(us)级延时函数(含代码)

一. 简介

      微秒级延时在许多通过软件实现的自定义通信协议中具有不可或缺的作用,本文介绍使用 S32K3 的 PIT 和内部 Systick 模块实现微妙级别的延时函数实现。

二. PIT 实现延时函数

      定义如下结构体类型,在 pit_config.h 中:

#ifndef __PIT_CONFIG
#define __PIT_CONFIG
#include "Pit_Ip.h"

#define     PIT_DELAY             1
#define     ENABEL_PIT_INT        0

#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

typedef struct
{
    uint32         pit_num;
    uint32         pit_channel;
    uint32         pit_period;
    uint32        delay_count;
    uint32        delay_time;
}pit_config_t;

void pit_init(volatile pit_config_t * pit_config);
#if PIT_DELAY
void pit_delay_us(uint32 time_us);
#endif
uint16 Siul2_Dio_Ip_Rev_Bit_16_self(uint16 value);

#endif

      结构体初始化函数如下,用于设定 PIT 初始化,开启 PIT 计时。在 pit_config.c 中:
#include "pit_config.h"


void pit_get_default_config(volatile pit_config_t * pit_config)
{
    pit_config->pit_num                    =        0U;
    pit_config->pit_channel                =        0U;
    pit_config->pit_period                 =        40;         /* equivalent to 1us */
    pit_config->delay_count                =        0U;
    pit_config->delay_time                 =        1U;
}

void pit_init(volatile pit_config_t * pit_config)
{
    pit_get_default_config(pit_config);
    switch(pit_config->pit_num)
    {
        case 0:
            /* Initialize PIT instance 0 - Channel 0 */
            Pit_Ip_Init(pit_config->pit_num, &PIT_0_InitConfig_PB_myVS);
#if ENABEL_PIT_INT
            /* set PIT 0 interrupt */
            IntCtrl_Ip_Init(&IntCtrlConfig_0);
            IntCtrl_Ip_EnableIrq(PIT0_IRQn);
#endif
            /* Initialize channel 0 */
            Pit_Ip_InitChannel(pit_config->pit_num, PIT_0_CH_0);
            break;
        default: break;
    }
    /* Enable channel interrupt */
#if ENABEL_PIT_INT
    Pit_Ip_EnableChannelInterrupt(pit_config->pit_num, pit_config->pit_channel);
#endif
    /* Start channel*/
    Pit_Ip_StartChannel(pit_config->pit_num, pit_config->pit_channel, pit_config->pit_period);
}


/* Reverse bit order in each halfword independently */
uint16 Siul2_Dio_Ip_Rev_Bit_16_self(uint16 value)
{
    uint8 i;
    uint16 ret = 0U;

    for (i = 0U; i < 8U; i++)
    {
        ret |= (uint16)((((value >> i) & 1U) << (15U - i)) | (((value << i) & 0x8000U) >> (15U - i)));
    }

    return ret;
}


#if PIT_DELAY
void pit_delay_us(uint32 time_us)
{
    ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].TCTRL &= ~0x1;        //100ns~150ns
    ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].TCTRL |= 0x1;         //100ns~150ns
    /*40Mhz means a clock cycle of 25ns. 1000000000/25=40000000 equal to 1s*/
    ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].LDVAL = (40U*time_us - 21U);        //50ns

    __asm("DSB");

    /*below two ways seem to be the seem effort*/
#if 1
    while(1)
    {
        if(unlikely(!((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].CVAL))
        {
            break;
        }
    }
#else
    while(((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].CVAL);
#endif
}
#endif
      PIT 延时函数如下,通过直接读写寄存器可以实现快速精确的定时设置:

      ① 关闭定时;

      ② 重新开启定时;

      ③ 通过设定 LDVAL 的重装载值,设置 PIT 的定时时间;

      ④ 通过 while 等待 PIT 计数值为 0。

#if PIT_DELAY
void pit_delay_us(uint32 time_us)
{
    ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].TCTRL &= ~0x1;        //100ns~150ns
    ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].TCTRL |= 0x1;         //100ns~150ns
    /*40Mhz means a clock cycle of 25ns. 1000000000/25=40000000 equal to 1s*/
    ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].LDVAL = (40U*time_us - 21U);        //50ns

    __asm("DSB");

    /*below two ways seem to be the seem effort*/
#if 1
    while(1)
    {
        if(unlikely(!((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].CVAL))
        {
            break;
        }
    }
#else
    while(((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].CVAL);
#endif
}
#endif

三. Systick 实现延时

  1. 通过调用 SDK 底层驱动实现:

      外设配置如下:

      延时函数如下:

void OsIfDelay(uint32 timeoutUs)
{
    uint32 curTime = 0u;
    uint32 endTime = 0u;
    uint32 timeoutCnt = 0u;
    curTime = OsIf_GetCounter(OSIF_COUNTER_SYSTEM);
    timeoutCnt = OsIf_MicrosToTicks(timeoutUs * 1u, OSIF_COUNTER_SYSTEM);

    while(1)
    {
        endTime += OsIf_GetElapsed(&curTime, OSIF_COUNTER_SYSTEM);
        if(timeoutCnt <= endTime)
        {
            break;
        }
    }
}
  1. 通过重新配置 Systick 寄存器实现:

void sys_tick_config(uint32 interval)
{
    uint32_t sysfreq = 160000000U;
    uint32_t reload_vlaue = 0;
    reload_vlaue = (sysfreq / 1000000 * interval) ; // SystemCoreClock = 500000000UL

    if ((reload_vlaue - 1UL) > 0xFFFFFFUL)
{
reload_vlaue = 0xFFFFFFUL;
}

S32_SysTick->RVR = (reload_vlaue & 0xFFFFFFUL) - 1;
NVIC_SetPriority(SysTick_IRQn, (1<<4) - 1);
S32_SysTick->CVR = 0;
S32_SysTick->CSRr |= S32_SysTick_CSR_TICKINT(0)
| S32_SysTick_CSR_ENABLE(1)
| S32_SysTick_CSR_CLKSOURCE(1);//禁用 SysTick 中断,使能 SysTick,时钟源选择内核时钟
}

void sys_delay_us( __IO uint32_t us) //us 1 per 10us
{
S32_SysTick->CSRr |= 1;
uint32_t i;
for (i=0; i<us; i++)
{
while ( !((S32_SysTick->CSRr)&(1<<16)) );
}
S32_SysTick->CSRr &=~ 1;
}

四. 应用代码

#include "Pit_Ip.h"
#include "Clock_Ip.h"
#include "IntCtrl_Ip.h"
#include "Siul2_Port_Ip.h"
#include "Siul2_Dio_Ip.h"
#include "nvic.h"

#define         SYS_DELAY           0
#define         SYS_OS_DELAY        0
#define         SUSPEND_TEST        0
#define         FOR_LOOP_TEST       0

volatile uint32 pit_config_mem[5] = {0};        /*array to create the space of struct*/
/**
* @brief        Main function of the example
* @details      Initialize the used drivers and uses the Pit
*               and Dio drivers to toggle a LED periodically
*/
int main (void)
{
    uint32 led_pin = 0;
    /* Initialize Clock */
    Clock_Ip_Init(&Clock_Ip_aClockConfig[0]);
    /* Initialize Pin */
    Siul2_Port_Ip_Init(NUM_OF_CONFIGURED_PINS_PortContainer_0_BOARD_InitPeripherals, g_pin_mux_InitConfigArr_PortContainer_0_BOARD_InitPeripherals);

    led_pin = Siul2_Dio_Ip_Rev_Bit_16_self((1UL << LED_PIN));
#if PIT_DELAY
    pit_init((volatile pit_config_t *)pit_config_mem);
#endif
#if SYS_OS_DELAY
    OsIf_Init(NULL);
#endif
#if SYS_DELAY
    sys_tick_config(1);
#endif
    /* Waiting for Interrupt occurred */
    while (1)
    {
#if SUSPEND_TEST
        SuspendAllInterrupts();                /*approximately take 50ns-100ns, different with different code*/
#endif
#if        FOR_LOOP_TEST
        for(int i = 0; i < 2; i++) /*approximately take none time for the start of loop*/
        {
#endif
            LED_PORT->PGPDO ^= led_pin;        //150ns~200ns
#if SYS_OS_DELAY
            OsIfDelay(1000);
#endif
#if SYS_DELAY
            sys_delay_us(1);
#endif
#if PIT_DELAY
            pit_delay_us(1);
#if        FOR_LOOP_TEST
        }
#endif
#if SUSPEND_TEST
            ResumeAllInterrupts();        /*approximately take 50ns-100ns, different with different code*/
#endif
#endif
    }
    return 0;
}
#ifdef __cplusplus
}
#endif

五. 关键问题探讨

  1. Gpio 翻转问题

      通过函数调用,空跑 IO 翻转时间 250kHz,无法满足测量条件。

       通过 LED_PORT->PGPDO ^= led_pin; 直接写寄存器,省去了函数调用所用的时间,实测空跑翻转可以到达 2.86MHz,方可满足 1us 延时时间测量的条件。

  1. 中断开销

      开启 PIT 中断将导致代码频繁进入中断,影响 pit_delay_us 的时间读取,因此延时函数的实现需要关闭 PIT 中断。

  1. 函数执行速度

      函数的执行速度,受到编译器优化的影响,提高函数运行速度可以采用精简代码的手段对代码进行优化,优化的 pit_delay_us 函数可以做到较 systick 更加精确的 us 级别的延时。

      在执行定时之前需要执行如下的三条语句,三条语句耗时 500ns:

        LED_PORT->PGPDO ^= led_pin; //150ns~200ns
        ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].TCTRL &= ~0x1;        //100ns~150ns
        ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].TCTRL |= 0x1;         //100ns~150ns

      在设定定时时间时要减去 20 * 25 ns = 500 ns 的偏差,此偏差消除可以根据需要更改。

        /*40Mhz means a clock cycle of 25ns. 1000000000/25=40000000 equal to 1s*/
        ((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].LDVAL = (40U*time_us - 21U);  
      在等待计时完成时要执行一下 DSB 指令确保寄存器写入及时生效,参考 https://blog.youkuaiyun.com/tilblackout/article/details/132030663:
        __asm("DSB");

      在面对判断语句时通过 likely 与 unlikely 指令可优化分支预测,参考 https://zhuanlan.zhihu.com/p/357434227:

        if(unlikely(!((PIT_Type *)IP_PIT_0_BASE)->TIMER[0].CVAL))
        {
            break;
        }

六. 参考目录

  1. 《S32K3XXRM.pdf》,链接:https://cache.nxp.com.cn/seured/assets/documents/en/reference-manual/S32K3XXRM.pdf?__gda__=1733117580_209ecd489e7dc3c064f83175e5a5c4bb&fileExt=.pdf

  1. ARM进阶:内存屏障(DMB/DSB/ISB)的20个使用例子详解_dsb和isb-优快云博客

  2. https://zhuanlan.zhihu.com/p/357434227

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值