原文http://blog.chinaunix.net/uid-14114479-id-3125685.html
ARM驱动蜂鸣器的方式有两种:一种是PWM输出口直接驱动,另一种是利用IO定时翻转电平产生驱动波形对蜂鸣器进行驱动。PWM输出口直接驱动是利用PWM输出口本身可以输出一定的方波来进行驱动。在ARM中可以用几个特殊功能寄存器对占空比和周期进行设置。通过设置这些寄存器产生符合蜂鸣器要求的方波后,这个时候利用这个方波就可以对蜂鸣器进行驱动了。使用PWM时,通过TCMPn可以决定脉宽,TCNTn决定频率,TCMPn/TCNTn决定占空比。使用IO口电平翻转时,需要使用定时器来做定时,通过定时翻转产生符合要求频率的波形。由于蜂鸣器一般的工作电流比较大,以至于IO口无法驱动,一般使用放大电路来驱动,一般使用三极管来放大电流就可以了。
用PWM控制蜂鸣器:
rTCFG0用来设置预分频值,rTCFG1用来设置多路选择器MUX的分频值,TCNTB0也起到分频作用。使用PWM控制蜂鸣器注意设置GPB0为TOUT0。这里设置PWM频率为1HZ,占空比50%,也就是半秒响一次。S3C2440定时器中有双缓冲rTCNTBn,rTCMPBn,他们是可以通过编程访问的,他们会将数据自动装入rTCNTn,rTCMPn,这两个16位计数器对程序员来说是透明的。
一般启动定时器的步骤如下:
将初始值写入到rTCNTn,rTCMPn
设置对应定时器的手动更新位,自动装载
启动定时器,并关闭手动更新位
#define rGPBCON (*(volatile unsigned *)0x56000010)
#define rGPBDAT (*(volatile unsigned *)0x56000014)
#define rGPBUP (*(volatile unsigned *)0x56000018)
#define rTCFG0 (*(volatile unsigned *)0x51000000)
#define rTCFG1 (*(volatile unsigned *)0x51000004)
#define rTCNTB0 (*(volatile unsigned *)0x5100000C)
#define rTCMPB0 (*(volatile unsigned *)0x51000010)
#define rTCON (*(volatile unsigned *)0x51000008)
int Main(){
rGPBCON &= 0xfffffc;
rGPBCON |= 0x2;
rTCFG0 &= ~0xff;
rTCFG0 |= 0x64;
rTCFG1 &= ~0xf;
rTCFG1 |= 0x3;
rTCNTB0 = 0x7530;
rTCMPB0 = rTCNTB0>>1;
rTCON &= ~0x1f;
rTCON |= (0x1)|(0x1<<1)|(0x1<<3);
rTCON &= ~2;
while(1);
return 0;
}
使用定时器产生中断使IO电平翻转,控制蜂鸣器:
定时器注意设置rINTMSK开中断,并在中断处理程序中清除中断请求位。rSRCPND |= 0x1<<10;rINTPND |= 0x1<<10;清除rINTPND通过设置相应为1进行清除。rSTCPND是向相应位写数据清除。如果不清除,会一直响应这个中断。在Main函数中清一下是为了防止以前这个位申请中断,所以清一下,以防万一。定时器接在APB总线上,所以用PCLK时钟。在这里我设预分频为0x64,除法器为16,rTCNTB0为0x7a12,所以 50M/0x64/16/0x7a12为1HZ,所以中断周期为1s,所以每一秒蜂鸣器响一下,然后隔一秒,然后再响。
#define rGPBCON (*(volatile unsigned *)0x56000010)
#define rGPBDAT (*(volatile unsigned *)0x56000014)
#define rGPBUP (*(volatile unsigned *)0x56000018)
#define rSRCPND (*(volatile unsigned *)0x4A000000)
#define rINTPND (*(volatile unsigned *)0x4A000010)
#define rTCFG0 (*(volatile unsigned *)0x51000000)
#define rTCFG1 (*(volatile unsigned *)0x51000004)
#define rTCNTB0 (*(volatile unsigned *)0x5100000C)
#define rTCON (*(volatile unsigned *)0x51000008)
#define rINTMSK (*(volatile unsigned *)0x4A000008)
#define U32 unsigned int
#define _ISR_STARTADDRESS 0x33ffff00
#define pISR_TIMER0 (*(unsigned *)(_ISR_STARTADDRESS+0x48))
int count;
void __irq Timer0_ISR(void){
rSRCPND |= 0x1<<10;
rINTPND |= 0x1<<10;
count++;
if(count %2 == 1)
rGPBDAT |= 0x01;
else
rGPBDAT &= 0xfe;
if(count == 1000)
count = 0;
}
int Main(){
count = 0;
rGPBCON &= 0xfffffc;
rGPBCON |= 0x1;
rGPBDAT &= 0xffe;
rGPBUP &= 0xfe;
pISR_TIMER0 = (U32)Timer0_ISR;
rSRCPND |= 0x1<<10;
rINTPND |= 0x1<<10;
rINTMSK &= ~(0x1<<10);
rTCFG0 &= ~0xff;
rTCFG0 |= 0x64;
rTCFG1 &= ~0xf;
rTCFG1 |= 0x3;
rTCNTB0 = 0x7a12;
rTCON &= ~0x1f;
rTCON |= 0xb;
rTCON &= ~0x2;
while(1);
return 0;
}
PWM(Pulse Width Modulation)——脉宽调制,它是利用微控制器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量、通信、功率控制与变换等许多领域。
s3c2440芯片中一共有5个16位的定时器,其中有4个定时器(定时器0~定时器3)具有脉宽调制功能,因此用s3c2440可以很容易地实现PWM功能。下面就具体介绍如何实现PWM功能。
1、PWM是通过引脚TOUT0~TOUT3输出的,而这4个引脚是与GPB0~GPB3复用的,因此要实现PWM功能首先要把相应的引脚配置成TOUT输出。
2、再设置定时器的输出时钟频率,它是以PCLK为基准,再除以用寄存器TCFG0配置的prescaler参数,和用寄存器TCFG1配置的divider参数。
3、然后设置脉冲的具体宽度,它的基本原理是通过寄存器TCNTBn来对寄存器TCNTn(内部寄存器)进行配置计数,TCNTn是递减的,如果减到零,则它又会重新装载TCNTBn里的数,重新开始计数,而寄存器TCMPBn作为比较寄存器与计数值进行比较,当TCNTn等于TCMPBn时,TOUTn输出的电平会翻转,而当TCNTn减为零时,电平会又翻转过来,就这样周而复始。因此这一步的关键是设置寄存器TCNTBn和TCMPBn,前者可以确定一个计数周期的时间长度,而后者可以确定方波的占空比。由于s3c2440的定时器具有双缓存,因此可以在定时器运行的状态下,改变这两个寄存器的值,它会在下个周期开始有效。
4、最后就是对PWM的控制,它是通过寄存器TCON来实现的,一般来说每个定时器主要有4个位要配置(定时器0多一个死区位):启动/终止位,用于启动和终止定时器;手动更新位,用于手动更新TCNTBn和TCMPBn,这里要注意的是在开始定时时,一定要把这位清零,否则是不能开启定时器的;输出反转位,用于改变输出的电平方向,使原先是高电平输出的变为低电平,而低电平的变为高电平;自动重载位,用于TCNTn减为零后重载TCNTBn里的值,当不想计数了,可以使自动重载无效,这样在TCNTn减为零后,不会有新的数加载给它,那么TOUTn输出会始终保持一个电平(输出反转位为0时,是高电平输出;输出反转位为1时,是低电平输出),这样就没有PWM功能了,因此这一位可以用于停止PWM。
PWM有很多用途,在这里我利用开发板的资源,用它来驱动蜂鸣器,并通过改变脉宽来改变蜂鸣器发声的频率。下面的程序就是利用PWM来驱动蜂鸣器,脉宽从低到高,再从高到低,周而复始。我们还利用4个LED来指示频率的高低,最高时LED全亮,最低时LED全灭。并且我们用两个按钮来分别暂停蜂鸣器和重新开启蜂鸣器:
#define _ISR_STARTADDRESS 0x33ffff00
#define U32 unsigned int
typedef unsigned char BOOL;
#define TRUE 1
#define FALSE 0
#define pISR_EINT0 (*(unsigned *)(_ISR_STARTADDRESS+0x20))
#define pISR_EINT1 (*(unsigned *)(_ISR_STARTADDRESS+0x24))
#define rSRCPND (*(volatile unsigned *)0x4a000000) //Interrupt request status
#define rINTMSK (*(volatile unsigned *)0x4a000008) //Interrupt mask control
#define rINTPND (*(volatile unsigned *)0x4a000010) //Interrupt request status
#define rGPBCON (*(volatile unsigned *)0x56000010) //Port B control
#define rGPBDAT (*(volatile unsigned *)0x56000014) //Port B data
#define rGPBUP (*(volatile unsigned *)0x56000018) //Pull-up control B
#define rGPFCON (*(volatile unsigned *)0x56000050) //Port F control
#define rEXTINT0 (*(volatile unsigned *)0x56000088) //External interrupt control register 0
#define rTCFG0 (*(volatile unsigned *)0x51000000) //Timer configuration
#define rTCFG1 (*(volatile unsigned *)0x51000004) //Timer configuration
#define rTCON (*(volatile unsigned *)0x51000008) //Timer control
#define rTCNTB0 (*(volatile unsigned *)0x5100000c) //Timer count buffer 0
#define rTCMPB0 (*(volatile unsigned *)0x51000010) //Timer compare buffer 0
BOOL stop;
static void __irq Key1_ISR(void) //暂停键,关闭蜂鸣器
{
rSRCPND = rSRCPND | (0x1<<1);
rINTPND = rINTPND | (0x1<<1);
rTCON &= ~0x8; //禁止定时器自动重载,即关闭定时器
stop = TRUE;
}
void __irq Key4_ISR(void) //重启键,开启蜂鸣器
{
rSRCPND = rSRCPND | 0x1;
rINTPND = rINTPND | 0x1;
stop = FALSE;
}
void delay(int a)
{
int k;
for(k=0;k<a;k++)
;
}
void Main(void)
{
int freq;
rGPBCON = 0x155556; //B0为TOUT0,B5~B8为输出,给LED
rGPBUP = 0x7ff;
rGPFCON = 0xaaaa; //F口为EINT,给按钮
//按钮的一些必要配置
rSRCPND = 0x07;
rINTMSK = ~0x07;
rINTPND =0x07;
rEXTINT0 = 0x22;
freq = 2500;
rTCFG0 &= 0xFFFF00;
rTCFG0 |= 0x31; //prescal 是49
rTCFG1 &= ~0xF; //1/2,因为PCLK为50MHz,所以50MHz/50/2=500kHz
rTCNTB0 = 5000;
rTCMPB0 = freq;
rTCON &= ~0x1F;
rTCON |= 0xf; //死区无效,自动装载,电平反转,手动更新,定时器开启
rTCON &= ~0x2 ; //手动更新位清零,PWM开始工作
pISR_EINT0 = (U32)Key4_ISR;
pISR_EINT1 = (U32)Key1_ISR;
stop = FALSE;
rGPBDAT = ~0x60; //两个LED亮
while(1)
{
//频率递增
for ( ; freq<4950 ; )
{
freq+=10;
rTCMPB0 = freq; //重新赋值
delay(20000);
while (stop == TRUE) //暂停
{
delay(1000);
if (stop ==FALSE) //判断是否重启
{
rTCON &= ~0x1F;
rTCON |= 0xf;
rTCON &= ~0x2 ; //恢复PWM功能
}
}
//4个LED随着频率的高低,时灭时亮
if(freq == 100)
rGPBDAT = ~0x1e0;
if(freq == 1300)
rGPBDAT = ~0xe0;
if(freq == 2500)
rGPBDAT = ~0x60;
if(freq == 3700)
rGPBDAT = ~0x20;
if(freq == 4900)
rGPBDAT = ~0x0;
}
//频率递减
for( ; freq>50 ; )
{
freq-=10;
rTCMPB0 = freq;
delay(20000);
while (stop == TRUE)
{
delay(1000);
if (stop ==FALSE)
{
rTCON &= ~0x1F;
rTCON |= 0xf;
rTCON &= ~0x2 ;
}
}
if(freq == 100)
rGPBDAT = ~0x1e0;
if(freq == 1300)
rGPBDAT = ~0xe0;
if(freq == 2500)
rGPBDAT = ~0x60;
if(freq == 3700)
rGPBDAT = ~0x20;
if(freq == 4900)
rGPBDAT = ~0x0;
}
}
}
这里还需要说明几点:
1、开发板上的蜂鸣器是高电平发声,低电平停止,而TOUT0定时无效时,是高电平输出,因此为了使PWM无效时,蜂鸣器不发声,我把输出电平进行了反转处理(置TCON中的输出反转位);
2、在这里,我是通过按键把stop标志变量置为FALSE来跳出while循环,重新开始蜂鸣,但不知什么原因,如果在while循环内不加一段等待时间,则永远不能跳出循环体,因此我不得不加了一个delay函数,让它等待一段时间。关于这个问题,我还给不出一个满意的解释,也不知是哪里出了问题!
它是通过寄存器TCON来实现的,一般来说每个定时器主要有4个位要配置(定时器0多一个死区位):启动/终止位,用于启动和终止定时器;手动更新位,用于手动更新TCNTBn和TCMPBn,这里要注意的是在开始定时时,一定要把这位清零,否则是不能开启定时器的;输出反转位,用于改变输出的电平方向,使原先是高电平输出的变为低电平,而低电平的变为高电平;自动重载位,用于TCNTn减为零后重载TCNTBn里的值,当不想计数了,可以使自动重载无效,这样在TCNTn减为零后,不会有新的数加载给它,那么TOUTn输出会始终保持一个电平(输出反转位为0时,是高电平输出;输出反转位为1时,是低电平输出),这样就没有PWM功能了,因此这一位可以用于停止PWM。
pwm输出步骤
TCNTBn :计数缓冲器,当定时器使能的时候,被加载到递减寄存器中的初始值(重载初值寄存器)