一、引入定时器的概念
定时器内部控制逻辑:
①每来一个时钟 TCNTn 减1
②当TCNTn == TCMPn,可以产生中断 ,也可以使对应的PWM引脚翻转(比如原来是高电平,现在是低电平)
③TCNTn继续减1
当TCNTn == 0时,可以产生中断,PWM引脚再次翻转
④TCNTn == 0时,可自动加载初值
CNTn 、TCMPn的值来自 TCMPBn TCNTBn
怎么使用定时器?
①设置时钟
②设置初值
③加载初值,启动定时器
④设置为自动加载
⑤中断相关设置
PWM是什么呢?
PWM(Pulse-Width Modulation 脉宽调制)
T1 T2 可调整就能输出不同的脉宽
用占空比可以改变的方波控制直流电机,就可以改变直流电机的输入平均电压,进而控制电机速度。采占空比可以改变的方波叫 PWM(Pulse-Width Modulation 脉宽调制)。脉宽调制大多用在直流电机调速上。给定的电压高,电机转速就快;给定的电压低,电机转速就慢。
二、编写程序:当计时器到0后,采用中断,然后点灯
打开我们的main函数
int main(void)
{
led_init();
interrupt_init(); /* 初始化中断控制器 */
//我们初始化了中断源,同样的,我们初始化timer
key_eint_init(); /* 初始化按键, 设为中断源 */
//初始化定时器
timer_init();
新建一个 timer.c,我们肯定需要操作一堆寄存器,添加头文件
#include "s3c2440_soc.h"
void timer_init(void)
{
设置TIMER0的时钟
设置TIMER0的初值
加载初值, 启动timer0
设置为自动加载并启动(值到0以后会自动加载)
}
设置中断,显然我们需要提供一个中断处理函数void timer_irq(void)在这里面我们需要点灯
1、初始化定时器
(1)设置TIMER0的时钟 / (2)设置TIMER0的初值
P313 PWM电路图:
P322公式:
Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
{prescaler value} = 0~255
{divider value} = 2, 4, 8, 16
定时器输入时钟频率 = PCLK/(预定标值 +1)/(分频因子)
预定标器存储系统时间频率。分频器接收来自预定标器的时钟信号,并进行分频处理,输出有 5 种模式:1/2,1/4,1/8,1/16 和外部时钟 TCLK。
需要操作的寄存器有:
- 设置TIMER CONFIGURATION REGISTER0 (TCFG0),确定预定标值
- 设置TIMER CONFIGURATION REGISTER1 (TCFG1),选择定时器0,确定分频因子
- 设置TIMER 0 COUNT BUFFER REGISTER 和 COMPARE BUFFER REGISTER (TCNTB0/TCMPB0),确定初值和对比值
(3)加载初值
第一次加载初值需要手工更新
需要操作的寄存器有:
TIMER CONTROL (TCON) REGISTER Register
Timer 0 manual update (note) [1]
NOTE: The bit has to be cleared at next writing.
(4)设置为自动加载并启动(值到0以后会自动加载), 启动timer0
需要操作的寄存器有:
TIMER CONTROL (TCON) REGISTER Register
Timer 0 auto reload on/off [3]
Timer 0 start/stop [0]
(5)完整代码:
void timer_init(void)
{
/* 设置TIMER0的时钟 */
/* Timer clk = PCLK / {prescaler value+1} / {divider value}
= 50000000/(99+1)/16
= 31250 意思就是从31250减到0,过去1s钟
*/
TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */
TCFG1 &= ~0xf;
TCFG1 |= 3; /* MUX0 : 1/16 */
/* 设置TIMER0的初值 */
TCNTB0 = 15625; /* 0.5s中断一次 */
/* 加载初值, 启动timer0 */
TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */
/* 设置为自动加载并启动 */
TCON &= ~(1<<1);
TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */
/* 设置中断 */
}
2、定时器中断
设置中断,显然我们需要提供一个中断处理函数void timer_irq(void)
在Timer里没有看到中断相关的控制器,我们需要回到中断章节去看看中断控制器,看看有没有定时器相关的中断
我们没有看到更加细致的Timer0寄存器
当TCNTn=TCMPn时,他不会产生中断,只有当TCNTn等于0的时候才可以产生中断,我们之前以为这个定时器可以产生两种中断,那么肯定有寄存器中断或者禁止两种寄存器其中之一,那现在只有一种中断的话,就相对简单些
设置中断的话,我们只需要设置中断控制器
设置interrupu.c中断控制器
*初始化中断控制器 void interrupt_init(void)
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
*把定时器相应的位清零就可以了,哪一位呢?
INTPND的哪一位?
INT_TIMER0第10位即可
interrupt.c
void interrupt_init(void)
{
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
INTMSK &= ~((1<<10));
}
void handle_irq_c(void)
{
int bit = INTOFFSET;
if(bit == 0 || bit == 2 || bit == 5)
{
key_init_irq(bit);
}
if(bit == 10)
{
timer_irq();
}
/* 清中断 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
timer.c
循环点亮
void timer_irq(void)
{
static int cnt = 0;
int tmp;
cnt++;
tmp = ~cnt;
tmp &= 7;
GPFDAT &= ~(7<<4);
GPFDAT |= (tmp<<4);
}
有个寄存器TCNTO0 可以用来调试,看计时器是否在计时
int main(void)
{
led_init();
interrupt_init(); /* 初始化中断控制器 */
key_eint_init(); /* 初始化按键, 设为中断源 */
timer_init();
puts("\n\rg_A = ");
printHex(g_A);
puts("\n\r");
while (1)
{
#if 0
puts("\n\rg_Char = ");
printHex(g_Char);
puts("\n\r");
puts("\n\rg_Char3 = ");
printHex(g_Char3);
puts("\n\r");
#endif
putchar(g_Char);
g_Char++;
putchar(g_Char3);
g_Char3++;
delay(1000000);
//printHex(TCNTO0);
}
return 0;
}
三、改进程序
进入main函数中执行 timer_init();
还需要修改interrupt.c
初始化函数void interrupt_init(void)
还需要调用中断处理函数void handle_irq_c(void)
每次添加一个中断我都需要修改handle_irq这个函数,这样太麻烦,我能不能保证这个interrupt文件不变,只需要在timer.c中引用即可,这里我们使用函数指针数组。
在interrupt.c里,定义函数指针,以及申请函数指针数组:
数组里存放中断执行函数。
为什么是32呢?因为SRCPND是32位的说明有32个中断源。
typedef void(*irq_func)(int);
irq_func irq_array[32];
void handle_irq_c(void)
{
int bit = INTOFFSET;
irq_array[bit](bit); //调用处理函数
/* 清中断 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
void register_irq(int bit, irq_func func)
{
INTMSK &= ~((1<<bit));
irq_array[bit] = func;
}
在timer.c里注册中断
void timer_init(void)
{
/* 设置Timer0时钟 */
//Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}
//定时器时钟频率 = 50000000ns / (99+1) / 16 = 31250
TCFG0 = 99;
TCFG1 |= (3<<0);
/* 设置Timer0初值 */
TCNTB0 = 15625; /* 0.5s中断一次 */
/* 加载Timer0初值 */
TCON |= (1<<1);
/* 设置Timer0自动加载,启动Timer0*/
TCON &= ~(1<<1);
TCON |= ((1<<0) | (1<<3));
register_irq(10, timer_irq);
}
/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
/* 配置GPIO为中断引脚 */
GPFCON &= ~((3<<0) | (3<<4));
GPFCON |= ((2<<0) | (2<<4)); /* S2,S3被配置为中断引脚 */
GPGCON &= ~((3<<6) | (3<<22));
GPGCON |= ((2<<6) | (2<<22)); /* S4,S5被配置为中断引脚 */
/* 设置中断触发方式: 双边沿触发 */
EXTINT0 |= (7<<0) | (7<<8); /* S2,S3 */
EXTINT1 |= (7<<12); /* S4 */
EXTINT2 |= (7<<12); /* S5 */
/* 设置EINTMASK使能eint11,19 */
EINTMASK &= ~((1<<11) | (1<<19));
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
}