- 需求
- 软件模拟PWM
- 需求分析 - why not?
- 解题思路
- 总结
需求
客户使用YTM32B1LE05
微控制器(下文简称LE05)开发车载ECU,本机作为传感器设备,需要通过输出1Hz
的PWM
信号,告知主机当前设备的采样值。这个PWM
信号的占空比可调,精度0.1%
,表示输出0 - 1000
范围内的采样数据。
软件模拟PWM
可用软件模拟,使用1ms周期的定时器中断:
- 用一个变量对定时器中断进行计数,
- 用一个常量作为周期计数器,
- 再用一个变量作为占空比控制量的计数器,
- 到了占空比事件和周期事件时,控制GPIO输出合适的电平即可。
对于48MHz主频的CPU,没有更多实时任务的场景中,以1ms周期的产生中断事件产生的系统负载是可以接受的。
- 如果考虑为了防止被别的中断打断而影响输出信号的准确度:如果中断延迟发生在非事件点,则对控制GPIO完全没有影响;如果中断延迟发生在事件点,对控制GPIO的时间偏差最大为1ms。这在应用层面上是可以接受的。
- 如果考虑为了防止打断别的中断服务而影响别的实时任务的时效性:定时器中断服务里仅仅对软件变量进行递增、判断,并在事件点控制GPIO输出电平而已,任务量极轻。
- 作为参考,一些RTOS的时间片中断的周期也有用5ms或者10ms的的,其中还运行了比较繁重的调度器任务,包括操作任务的上下文切换等等。
但是,但是,但是,客户还是考虑尽量用硬件实现这个1Hz的功能,进一步减轻CPU的负载。(话说不影响性能的情况下,软件实现的可移植性不是更好么?)。好吧,大略一想,还是有机会实现的。
需求分析 - why not?
LE05的eTMR定时器外设模块可用于产生高速和高精度的PWM信号,但不能直接输出频率低至1Hz的PWM。
阅读LE05的手册可知,eTMR定时器的时钟源被绑定到高速总线时钟(FAST_BUS_CLK),并且没有分频可选,如图x所示。
图x IPC中为eTMR模块分配的时钟源
此处的FAST_BUS_CLK
时钟源为48MHz,即为eTMR的计数器时钟。eTMR内部分频配置寄存器eTMR_CTRL[CLKPRS]
只有7位,对应最大分频可以达到128。如图x
图x eTMR中计数器的预分频配置寄存器
再算上eTMR的定时器计数器是16位,按照最大分频因子计算,eTMR能够实现PWM信号的最小频率是:48MHz / 2^7 / 2 ^ 16 = 5.722Hz
,对应最长的计数周期为174.7ms
。不行,还是达不到1000ms(1Hz)这么长的计数周期。
解题思路
仍然是利用eTMR定时器的输出比较功能产生PWM信号,可以通过配置通道寄存器eTMR_CHn_VAL0
硬件设置比较事件的时间点,以控制占空比。但不是在单个计数周期中产生匹配事件,而是利用硬件计数器周期控制1000ms中的其中一段,将多个硬件计数周期拼接成一个完整的1Hz周期。如此,可以用较少的中断次数,实现硬件定时器eTMR产生PWM的功能。如图x所示。
图x 多段硬件计数周期拼接成一个长周期PWM信号
这里还利用的eTMR硬件定时器的同步机制,即在即将产生输出比较事件的前一个时间段的中断服务程序中,软件写入定时器的计数控制相关的寄存器eTMR_CHn_VAL0
、eTMR_MOD
等均不会立即生效,而是要等到接下来的计数溢出事件产生后才更新。这就为连续多段硬件定时器计数周期拼接在一起的连贯性奠定了基础。
例如,可以设计eTMR的溢出周期为100ms。由10个溢出周期拼接成一个1Hz信号周期。
- 设置分频因子
eTMR_CTRL[CLKPRS] = 127
,得到每个计数的步长的频率为48MHz / 128 = 375000Hz
, - 为了得到10Hz(100ms)的频率,设置溢出周期中包含计数步长的个数为
eTMR_MOD = 375000 / 10 = 37500
,这个分频小于65536(2^16),是可以写入到eTMR_MOD
寄存器中的。
另外,还要设定eTMR输出占空比的通道的引脚电平(eTMR_CHn_CTRL[CHINIT]
),其输出的初值为高电平,eTMR_CHn_VAL0
的匹配事件为控制引脚输出低电平(eTMR_CHn_CTRL[VAL0CMP]
)。
编写设置占空比的伪代码如下:
volatile uint32_t pwm1hz_etmr_period_count = 0u;
volatile uint32_t pwm1hz_etmr_step_count = 0u;
void pwm1hz_set_duty(uint32_t duty) /* 0 ... 1000 */
{
pwm1hz_etmr_period_count = duty / 100u;
pwm1hz_etmr_step_count = (duty % 100) * 375;
}
编写eTMR溢出定时器中断的伪代码如下:
volatile uint32_t pwm1hz_etmr_period_index = 0u;
void etmr_irq_handler(void)
{
pwm1hz_etmr_period_index = (pwm1hz_etmr_period_index + 1) % 10u;
if (pwm1hz_etmr_period_index == pwm1hz_etmr_period_count)
{
eTMR_CHn_VAL0 = pwm1hz_etmr_step_count; /* toggle the output level in coming period. */
}
else if (pwm1hz_etmr_period_index < pwm1hz_etmr_period_count)
{
eTMR_CHn_VAL0 = 37500u + 1u; /* always output high level in coming period. */
}
else
{
eTMR_CHn_VAL0 = 0; /* always output low level in coming period. */
}
}
总结
使用多段定时器溢出周期拼接成一个很长周期的PWM信号,可以突破硬件计数器计数范围的限制,以较低的中断负载,实现无穷长周期PWM信号。
作者:安德鲁苏
来源:安德鲁的设计笔记本
推荐阅读
欢迎大家点赞留言,更多Arm技术文章动态请关注极术社区嵌入式客栈专栏欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。