在微控制器平台上,设计多数能够产生多路PWM信号输出的高精度定时器外设(例如NXP的FTM
、FlexPWM
等模块),大多会使用两路时钟源驱动电路系统工作:一路是总线时钟,用于用户(CPU或DMA等总线master设备)访问外设寄存器的数字电路部分(digital wrapper);另一路专供给计数器(也可称为模块的“功能时钟”),这个时钟根据设计需求,大多越快越好,将精细的计数节拍粒度也对应着输出PWM信号更精密的控制分辨率。然而,当用户通过配置寄存器要作用于计数器相关的电路时,因为两个时钟源的频率和相位都是不同步的(来自于两个不同的独立时钟源),另外,在有些应用中,软件配置需要在特定的安全的时刻才能生效,这就是所谓的同步机制。
YTM32的增强型定时器eTMR外设,也设计了这种同步机制,并且对应使用了“影子寄存器”用于暂存尚未生效的寄存器配置值。一些用户特别是无刷电机应用中,需要严格控制eTMR输出PWM的信号的多项配置同时更新(软件写入寄存器还是有先后顺序的,但硬件要同时载入生效,否则会产生“冲突冒险”危险状态),对eTMR影子寄存器的同步机制需要特别了解。本文面向这些应用场景,以YTM32B1ME微控制器为例,从RM手册出发,将详细讲解对eTMR的同步机制。
本例中,YTM32B1ME05
微控制器的手册中有表Table 13.1: IP Clock Control Table
,记录eTMR使用FAST_BUS_CLK
作为总线时钟,如图x所示。
在表Table 13.1: IP Clock Control Table
的备注里,专门注明了eTMR外设模块的功能时钟是固定接入了FAST_BUS_CLK
。如图x所示。
额,这只是碰巧使用了同一个时钟源。
在eTMR外设模块内部,还可以通过配置寄存器eTMR_CTRL[CLKSRC]
,选择使用计数的功能时钟源,来自FAST_BUS_CLK
?或是从eTMR_TCLK_IN0
引脚接入的外部时钟源。而在CIM
外设中,可以通过配置寄存器CIM_ETMROPT0[ETMRx_EXTCLK_SEL]
,选择eTMR_TCLK_IN0
/eTMR_TCLK_IN1
/eTMR_TCLK_IN2
其中之一生效。
eTMR的计数时钟信号CLK_SRC
进入eTMR外设后,还需要经过一个7位的分频器,才会送入eTMR的计数器驱动计数CLK_CNT
。这个分频器的分频因子,是在寄存器eTMR_CTRL[CLKPRS]
中设定的。有计数器计数时钟频率的计算公式如下:
C
L
K
C
N
T
=
C
L
K
S
R
C
C
T
R
L
[
C
L
K
P
R
S
]
+
1
CLK_{CNT} = \frac{CLK_{SRC}}{CTRL[CLKPRS]+1}
CLKCNT?=CTRL[CLKPRS]+1CLKSRC??
经过分频的时钟信号送入计数器,驱动计数。
eTMR外设的部分寄存器设计了缓冲机制,在初始化配置好eTMR后,一旦启动eTMR(计数器开始转起来),这些寄存器的值就被锁定了,在计数器运转的过程中,若是被写入新数,也不会立即作用到硬件电路上生效。写入的新数被暂存在缓冲寄存器中,只能在用户配置的特定时机,才会载入电路生效。这些被锁定寄存器的缓冲寄存器,就可被称为“影子寄存器”。这些寄存器大多都是同计数器相关的,有:
eTMR_CHMASK
,控制PWM输出引脚的电平。eTMR_INIT
,配置eTMR内部计数器周期计数的起始值。eTMR_MID
,配置eTMR内部计数器周期计数的一个中间值,用户可配置在计数周期中间的某个位置触发一些事件。eTMR_MOD
,配置eTMR内部计数器周期计数的折返点,下一个计数折返至起始值。eTMR_CHn_VAL0
,配置eTMR各自通道的第一个事件触发点,例如输出PWM信号第一个边沿的时刻。eTMR_CHn_VAL1
,配置eTMR各自通道的第二个事件触发点,例如输出PWM信号第二个边沿的时刻。手册中设计了一张示意图,用户描述影子寄存器的应用场景,如图x所示。其中,用户通过APB总线向这些同计数器相关的寄存器写数,但实际上是写入了相对应的影子寄存器B_xxx
中。之后在合适的载入事件/时机,eTMR才能将多个影子寄存器的数同时同步载入到硬件电路中生效,此时,用户才可以通过APB总线读到生效之后的寄存器值。这里特别提一点,当写入新数到影子寄存器,但载入事件未到来之时,读取原始寄存器的值还是最近一次载入生效的值,此时读出的值和刚写入的值可能是不同的。
那么,eTMR的影子寄存器组在什么情况下才会被载入生效呢?这里有个使用同步机制的流程框架。
在初始化eTMR的阶段,配置同步载入的事件/时机:
之后启动计数器Counter,影子寄存器组被激活。
在计数器运转期间,每次需要更新这些计数器相关寄存器时:
eTMR_SYNC[LDOK]=1
,请求同步。eTMR_STS[RF]
被置位,表示载入成功。eTMR_INIT
、eTMR_MID
、eTMR_MOD
、eTMR_CHn_VAL0
、eTMR_CHn_VAL1
这些寄存器有明确的影子寄存器,也被称为“常规的”带有载入机制的寄存器,在预设的载入时机到来之时,这些寄存器会同步、同时在硬件电路上生效。
用户可以配置寄存器字段eTMR_SYNC[REGLD]
,选择计数器到达最近一次生效的eTMR_MOD
或者eTMR_MID
中设定的时刻执行更新载入,或者是进一步由寄存器字段eTMR_SYNC[TRIGS0]
选择一个触发信号作为载入时机。如图x所示。
在eTMR_SYNC[TRIGS0]
字段的说明中,提到了关于软件触发和硬件触发。此处如果选择使用了软件触发,则可以通过用户手动向eTMR_SYNC[SWTRIG]
寄存器位写1产生触发信号;如果选择使用了硬件触发(hardware trigger),则可以进一步通过配置eTMR_SYNC[TRIG0EN]
、eTMR_SYNC[TRIG1EN]
、eTMR_SYNC[TRIG2EN]
等寄存器位,开放这些硬件触发信号的入口,而这些硬件触发信号的来源,将来自于微控制器芯片内部集成的硬连线(可能在CIM模块中配置),或者专门的触发信号管理器模块(例如YTM32的TMU),例如ME芯片上的TMU有设计送给eTMR外设模块的触发源,如图x所示。
这里还有一个要点,eTMR这个外设的IP设计最多可以使用3个外部的硬件触发输入,实际ME芯片在集成时,这三个硬件触发信号的输入来自于三个不同的模块:
eTMR_SYNC[TRIG0EN]
对应的HWTRIG0
,来自于TMU,如图x所示。eTMR_SYNC[TRIG1EN]
对应的HWTRIG1
,来自于CIM。(因具体料号,未考证)eTMR_SYNC[TRIG2EN]
对应的HWTRIG2
,来自于引脚。(因具体料号,未考证)eTMR_SYNC[REGLD]
设置了这些通用寄存器的同步载入时机,eTMR_INIT
、eTMR_MOD
和eTMR_MID
寄存器就可以在合适的时机执行同步载入影子寄存器值的操作,但对于各通道的eTMR_CHn_VAL0
和eTMR_CHn_VAL1
寄存器,还有一个额外的控制开关,寄存器位eTMR_CHn_CTRL[LDEN]
,可以选择在通用寄存器的同步载入时机是否也一起更新。如图x所示。
手册中还提供了两张时序图,用于演示影子寄存器生效的时机。
在图x中,展示了当配置为MOD match
事件触发同步载入时,影子寄存器生效的过程:
counter <= 54
时,INIT = 20
,在counter > 54
之后,用户向INIT
寄存器写入新数30
,实际是写入到影子寄存器B_INIT
中,此时B_INIT=30
,但实际的INIT
值仍为20
。counter <= 55
时,MOD=80
,在counter > 55
之后,用户向MOD
寄存器写入新数70
,实际是写入到影子寄存器B_MOD
中,此时B_INIT=70
,但实际的MOD
值仍为80
。counter=76
时,设置LDOK=1
,打开了执行同步更新的开关,此时因MOD match
事件尚未到来,还未执行更新事件。counter=80
时,碰到了当前实际生效的MOD=80
,触发了MOD match
事件!此时,FTM模块同时将B_MOD
和B_INIT
的值对应更新到实际的MOD
和INIT
寄存器中生效。从图上可以看到,counter=80
之后,载入了新的INIT
值,从30
开始重新计数,之后INIT=30
,MOD=70
。LDOK
寄存器位被硬件自动清零。图x中,描述了类似的同步机制,但是MOD match
事件被换成了trigger
。
在影子寄存器组中,eTMR_CHMASK
寄存器也有额外的同步机制,所以在手册中被称为影子寄存器组中的特殊寄存器(Special Register)。
用户可以配置寄存器字段eTMR_SYNC[MASKLD]
,选择同步更新eTMR_CHMASK
寄存器的时机是同其它影子寄存器同时同步进行(匹配事件或者触发信号),在计数器匹配到eTMR_MOD
、eTMR_MID
寄存器的值,或是由eTMR_SYNC[TRIGS2]
指定的外部触发输入事件。而eTMR_SYNC[TRIGS2]
同eTMR_SYNC[TRIGS0]
使用同一组选项,可以是来自于eTMR公用的软件触发信号、输入触发信号,或者二者相或的均可触发。如图x所示。
备注:eTMR_CHMASK
寄存器是控制各通道PWM输出引脚的绝对电平:当未启用对应通道的CHMASK
时,通道引脚受计数器和通道的匹配事件控制,正常输出产生的PWM信号;但若启用对应通道的CHMASK
后,由CHMASK
中预设的值直接控制通道引脚的输出电平,通道输出信号输出到引脚的通路将被断开。有eTMR_CHMASK
寄存器字段的定义,如图x所示。
Counter计数器(eTMR_CNT
寄存器)也可以同步更新?没有看错,在eTMR里确实有这样的设计。但Counter寄存器没有影子寄存器,即不允许用户自定义数,而是从eTMR_INIT
寄存器载入(这样看来,eTMR_INIT
也相当于是Counter计数器的影子寄存器了)。在eTMR_SYNC[CNTLD]
寄存器中可以配置Counter运转过程中的更新时机,可以是在当前的Counter值匹配到eTMR_MID
寄存器中的预设值时更新,也可以跟着其他通用寄存器同步同时更新,或者响应一个可配置的触发信号(eTMR_SYNC[TRIGS1]
)。其寄存器配置选项,如图x所示。
另外,eTMR_CNT
寄存器还有一个有趣的特性:当未启动计数器时(eTMR_CTRL[EN]=0
),写eTMR_CNT
寄存器可以直接生效,相当于是为eTMR_CNT
寄存器赋初值;当计数器已经被启动后(eTMR_CTRL[EN]=1
),写eTMR_CNT
寄存器的操作,将会立即触发eTMR_CNT
寄存器从eTMR_INIT
寄存器载入数值。具体说明可见手册中对eTMR_CNT
寄存器的说明,如图x所示。
更进一步,关于启动计数器之前,初始化eTMR_CNT
寄存器初值的方法上,还有一个相关的寄存器控制位,eTMR_SYNC[CNTINIT]
,这个寄存器是说,在启动计数器之前,eTMR_CNT
寄存器的初值是来自于直接向CNT寄存器写的数?还是执行一次从eTMR_INIT
寄存器的载入操作?具体寄存器说明,如图x所示。
之前我对同步更新的周期(频率)这个功能的实用性是持怀疑态度的,一直未多关注。直到有个做无线充的工程师专门问我要这个功能,我才注意到,这个寄存器管理的也是一个比较重要的功能。
eTMR_SYNC[LDFRQ]
寄存器可用于配置同步载入事件生效的周期。也就是说,eTMR内部对同步更新事件也设计了一个计数器,对实际到来的同步更新事件进行递增计数,当这个计数器的值小于eTMR_SYNC[LDFRQ]
寄存器字段中的预设值时,同步更新事件仍是不生效的,只有当计数值达到eTMR_SYNC[LDFRQ]
寄存器的预设值,才能执行一次前文讲述的同步更新事件。eTMR_SYNC[LDFRQ]
寄存器字段的长度是4个bit,即最多可以攒够16次同步时机再实际生效一次。当然,默认情况下,eTMR_SYNC[LDFRQ]=0
,对应着每次同步更新事件都会生效,用户也感受不到eTMR_SYNC[LDFRQ]
寄存器的作用。
类似的设计,在NXP的S32K、Kinetis等系列微控制器的FTM模块上也出现过。如图x所示。
YTM32微控制器上集成的增强型定时器eTMR外设模块,具有多路输出PWM的功能,考虑总线访问寄存器的时钟和计数器的计数时钟为两个可能不同频不同相的时钟源,同时在具体应用中,人工修改计数相关的寄存器的值存在先后关系(软件需要一步一步地配置不同的寄存器),但硬件上要求多个寄存器必须同时同步生效,因此设计了影子寄存器组用于缓冲将要同时同步生效的多个寄存器的配置值,并且在可配置的特定时机,将多个寄存器的值从各自对应的影子寄存器载入生效。eTMR面向的电机控制等对硬件电路的功能和时效性要求比较高的场景,用户在对eTMR模块编程时,更靠近底层,需要考虑的点相对比较零散且繁多。本文基于Reference Manual手册,对这些相关的知识点进行了梳理,并结合实践经验,对手册中相对简要的描述进一步进行了较为详细的说明。
eTRM外设模块中,配置和管理同步机制的寄存器字段,主要分布在eTMR_SYNC
寄存器中。