一、什么是定时器(timer)
1、定时器是 SoC 中常见外设
(1) 定时器与计数器。计数器是用来计数的(每隔一个固定时间会计一个数);因为计数器的计数时间周期是固定的,因此到了一定时间只要用计数值×计数时间周期,就能得到一个时间段,这个时间段就是我们定的时间(这就是定时器了)。
(2) 定时器/计数器作为 SoC 的外设,主要用来实现定时执行代码的功能。定时器相对于 SoC 来说,就好象闹钟相对于人来说意义一样。
2、定时器有什么用
(1) 定时器可以让 SoC 在执行主程序的同时,可以(通过定时器)具有计时功能,到了一定时间(计时结束)后,定时器会产生中断提醒 CPU,CPU 会去处理中断并执行定时器中断的 ISR 。从而去执行预先设定好的事件。
(2) 定时器就好象是 CPU 的一个秘书一样,这个秘书专门管帮 CPU 来计时,并到时间后提醒 CPU 要做某件事情。所以 CPU 有了定时器之后,只需预先把自己 xx 时间之后必须要做的事情绑定到定时器中断 ISR 即可,到了时间之后定时器就会以中断的方式提醒 CPU 来处理这个事情。
3、定时器的原理
(1) 定时器计时其实是通过计数来实现的。定时器内部有一个计数器,这个计数器根据一个时钟(这个时钟源来自于 ARM 的 APB 总线,然后经过时钟模块内部的分频器来分频得到)来工作。每隔一个时钟周期,计数器就计数一次,定时器的时间就是计数器计数值 × 时钟周期。
(2) 定时器内部有 1 个寄存器 TCNT,计时开始时我们会把一个总的计数值(譬如说 300)放入 TCNT 寄存器中,然后每隔一个时钟周期(假设为 1ms )TCNT 中的值会自动减 1(硬件自动完成,不需要 CPU 软件去干预),直到 TCNT 中减为 0 的时候,TCNT 就会触发定时器中断。
(3) 定时时间是由 2 个东西共同决定的:一个是 TCNT 中的计数值,一个是时钟周期。譬如上例中,定时周期就为 300 × 1 ms = 300 ms。
4、定时器和看门狗、RTC、蜂鸣器的关系
(1) 这几个东西都是和时间有关的部件。
(2) 看门狗其实就是一个定时器,只不过定时时间到了之后不只是中断,还可以复位 CPU。
(3) RTC 是实时时钟,它和定时器的差别就好象闹钟(定时器)和钟表(RTC)的差别一样。
(4) 蜂鸣器是一个发声设备,在 ARM 里面蜂鸣器是用定时器模块来驱动的。
二、 S5PV210 中的定时器
在 S5PV210 内部,一共有 4 类定时器件。这 4 类定时器件的功能、特征是不同的。
1、PWM 定时器
(1) 这种是最常用的,平时所说的定时器一般指的是这个。像简单的单片机(譬如 51 单片机)中的定时器也是这类。
(2) 为什么叫 PWM 定时器,因为一般 SoC 中产生 PWM 信号都是靠这个定时器模块的。
2、系统定时器
(1) 系统(指的是操作系统)定时器,系统定时器也是用来产生固定时间间隔(TCNT×时钟周期)信号的,称为 systick,这个 systick 用来给操作系统提供 tick 信号。
(2) 产生 systick 作为操作系统的时间片(time slice)的。
(3) 一般做操作系统移植的时候,这里不会由我们自己来做,一般原厂提供的基础移植部分就已经包含了。
3、看门狗定时器
(1) 看门狗定时器本质上也是一个定时器,和上面 2 个没有任何本质区别。
(2) 看门狗定时器可以设置在时间到了的时候产生中断,也可以选择发出复位信号复位 CPU。
(3) 看门狗定时器在实践中应用很多,尤其是工业领域(环境复杂、干扰多)机器容易出问题,而且出问题后后果很严重,此时一般都会用看门狗来进行系统复位。
4、实时时钟 RTC(real time clock)
(1) 区分时间段和时间点。时间段是相对的,两个时间点相减就会得到一个时间段;而时间点是绝对的,是绝无仅有的一个时间点。
(2) 定时器关注的是时间段(而不是时间点),定时器计时从开启定时器的那一刻开始,到定的时间段结束为止产生中断;RTC 中工作用的是时间点(xx年x月x日x时x分x秒星期x)。
(3) RTC和定时器的区别,就相当于是钟表和闹钟的区别。
三、S5PV210 的 PWM 定时器1
1、为什么叫 PWM 定时器
(1) 叫定时器说明它本质上的原理是定时器。
(2) 叫 PWM 定时器,是因为这个定时器天然是用来产生 PWM 波形的。
2、PWM 定时器介绍
(1) S5PV210 有 5 个PWM 定时器。其中 0、1、2、3 各自对应一个外部 GPIO,可以通过这些对应的 GPIO 产生 PWM 波形信号并输出;timer4 没有对应的外部 GPIO(因此不是为了生成 PWM 波形而是为了产生内部定时器中断而生的)。
(2) S5PV210 的 5 个 PWM 定时器的时钟源为 PCLK_PSYS,timer0 和 timer1 共同使用一个预分频器,timer2、3、4 共同使用一个预分频器;每个 timer 有一个专用的独立的分频器;预分频器和分频器构成了 2 级分频系统,将 PCLK_PSYS 两级分频后生成的时钟供给 timer 模块作为时钟周期。
Each timer has its own 32-bit down-counter which is driven by the timer clock. The down-
counter is initially loaded from the Timer Count Buffer register (TCNTBn). If the down-
counter reaches zero, the timer interrupt request is generated to inform the CPU that
the timer operation is complete. If the timer down-counter reaches zero, the value
of corresponding TCNTBn automatically reloads into the down-counter to start a next
cycle. However, if the timer stops, for example, by clearing the timer enable bit
of TCONn during the timer running mode, the value of TCNTBn is not reloaded into
the counter.
The PWM function uses the value of the TCMPBn register. The timer control logic changes
the output level if down-counter value matches the value of the compare register in
timer control logic. Therefore, the compare register determines the turn-on time
(or turn-off time) of a PWM output.
The TCNTBn and TCMPBn registers are double buffered to allow the timer parameters to
be updated in the middle of a cycle. The new values do not take effect until the
current timer cycle completes.
3、S5PV210 的 PWM 定时器框图简介
(1) 关键点:时钟源、预分频器、分频器、TCMPB & TCNTB、dead zone
四、S5PV210 的 PWM 定时器 2
1、预分频器与分频器
(1) 两级分频是串联(级联)的,所以两级分频的分频数是相乘的。
(2) 两级分频的分频系数分别在 TCFG0 和 TCFG1 两个寄存器中设置。
(3) 预分频器有 2 个,prescaler0 为 timer0 & timer1 共用;prescaler1 为 timer2、3、4 共用;两个 prescaler 都是 8 bit 位,因此 prescaler value 范围为 0~255;所以预分频器的分频值范围为 1~256(注意实际分频值为 prescaler value + 1)。
(4) 分频器实质上是一个 MUX 开关,多选一开关决定了走哪个分频系数路线。可以选择的有 1/1,1/2,1/4,1/8,1/16 等。
(5) 计算一下,两级分频下来,分频最小为 1/1(也可能是 1/2 ),最大分频为 1/256 × 16(1/4096).
(6) 在 PCLK_PSYS 为 66 MHz的情况下(默认时钟设置就是 66 MHz的),此时两级分频后的时钟周期范围为 0.03us 到 62.061us;再结合 TCNTB 的值的设置(范围为1~2 ^ 32),可知能定出来的时间最长为 266548.27s(折合74小时多,远远够用了)。
2、TCNT&TCMP、TCNTB&TCMPB、TCNTO
(1) TCNT 和 TCNTB 是相对应的,TCNTB 是有地址的寄存器,供程序员操作;TCNT 在内部和TCNTB 相对应,它没有寄存器地址,程序员不能编程访问这个寄存器。
(2) TCNT 寄存器功能就是用来减 1 的 ,它是内部的不能读写;我们向 TCNT 中写,要通过TCNTB 往进写;读取 TCNT 寄存器中的值要通过读取相对应的 TCNTO 寄存器。
(3) 工作流程就是:我们事先算好 TCNT 寄存器中开始减的那个数(譬如 300),然后将之写入TCNTB 寄存器中,在启动 timer 前,将 TCNTB 中的值刷到 TCNT 寄存器中(有一位寄存器专门用来操作刷数据过去的),刷过去后就可以启动定时器开始计时;在计时过程中如果想知道 TCNT 寄存器中的值减到多少了,可以读取相应的 TCNTO 寄存器来得知。
(4) 定时功能只需要 TCNT、TCNTB 两个即可;TCNTO 寄存器用来做一些捕获计时;TCMPB 用来生成PWM波形。
3、自动重载和双缓冲(auto-reload and double buffering)
(1) 定时器工作的时候,一次定时算一个工作循环。定时器默认是单个循环工作的,也就是说定时一次,计时一次,到期中断一次,就完了。下次如果还要再定时中断,需要另外设置。
(2) 但是现实中用定时器来做的时候往往是循环的,最简单最笨的方法就是写代码反复重置定时器寄存器的值(在每次中断处理的 isr 中再次给 TCNTB 中赋值,再次刷到 TCNT 中再次启动定时器),早期的单片机定时器就是这样的;但是现在的高级 SoC 中的定时器已经默认内置了这种循环定时工作模式,就叫自动装载(auto-reload)机制。
(3) 自动装载机制就是当定时器初始化好开始计时后再不用管了,他一个周期到了后会自己从TCNTB 中再次装载值到 TCNT 中,再次启动定时器开始下个循环。
4、一个定时器实例
五、S5PV210 的PWM 定时器 3
1、什么是 PWM ?
(1) PWM(pulse wide modulation 脉宽调制)。
(2) PWM 波形是一个周期性波形,周期为 T,在每个周期内波形是完全相同的。每个周期内由一个高电平和一个低电平组成。
(3) PWM 波形有 2 个重要参数:一个是周期 T,另一个是占空比 duty(占空比就是一个周期内高电平的时间除以周期时间的商)。
(4) 对于一个 PWM 波形,知道了周期 T 和占空比 duty,就可以算出这个波形的所有细节。譬如高电平时间为 T * duty,低电平时间为 T * (1-duty)。
(5) PWM波形有很多用处,譬如通信上用 PWM 来进行脉宽调制对基波进行载波调制;在发光二极管 LED 照明领域可以用 PWM 波形来调制电流进行调光;用来驱动蜂鸣器等设备。
2、PWM 波形的生成原理
(1) PWM 波形其实就是用时间来控制电平高低,所以用定时器来实现 PWM 波形是天经地义的。
(2) 早期的简单单片机里(譬如 51 单片机)是没有专用的 PWM 定时器的,那时候我们需要自己结合 GPIO 和定时器模块来手工生产 PWM 波形(流程是这样:先将 GPIO 引脚电平拉高、同时启动定时器定 T * duty 时间,时间到了在 isr 中将电平拉低,然后定时 T * (1-duty) 后再次启动定时器,然后时间到了后在 isr 中将电平拉高,然后再定时 T * duty 时间再次启动定时器····如此循环即可得到周期为 T,占空比为 duty 的 PWM 波形)。
(3) 后来因为定时器经常和 PWM 产生纠结在一起,所以设计 SoC 的时候就直接把定时器和一个 GPIO 引脚内部绑定起来了,然后在定时器内部给我们设置了 PWM 产生的机制,可以更方便的利用定时器产生 PWM 波形。此时我们利用 PWM 定时器来产生 PWM 波形再不用中断了。
绑定了之后坏处就是 GPIO 引脚是固定的、死板的、不能随便换的;好处是不用进入中断 isr 中,直接可以生成 PWM。
(4) 在 S5PV210 中,PWM 波形产生有 2 个寄存器很关键,一个是 TCNTB、一个是 TCMPB。其中,TCNTB 决定了 PWM 波形的周期,TCMPB 决定了 PWM 波形的占空比。
(5) 最终生成的 PWM 波形的周期是:TCNTB × 时钟周期(PCLK_PSYS 经过两极分频后得到的时钟周期)。注意这个周期是 PWM 中高电平+低电平的总时间,不是其中之一。
(6) 最终生成的 PWM 波形的占空比是:TCMPB / TCNTB.
3、输出电平翻转器
(1) PWM 定时器可以规定:当 TCNT > TCMPB 时为高电平,当 TCNT < TCMPB 时为低电平。也可以规定:当 TCNT > TCMPB 时为低电平,当 TCNT < TCMPB 时为高电平。在这两种规定下,计算时 TCMP 寄存器的值会变化。
(2) 基于上面讲的,当 duty 从 30% 变到 70% 时,我们 TCMPB 寄存器中的值就要改(譬如 TCNTB 中是 300 时,TCMPB 就要从 210 变化到 90 )。这样的改变可以满足需要,但是计算有点麻烦。于是乎 210 的 PWM 定时器帮我们提供了一个友好的工具叫做电平翻转器。
(3) 电平翻转器在电路上的实质就是一个电平取反的部件,在编程上反映为一个寄存器位。写 0 就关闭输出电平反转,写 1 就开启输出电平反转。开启后和开启前输出电平刚好高低反转。(输出电平一反转 30% 的 duty 就变成 70% 了)
(4) 实战中到底是 TCNT 和 TCMPB 谁大谁小时高电平还是低电平,一般不用理论分析,只要写个代码然后用示波器实际看一下出来的波形就知道了;如果反了就直接开启电平翻转器即可。
4、死区生成器
(1) PWM 有一个应用就是用在功率电路中用来对交流电压进行整流。整流时 2 路整流分别在正电平和负电平时导通工作,不能同时导通(同时导通会直接短路,瞬间的同时导通都会导致电路烧毁)。大功率的开关电源、逆变器等设备广泛使用了整流技术。特别是逆变器,用 SoC 的 GPIO 输出的 PWM 波形来分别驱动 2 路整流的IGBT。
(2) PWM 波形用来做整流时要求不能同时高或低,因为会短路。但是实际电路是不理想的,不可能同时上升/下降沿,所以比较安全的做法是留死区。
(3) 死区这东西离不了也多不了。死区少了容易短路,死区多了控制精度低了不利于产品性能的提升。
(4) S5PV210 给大家提供了自带的死区生成器,只要开启死区生成器,生产出来的 PWM 波形就自带了死区控制功能,用户不用再自己去操心死区问题。
(5) 大部分人工作是用不到这个的,直接关掉死区生成器即可。
六、蜂鸣器和 PWM 定时器编程实践1
1、蜂鸣器的工作原理
(1) 蜂鸣器里面有 2 个金属片,离的很紧但没挨着;没电的时候两个片在弹簧本身张力作用下分开彼此平行;有电的时候两边分别充电,在异性电荷的吸力作用下两个片挨着;
(2) 我们只要以快速的频率给蜂鸣器的正负极:供电、断电。进行这样的循环,蜂鸣器的两个弹簧片就会挨着分开挨着分开···形成敲击,发出声音。
(3) 因为人的耳朵能听见的声音频率有限制(20Hz-20000Hz),我们做实验时一般给个 2KHz 的频率,大部分人都能听到。
(4) 频率高低会影响声音的音频,一般是音频越低声音听起来越低沉、音频越高听起来越尖锐。
(5) 根据以上的分析,可以看出,只要用 PWM 波形的电压信号来驱动蜂鸣器,把 PWM 波形的周期 T 设置为要发出的声音信号的 1/频率 即可;PWM 的占空比只要确保能驱动蜂鸣器即可(驱动能力问题,一般引脚驱动能力都不够,所以蜂鸣器会额外用三极管来放大流来供电)。
2、原理图和硬件信息
(1) 查阅原理图可知,开发板底板上的蜂鸣器通过 GPD0_2(XpwmTOUT2) 引脚连接在 SoC 上。
(2) GPD0_2 引脚通过限流电阻接在三极管基极上,引脚有电,蜂鸣器就会有电(三极管导通);引脚没电,蜂鸣器就会没电(三极管关闭)。这些都是硬件问题,软件工程师不用管,软件工程师只要写程序控制 GPD0_2 引脚的电平产生 PWM 波形即可。
(3) GPD0CON (0xE02000A0),要把 bit8~bit11 设置为 0b0010(功能选择为TOUT_2,就是把这个引脚设置为 PWM 输出功能)。
(4) 从 GPD0_2 引脚可以反推出使用的是 timer2 这个PWM定时器。
3、PWM定时器的主要寄存器详解
(1) 相关的寄存器有 TCFG0、TCFG1、CON、TCNTB2、TCMPB2、TCNTO2。
七 、蜂鸣器和 PWM 定时器编程实践2
注意:PWM 定时器来产生 PWM 波形时是不需要中断干预的。
#define GPD0CON (0xE02000A0)
#define TCFG0 (0xE2500000)
#define TCFG1 (0xE2500004)
#define CON (0xE2500008)
#define TCNTB2 (0xE2500024)
#define TCMPB2 (0xE2500028)
#define rGPD0CON (*(volatile unsigned int *)GPD0CON)
#define rTCFG0 (*(volatile unsigned int *)TCFG0)
#define rTCFG1 (*(volatile unsigned int *)TCFG1)
#define rCON (*(volatile unsigned int *)CON)
#define rTCNTB2 (*(volatile unsigned int *)TCNTB2)
#define rTCMPB2 (*(volatile unsigned int *)TCMPB2)
/******************************************************************************/
#define BIT_WIDTH_GPD0_CON (4)
#define BIT_LOCATION_GPD0_2_FUNC (0xf << (2 * BIT_WIDTH_GPD0_CON))
#define GPD0_2_FUNC_INPUT (0b0000 << (2 * BIT_WIDTH_GPD0_CON))
#define GPD0_2_FUNC_OUTPUT (0b0001 << (2 * BIT_WIDTH_GPD0_CON))
#define GPD0_2_FUNC_TOUT2 (0b0010 << (2 * BIT_WIDTH_GPD0_CON))
#define GPD0_2_FUNC_INT2 (0b1111 << (2 * BIT_WIDTH_GPD0_CON))
/******************************************************************************/
#define BIT_LOCATION_TCFG0_PRESCALER0 (0xff) // 1~255
#define BIT_LOCATION_TCFG0_PRESCALER1 (0xff << 8) // 1~255
/******************************************************************************/
#define BIT_WIDTH_TCFG1_DIVIDER (4)
#define BIT_LOCATION_TCFG1_DIVIDER_MUX2 (0xf << (2 * BIT_WIDTH_TCFG1_DIVIDER))
#define TCFG1_DIVIDER_MUX2_DIVID_1 (0b0000 << (2 * BIT_WIDTH_TCFG1_DIVIDER))
#define TCFG1_DIVIDER_MUX2_DIVID_2 (0b0001 << (2 * BIT_WIDTH_TCFG1_DIVIDER))
#define TCFG1_DIVIDER_MUX2_DIVID_4 (0b0010 << (2 * BIT_WIDTH_TCFG1_DIVIDER))
#define TCFG1_DIVIDER_MUX2_DIVID_8 (0b0011 << (2 * BIT_WIDTH_TCFG1_DIVIDER))
#define TCFG1_DIVIDER_MUX2_DIVID_16 (0b0100 << (2 * BIT_WIDTH_TCFG1_DIVIDER))
#define TCFG1_DIVIDER_MUX2_DIVID_SCLK_PWM (0b0101 << (2 * BIT_WIDTH_TCFG1_DIVIDER))
/******************************************************************************/
#define BIT_LOCATION_TCON_TIMER2_START_STOP (0b1 << 12)
#define TCON_TIMER2_FUNC_STOP (0b0 << 12)
#define TCON_TIMER2_FUNC_START (0b1 << 12)
#define BIT_LOCATION_TCON_TIMER2_MANUAL_UPDATE (0b1 << 13)
#define TCON_TIMER2_FUNC_NO_UPDATE (0b0 << 13)
#define TCON_TIMER2_FUNC_MANUAL_UPDATE (0b1 << 13)
#define BIT_LOCATION_TCON_TIMER2_OUTPUT_INVERTER (0b1 << 14)
#define TCON_TIMER2_FUNC_TOUT2_INVERTER_OFF (0b0 << 14)
#define TCON_TIMER2_FUNC_TOUT2_INVERTER_ON (0b1 << 14)
#define BIT_LOCATION_TCON_TIMER2_AUTO_RELOAD (0b1 << 15)
#define TCON_TIMER2_FUNC_AUTO_RELOAD_OFF (0b0 << 15)
#define TCON_TIMER2_FUNC_AUTO_RELOAD_ON (0b1 << 15)
//初始化 PWM Timer2,使其输出 PWM 波形: 频率是 2KHZ, duty 为 50%
void timer2_pwm_init()
{
//设置 GPD0_2 引脚,将其配置为 XpwmTOUT_2
rGPD0CON &= ~(BIT_LOCATION_GPD0_2_FUNC);
rGPD0CON |= (GPD0_2_FUNC_TOUT2);
//Timer Input Clock Frequency = PCLK / ( {prescaler value + 1} ) / {divider value}
//设置 PWM 定时器的若干寄存器, 使其工作
rTCFG0 &= ~(BIT_LOCATION_TCFG0_PRESCALER1);
rTCFG0 |= (65 << 8); // 66 分频
rTCFG1 &= ~(BIT_LOCATION_TCFG1_DIVIDER_MUX2);
rTCFG1 |= TCFG1_DIVIDER_MUX2_DIVID_2; //MUX2 设置为 1/2,分频后时钟周期为 500KHZ
//时钟设置好,我们的时钟频率是 500KHZ, 对应的时钟周期是 2us.也就是说每隔 2us
//计一次数. 如果要定的时间是 x,则 TCNTB 中应该写入 x/2us
rCON |= (TCON_TIMER2_FUNC_AUTO_RELOAD_ON); //使能 auto-reload,反复定时才能发出 PWM 波形
//设置 PWM 波形为 2KHZ, 即周期是 0.5ms; 500us/2us = 250,即计数 250,就可以定时 0.5ms.
rTCNTB2 = 250;
rTCMPB2 = 125;
//设置 PWM 波形为 10KHZ, 即周期是 0.1ms; 100us/2us = 50,即计数 50,就可以定时 0.1ms.
rTCNTB2 = 50;
rTCMPB2 = 25;
//设置 PWM 波形为 100HZ, 即周期是 10ms; 10,000us/2us = 5000, 即计数 5000,就可以定时 10ms.
rTCNTB2 = 5000;
rTCMPB2 = 2500;
//第一次需要手工将 TCNTB 中的值刷新到 TCNT 中, 以后就可以 auto-reload 了
rCON |= TCON_TIMER2_FUNC_MANUAL_UPDATE; //打开自动刷新功能
rCON &= ~(BIT_LOCATION_TCON_TIMER2_MANUAL_UPDATE); //关闭自动刷新功能
rCON |= (TCON_TIMER2_FUNC_START); //开 timer2 定时器,要先把其他设置好才能开定时器
}
示例代码可以参照下图所示的图解。
源自朱有鹏老师.