一、定时器
简单的理解就是设定一个时间,例如为5s;当定时器启动的时候,时间就开始递减,减到0的时候就代表定时时间到达。此时会产生一些效果,例如通常玩QQ斗地主的时候,它有一个定时器,如果定时时间到大,它就会自动帮你出牌。
现在的很多SOC上面都有定时器,这些定时器被设计成不同的功能。有些可以用来进行脉宽调制,产生不同占空比的PWM波,有些被设计成简单的定时功能。
呵呵,一些子说了很多专业名字,对于初学者肯定感到很晕。没关系,我们一步步的来。先来接着说我们的定时器。
在SOC上的这些定时器都有一个共同特点,那就是它们都有一个定时计数器(TCNT),也就是刚开始工作的时候,都需要给他们初始化一个数值。他们工作的时候,这个数值就会递减。在S5PC100上,一个定时器的TCNT减到0时,可以向CPU产生一个中断,以告诉CPU我已经计数完了哦。
现在的数字电路中,每个模块工作的时候都应该有一个时钟信号。我们的定时器也一样,它也需要一个时钟信号,每当一个时钟信号达到的时候它就减1。
例如:一个定时器,它的输入时钟频率是50HZ。
呵呵,频率,初中物理知识,它只的是单位时间内震动的次数(也就是时钟脉冲的个数),与周期成倒数关系(即0.02s)。我们常常觉得当年学的那些没用,其实不然,学了都是终生的财富呀!我们应该好好学习。
前面我们说到,定时器的TCNT的值就是当一个时钟信号达到的时候就减1,这里输入的是50HZ,也就是说每0.02秒减1。这里思考一下,如果我想定时1s中,需要将TCNT的值设为多少呢?不要笨笨,要聪聪,是不是50。下一次别人跟你说,输入的定时器时钟频率是10MHZ,你就知道是表示,TCNT的值从10M = 10 * 1000 = 10000递减到0需要1s。
好,定时器就说到这里。
二、PWM
PWM的英文名叫Pulse Width Modulation,中文名叫脉宽调制。到底是什么东东呢?其实它也是定时器产生的,它比普通定时器多一个TCMP。
如果要想产生PWM波,需要将定时器长生的信号通过引输出。假如定时器工作的时候,和它相连的引脚输出的低电平。这时TCNT的值开始递减,当它的值递减到和TCMP的值相等的时候,这个时候电平从低电平翻转到高电平。然后TCNT的值继续递减,等递减到0的时候,电平又从高电平翻转到低电平。如下图所示:
TCNT从初始值递减到0的的这段时间,称为PWM的一个周期。这段时间内,高电平持续的时间 / 周期 = 占空比(duty cycle)。
上图显示了三种不同的PWM信号。
一个占空比为10%的PWM输出,即在信号周期中,10%的时间通(高电平),其余90%的时间断。另外两个显示的分别是占空比为50%和70%的PWM输出。
说了这么多,PWM能干什么呢?
可以做步进电机的调速、可以通过无缘蜂鸣器播放音乐、可以通过滤波的方法做DA转换,还可以控制灯的亮度,就这些了吧,对了还可以做信号调制。一般用于电机调速,还有开关电源。
三 S5PC100的PWM Timer
S5PC100有5个32的定时器,其中Timer0,Timer1,Timer2有脉宽调制功能,可以通过相应的引脚输出PWM波。Timer3,Timer4用于内部定时,没有输出。整个框图如下:

通过上图可以看到,S5PC100的定时器时钟,有PCLK提供。每一个定时器在工作的时候可能需要不同的时钟频率,所以S5PC100的每个Timer 可以通过预分频(PRESCALER)和分频器(1/1 ,1 / 2, 1 / 4,1 / 8,1 / 16)去配置自己想要的时钟频率。
通过上图还可以看到,Timer0,Timer1,Timer2都有一个反向器,通过它可以将默认输出的是低电平通过反相器的作用让它输出高电平。
其中Timer0和Timer1还共用一个死区发生器。
死区发生器是有什么作用呢?死区发生器主要用于对大功率设备进行PWM控制。该特性用于在开关设备的断开和另一个开关设备的闭合之间插入一个时间缺口。这个时间缺口阻止两个开关设备处于同时闭合的状态,即使是非常短的时间。如常见的H桥电路。
下面以PWM对直流电机的控制能说明一下死区的作用。在直流电机的控制中经常用到H桥,如下图:
从上图可以看到,当PWM输出的是高电平是,图中的Q3和Q2被导通,此时电机正转,如果高电平的时间持续的越长,电机的转速会越快。如果PWM输出的是低电平时,此时Q1和Q4被导通,此时电机反转。
需要主意的是,PWM从高电平到低电平的切换过程中,高时Q4导通,低时Q3导通。如果切换到从高切换到低时,Q4还没反应过来,此时电流就从Q3到达Q4然后流入地,这种情况在H桥中是绝对不允许的,应为电流过大,会大致Q3和Q4烧毁。
如果有了死区时间,就不一样了,如下图:

