STM8单片机蜂鸣器实现和弦音

1,741 阅读4分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

  平时在使用单片机驱动蜂鸣器的时候,大多数使用的是有源蜂鸣器,只能发出滴滴的声音,声音比较单调也比较刺耳。在使用空调的时候,按下空调上的按键,发出的声音是叮铃铃那种,听起来比较悦耳,很好听。 那么自己能不能蜂鸣器实现这种效果呢?当然是可以的,这种声音可以通过无源蜂鸣器发出和旋音来实现。

驱动原理图如下: image.png 要实现和弦音必须通过2个IO口来控制,其中一个口控制蜂鸣器供电,另一个口控制震荡频率。

  首选A点输出高电平,三极管Q1和Q2导通,开始给电容C1充电。然后B点开始输出PWM波,此时蜂鸣器开始发声,输出一定频率的声音,在音乐结束前,A点输出低电平,此时Q1和Q2关断,蜂鸣器靠电容C1放电提供能量给蜂鸣器发声。这样蜂鸣器的声音随着电容的放电就会越来越小,就能实现拖音的效果,然后在不同的时间设置PWM输出的频率不同。这样蜂鸣器就可以实现演奏音乐的效果了。

实现代码如下:

#include "buzzer.h"
#include "stm8s103f3p.h"

_Bool Beep_Power  @PD_ODR:3;        //控制电源
_Bool Beep_Pre  @PD_ODR:4;          //输出PWM

// 频率 震荡持续时间 供电持续时间
TONE_Def Tone1[] = {{FREQ_2K6, 100, 20},{FREQ_NO, 0, 0}};//单音
TONE_Def Tone2[] = {{FREQ_2K3, 20, 20},{FREQ_2K6, 20, 20},{FREQ_2K9, 210, 10},{FREQ_NO, 0, 0}};//开机和弦音
TONE_Def Tone3[] = {{FREQ_2K9, 20, 20},{FREQ_2K6, 20, 20},{FREQ_2K3, 210, 10},{FREQ_NO, 0, 0}};//关机和弦音
 
TONE_Def  * pTone;
unsigned char BuzzerStatus =2;

void buzzer_gpio_init(void)
{
    PD_DDR|=(1<<3);					//PD3 输出  控制蜂鸣器供电
    PD_CR1|=(1<<3);					//PD3 推挽输出
    PD_DDR|=(1<<4);					//PD4 输出  控制蜂鸣器频率
    PD_CR1|=(1<<4);					//PD4 推挽输出
}

//用于蜂鸣器供电引脚  
void BeepPwrOn(void)
{
  Beep_Power=1;
}
void BeepPwrOff(void)
{
  Beep_Power=0;
}
////////////////////////

//用于kai关PWM  
void BEEP_On(void)
{
  TIM1_CR1 |=(1<<0);        //使能定时器
  Beep_Pre=1;               //频率控制引脚打开
}
void BEEP_Off(void)
{
   TIM1_CR1 &=~(1<<0);      //禁止定时器    
  Beep_Pre=0;               //关闭频率控制引脚
}
 
//设置铃声类型 0 1 2
void BuzzerStart(Tone_Type ToneType)
{
  switch (ToneType)
  {
	case MONO:          //0 单音 
	  pTone = Tone1;
	  break;
	case POLY_ON:       //1 开机
	  pTone = Tone2;
	  break;
	case POLY_OFF:      //2 关机
	  pTone = Tone3;
	  break;
	default:
	  pTone = Tone1;    //单音
	  break;
  }
  BuzzerStatus = 1;
}
 
//响声控制
void BuzzerCtrl(void)
{
  static TONE_Def Tone;     //音调 频率 震荡时间 供电时间
 
  switch (BuzzerStatus)     //
  {
	case 1:             //调用音调数组
	  Tone = *pTone;
	  if (Tone.Freq != FREQ_NO)     //频率不为0
	  {
		//先判断供电持续时间
		if (Tone.PWRTime != 0)      //供电时间不为0  
		{
		  Tone.PWRTime --;
		  BeepPwrOn();
		}
		else
		{
		  BuzzerStatus = 3;
		  break;
		}
		//再判断振荡持续时间
		if (Tone.OSCTime != 0)      //振荡时间不为0 
		{
		  Tone.OSCTime--;
		  BEEP_SetFreq(Tone.Freq);  //设置频率
		  BEEP_On();
		}
		else
		{
		  BeepPwrOff();
		  BuzzerStatus = 3;
		  break;
		}
		//判断完成,开始递减计时
		BuzzerStatus = 2;
	  }
	  else /* Tone.Freq == FREQ_NO */ //是结束符
	  {
		BuzzerStatus = 3;
	  }
	  break;
	case 2:             //延迟 直到时间为0 切换下一个音调
	  if (Tone.PWRTime != 0)
	  {
		Tone.PWRTime --;
	  }
	  else
	  {
		BeepPwrOff();
	  }
	  if (Tone.OSCTime != 0)
	  {
		Tone.OSCTime --;
	  }
	  else
	  {
		BEEP_Off();
		pTone ++;   //取下一个音调
		BuzzerStatus = 1;
	  }
	  break;
	default:
	  break;
  }
}

