ARM-PWM蜂鸣器实验
-
一、什么是PWM
-
二、PWM信号的输出和应用
-
三、PWM定时器
-
-
(1)时钟信号10Hz等价于这块芯片的心跳,一块芯片主频,每隔0.1s会被计数器接收,计数器每接收一次数值就会减1,一直到减到0为止;如果周期是0.1s,而计数器是100,则定时时间为10s,当计数器减到0后会产生一个中断。
-
(2)输入信号是10Hz,计数器的值是100,比较器的值是50,计数器从100减到50需要5s,5s后计数器的值和比较器的值相等,输出管脚pin输出的电平就会进行反转,从低电平跳到高电平,此时计数器还有50,50减到0需要5s,5s后输出管脚pin从高电平跳转到低电平。
-
(3)比较器控制输出信号周期内的高电平时间,计数器和输入时钟信号频率决定输出时钟信号频率
-
(4)计数器的值 = 输入时钟信号频率 / 输出时钟信号频率
-
(5)周期T(Period):一个完整PWM波形所持续的时间
-
(6)频率f(Frequency):一秒内的周期数
-
(7)占空比(Duty):高电平持续时间(Ton )与周期时间(Period)的比值
-
(8)T = 1 / f、Duty = (Ton / Period) x 100%
-
-
1、定时器和计数器的区别
-
2、面积等效原理
-
-
四、Samsung Exynos 4412芯片内部PWM模块
-
1、概述
-
(1)Samsung Exynos 4412 SCP(System Control Processor)有五个32位PWM(脉冲宽度调制)定时器。这些定时器产生内部中断对于ARM子系统。定时器0、1、2和3包括驱动外部I/O的PWM功能信号。定时器0中的PWM有一个可选的死区发生器功能,以支持大量的设备。定时器4是一个没有输出引脚的内部定时器。
-
(2)定时器使用APB-PCLK作为源时钟。定时器0和1共享可编程8位预分频器为PCLK提供第一级分频。定时器2、3和4共享不同的8位预分频器。每个计时器都有它自己的专用时钟分频器,提供第二级时钟分频(预分频器除以2、4、8或16)。
-
(3)每个定时器都有它的32位递减计数器;定时器时钟驱动这个计数器。==定时器计数缓冲寄存器(TCNTBn)==加载递减计数器的初始值。如果递减计数器达到零,它将生成计时器中断请求,通知CPU定时器操作完成。如果定时器下降计数器达到零,相应TCNTBn的值自动重新加载到下一个循环开始。但是,如果定时器停止,例如,在定时器运行模式下,通过清除TCONn的定时器使能位,TCNTBn的值将不会重新加载到计数器中。
-
(4)PWM功能使用定时器比较缓冲寄存器(TCMPBn)的值。定时器控制逻辑改变输出电平下计数寄存器的值与定时器控制逻辑中比较寄存器的值相匹配。因此,比较寄存器决定PWM输出的开启时间或关闭时间。
-
(5)每个定时器都是双缓冲结构,带有TCNTBn和TCMPBn寄存器,允许定时器参数在周期中更新。新值在当前计时器周期完成之前不会生效。
-
-
-
2、Samsung Exynos PWM定时器的特性
-
3、PWM内部模块图
-
-
工作步骤
-
(1)当系统时钟PCLK被使能后,定时器计数缓冲寄存器(TCNTBn)把计数器初始值加载到递减计数器中。
-
(2)定时器比较缓冲寄存器(TCMPBn)把其初始值加载到比较器中,并将该值与递减计数器的值进行比较。当递减计数器和比较器值相同时,输出电平翻转。
-
(3)递减计数器减至0后,输出电平再次翻转,完成一个输出周期。这种基于TCNTBn和TCMPBn的双缓冲特性使定时器在频率和占空比变化时能产生稳定的输出。
-
(4)每个定时器都有一个专用的由定时器时钟驱动的16位递减计数器。当递减计数器的数值达到0时,就会产生定时器中断请求来通知CPU定时器操作已完成,如果设置了Auto Reload功能,相应的TCNTBn的值会自动加载到递减计数器中以继续下次操作。
-
(5)如果定时器停止了,比如在定时器运行时清除TCON中定时器使能位,TCNTBn的值不会被加载到递减计数器中。
-
(6)TCMPBn 的值用于脉冲宽度调制。当定时器的递减计数器的值和比较器的值相匹配的时候,定时器控制逻辑将改变输出电平。因此,比较器决定了PWM 输出的开关时间。
-
-
-
-
五、蜂鸣器
-
六、PWM蜂鸣器实验
-
本实验采用==直接通过芯片内部模块输出PWM信号==
-
1、查看原理图
-
2、查看芯片手册
-
3、操作GPIO
-
4、编写代码
-
//buzzer.c #include "exynos_4412.h" #include "exynos_setup.h" //GPD0_0:GPD0的第0个引脚(每个引脚4位)BZ1 /****************************************/ /* 让蜂鸣器BZ1发出声音,声音频率:523HZ */ /****************************************/ //蜂鸣器BZ1初始化 void buzzer_init(int timer_count_buffer, int timer_compare_buffer) { //1、将GPD0CON寄存器[3,0]位清0并赋值0x2(TOUT_0),设置为定时器输出模式 SET_GPIO_MODE(GPD0.CON, 0, 0x2); //GPD0.CON = (GPD0.CON & ~0xf) | 0x2; //与上面语句效果一致 /* * PWM定时器输入时钟信号频率PCLK公式: * Timer Input Clock Frequency = PCLK / (prescaler value + 1) / (divider value) * = 100MHz / (199 + 1) / 2 = 250kHz */ //2、将TCFG0配置寄存器0的[7,0]位清0并赋值0xc7(预分频器Prescaler0的值) PWM.TCFG0 = PWM.TCFG0 & ~(0xff) | 0xc7; //一级分频 预分频器199(1~255) //3、将TCFG1配置寄存器1的[3,0]位清0并赋值0x1(Divider MUX0的值) PWM.TCFG1 = PWM.TCFG1 & ~(0xf) | 0x1; //二级分频 1/2分频 //4、确定计数器和比较器的值(定时器计数缓冲寄存器TCNTB0和定时器比较缓冲寄存器TCMPB0赋值) PWM.TCNTB0 = timer_count_buffer; // 250000 / 523 = 478<==>0x1de(计数器值 = 输入时钟频率 / 输出时钟频率) PWM.TCMPB0 = timer_compare_buffer; // 比较器值设置为计数器值的一半239<==>0xef(占空比50%) //5、TCON定时器控制寄存器第1位(Timer 0 manual update)置1,开启手动更新,加载TCNTB0和TCMPB0的值(手动写入) PWM.TCON = PWM.TCON | (0x1 << 1); //6、TCON定时器控制寄存器第1位(Timer 0 manual update)置0,关闭手动更新 PWM.TCON = PWM.TCON & ~(0x1 << 1); //7、TCON定时器控制寄存器第3位(Timer 0 auto reload on/off)置1,开启自动重新加载(TCNTB0和TCMPB0的值) PWM.TCON = PWM.TCON | (0x1 << 3); } //开启蜂鸣器BZ1 void buzzer_on(void) { //TCON定时器控制寄存器第0位置1(开启定时器),实现周期性脉冲 PWM.TCON = PWM.TCON | 0x1; } //关闭蜂鸣器BZ1 void buzzer_off(void) { //TCON定时器控制寄存器第0位置0(关闭定时器) PWM.TCON = PWM.TCON & ~(0x1); } //延时函数(单位:ms) void delay_ms(unsigned int num) { int i, j; for(i = num; i > 0; i--) { for(j = 1000; j > 0; j--) { ; } } } //蜂鸣器测试 void buzzer_test(void) { buzzer_init(478, 239); while (1) { buzzer_on(); delay_ms(100); // 延时100ms buzzer_off(); delay_ms(100); // 延时100ms } }
-
//music.c extern void buzzer_init(int timer_count_buffer, int timer_compare_buffer); extern void buzzer_on(void); extern void buzzer_off(void); // 音调 int tone[][2]= { 0xff,0xff,//占位 /* 低1-7 */ 629,314, // Do 1 561,281, // Re 2 500,250, // Mi 3 473,236, // Fa 4 421,210, // SO 5 375,188, // La 6 334,167, // Si 7 /* 中1-7 */ 315,158, // Do 8 1 281,141, // Re 9 2 250,125, // Mi a 3 236,118, // Fa b 4 210,105, // So c 5 187, 94, // La d 6 167, 84, // Si e 7 }; // 猪八戒背媳妇儿的简谱 // 0x64 ====> 6表示发的是低音的拉,4表示发出的声音是4拍 unsigned char PigBajieCarryWife[]={ 0x64,0xA3,0xC1,0xA2,0x62,0x84,0x61,0x81,0x61, 0xA4,0xA1,0x91,0xA1,0x81,0x64,0xA3,0xC1,0xD2, 0xD2,0xD2,0xA2,0xC4,0xA1,0xC1,0xA1,0xC1,0xD2, 0xD2,0xD2,0xA2,0xC4,0xC2,0x62,0xC2,0x62,0xA2, 0xA2,0x84,0x94,0x94,0x92,0x81,0x91,0xA2,0xC2, 0xD4,0xE4,0xA4,0xE4,0xA2,0xE2,0xA2,0xE2,0xA2, 0xA2,0x84,0x94,0x94,0x92,0x81,0x91,0xA2,0xC2, 0xD8 }; // 两只老虎的简谱 /* unsigned char TwoTigers[]={ 0x12,0x22,0x32,0x12,0x12,0x22,0x32,0x12,0x32, 0x42,0x52,0x2,0x32,0x42,0x52,0x2,0x51,0x61, 0x51,0x41,0x32,0x12,0x51,0x61,0x51,0x41,0x32,0x12, 0x12,0x52,0x12,0x2,0x12,0x52,0x12,0x2 }; */ // 卡农的简谱 /* unsigned char Canon[]={ 0xc2,0xa1,0xb1,0xc2,0xa1,0xb1,0xc1,0x51,0x61,0x71, 0x81,0x91,0xa1,0xb1,0xa2,0x81,0x91,0xa2,0x31,0x41, 0x51,0x61,0x51,0x41,0x51,0x31,0x41,0x51,0x42,0x61, 0x51,0x42,0x31,0x21,0x31,0x21,0x11,0x21,0x31,0x41, 0x51,0x61,0x42,0x61,0x51,0x62,0x71,0x81,0x51,0x61, 0x71,0x81,0x91,0xa1,0xb1,0xc1,0xa2,0x81,0x91,0xa2, 0x91,0x81,0x91,0x71,0x81,0x91,0xa1,0x91,0x81,0x71, 0x82,0x61,0x71,0x82,0x11,0x21,0x31,0x41,0x31,0x21, 0x31,0x81,0x71,0x81,0x62,0x81,0x71,0x62,0x51,0x41, 0x51,0x41,0x31,0x41,0x51,0x61,0x71,0x11,0x62,0x81, 0x71,0x82,0x71,0x61,0x71,0x81,0x91,0x81,0x71,0x81, 0x61,0x71 }; */ // 延时函数 void ms_delay(int ms) { int i = 0, j = 0; for (i = 1; i < ms; i++) { for (j = 1; j < 5000; j++) { ; } } return ; } // 播放音调 int play_tone(unsigned char index, unsigned char beat) { buzzer_init(tone[index][0], tone[index][1]); buzzer_on(); ms_delay(beat * 100); // 延时10ms buzzer_off(); ms_delay(10); // 延时10ms return 0; } // 播放音乐 int play_music(void) { int i = 0; unsigned char index, beat; unsigned char *p = PigBajieCarryWife; for(i = 0; i < sizeof(PigBajieCarryWife) / sizeof(char); i++) { //0x64 index = p[i] >> 4; // 音调在数组中的下标 beat = p[i] & 0xf; // 音调响的时间 play_tone(index, beat); // 播放音调 } return 0; }
-
//main.c // extern void buzzer_test(void); // 蜂鸣器测试函数声明 extern int play_music(void); int main(void) { // buzzer_test(); // 蜂鸣器测试 play_music(); return 0; }
-
//start.s .global _start _start: b reset ldr pc,_undefined_instruction @ B undefined_instruction ldr pc,_software_interrupt @ B software_interrupt ldr pc,_prefetch_abort @ B prefetch_abort ldr pc,_data_abort @ B data_abort ldr pc,_not_used @ B not_used ldr pc,_irq @ B irq ldr pc,_fiq @ B fiq @ 异常向量表 _undefined_instruction:.word _undefined_instruction @ 未定义异常 _software_interrupt:.word software_interrupt @ 软件中断异常 _prefetch_abort:.word _prefetch_abort @ 取指令中止异常 _data_abort:.word _data_abort @ 取数据中止异常 _not_used:.word _not_used @ 未使用异常 _irq:.word irq @ 一般中断异常 _fiq:.word _fiq @ 快速中断异常 reset: @告诉ARM核异常向量表所在的基地址 adr r0,_start @获得异常向量表所在的地址 mcr p15,0,r0,c12, c0, 0 @将异常向量表的基地址写入cp15协处理器的寄存器c12 ldr sp,=0x40008000 bl main stop: b stop software_interrupt: ldr sp,=0x40009000 stmfd sp!,{r0-r12,lr} ldr r0,[lr,#-4] mov r1,#0xff bic r0,r0,r1,lsl #24 ldmfd sp!,{r0-r12,pc}^ irq: ldr sp,=0x40010000 sub lr,lr,#4 stmfd sp!,{r0-r12,lr} ldmfd sp!,{r0-r12,pc}^
-
//Makefile CROSS_COMPILE = arm-none-eabi- GCC = $(CROSS_COMPILE)gcc LOAD = $(CROSS_COMPILE)ld OBJECTCOPY = $(CROSS_COMPILE)objcopy ELF = buzzer.elf BIN = buzzer.bin CINCLUDES = -I ./Include LOADFLAGS += -static -L ./Lib -lc -lm -lnosys LOADFLAGS += -static -L ./Lib -lgcc include Config.mk # arm-none-eabi-ld -Ttext=0x40000000 start.o main.o buzzer.o music.o -o buzzer.elf # arm-none-eabi-objcopy -O binary buzzer.elf buzzer.bin $(ELF):Start/start.o main.o $(COBJECTS) $(LOAD) -Ttext=0x40000000 $^ -o $@ $(LDFLAGS) $(OBJECTCOPY) -O binary $(ELF) $(BIN) # arm-none-eabi-gcc -c start.s -o start.o # arm-none-eabi-gcc -c buzzer.c -o buzzer.o # arm-none-eabi-gcc -c music.c -o music.o # arm-none-eabi-gcc -c main.c -o main.o %.o:%.s $(GCC) -c $< -o $@ %.o:%.c $(GCC) -c $< -o $@ $(CINCLUDES)
-
//Config.mk COBJECTS += Driver/buzzer.o COBJECTS += Driver/music.o
-
-
5、编译代码
-
6、下载代码到开发板上
-