由于有死区的存在,从低电平切换到高电平时,总是有一个死区时间做延时,就不会出现同时导通的情况。
四 S5PC100 Timer的双缓冲机制
S5PC100的TCNTn的值和TCMPn的值是不能直接设置的,他们是通过加载TCNTBn和TCMPBn得到的,如下图所示:

五、S5PC100 PWM 定时器配置
A.驱动蜂鸣器

需要做的事情:
1.将PWMTOUT1对应的引脚配置成,PWM输出模式
2.启动对应的定时器,产生PWM波
3.不断的改变占空比和PWM波的频率可以让蜂鸣器发出不同的声音
实验核心代码如下:
-
void pwm_init()
-
{
-
//设置GPDCON[1]:0010 为TOUT1
-
//这种写法,在当前编译器得到的结果和后面的写法不一样,+优先级 > 逻辑运算符
-
//GPD.GPDCON = GPD.GPDCON & ~(0xf << 4) + (0x2 << 4);
-
GPD.GPDCON = GPD.GPDCON & ~(0xf << 4) | (0x2 << 4);
-
//使能PCLK for PWM
-
CLK_GATE_D1.CLK_GATE_D1_3 |= (1 << 6);
-
/*TIME1:
-
*prescaler value:255
-
*divider value :2
-
*Input Clock Frequency
-
*ICF = PCLK / ( {prescaler value + 1} ) / {divider value}
-
* = 66MHZ/ ({255 + 1})/{2}
-
* = 515625HZ
-
*/
-
TIMER.TCFG0 = 0xff << 0;
-
TIMER.TCFG1 = 1 << 4;
-
/*初始化TCNTB1的值为200000,
-
*TCMPB1的值为100000
-
*/
-
TIMER1.TCNTB1 = 50;
-
TIMER1.TCMPB1 = 25;
-
/*手动装载TCNT1和TCMP1的值*/
-
TIMER.TCON = 1 << 9;
-
return;
- }
关于这些寄存器的值是怎么得来,大家可以自行看S5PC100的手册进行设置。
B.使用Timer延时
/*TIME4 的计数器的值*/
#define TIMER4_COUNTER 825
/*记录上一次计数器的值*/
static unsigned int last_dec = 0;
/*累加计数器的值*/
static unsigned int already_dec = 0;
void time4_init()
{
/*
*Timer Input Clock Frequency = PCLK / ( {prescaler value + 1} ) / {divider value}
*{prescaler value} = 1~255
*{divider value} = 1, 2, 4, 8, 16, TCLK
*这里PCLK : 66MHZ,
*设置 prescaler value : 4, divider value : 16
*ICF = 66MHZ/(4 + 1)/16 = 825000HZ
*它的意思是定时器从计数器从825000减到0需要1s中,即从825减到0,需要1ms
*/
TIMER.TCFG0 = TIMER.TCFG0 & ~(0xff << 8) | (4 << 8);
TIMER.TCFG1 = TIMER.TCFG1 & ~(0xf << 16) | (4 << 16);
//load value for 1ms timeout
TIMER4.TCNTB4 = TIMER4_COUNTER;
//Manual Update time4
TIMER.TCON |= 1 << 21;
//Auto Reload and start time4
TIMER.TCON = TIMER.TCON & ~(1 << 21) | (1 << 20) | (1 << 22);
//记录一开始计数器的值
last_dec = TIMER4_COUNTER;
return;
}
/*获得已经计数器的值*/
unsigned int get_already_count()
{
unsigned int now = TIMER4.TCNTO4;
/*还没有重载,接着上一次的值递减的*/
if( now <= last_dec)
{
already_dec += last_dec - now;
/*已经重载过了,即上一次的值已经递减为0了*/
}else{
already_dec += TIMER4_COUNTER - now + last_dec;
}
//最为下次计数器开始的值
last_dec = now;
//返回总共累加的计数
return already_dec;
}
void mdelay(unsigned int msec)
{
unsigned int need_count = 0,
start = 0,now = 0;
/*延时msec,需要的总共计数*/
need_count = msec * TIMER4_COUNTER;
/*启动TIME4定时器*/
time4_init();
/*获得已经计数器的值*/
start = get_already_count();
/*循环,直到满足要求的计数*/
do{
now = get_already_count();
}while((now - start) <= need_count);
/*停止TIME4*/
TIMER.TCON = TIMER.TCON & ~(1 << 20);
last_dec = 0;
already_dec = 0;
return;
}
可以先看之前先的FS2410裸奔之定时器延时的博文,这样会有一个更深刻的理解。其实核心思想很简单,就是要长生的延时时间对应定时器需要计数多少,如果达到要求的计数值,结束循环,如果没有继续计数。核心代码如下:

