LPC55S16 SCTimer Update PWM 处理时间问题

一、概念

       SCTimer 中文全称为状态可配置定时器,是 NXP 特有的一种外设,它可以像大多数传统的定时器一样工作,但也会添加状态机,能够提供更高水平的可配置性和控制度。
LPC55S16 可支持 SCTimer 外设模块,该模块支持 8 路输入,10 路输出,16 个匹配/捕获寄存器,十六个事件和 32 个状态。在配置成 PWM 时,能够支持最高同时输出 8 路单边或 4 路双边的具有独立占空比和同一PWM 周期长度的 PWM 输出,资源十分丰富。
 

二、实际应用

       在实际产品应用中,有些客户可能需要将 SCT 的引脚配置成 PWM 功能用于 RGB 灯的控制,客户在调试中调用了 SCTIMER_UpdatePwmDutycycle 函数去更新 PWM占空比,发现每次调用此函数进行 8 路 PWM 占空比更新时,需要用到 100 多 us 的时间去更新,显然,这对于客户的产品开发来说是相当不友好的。客户使用了 SCTimer 和 CTimer 的引脚组成矩阵结构,达到扫描更新灯效的效果,如此长的处理时间肯定是需要进行优化的,那么我们先对 SCTimer 的 PWM 占空比函数进行理解。

       首先,我们查看 LPC55S16 用户手册相关章节可知,在每次更新 PWM 占空比时,都要将定时器停止,更新完新的占空比,重新再打开定时器才会生效。具体说明如下图:


图一

       通过查看 SCTIMER_UpdatePwmDutycycle 的 API 函数内容,除了一些变量的声明和简单的加减乘除操作外,大部分都是直接进行寄存器的操作。而单片机的寄存器操作是几乎不耗时间的,那么问题出在哪里呢?

void SCTIMER_UpdatePwmDutycycle(SCT_Type *base, sctimer_out_t output, uint8_t dutyCyclePercent, uint32_t event)
{
//assert(dutyCyclePercent <= 100U);
assert((uint32_t)output < (uint32_t)FSL_FEATURE_SCT_NUMBER_OF_OUTPUTS);
assert(1U == (base->CONFIG & SCT_CONFIG_UNIFY_MASK));

uint32_t periodMatchReg, pulseMatchReg;
uint32_t pulsePeriod = 0, period;

/* Retrieve the match register number for the PWM period */
periodMatchReg = base->EV[event].CTRL & SCT_EV_CTRL_MATCHSEL_MASK;

/* Retrieve the match register number for the PWM pulse period */
pulseMatchReg = base->EV[event + 1U].CTRL & SCT_EV_CTRL_MATCHSEL_MASK;

period = base->MATCH[periodMatchReg];

/* Calculate pulse width and period match value:
* For EdgeAlignedPwm, "pulsePeriod = 0" results in 0% dutycyle, "pulsePeriod = period - 1U" results in 100%
* dutycyle. For CenterAlignedPwm, , "pulsePeriod = 0" results in 0% dutycyle, "pulsePeriod = period + 2U"
* results in 100% dutycyle.
*/
pulsePeriod = (uint32_t)(((uint64_t)period * dutyCyclePercent) / 255U);

if (dutyCyclePercent == 255U)
{
if (0U == (base->CTRL & SCT_CTRL_BIDIR_L_MASK))
{
pulsePeriod = period + 2U;
}
else
{
pulsePeriod = period - 1U;
}
}
/* Stop the counter before updating match register */
SCTIMER_StopTimer(base, (uint32_t)kSCTIMER_Counter_U);

/* Update dutycycle */
base->MATCH[pulseMatchReg] = SCT_MATCH_MATCHn_L(pulsePeriod);
base->MATCHREL[pulseMatchReg] = SCT_MATCHREL_RELOADn_L(pulsePeriod);

/* Restart the counter */
SCTIMER_StartTimer(base, (uint32_t)kSCTIMER_Counter_U);
}

 

       我们先将 SCTIMER_UpdatePwmDutycycle 函数中的内容全部提取出来,直接搬运到 SysTick_Handler 的中断处理函数中进行控制,其中,时钟和 PWM 周期的选择可以在 SCT 相关初始化后直接进行配置。

    SCTIMER_GetDefaultConfig(&sctimerInfo);

/* Initialize SCTimer module */
SCTIMER_Init(SCT0, &sctimerInfo);

SCTIMER_SetupPwm(SCT0, &Sctimer_Para, kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent1);//PIO0_26
#if 0
SCTIMER_SetupPwm(SCT0, &SCT0_pwmSignalsConfig[0], kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent[0]);
SCTIMER_SetupPwm(SCT0, &SCT0_pwmSignalsConfig[1], kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent[1]);
SCTIMER_SetupPwm(SCT0, &SCT0_pwmSignalsConfig[2], kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent[2]);
SCTIMER_SetupPwm(SCT0, &SCT0_pwmSignalsConfig[3], kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent[3]);
SCTIMER_SetupPwm(SCT0, &SCT0_pwmSignalsConfig[4], kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent[4]);