//定时器1初始化  测试用
void TIM1_Init(void)
{
	TIM1_CR1 = 0x00;  //向上的计数方向,中断计数不停
	TIM1_IER = 0x01;  //允许更新中断
	TIM1_PSCRH = 0x00;  //进行16分频 =16M/(15+1)=1M  CNTR计数一次为 1/1M=1us
	TIM1_PSCRL = 0x0e;

	TIM1_ARRH = 265/255;	//250us 
	TIM1_ARRL = 265%255;
    
	TIM1_CR1 |=0x01;    //启动定时器
}

//定时器1 中断函数
//250us取反一次 500us一个周期 频率为2K
@far @interrupt void TIM1_OVER_Int(void)
{
	TIM1_SR1 = 0x00;
	Beep_Pre=~Beep_Pre;
}
//改变蜂鸣器频率
void BEEP_SetFreq(FREQ_Type Freq)
{
	unsigned char prescal=0;	
	switch(Freq)
	{
		case FREQ_2K :prescal=265;      //2K    265
                    break;
		case FREQ_2K3:prescal=230;      //2.3K  230
                    break;
		case FREQ_2K6:prescal=205;      //2.6K  205
                    break;
		case FREQ_2K9:prescal=182;      //2.9K  182
                    break;
		default:      prescal=265;
                    break;
	}
	TIM1_CR1|=(1<<7);       //自动预装载允许
	TIM1_IER = 0x01;        //允许更新中断
	TIM1_PSCRH = 0x00;      //进行3分频 =16M/(15+1)=1M  CNTR计数一次为 1/1M=1us
	TIM1_PSCRL = 0x0e;
	TIM1_ARRH = prescal/255;	    //1000ms
	TIM1_ARRL = prescal%255;
    
	TIM1_CR1 |=0x01;        //使能定时器
	
}

单片机的PD3引脚控制电源开关,PD4引脚输出PWM波。PWM的频率通过定时器来实现,在定时器中对PD4引脚取反,改变定时器的定时时间会改变PD4引脚的翻转时间,从而改变PWM输出的频率。

下来在主函数中直接调用蜂鸣器输出和弦音。

#include "stm8s103f3p.h"
#include "delay.h"
#include "buzzer.h"

extern unsigned char BuzzerStatus;
void CLK_Init(void)
{
	CLK_SWR=0xe1; 				//HSI为主时钟源  16MHz CPU时钟频率
	CLK_CKDIVR=0x00;			//CPU时钟0分频,系统时钟0分频
}

void test(void)
{
	char i=0;
	for(i=0; i<10; i++)
	{
		delay_ms(500);
		BuzzerStart(0);   //单声音
		while(BuzzerStatus!=3)
		{
			BuzzerCtrl();
			delay_ms(5);
		}
	}
	for(i=0; i<10; i++)
	{
		delay_ms(500);
		BuzzerStart(1);   //开机和弦
		while(BuzzerStatus!=3)
		{
			BuzzerCtrl();
			delay_ms(5);
		}
	}
	for(i=0; i<10; i++)
	{
		delay_ms(500);
		BuzzerStart(2);   //关机和弦
		while(BuzzerStatus!=3)
		{
			BuzzerCtrl();
			delay_ms(5);
		}
	}
}

main()
{
	unsigned char key;
	CLK_Init();
	_asm("sim");            //禁止中断
	delay_init(16);
	buzzer_gpio_init();
	_asm("rim");            //开启中断
	while (1)
	{
		test();
	}
}

在主函数中对三种和弦音进行了一个调用测试,每种声音循环调用10次。如果将PWM输出的频率按照歌曲的节拍输出,这样通过无源蜂鸣器就可播放音乐了。