2440裸机驱动之PWM开发

原文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控制蜂鸣器注意设置GPB0TOUT0。这里设置PWM频率为1HZ,占空比50%,也就是半秒响一次。S3C2440定时器中有双缓冲rTCNTBnrTCMPBn,他们是可以通过编程访问的,他们会将数据自动装入rTCNTnrTCMPn,这两个16位计数器对程序员来说是透明的。

一般启动定时器的步骤如下:

将初始值写入到rTCNTnrTCMPn

设置对应定时器的手动更新位,自动装载

启动定时器,并关闭手动更新位

#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,除法器为16rTCNTB00x7a12,所以 50M/0x64/16/0x7a121HZ,所以中断周期为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输出步骤

1.S3C2440有5个16位的定时器,其中0,1,2,3定时器都有一个输出引脚,通过定时器控制引脚周期性的输出高低电平(通过引进GPB0~GPB3输出,这是GPB引脚的第三功能),从而实现pwm功能。
 
2.定时器的时钟源是PCLK,太高,进过两次分频,第一级8位预分频(0~255),第二级4位分频,然后输出5种频率的时钟,2分频,4分频,8分频,16分频和外部时钟TCLK0和TCLK1。定时器的时钟频率=PCLK/(预分频值+1)/(分频值)【预分频值prescaler和分频值mux在寄存器TCFG0和TCFG1中设置】
 
3.电平反转的原理:当TCNTn的值和TCMPn的值相同时,定时器n有一个反转,会在出书引脚输出一个电平,然后TCNTn继续减1,直至为0,再发生一次反转,在引脚输出一个电平,这样就实现了高低电平的反转,减为0时会触发中断。
 
4.变相器开关决定了起始输出为高电平还是低电平,也就决定了pwm输出的形式(输出波形图的不同,当定时器停止工作后输出的是最后时刻的电平状态,这样也不一样,通过在开始的时候设置变相器开关的转台就保证了一些设备对电平的要求)
 
5.自动重载:自动重载在TCNTn的只为0时,将TCNTBn的值重装到TCNTn中,TCMPBn的值会重装到TCMPn中,可以从TCNTon读取TCNTn当前的值 【定时器控制寄存器TCON设置】
 
6.在停止定时器的之前要停止自动重载,否则一直重装初值,TCNTn的值一直和TCMPn的值进行比较,在进行比较的时候TCMPn的值不变,而TCNTn的值不断减1,当TCNTn=TCMPn时,反转电平,然后TCNTn继续减1,到0时,再次反转电平,并触发一个中断,并且自动重装。
 
7.定时器初始化:1)定时器时钟频率初始化:(定时器频率:每秒会把计数器减去该频率,即每秒减去计数器值得个数叫定时器频率)
                           2)设置定时器计数值:看项目要求需要输出怎样的方波,譬如需要led灯每隔0.5s闪烁一次,则方波周期t=1s,假设比较值为0 ,则需要每隔0.5秒输出引脚反转一次,而定时器的频率假设为50mhz/(49+1)/16,分频后频率为62500hz,即每秒计数62500,就是说计数到62500个数后电平反转一次,而要求0.5秒反转一次,所以应该把计数初值设为625000/2
                          3)设置中断函数:用来处理具体的功能,譬如led的亮灭控制
 
8.寄存器

TCNTBn   :计数缓冲器,当定时器使能的时候,被加载到递减寄存器中的初始值(重载初值寄存器)

TCMPBn : 比较缓冲器, 加载到比较寄存器中与递减寄存器相比较的初值,决定占空比
TCNTn    :定时器计数器,递减计数器
TCMPn   :定时器比较器,在比较的过程中保持不变
TCNTOn :定时器监视寄存器,能读出TCNTNn和TCNTBn的值
TCFG0    :定时器配置寄存器,决定了各定时器的预分频值
TCFG1      :定时器配置寄存器,决定了各定时器的分频值    定时器的时钟频率=PCLK/(预分频值+1)/(分频值)
TCON       :定时器控制寄存器,决定了各定时器的加载方式,变相器开关,手动更新和定时器启动
原文地址:https://www.cnblogs.com/jiangu66/p/3186853.html