本章以CW32通用定时器为例介绍单片机定时器的用法。
定时器是单片机中一个非常传统且重要的外设,定时器的本质其实就是一个计数器,只不过被计数的对象是定时器的时钟源。定时器要正常工作,需要这几个要点步骤:有时钟源输入、计数器工作、有一个可以比较的值(自动重装载值)。其工作流程是这样的:计数器会随着时钟源对时钟源提供的脉冲进行计数,计数值不断上涨(或下降),如果计数值和自动重装载值一样,那么计数器的值就会被硬件清零重新计数,这个清零重新计数被称为定时器计数溢出,这个事情会触发一个中断,被叫做定时器溢出中断,也就是说,定时器依靠对稳定的时钟源定次数计数来实现定时,并且每一个定时周期完成都会产生溢出中断。
上面讲述的就是任何一个定时器都会具备的功能,只要是个定时器就会有,程序上为了方便,关于上述功能的配置项都被以”base”命名,比如这样:
看图中的结构体,这个结构体的成员同样对应了定时器相关的寄存器,由于定时器本质是一个计数器,所以根据时钟源的选择,定时器会有不同的工作模式,如外部计数模式、编码器模式等。这里选择定时器模式(就是开头介绍的那种工作过程),之后定时器会自动选择单片机自己工作使用的时钟作为时钟源,连续计数模式下,定时器会自动重复执行上述溢出中断的过程,预分频系数根据用户需要进行配置,它和下面的重装载值共同决定定时器的溢出周期。
以图中的配置为例,该定时器的时钟源是48MHz,现在需要一个50Hz(也就是周期为20ms)的定时器,该定时器会每20ms触发一次定时器中断。在不进行干涉的情况下,定时器每秒会计数48M次,预分频系数设置成32之后,定时器每秒计数150万次,将重装载值设置为30000,定时器在每计数30000次之后触发一次中断,1秒触发50次中断,正好是需要的50Hz频率。但是填入的时候不能直接填入30000,因为30000是人类从1开始计算第一个数字得出的结果,计算机的第一个数字是0,因此需要在最后减1。图中的代码直接列出了上述文字表达的公式,其中的50就是频率。
随后设置好中断,完成必要的初始化,定时器的基本功能就可以使用了,中断服务函数可以在函数列表中找到。
好的,你已经掌握了所有单片机定时器的基本用法,不过细心的小伙伴肯定想过:为什么CW32的定时器叫ATIM、GTIM和BTIM呢?TIM就是timer,也就是定时器,A是advanced的缩写,ATIM就是高级定时器,GTIM是通用定时器,BTIM自然是基本定时器。这是根据功能对定时器资源进行划分的,这么划分的好处是不需要查手册就能通过代码直接看出来某个定时器具备什么功能,基本定时器只具备上述基本功能,通用定时器额外拥有捕获/比较功能,高级定时器包含通用定时器所有的功能,而且还有更多其他功能。理论上来说这些附带的功能都可以通过代码来实现,但由于很多工业场景需要用到,所以做到硬件层面会更加稳定,也更方便。
本章使用的是通用定时器,下面介绍高级定时器的捕获/比较功能,因为这个功能很常用。
首先需要着重声明的一点是,捕获比较功能大概率拥有多个通道,但是定时器,也就是上述的基础功能只有一个,所以即使使用很多个捕获比较通道,其所属定时器的定时周期也是相同的。
下面就来看看通用定时器的结构框图,初看这个图可能会不知所措,我们可以先进行简单的划分,框图上半部分的右侧有一个16位计数器,计数器可以从左侧选择输入的时钟源,可以对输入进行分频。框图下半部分展示了定时器的4个捕获比较通道channel1~channel4,通道可以用来输出也可以用来输入,但同一时间只能使用输入|输出中的一个功能。笔者刚学习单片机的时候,不知道通道是什么,总是稀里糊涂的,通道就是让信号走的路,放到这里就是说,这个定时器拥有4个可以用来输出|输入的电信号道路。那这个通道输出的是什么东西呢?
我们都知道,对电平进行周期反转就可以制造方波,而定时器基本功能就可以实现这个效果,只需要在中断中反转IO电平即可。但是这样很不方便,比如我想要在不调整周期的情况下去控制方波的占空比,这种原始的办法就会略显麻烦,需要在中断内修改定时器的设置来实现。为了避免这种麻烦,出现了一种带输出比较功能的定时器。理念也很简单,定时器自己有一个在有限区间内周期性增长归零的计数器,那我直接设置一个新的门限值:当这个自增的计数值小于门限时,输出高电平;计数值大于门限时,输出低电平。这就是定时器的输出比较功能,对应上图下半部分右侧的输出功能。这种方式可以便捷快速地输出一个可轻松修改占空比的方波,而这种对信号的处理方式,也叫做脉宽调制(Pulse-widthmodulation),简称PWM,用这种方式输出的方波也叫做PWM波。
现在来看使用PWM功能需要进行哪些操作。先思考,除去基本的定时器配置之外,PWM需要用到捕获比较通道,那必然会有对比较捕获功能相关寄存器的配置,它需要输出一个波,那必定会有引脚相关的初始化。
下面看代码:首先当然是对IO的初始化,相信经过对前几章的阅读,读者必定是能轻松配置GPIO了,这里着重介绍对PWM输出功能的配置。第一步当然是找到输出比较功能的函数,输出比较的英文是output compare,简写是OC,所以直接找到函数“通用定时器_输出比较初始化”。这个函数有3个参数,按顺序分别表示要初始化的定时器是哪个、要初始化的通道是哪个、以及这个通道的输出模式。输出模式就是设定:当计数值大于|小于门限值的时候,是该输出高电平还是低电平。这里设定的是计数值小于门限时输出高电平。第二步就是设置这个关键的门限值,我们可以直接找到“通用定时器_设置比较1”来设置门限值,这里我把门限值设定为重装载值的一半,最后的效果就是输出一个占空比50%的方波。
对占空比的修改不一定需要用到这个设置占空比的函数,我们可以直接修改寄存器来实现。单片机中,存储这个门限值的是一个叫做CCR的寄存器,所以为什么叫CCR?没错,他原名叫Capture Comparison Register,所以就简写为CCR。定时器的每一个通道都有一个自己的捕获比较寄存器,所以CCR一共有四个,故而上图那个设置门限值的函数也有4个,但是由于整个寄存器都只用来装这一个值,所以我们修改的时候可以直接操作寄存器修改,就像这样CW_GTIM1->CCR1=0,我们也可以直接对这个赋值号左侧的部分进行自增操作或是别的什么操作都可以,但是写入操作仅限于作为输出模式时使用。
下面就是紧张刺激的验证环节了,笔者手上没有可以接的用来发光的灯泡,所以直接用万用表测量输出引脚的电压来验证PWM功能,万用表在测量方波时,会显示该方波的平均值,所以如果PWM正常,万用表的直流档会显示1.65V左右的电压,交流档会显示3.3V的电压,这里我为了使现象更明显,在中断中对PWM波的占空比进行周期性修改。
经过测量,占空比50%时,PA6输出电压为1.62V,算上误差这个在预期结果内。而加入中断的代码后,万用表示数会周期性跳变,符合预期结果,可以认定该配置下,PWM功能正常工作。