SCTIMER_SetupPwm(SCT0, &SCT0_pwmSignalsConfig[5], kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent[5]);
SCTIMER_SetupPwm(SCT0, &SCT0_pwmSignalsConfig[6], kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent[6]);
SCTIMER_SetupPwm(SCT0, &SCT0_pwmSignalsConfig[7], kSCTIMER_CenterAlignedPwm, 24000U, sctimerClock, &SCT0_pwmEvent[7]);
#endif
/* Start the 32-bit unify timer */
SCTIMER_StartTimer(SCT0, kSCTIMER_Counter_U);

g_clock =sctimerClock / (((SCT0->CTRL & SCT_CTRL_PRE_L_MASK) >> SCT_CTRL_PRE_L_SHIFT) + 1U);
g_period = g_clock / (24000U * 2U);
g_pulsePeriod = (uint32_t)(((uint32_t)g_period * 50) / 100U);

/* Set systick reload value to generate 1ms interrupt */
if (SysTick_Config(SystemCoreClock / 100U))
{
while (1)
{
}
}

 

       而 PWM 的占空比更新部分则是放到中断处理函数中:

 void SysTick_Handler(void)
{
GPIO->B[0][1] = 1;
if(dutycycle < 100U)
{
dutycycle ++;
}
else
{
dutycycle = 0;
}
g_pulsePeriod = (uint32_t)(((uint64_t)g_period * dutycycle) / 100U);
periodMatchReg = SCT0->EV[SCT0_pwmEvent1].CTRL & SCT_EV_CTRL_MATCHSEL_MASK;
pulseMatchReg = SCT0->EV[SCT0_pwmEvent1 + 1U].CTRL & SCT_EV_CTRL_MATCHSEL_MASK;
SCT0->CTRL |= (SCT_CTRL_HALT_L_MASK);
SCT0->MATCH[pulseMatchReg] = SCT_MATCH_MATCHn_L(g_pulsePeriod);
SCT0->MATCHREL[pulseMatchReg] = SCT_MATCHREL_RELOADn_L(g_pulsePeriod);
SCT0->CTRL &= ~(SCT_CTRL_HALT_L_MASK);
GPIO->B[0][1] = 0;
//__enable_irq();
}


       实际测试发现更新占空比的时间还是差不多,在与客户沟通交流的过程中,提及到 CTimer 的占空比更新函数不会耗时,更新单个 PWM 信号占空比所需时间为几百个 ns。

void CTIMER_UpdatePwmDutycycle(CTIMER_Type *base,
const ctimer_match_t pwmPeriodChannel,
ctimer_match_t matchChannel,
uint8_t dutyCyclePercent)
{
uint32_t pulsePeriod = 0, period;

/* Specified channel pwmPeriodChannel defines the PWM period */
period = base->MR[pwmPeriodChannel];

/* For 0% dutycyle, make pulse period greater than period so the event will never occur */
if (dutyCyclePercent == 0U)
{
pulsePeriod = period + 1U;
}
else
{
pulsePeriod = (period * (100U - (uint32_t)dutyCyclePercent)) / 100U;
}

/* Update dutycycle */
base->MR[matchChannel] = pulsePeriod;
}


       对比 SCT 和 CTimer 的占空比更新函数后,唯一发现的耗时差异则是以下两行代码:

       SCTimer:g_pulsePeriod = (uint32_t)(((uint64_t)g_period * dutycycle) / 100U);

       CTimer:pulsePeriod = (period * (100U - (uint32_t)dutyCyclePercent)) / 100U;

       根据了解,我们可以知道 CPU 进行四则运算的处理时间是有差异的,CPU 计算加减法的速度跟位运算(与、或、非、异或)相当,而乘法的速度比加减法慢近 10 倍,除法的速度比加减法慢(近 20 倍——8 位、近 30 倍——16 位、40 倍以上——32 位…)。由此可见,在不同类型的变量的四则运算下,处理耗时时间是有相当大差异的。

       我们可以把 SCTimer 中的 g_pulsePeriod 计算方式进行修改,原厂的 API 中将数据整形到 64 bit 可能是担心乘法后数据会出现溢出,但却忽略了时间长度的问题,我们可以添加一个变量先进行除法运算,再用这个变量去进行后面的乘法运算,防止溢出。经确认变量值和测试后,我们可以直接将 uint64_t 修改为 uint32_t。最终测试发现,同时更新 8 路 PWM 时,所需的处理时间只需要 6.25us。

 

图二


三、总结

       经过以上的讲解,相信大家已经知道如何处理类似的问题了,在熟悉芯片开发的过程中,我们要对相应的寄存器有足够的了解,也要知道如何去使用它们,去制定自己所需的 API。


四、参考资料

【1】LPC55S1X_UserManual.pdf

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论