一、概述
脉冲宽度调制 PWM(Pulse Width Modulation),简称脉宽调制。是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,将模拟信号转换为脉波,一般转换后脉波的周期固定,但脉波的占空比会依模拟信号的大小而改变。广泛应用在从测量、通信到功率控制与变换的许多领域中。
简而言之,PWM是一种对模拟信号电平进行数字编码的方法。
实质上是定时器的应用。
1、定时器的本质
定时器的本质是计数器,计数器计的是一个均匀脉冲,即计数固定周期的脉冲(固定频率)。
2、定时器的应用
在某个时间,电平翻转,则可以获得一个脉冲,新的频率属性(周期T、脉宽比等)都是由定时器决定。通过这个新的频率,可以控制一些外设。
3、PMW
占空比:就是输出的PWM中,高电平保持的时间 与该PWM的时钟周期的时间之比,即
脉宽比(占空比) = T(高电平) / 时钟周期 (若为均匀脉冲,则脉宽比为50%。高电平时间越长,脉宽比越大)
4、PMW的应用
它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用于测量,通信,功率控制与变换等许多领域。脉冲宽度调制(PWM)是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。
实际上实现的是能量控制。
应用:调光灯,电机控制.....
二、PWM定时器
1、基本概念
S3C2440A 有 5个16 位定时器。其中定时器0、1、2和3 具有脉宽调制(PWM)功能。定时器4是一个无输出引脚的内部定时器。定时器0还包含用于大电流驱动的死区发生器。
定时器 0和1 共用一个8 位预分频器,定时器2、3和4 共用另外的8 位预分频器。每个定时器都有一个可以生成5种不同分频信号(1/2,1/4,1/8,1/16和TCLK)的时钟分频器。每个定时器模块从相应8位预分频器得到时钟的时钟分频器中得到其自己的时钟信号。8位预分频器是可编程的,并且按存储在TCFG0和TCFG1寄存器中的加载值来分频PCLK。
定时计数缓冲寄存器(TCNTBn)包含了一个当使能了定时器时的被加载到递减计数器中的初始值。
定时比较缓冲寄存器(TCMPBn)包含了一个被加载到比较寄存器中的与递减计数器相比较的初始值。这种 TCNTBn和TCMPBn的双缓冲特征保证了改变频率和占空比时定时器产生稳定的输出。TCMPBn的值是用于脉宽调制(PWM)。当递减计数器的值与定时器控制逻辑中的比较寄存器的值相匹配时定时器控制逻辑改变输出电平。因此,比较寄存器决定PWM输出的开启时间(或关闭时间)。
简言之,定时计数缓冲寄存器(TCNTBn)决定频率, 定时比较缓冲寄存器(TCMPBn)决定周期(何时翻转电平)。脉宽比由两者同时决定。
特性:
– 五个 16 位定时器
– 两个 8 位预分频器和两个 4 位分频器
– 可编程输出波形的占空比控制(PWM)
– 自动重载模式或单稳脉冲模式
– 死区发生
2、PWM定时器操作
1)基本时序操作
2)自动重载和双缓冲
S3C2440A PWM 定时器包含双缓冲功能,允许在不停止当前定时器操作的情况下为下次定时器操作改变重载值。所以即使设置了新的定时器值,当前定时器操作仍然顺利的被完成。
定时器值可以被写入到定时器计数缓冲寄存器(TCNTBn)中并且可以从定时器计数监视寄存器(TCNTOn)中读取当前定时器的计数值。如果读取TCNTBn,读出的值不是指示当前计数器的状态而是下次定时器持续时间的重载值。
自动重载操作在 TCNTn到达0 时复制 TCNTBn 到 TCNTn。写入到TCNTBn的该值,只有在TCNTn到达0并且使能了自动重载时才被加载到TCNTn。如果TCNTn变为0 并且自动重载位为0,TCNTn不会进一步任何操作 。
实例:使用手动更新位和变相位初始化定时器
当递减计数器到达 0时发生定时器的自动重载操作。所以必须预先由用户定义一个TCNTn的起始值。在这种情况下,必须通过手动更新位加载起始值。以下步骤描述了如何启动一个定时器:
1) 初始值写入到 TCNTBn和TCMPBn中。
2) 设置相应定时器的手动更新位。推荐你配制变相开/关位。(无论是否使用变换极性)
3) 设置相应定时器的开始位来启动定时器(并且清除手动更新位)。
如果定时器被强制停止,TCNTn保持计数器值并且不会从TCNTBn重载。如果需要设置一个新值,执行手动更新。
注意:
无论何时 TOUT变相开/关位改变,是否定时器运行中都将改变TOUT逻辑值。因此,最好带手动更新位配制变相开/关位。
上述图显示了以下过程的结果:
1. 使能自动重载功能。设置 TCNTBn 为 160(50+110)并且设置TCMPBn为110。置位手动更新位并且配制变相位 (开/关)。 手动更新位分别设置TCNTn和TCMPn到TCNTBn和TCMPBn的值中。 然后分别设置TCNTBn和TCMPBn为80(40+40)和40,以决定下次重载值。
2. 设置启动位,预设手动更新位为0,变相位为关,自动重载位为开。定时器在定时器分辨率内的等待时间后启动递减计数。
3. 当 TCNTn与TCMPn的值相同时,TOUTn的逻辑电平从低电平变为高电平。
4. 当 TCNTn到达0时, 发出中断请求并且TCNTBn的值加载到暂存器中。 在下一个定时器标记时刻, 重载TCNTn为暂存器(TCNTBn)的值。
5. 中断服务程序(ISR)中,为下一个持续时间分别设置TCNTBn和TCMPBn为80(20+60)和60。
6. 当 TCNTn与TCMPn的值相同时,TOUTn的逻辑电平从低电平变为高电平。
7. 当当 TCNTn到达0时,触发一个中断自动重载TCNTn为TCNTBn的值。
8. 中断服务程序(ISR)中,禁止自动重载和中断请求以停止定时器。
9. 当 TCNTn与TCMPn的值相同时,TOUTn的逻辑电平从低电平变为高电平。
10. 尽管 TCNTn到达0,但因为禁止了自动重载,所以TCNTn并不会再次重载并且定时器已经停止了。
11. 不再产生中断请求。
三、实战演练
1、实现蜂鸣器播放音调的功能
area Init,code, readonly
code32
entry
start
ldr sp, =0x34000000
import Main
b Main
stop
b stop
end
#include "2440addr.h"
/*
tclk = pclk / ((pre + 1) * div)
fout = 1kHz
pclk = 50MHz
pre = 15
div = 8
1/fout = count * (1/tclk)
1/fout = count * ((pre+1) * div) / pclk
count = pclk / (fout*(pre+1)* div)
= 50Mhz / (1KHz * 16 * 8)
= 390.625 = 391
*/
/*
*/
#define PCLK 50000000
unsigned int gFre[7] =
{500, 650, 880, 1000, 1200, 1400, 1600};
void setFre(int fre)
{
// 定时器配置
rTCFG0 |= (15<<0); // pre = 15
rTCFG1 |= (2<<0); // div = 8
rTCNTB0 = PCLK / (fre * 16 * 8);
rTCMPB0 = rTCNTB0 >> 1; // 占空比50%开发板
rTCON |= (1<<3) | (1<<1) | (1<<0); // 自动加载,手动更新,启动定时器。
rTCON &= ~(1<<1); // 关闭手动加载
}
void toutIoCfg(void)
{
// 引脚配置
rGPBCON &= ~(3<<0);
rGPBCON |= (2<<0); // 配置为tout0功能
}
void tout0Init(void)
{
// 引脚配置
rGPBCON &= ~(3<<0);
rGPBCON |= (2<<0); // 配置为tout0功能
// 定时器配置
rTCFG0 |= (15<<0); // pre = 15
rTCFG1 |= (2<<0); // div = 8
rTCNTB0 = 391;
rTCMPB0 = rTCNTB0 >> 1; // 占空比50%
rTCON |= (1<<3) | (1<<1) | (1<<0); // 自动加载,手动更新,启动定时器。
rTCON &= ~(1<<1); // 关闭手动加载
}
void delay(unsigned int loops)
{
while(loops-->0);
}
void Main(void)
{
int i=0;
toutIoCfg();
while(1)
{
for(i=0; i<7; i++)
{
setFre(gFre[i]);
delay(0x10000000);
}
}
}
2、实现蜂鸣器播放音乐 《大海》 的功能
AREA Init,CODE,READONLY
ENTRY
CODE32
;IMPORT Eint_Irq
import Main
EXPORT __ENTRY
__ENTRY
start
b reset_handler
nop
b swi_handler
nop
nop
nop
b irq_handler
nop
reset_handler
;ldr r0 , =0x53000000
;mov r1 , #0x0
;str r1 , [r0]
ldr r0,=0x34000000
mov sp, r0
sub r0, r0, #0x500
mrs r1, cpsr
bic r1, r1, #0x1f
orr r1, r1, #0x12
msr cpsr_c, r1
mov sp, r0
sub r0, r0, #0x500
mrs r1, cpsr
bic r1, r1, #0x1f
bic r1, r1, #0x80
orr r1, r1, #0x1f
msr cpsr_c, r1
mov sp, r0
b Main
stop
b stop
irq_handler
sub lr, lr, #4
stmfd sp!, {r0-r12, lr}
ldr r1, =0x56000014
ldr r0, =0
str r0, [r1]
;bl Eint_Irq
ldmfd sp!, {r0-r12, pc}^
;import c_swi_handler
swi_handler
stmfd sp!, {r0-r12, lr}
ldr r0, [lr, #-4]
bic r0, r0, #0xff000000
;bl c_swi_handler
ldmfd sp!, {r0-r12, pc}^
export asmdelay
asmdelay
;mov r7, #0x1000000
mov r7, r0
del_loop
sub r7, r7, #1
cmp r7, #0
bne del_loop
mov pc, lr
END
#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 rTCFG0 (*(volatile unsigned *)0x51000000) //Timer 0 configuration
#define rTCFG1 (*(volatile unsigned *)0x51000004) //Timer 1 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
#define rTCNTO0 (*(volatile unsigned *)0x51000014) //Timer count observation 0
#define PCLK 50000000
void Buzzer_init(void)
{
rGPBCON &= ~3; //set GPB0 as tout0, pwm output
rGPBCON |= 2;
rTCFG0 &= ~0xff;
rTCFG0 |= 15; //prescaler = 15+1
rTCFG1 &= ~0xf;
rTCFG1 |= 2; //mux = 1/8
rTCNTB0 = (PCLK>>7)/1000;
rTCMPB0 = rTCNTB0>>1; // 50%
rTCON &= ~0x1f;
rTCON |= 0xb; //disable deadzone, auto-reload, inv-off, update TCNTB0&TCMPB0, start timer 0
rTCON &= ~2; //clear manual update bit
}
void Buzzer_Freq_Set( unsigned int freq )
{
rTCNTB0 = (PCLK>>7)/freq;
rTCMPB0 = rTCNTB0>>1; // 50%
}
void Buzzer_Stop( void )
{
rGPBCON &= ~3; //set GPB0 as output
rGPBCON |= 1;
rGPBDAT &= ~1;
}
/*
* 调用该函数, 可以控制蜂鸣器响起, 原理是使能了PWM的TOU1功能为动重装模式
*/
void beep_on( void )
{
rTCON = (1<<0) | (1<<3);//clear manual update bit
}
/*
* 调用该函数, 可以控制蜂鸣器响起, 原理是停止了PWM的TOU1功能
*/
void beep_off( void )
{
rTCON &= ~1;
}
//音阶频率表
const unsigned int yinyue[3][7]
={
262, 294, 330, 349, 392, 440, 494, //低音
523, 587, 659, 698, 784, 880, 988, //中音
1046, 1174, 1318, 1396, 1568, 1760, 1975 //高音
};
/*
* 大海的简谱, 如果大海能够.....
*/
const unsigned char dahai[] =
{
0x13, 0x15 , 0x16, 0x16, 0x16, 0x16, 0x21, 0x16, 0x15, 0x15, 0x16, 0x15, //哀愁
0x13, 0x12 , 0x11, 0x11, 0x11, 0x11, 0x12, 0x13,
0x13, 0x12 , 0x11, 0x11, 0x11, 0x11, 0x21, 0x16, 0x15, 0x15, 0x16, 0x15,
0x13, 0x15, 0x16, 0x21, 0x21, 0x16, 0x15, 0x15,//飘远
};
/*
* 大海的简谱, 控制每一个音的时间 1为长时间 4为短时间
*/
const unsigned char time[] =
{
4, 4, 4, 2, 4 , 2, 4, 4, 4, 2, 4, 2 ,//哀愁
4, 4, 4, 2, 4, 2, 2, 1,
4, 4, 4, 2, 4, 2, 4, 4, 4, 2, 4, 2,
4, 4, 2, 4, 2, 4, 4, 1 //飘远
};
//主函数
int Main(void)
{
unsigned int loop = 0; //循环控制变量
unsigned int a = 0; //计算高中低音使用的变量
unsigned int b = 0; //计算do re mi ....使用的变量
unsigned int delay = 0; //计算每一个音延时使用的变量
//GPH0.GPH0CON = (GPH0.GPH0CON) & ~(0x3f<<1); //初始化key1-key6的引脚功能为输入
Buzzer_init(); //初始化PWM硬件, 此刻没有启动PWM
//while( ( GPH0.GPH0DAT & (0x3ful<<1) ) == (0x3ful<<1) );//等待有任意一个按键被按下,程序才会向下
while(1)
{
//循环播放 音乐
for(loop=0; loop
>4; //计算出高低音
b = (dahai[loop] & 0x0f )-1; //计算出DO RE MI FA SO LA XI
delay = 0x6500000ul/time[loop]; //计算出每个音的播放间隔时间
Buzzer_Freq_Set( yinyue[a][b] ); //重新设定频率
beep_on(); //发出一个音
asmdelay(delay); //播放延时
beep_off(); //关闭蜂鸣器, 每个音播放完成后有间隔感
asmdelay(0x20); //关闭蜂鸣器
}
asmdelay(1000000); //播放延时
}
}