S5PV210 有 5 个 32 位的 Pulse Width Modulation(PWM)定时器,这些定时器产生内部中断给 ARM 子系统,定时器 0,1,2,3 有脉冲宽度调制功能(PWM),可以驱动一个外部
I/O 信号。定时器 0 有一个用于大电流设备的死区(dead-zone)生成器。定时器 4 是一个没有输出引脚的内部定时器。
定时器都使用 APB-PCLK 作为时钟源输入。定时器 0 和 1 共享一个 8 位的来自 PCLK 的一级预分频器。定时器 2,3,4 共享另一个 8 位的预分频器。预分频器的输出将进入第二级
分频器, 每个定时器有一个时钟分频器,可以输出 6 种不同的分频信号(1 分频,2 分频,4 分频,8 分频,16 分频,SCLK_PWM 时钟输入信号) 。
每个定时器有一个由定时器时钟驱动的 32 位递减计数器。递减计数器的初始值是由 TCNTBn 装载而获得的。如果递减计数器减到 0 时,定时器发出中断请求通知 CPU 定时器操作已经完成,当定时器递减计数器到达 0,相应的 TCNTBn的值也会自动的装载到递减计数器中以继续下一次循环操作。 但是,如果定时器停止了,例如,在定时器正在运行模式下通过对 TCONn 的使能位清零,则 TCNTBn的值不会自动装载到计数器中。TCMPBn 寄存器的值用于脉宽调制功能(PWM) 。 当递减计数器的值和定时器控制逻辑单元中的比较寄存器的值匹配时,定时器控制单元会改变输出电平。因此,比较寄存器的值决定了 PWM 的占空比。当定时器使能,定时器计数存寄存器(TCNTBn)得到一个被装载到递减计数器中的初始值。定时器比较缓存寄存器(TCMPBn)有一个被装载到比较寄存器中用来和递减计数器的值作比较的初始值。 TCNTBn 和 TCMPBn 双缓存特点使得当频率和负荷发生改变时,定时器生成一个稳定的输出。
S5PV210 PWM 定时器的主要特性
② 2 个一级的 8 位的预分频器和 2 个二级分频器。
③ 每个独立的 PWM 通道有可编程的时钟逻辑单元。
④ 自动重载模式或单脉冲模式。
⑤ 在定时器 0 和 1 上有二个死区生成器。
S5PV210 PWM 定时器的操作步骤
//timer.h
#ifndef _TIMER_H_
#define _TIMER_H_
/* 定时器0、1、2、3、4公共的配置寄存器 */
#define PWMTIMER_BASE (0xE2500000)
#define TCFG0 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x00)) )
#define TCFG1 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x04)) )
#define TCON ( *((volatile unsigned long *)(PWMTIMER_BASE+0x08)) )
/* 定时器0寄存器 */
#define TCNTB0 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x0C)) )
#define TCMPB0 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x10)) )
#define TCNTO0 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x14)) )
/* 定时器1寄存器 */
#define TCNTB1 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x18)) )
#define TCMPB1 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x1C)) )
#define TCNTO1 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x20)) )
/* 定时器2寄存器 */
#define TCNTB2 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x24)) )
#define TCMPB2 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x28)) )
#define TCNTO2 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x2C)) )
/* 定时器3寄存器 */
#define TCNTB3 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x30)) )
#define TCMPB3 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x34)) )
#define TCNTO3 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x38)) )
/* 定时器4寄存器 */
#define TCNTB4 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x3C)) )
#define TCNTO4 ( *((volatile unsigned long *)(PWMTIMER_BASE+0x40)) )
/* 中断控制和状态寄存器 */
#define TINT_CSTAT ( *((volatile unsigned long *)(PWMTIMER_BASE+0x44)) )
/* 函数声明 */
void start_timer(unsigned long );
void stop_timer(unsigned long );
void stop_all_timer(void);
void reset_time_val(void);
void isr_timer(void);
void timer_init(unsigned int ,unsigned long ,unsigned long ,unsigned long ,unsigned long );
#endif //_TIME_H_
//key.c
void key_init(void)
{
GPH2CON |= 0xfff; //把GPH2_0、GPH2_1、GPH2_2设为外部中断16、17、18模式
EXT_INT_2_CON &= (~(7<<0))&(~(7<<4))&(~(7<<8)); //把外部中断16、17、18设为下降沿触发
EXT_INT_2_CON |= (1<<1)|(1<<5)|(1<<9);
EXT_INT_2_MASK &= (~(1<<0))&(~(1<<1))&(~(1<<2)); //清除外部中断16、17、18的屏蔽,使能GPIO中断
}
/* 按键中断真正的处理函数 */
void isr_key(void)
{
if(EXT_INT_2_PEND & (1<<0))
{
delay(100); //简单延时去抖动
GPJ2DAT = 0xe; // 1110,点亮第1盏LED灯
stop_timer(0); //停止定时器0
printf("\nYou have stopped count ~_~,you can press S3(key) to start count.\n");
}
else if(EXT_INT_2_PEND & (1<<1))
{
delay(100); //简单延时去抖动
GPJ2DAT = 0xd; // 1101,点亮第2盏LED灯
reset_time_val(); //停止定时器0,并将计数变量重置为0
printf("\nYou have stopped count ~_~,you can press S3(key) to start count again!\n");
}
else if(EXT_INT_2_PEND & (1<<2))
{
delay(100); //简单延时去抖动
GPJ2DAT = 0xb; // 1011,点亮第3盏LED灯
start_timer(0); //启动定时器0
printf("\nTime is starting to count ^_^\n");
}
VIC0ADDR = 0; //清空中断向量地址
EXT_INT_2_PEND = 0xff; //清除中断标志
}
unsigned int time_val; // 全局定时计数变量
/* 功能:
* 启动某个定时器,如启动定时器0,则调用start_time(0);
*/
void start_timer(unsigned long num)
{
if(num < 2)
{
TCON |= 1<< (num * 8);
}
else
{
TCON |= 1<< ((num + 1 )* 4);
}
}
/* 功能:
* 关闭某个定时器,如关闭定时器3,则调用start_time(3);
*/
void stop_timer(unsigned long num)
{
if(num < 2)
{
TCON &= ~(1<< (num * 8));
}
else
{
TCON &= ~(1<< ((num + 1 )* 4));
}
}
/* 功能:
* 复位并关闭定时器0
*/
void reset_time_val(void)
{
time_val = 0;
stop_timer(0);
}
/* 功能:
* 关闭所有PWM定时器
*/
void stop_all_timer(void)
{
TCON = 0;
}
/* 功能:
* timer0中断的真正的中断处理函数
*/
void isr_timer()
{
TINT_CSTAT = TINT_CSTAT; //清timer0的中断状态寄存器
VIC0ADDR = 0; //清中断控制向量
printf("\nCurrent count time = %d.%d\r",time_val/10,time_val%10); // 打印中断发生次数
time_val++;
}
/*
* 定时器0初始化调用timer_init(0,65,4,6250,0);
* timer_num = 0,uprescaler = 65, udivider = 4 = 0x0100,utcntb=6250,utcmpb = 0.
* 定时器的输入时钟 = PCLK / ( {prescaler value + 1} ) / {divider value}
* = PCLK/(65+1)/16=62500hz
* 本实验定时器0.1秒
*/
void timer_init(unsigned int timer_num,unsigned long uprescaler,unsigned long udivider,unsigned long utcntb,unsigned long utcmpb)
{
unsigned long temp0;
temp0 = TCFG0; //设置预分频系数为66
time_val = 0;
if((timer_num < 2) && (timer_num >= 0)) // 一级预分频参数的设置(timer0,1)
{
temp0 = (temp0 & (~(0xff))) | ((uprescaler-1)<<0);
TCFG0 = temp0;
}
else if((timer_num < 5) && (timer_num >= 2)) // 一级预分频参数的设置(timer2,3,4)
{
temp0 = (temp0 & (~(0xff << 8))) | ((uprescaler-1)<<8);
TCFG0 = temp0;
}
TCFG1 = (TCFG1 & (~(0xf<<4*timer_num))& (~(1<<20))) |(udivider<<4*timer_num); // 二级预分频,这里是16分频
switch(timer_num) // 1s = 62500hz,即每秒计数器计数62500次
{
case 0:
TCNTB0 = utcntb;
TCMPB0 = utcmpb;
break;
case 1:
TCNTB1 = utcntb;
TCMPB1 = utcmpb;
break;
case 2:
TCNTB2 = utcntb;
TCMPB2 = utcmpb;
break;
case 3:
TCNTB3 = utcntb;
TCMPB3 = utcmpb;
break;
case 4:
TCNTB4 = utcntb;
break;
default:
break;
}
TCON |= 1<<(1+(timer_num*8)); //第一次必须手动更新
TCON &= ~(1<<(1+(timer_num*8))); //紧接着要清除手动更新位
TCON |= (1<<(0+(timer_num*8)))|(1<<(3+(timer_num*8))); // 自动加载和启动timer0
TINT_CSTAT = (TINT_CSTAT & (~(1<<timer_num)))|(1<<(timer_num)); // 使能timer0中断
}
void irq_handler(void)
{
void (*isr_p)(void); //定义一个函数指针,这个指针指向一个返回值类型为void,参数为void的函数
if(VIC0IRQSTATUS) //VICxIRQSTATUS = 1 表示有中断发生
isr_p = (void (*)(void))VIC0ADDR; //取出中断服务程序地址
else if(VIC1IRQSTATUS)
isr_p = (void (*)(void))VIC1ADDR; //取出中断服务程序地址
else if(VIC2IRQSTATUS)
isr_p = (void (*)(void))VIC2ADDR; //取出中断服务程序地址
else if(VIC3IRQSTATUS)
isr_p = (void (*)(void))VIC3ADDR; //取出中断服务程序地址
(*isr_p)(); //跳转到真正的中断处理函数
}
void int_init(void)
{
pExceptionIRQ = (unsigned long)IRQ_handle; //设置中断跳转地址
VIC0VECTADDR21 = (unsigned long)isr_timer; //设置中断服务程序地址
VIC0VECTADDR16 = (unsigned long)isr_key;
VIC0INTSELECT &= (~(1<<21))&(~(1<<16)); //将外部中断21、16设为IRQ模式
VIC0INTENABLE |= (1<<21)|(1<<16); //使能中断控制器
}
.global _start
.global IRQ_handle
_start:
ldr sp, =0x40000000 @设置栈,以便调用c函数
mov r0, #0x53 @进入SVC模式,开中断(把I位设为0)
msr CPSR_cxsf, r0
bl main @调用main函数
IRQ_handle:
ldr sp, =0xD0037F80
sub lr, lr, #4 @计算返回地址
stmfd sp!, {r0-r12, lr} @保存现场
bl irq_handler @跳转到中断处理函数
ldmfd sp!, {r0-r12, pc}^ @恢复现场