2009-06-03 14:27:00 NDSC_09 阅读数 805
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2489 人正在学习 去看看 朱有鹏

一、设计任务
设计题目:  单片机音乐播放器
设计要求:
1、完成89C51的外围复位、时钟和电源等外围电路的设计,完成扬声器驱动设计。
2、根据各音阶频率算出定时器定时常数,根据节拍给出该音阶持续的时间。
3、完成音乐播放软件并完成乐谱表的翻译,最好能编写一个播放子程序用于播放。
4、播放音乐《南泥湾》片段。
二、方案论证
设计方案:
设计中我们考虑了两种设计方案,两种设计方案中主要去区别在于硬件电路的不同,对于本设计通过模拟电路和单片机设计均可以实现,最后根据设计要求、可行性和设计成本的考虑选择了单片机设计的方案。现在一一介绍论证如下:
[方案一]:
此方案采用的是模拟电路,其中涉及有几部分:
(1) 基准频率产生器,产生基准频率,其值应根据音调发生器的频率要求决定。电路可由晶振构成时钟脉冲振荡器。
(2) 音调发生器,音调发生器产生各个音区与音符所对应的频率;音符代码存储器,用来存储与乐曲的音符对应的数字代码及乐曲的数量。
(3) 通常先将乐曲进行编码,再将其代码存储在EPROM存储器。
(4) 节拍发生器与地址计数器,节拍发生器的振荡频率由乐曲演奏的速度所决定。演奏的速度越快,节拍发生器的速度越高。
(5)节拍分配器,将节拍分配好,产生驱动打击的节拍信号。
(6)声音驱动电路,使乐曲的节拍和频率通过发音演奏出所想要的乐曲。
方案二采用单片机来设计电路。此电路包括时钟电路、复位电路、音驱动电路和MCS-51单片机。各部分功能如下:
(1)时钟电路:由石英晶体振荡器产生单片机工作时所必须的时钟信号。振荡器采用12M的晶振,使之机器周期 Tcy=1us,方便发音程序的计算和编译。

2008-09-07 19:58:00 zhangxinchunleo 阅读数 643
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2489 人正在学习 去看看 朱有鹏
 

相关知识

要产生音频脉冲,只要算出某一音频的周期(1/频率),然后将此周期除以2,即为半周期的时间。利用定时器计出这个半周期的时间,每当计时到后就将输出脉冲的I/O反相,然后重复计时此半周期时间再对I/O反相,就可以在I/O脚上得到此频率的脉冲。

计数脉冲值与频率的关系公式如下:

N=Fi÷2÷Fr  

N:计数值

Fi:内部计数时一次为1us,故其频率为1MHz

Fr: 要产生的频率

其计数值的求法如下:

T=65536-N = 65536- Fi÷2÷Fr

例如:已知中音DO523Hz),求其计数值

T = 65536 500000 / 523 = 64580

2、按图连接:

2、常用节拍对照表:

 

 

/************************************************************************

*****music.c

*****created by zhangyongchang

*****date 2006 4.5

/**********************************************************************/

#include <reg51.h>

unsigned char m;   //m为频率常数

unsigned char n=0; //n为节拍常数

unsigned char i=0;

sbit bell=P1^7;

unsigned char code music_tab[] ={

0x18, 0x30, 0x1C , 0x10, //格式为: 频率常数, 节拍常数, 频率常数, 节拍常数,

0x20, 0x40, 0x1C , 0x10,

0x18, 0x10, 0x20 , 0x10,

0x1C, 0x10, 0x18 , 0x40,

0x1C, 0x20, 0x20 , 0x20,

0x1C, 0x20, 0x18 , 0x20,

0x20, 0x80, 0xFF , 0x20,

0x30, 0x1C, 0x10 , 0x18,

0x20, 0x15, 0x20 , 0x1C,

0x20, 0x20, 0x20 , 0x26,

0x40, 0x20, 0x20 , 0x2B,

0x20, 0x26, 0x20 , 0x20,

0x20, 0x30, 0x80 , 0xFF,

0x20, 0x20, 0x1C , 0x10,

0x18, 0x10, 0x20 , 0x20,

0x26, 0x20, 0x2B , 0x20,

0x30, 0x20, 0x2B , 0x40,

0x20, 0x20, 0x1C , 0x10,

0x18, 0x10, 0x20 , 0x20,

0x26, 0x20, 0x2B , 0x20,

0x30, 0x20, 0x2B , 0x40,

0x20, 0x30, 0x1C , 0x10,

0x18, 0x20, 0x15 , 0x20,

0x1C, 0x20, 0x20 , 0x20,

0x26, 0x40, 0x20 , 0x20,

0x2B, 0x20, 0x26 , 0x20,

0x20, 0x20, 0x30 , 0x80,

0x20, 0x30, 0x1C , 0x10,

0x20, 0x10, 0x1C , 0x10,

0x20, 0x20, 0x26 , 0x20,

0x2B, 0x20, 0x30 , 0x20,

0x2B, 0x40, 0x20 , 0x15,

0x1F, 0x05, 0x20 , 0x10,

0x1C, 0x10, 0x20 , 0x20,

0x26, 0x20, 0x2B , 0x20,

0x30, 0x20, 0x2B , 0x40,

0x20, 0x30, 0x1C , 0x10,

0x18, 0x20, 0x15 , 0x20,

0x1C, 0x20, 0x20 , 0x20,

0x26, 0x40, 0x20 , 0x20,

0x2B, 0x20, 0x26 , 0x20,

0x20, 0x20, 0x30 , 0x30,

0x20, 0x30, 0x1C , 0x10,

0x18, 0x40, 0x1C , 0x20,

0x20, 0x20, 0x26 , 0x40,

0x13, 0x60, 0x18 , 0x20,

0x15, 0x40, 0x13 , 0x40,

0x18, 0x80, 0x00

};

void time0(void)  interrupt 1 using 1  //产生10MS精确定时

{  TH0=(65536-10000)/256;

   TL0=(65536-10000)%256;

   n--;

}

void delayms(unsigned char a)  //微秒延时程序

{

  while(--a);                  

}

void delay(void)   //延迟程序

{

int i,j;

for(i=50;i--;i>0)

  for(j=10000;j--;j>0) ;

}

void main()

{

    TMOD=(TMOD&0xf0)|0x01;

     TH0=(65536-10000)/256;

     TL0=(65536-10000)%256;

     EA=1;

     ET0=1;

     TR0=0;  

     while(1)

     {

     switch(music_tab[i])

     {

        case 0x00:

            i=0;delay(); break; // 曲终停顿一定时间

        case 0xff:

            i=i+1;delayms(100);break;  //遇到休止符停顿一定时间100微秒,再取下一个频率

        default:

m=music_tab[i++];n=music_tab[i++];//取频率到m,取节拍到n

                 TR0=1;

                 while(n!=0)

                  {

                  bell=!bell;

                  delayms(5*m);//调整产生的频率,可以通过在m前的值来修正音调的高低

                   }

    TR0=0;

      }

     }

}

2019-11-28 22:36:11 weixin_44223883 阅读数 306
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2489 人正在学习 去看看 朱有鹏

单片机音乐盒完整代码

该设计是使用C51单片机制作的音乐盒制作的一个音乐盒,可以实现进行歌曲的切换,暂停/播放。此外还增加了随机点歌,获取大气温湿度等功能。使用LCD1602显示器进行歌曲名称和大气温湿度的显示。将完整代码帖在下面,有需要的可以自己获取。完整代码可以在我的博客下面,我的资源下面进行下载使用。

由于我上传的资源需要积分,所以我重新更新了代码,现在以将完整代码贴出,有需要的可以自己看一下,如果遇到有什么问题的可以问一下我。如果觉得还行的请帮我点个赞,谢谢大家。

如果下面贴出的代码,大家不能看懂的,或者使用有麻烦的可以给我私信。

下面是一些实物图片

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

部分代码如下

main.c

#include "main.h"
#include "delay.h"
#include "beep.h"

uchar RH,RL,TH,TL;//定义变量用来存储温湿度变量的值

extern uchar timer1h, timer1l,time,K_Value,Singin_interface,Start,Stop;
extern char Number;



extern uchar code Player_Number1[9];


uchar KEY;//定义变量保存键值信息


//自定义1602显示心形字符
uchar code table1[]=
{   0x03,0x07,0x0f,0x1f,0x1f,0x1f,0x1f,0x1f,
    0x18,0x1E,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
    0x07,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,
    0x10,0x18,0x1c,0x1E,0x1E,0x1E,0x1E,0x1E,
    0x0f,0x07,0x03,0x01,0x00,0x00,0x00,0x00,
    0x1f,0x1f,0x1f,0x1f,0x1f,0x0f,0x07,0x01,
    0x1f,0x1f,0x1f,0x1f,0x1f,0x1c,0x18,0x00,
    0x1c,0x18,0x10,0x00,0x00,0x00,0x00,0x00,//心图案
};

//定义流水灯断码
uchar code LED1[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40};
	


//定时器初始化函数
void  timer_init()
{
    TMOD=0x11;//定时器0和1都工作再方式一
	  TH0=(65536-30000)/256;
	  TL0=(65536-30000)%256;
	  EA=1;//开总中断
	  //开定时器中断
	  ET0=1;
    ET1=1;
    TR1=0;//首先关闭定时器T1
	  TR0=1;//开启定时器T0
	  PT1=1;
    PT0=0;

}


void  main()
{
    uchar i;
	  uchar flag1=0;
	  Player=1;
    Last_Song=1;
    Next_Song=1;
	  Choose_Song=1;//点歌按钮
    Confirmation=1;//确定按钮
   
	  P0=0xff;
    timer_init();
    lcd_init ();
    delay (50);
    
	
	  //向1602写入自定义字符
    Write_CGRAM(0x40,table1);
	
	  //再1602上显示自定义字符
	  //显示字符的前四位
    lcd_pos (0x0c);
    for(i=0; i<4; i++)
    {
        lcd_wdat(i);
    }
		//显示自定义字符的后四位
    lcd_pos (0x4c);
    for(i=4; i<8; i++)
    {
        lcd_wdat(i);
    }
		
		//在1602上显示自定义布局
		lcd_pos(0x00);
		lcd_word("T:");
		//温度
		lcd_pos(0x04);
		lcd_wdat(0xdF);
		lcd_wdat('C');
		//湿度
		lcd_pos(0x07);
		lcd_word("H:");
		lcd_pos(0x0b);
		lcd_wdat('%');
		
		
    lcd_pos (0x40);
    lcd_word("Sg:");

    lcd_word(Player_Number1);
    while(1)
    {	
					  Song(Number);  

		}			
        
}



void t0int() interrupt 1
{
	
	 static uchar Num0=0,Num1=0,i=0;
	
	 TH0=(65536-30000)/256;
	 TL0=(65536-30000)%256;
	 Num1++;	 
	//表示没有处于点个界面中,则进行数据刷新
	 if(Singin_interface==0)
	 {
	 Num0++;
	 if(Num0==90)//控制定时2.7秒钟,用来刷新数据
	 {		
	   receive();		 
		 //显示温湿度
     lcd_pos(0x02);
     lcd_wdat(TH/10+0x30);
		 lcd_wdat(TL%10+0X30);
		 
		 //显示湿度
		 lcd_pos(0x09);
     lcd_wdat(RH/10+0X30);
		 lcd_wdat(RL%10+0X30);
		 
		 Num0=0;
		
	 }
 }
	 
 
 //表示处于点歌界面中
 else if(Singin_interface==1)
 {
	     if(Start==1&&Stop==0)
			 {
        Number=rand_init();
        lcd_pos(0x48);
	      lcd_wdat(Number+0x30);
			 }
 
 }
	
 if(Num1==5)
	 {
	    P0=LED1[i];
	    i++;
		  if(i==7)
			{
			   i=0;
			}
			Num1=0;
	 }
 
 
}

void t1_int() interrupt 3     //T0中断程序,控制发音的音调         
 {                              
       TR1 = 0;                      //先关闭T0          s
       spaker =!spaker;           //输出方波, 发音                   
       TH1 = timer1h;               //下次的中断时间, 这个时间, 控制音调高低         
       TL1 = timer1l;          
       TR1 = 1;                     //启动T0        
 }  

main.h

#ifndef _main_h
#define _main_h

#include <reg52.h>
#include <intrins.h>

#define uchar unsigned char
#define uint unsigned int
	
typedef bit BOOL;




sbit spaker=P1^0;//定义音频输出管脚

sbit Player=P3^2;//播放按钮
sbit Last_Song=P3^3;//上一首按钮
sbit Next_Song=P3^5;//下一首

sbit Choose_Song=P3^6;//点歌按钮
sbit Confirmation=P3^7;//确定按钮


//1602相关函数
void lcd_wcmd (uchar cmd);
void lcd_pos (uchar pos);
void lcd_wdat (uchar dat);
void lcd_init ();
void lcd_word(uchar *word);
void delay (int m);
void Write_CGRAM(uchar *adder,uchar *p);


//DHT11相关函数
void start1();
uchar receive_byte();
void receive();


//音乐发声相关函数
void song();
void key_scan();
void Song(uchar j); 
	

#endif

1602.c

/*
该文件用来编写LCD1602方面的程序

*/
#include "main.h"
#include "delay.h"


#define Dat_1602  P2//1602数据口
sbit rs=P1^7;
sbit rw=P1^6;
sbit ep=P1^5;


void delay (int m)
{
 unsigned char i,j;
 for (i=0;i<m;i++)
 for (j=0;j<253;j++);
}

//判断LCD1602是否处于忙碌状态
BOOL lcd_bz()
{
  BOOL result;
  rs=0;           // 读忙信号
  rw=1;
  ep=1;
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  result = (BOOL)(Dat_1602&0x80);
  ep=0;
  return result;
}




//向LCD62写命令
void lcd_wcmd (uchar cmd)
{
  while (lcd_bz());
  rs=0;
  rw=0;
  ep=0;
  _nop_();
  _nop_();
  Dat_1602=cmd ;
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  ep=1;
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  ep=0;
}


//指定当前LCD1602要显示字符的位置
void lcd_pos (uchar pos)
{
  lcd_wcmd (pos|0x80);
}

void lcd_wdat (uchar dat)
{
   while (lcd_bz());
  rs=1;
  rw=0;
  ep=0;
  _nop_();
  _nop_();
  Dat_1602=dat ;
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  ep=1;
  _nop_();
  _nop_();
  _nop_();
  _nop_();
  ep=0;
}


//LCD1602初始化
void lcd_init ()
{
  lcd_wcmd (0x38); //显示模式设置:16×2显示,5×7点阵,8位数据接口
  delay (1);
  lcd_wcmd (0x0c); //显示模式设置:光标右移,字符不移  
  delay (1);
  lcd_wcmd (0x06); //显示模式设置:光标右移,字符不移  
  delay (1);
  lcd_wcmd (0x01); //清屏幕指令,将以前的显示内容清除 
  delay (1);


}
//向LCD1602发送字符串
void lcd_word(uchar *word)
{
   uchar i=0;
	 while(word[i]!='\0')
	 { 
	   lcd_wdat(word[i]);
		 i++;
	 }

}


//LCD1602自定义字符函数
void Write_CGRAM(uchar *adder,uchar*p)
{

    //uchar LOL=0x40;//操作CGRAM的命令码
    uchar i,j,V=0;
	  for(j=0;j<8;j++)
	{
	  for(i=0;i<8;i++)
	  {
	     lcd_wcmd(adder+i);
			 lcd_wdat(p[V]); 
       V++;			
	  }
		adder+=8;
	}

}


delay.h

#include "main.h"
#include "math.h"
#include "stdio.h"





void delay1(uchar t)
{
 uchar t1;
unsigned long t2;
for(t1=0;t1<t;t1++)
{
for(t2=0;t2<8000;t2++)
{
   ;
}
}
TR0=0;
}





void delay8us()//一个FOR循环大概需要8个机器周期,一个机器周期大约是1us也就是说12MHZ的晶振对此函数的延时
//是8US多 所以此延时函数更精确
{
    unsigned char i;
    for(i = 0; i < 1; i++);

}


//定义延时函数
void delay_WO(unsigned char ms)
{
    unsigned char i;
    while(ms--)
        for(i = 0; i < 100; i++);

}


void delay_player(unsigned char t)    //延时子函数,控制发音的时间长度
{   uchar t1;
    unsigned long t2;
    for(t1 = 0; t1 < t; t1++)          //嵌套循环, 共延时t个半拍
    for(t2 = 0; t2 < 8000; t2++); //延时期间, 可进入T0中断去发音   8
    TR1 = 0;                        //关闭T0, 停止发音

}


void Delay5ms()		//@12.000MHz
{
	unsigned char i, j;

	i = 59;
	j = 90;
	do
	{
		while (--j);
	} while (--i);
}


void Delay100ms()		//@12.000MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 5;
	j = 144;
	k = 71;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}



//随机数产生函数
unsigned char rand_init()
{
    uchar i;
    i=rand()%8;
    return i;
}

dht11.c

#include "main.h"
#include "delay.h"

//DHT11的数据管脚
sbit io=P1^4;

uchar data_byte;

extern uchar RH,RL,TH,TL;




//单片机发送开始信号
void start1()
{
   io=1;
   delay8us();
   io=0;
   delay_WO(25);
   io=1;
   delay8us();
   delay8us();
   delay8us();



}

uchar receive_byte()
{
  unsigned char i,temp;
  for(i=0;i<8;i++)
  {
    while(!io);
	delay8us();
	delay8us();
	delay8us();
	temp=0;
	if(io==1)
	temp=1;
	while(io);
	data_byte<<=1;
	data_byte|=temp;
  }
  return data_byte;
}

void receive()
{
  uchar T_H,T_L,R_H,R_L,check,num_check,i;
	start1();
	io=1;
	if(!io)
	{
	   while(!io);
	   while(io);
	   R_H=receive_byte();
	   R_L=receive_byte();
	   T_H=receive_byte();
	   T_L=receive_byte();
	   check=receive_byte();
	   io=0;
	   for(i=0;i<7;i++)
	   delay8us();
	   io=1;		
	   num_check=R_H+R_L+T_H+T_L;
	   if(num_check==check)
	   {
	   	RH=R_H;
		  RL=R_L;
		  TH=T_H;
		  TL=T_L;
		  check=num_check;	   	   
	   }
	
	}

}

beep.c

#include "main.h"
#include "delay.h"
#include "beep.h"


// 频率-半周期数据表 高八位     本软件共保存了四个八度的28个频率数据
uchar code FREQH[] = {     0xF2, 0xF3, 0xF5, 0xF5, 0xF6, 0xF7, 0xF8,    //低音1234567
                           0xF9, 0xF9, 0xFA, 0xFA, 0xFB, 0xFB, 0xFC, 0xFC,//1,2,3,4,5,6,7,i
                           0xFC, 0xFD, 0xFD, 0xFD, 0xFD, 0xFE,            //高音 234567
                           0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFF
                     };   //超高音 1234567
// 频率-半周期数据表 低八位
uchar code  FREQL[] = {    0x42, 0xC1, 0x17, 0xB6, 0xD0, 0xD1, 0xB6,    //低音1234567
                           0x21, 0xE1, 0x8C, 0xD8, 0x68, 0xE9, 0x5B, 0x8F, //1,2,3,4,5,6,7,i
                           0xEE, 0x44, 0x6B, 0xB4, 0xF4, 0x2D,             //高音 234567
                           0x47, 0x77, 0xA2, 0xB6, 0xDA, 0xFA, 0x16
                      };   //超高音 1234567              //----------------------------------------------------------------------------------------------
// 要想演奏不同的乐曲, 只需要修改这个数据表
uchar code music[8][500] = {

    //乡间小路数据表
    {
        3,1,2, 3,1,1, 3,1,1, 6,0,1, 6,0,1, 1,1,2, 6,0,1, 5,0,1, 6,0,4, 6,0,2, 6,0,1 ,6,0,1, 6,0,2, 6,0,1,
        1,1,1, 2,1,2, 2,1,1, 3,1,1, 2,1,4, 3,1,1, 3,1,1, 3,1,1, 2,1,1, 4,1,2, 4,1,2, 3,1,2, 2,1,1, 1,1,1,
        2,1,4, 7,0,1, 7,0,1, 7,0,1, 6,0,1, 5,0,1, 5,0,1, 6,0,1, 7,0,1, 7,0,1, 6,0,1, 5,0,1, 6,0,4, 6,1,2,
        3,1,1, 6,1,1, 7,1,1, 6,1,1, 5,1,1, 5,1,2, 5,1,1, 2,1,1, 5,1,1, 6,1,1, 5,1,1, 4,1,1, 4,1,2, 4,1,1,
        3,1,1, 2,1,2, 1,1,1, 2,1,1, 3,1,1, 2,1,1, 1,1,1, 2,1,1, 3,1,4, 6,1,2, 3,1,1, 6,1,1, 7,1,1, 6,1,1,
        5,1,1, 5,1,2, 2,1,1, 5,1,1, 6,1,1, 5,1,1, 4,1,1, 4,1,2, 4,1,1, 5,1,1, 6,1,1, 4,1,1, 3,1,1, 2,1,2,
        2,1,2, 5,1,4, 0,0,0
    },

    // 送别数据表
    {   5,1,2, 3,1,3, 5,1,1, 1,2,3, 6,1,2, 1,2,2, 5,1,4,
        5,1,2, 1,1,1, 2,1,1, 3,1,2, 2,1,1, 1,1,1, 2,1,4,
        5,1,2, 3,1,1, 5,1,1, 1,2,2, 7,1,1, 6,1,2,
        1,2,2, 5,1,4, 5,1,2, 2,1,1, 3,1,1, 4,1,2, 7,0,1,
        1,1,4, 6,1,2, 1,2,2, 1,2,4, 7,1,2, 6,1,1, 7,1,1,
        1,2,4, 6,1,1, 7,1,1, 1,2,1, 6,1,1, 6,1,1, 5,1,1,
        3,1,1, 1,1,1, 2,1,8, 5,1,2, 3,1,1, 5,1,1, 1,2,2,
        7,1,1, 6,1,2, 1,2,2, 5,1,4, 5,1,2, 2,1,1, 3,1,1,
        4,1,2, 7,0,2, 1,1,4, 0,0,0
    },

    //一辈子的孤单数据表
    {   5,0,1, 5,0,1, 3,1,1, 2,1,1, 2,1,1, 1,1,1, 3,1,1, 3,1,1, 2,1,1, 2,1,4,
        5,0,1, 5,0,1, 3,1,1, 2,1,1, 2,1,1, 2,1,1, 1,1,1,
        2,1,1, 3,1,1, 3,1,4, 5,0,1, 5,0,1, 3,1,1, 2,1,1, 2,1,1, 1,1,1, 3,1,1, 3,1,1, 2,1,1, 2,1,4, 2,1,1, 1,1,1,
        -3,1,1, 2,1,1,
        1,1,1, 2,1,1, 1,1,1, 1,1,8, 3,1,1, 2,1,1, 2,1,1, 1,1,1, 1,1,1, 2,1,1, 2,1,6, 3,1,1, 2,1,2, 1,1,1, 1,1,1,
        -5,0,1, 6,0,1,
        5,0,1, 5,0,6, 3,1,1, 2,1,1, 2,1,1, 1,1,1, 1,1,1, 2,1,1, 2,1,6, 5,0,1, 3,1,1, 2,1,2, 1,1,1, 2,1,2, 3,1,1,
        -3,1,4,
        5,0,1, 3,1,1, 5,1,2, 6,1,1, 3,1,1, 2,1,1, 2,1,6, 5,0,1, 3,1,1, 2,1,2, 1,1,1, 2,1,2, 3,1,1, 3,1,4,
        5,0,1, 3,1,1, 2,1,1, 2,1,1, 1,1,1, 3,1,1, 2,1,1, 2,1,4, 2,1,1, 1,1,1, 3,1,1, 2,1,1, 1,1,1, 2,1,1, 1,1,1,
        -1,1,1, 1,1,6,
        3,1,1, 2,1,1, 2,1,1, 1,1,1, 3,1,1, 2,1,1, 2,1,6, 5,0,1, 3,1,1, 2,1,1, 2,1,1, 1,1,1, 2,1,1, 3,1,1, 3,1,6,
        5,0,1, 3,1,1, 2,1,1, 2,1,1, 1,1,1, 3,1,1, 2,1,1, 2,1,6, 5,0,1, 3,1,1, 2,1,1, 2,1,1, 1,1,1, 2,1,1, 3,1,1,
        -3,1,6,0,0,0
    },


    //世上只有妈妈好数据表
    {   6,2,3,      5,2,1,      3,2,2, 5,2,2, 1,3,2, 6,2,1, 5,2,1,
        6,2,4,      3,2,2,      5,2,1, 6,2,1, 5,2,2, 3,2,2, 1,2,1,
        6,1,1,      5,2,1,      3,2,1, 2,2,4, 2,2,3, 3,2,1, 5,2,2,
        5,2,1,      6,2,1,      3,2,2, 2,2,2, 1,2,4, 5,2,3, 3,2,1,
        2,2,1,      1,2,1,      6,1,1, 1,2,1, 5,1,6, 0,0,0

    },


    {   5,1,1, 5,1,1, 6,1,2, 5,1,2, 1,2,2, 7,1,4,
        5,1,1, 5,1,1, 6,1,2, 5,1,2, 2,2,2, 1,2,4,
        5,1,1, 5,1,1, 5,2,2, 3,2,2, 1,2,2, 7,1,2, 6,1,2,
        4,2,1, 4,2,1, 3,2,2, 1,2,2, 2,2,2, 1,2,4
    },

    //发如雪
    {
        5,1,1, 2,2,1, 3,2,2, 2,2,1, 3,2,1, 5,2,1, 6,2,1, 5,2,3, 1,2,1, 2,2,1,
        3,2,1, 6,2,1, 5,2,1, 3,2,1, 5,2,3, 5,2,1, 6,2,1, 1,3,2, 6,2,1, 5,2,1,
        3,2,1, 5,2,1, 3,2,2, 1,2,1, 2,2,1, 3,2,1, 1,2,1, 6,1,1, 3,2,1, 2,2,1,
        5,1,1, 2,2,1, 3,2,2, 2,2,1, 3,2,1, 5,2,1, 6,2,1, 5,2,3, 1,2,1, 2,2,1,
        3,2,1, 6,2,1, 5,2,3, 5,2,1, 6,2,1, 1,3,2, 6,2,1,
        5,2,1, 3,2,1, 5,2,1, 3,2,2, 1,2,1, 6,1,1, 3,2,1, 2,2,1, 1,2,1, 6,1,1,
        1,2,1, 2,2,1, 2,2,1, 1,2,4, 0,0,0
    },

    //当你孤单会想起谁
    {
        3,2,2, 3,2,1, 4,2,1, 3,2,2, 2,2,1, 1,2,1,  2,2,2, 5,2,2, 2,2,2, 2,2,3,
        1,2,2, 1,2,1, 2,2,1, 1,2,2, 7,1,1, 6,1,1, 7,1,2, 3,2,2, 7,1,2, 7,1,3,
        6,1,2, 2,2,1, 3,2,1, 2,2,1, 1,2,1, 6,1,2, 5,1,2, 2,2,1, 3,2,1, 2,2,1,
        1,2,1, 6,1,2, 6,1,2, 2,2,1, 3,2,1, 2,2,1, 1,2,1, 6,1,1, 7,1,1, 1,2,6, 0,0,0
    },
		
		//简单爱
    {
     5,1,1, 1,2,1, 2,2,1, 3,2,1, 2,2,1, 3,2,1, 4,2,1, 5,2,1, 5,2,1, 5,2,1, 
         4,2,1, 3,2,1, 2,2,3, 5,1,1, 1,2,1, 2,2,1, 3,2,1, 4,2,1, 5,2,1, 5,2,1, 
         5,2,1, 6,2,1, 5,2,2, 2,2,1, 3,2,1, 1,2,2, 1,2,1, 6,1,1, 2,2,1, 2,2,1, 
         3,2,1, 3,2,1, 1,2,1, 5,2,1, 1,2,1, 5,2,1, 1,2,1, 7,1,1, 1,2,1, 1,2,1, 6,1,1, 
         2,2,1, 2,2,1, 3,2,1, 3,2,1, 5,2,1, 5,2,1, 
         5,2,1, 6,2,1, 5,2,2, 2,2,1, 3,2,1, 1,2,2,  
         5,1,1, 1,2,1, 2,1,2, 3,2,1, 2,2,1, 3,2,1, 4,2,1, 5,2,1, 5,2,1, 5,2,1, 
         4,2,1, 3,2,1, 2,2,3, 5,1,1, 1,2,1, 2,2,1, 3,2,1, 2,2,1, 3,2,1, 4,2,1, 
         5,2,1, 5,2,1, 5,2,2, 2,2,1, 3,2,1, 1,2,2, 1,2,1, 6,1,1, 2,2,1, 2,2,1, 
         3,2,1, 3,2,1, 1,2,2, 5,2,1, 1,2,1, 5,2,1, 5,2,1, 7,1,1, 1,2,1, 0,0,0 }
};


//定义歌曲名字
uchar code Player_Number1[10]="1.CyRoad ";
uchar code Player_Number2[10]="2.FeWell ";
uchar code Player_Number3[10]="3.LCover ";
uchar code Player_Number4[10]="4.Mother ";
uchar code Player_Number5[10]="5.Birhday";
uchar code Player_Number6[10]="6.SnHair ";
uchar code Player_Number7[10]="7.Althink";
uchar code Player_Number8[10]="8.SimLove";


//定义9各空格来进行下一首歌曲名字的显示
uchar code Tab[9]="         ";

//定义变量Singin_interface来判断是否处于点歌界面当变量等于一时表示处于点个界面中
//定义变量Start,Stop来判断点歌的开始与停止等于一时有效
uchar Singin_interface=0,Start=0,Stop=0;



/*定义变量timer1h, timer1l来存储定时器的初值,TIME用来控制延时时间,
K_Value用来存储键值Num用来记录
音乐播放到那一首*/


uchar timer1h, timer1l,time,K_Value;
char Number=0;


//发声控制函数
void song()
{
    TH1 = timer1h;               //控制音调
    TL1 = timer1l;
    TR1 = 1;                     //启动T0, 由T0输出方波去发音
    delay_player(time);                 //控制时间长度
}


void key_scan()
{

    //判断是否按下了播放按键
    if(Player==0)
    {
        Delay5ms();
        if(Player==0)
        {
            while(Player==0);
            K_Value=1;
        }


    }


    //判断是否按下了下一首按键
    else if(Next_Song==0)
    {
        Delay5ms();
        if(Next_Song==0)
        {
            while(Next_Song==0);
            K_Value=2;
        }

    }
    //判断是否按下了上一首的按键
    else if(Last_Song==0)
    {
        Delay5ms();
        if(Last_Song==0)
        {
            while(Last_Song==0);
            K_Value=3;
        }
    }

    //判断是否按下了点歌按钮
    else if(Choose_Song==0)
    {
        Delay5ms();
        if(Choose_Song==0)
        {
            while(Choose_Song==0);
            K_Value=4;
        }

    }
    //判断是否按下了确定按钮
    else if(Confirmation==0)
    {
        Delay5ms();
        if(Confirmation==0)
        {
            while(Confirmation==0);
            K_Value=5;
        }

    }
}

//音乐播放控制函数
void Song(uchar j) 	//j表示第n+1首曲   9
{
    uchar k, i,flag_play=0;
    uchar LCD_i=0;//用来显示从点歌界面切换回来时的自定义字符
    static char flag_next_song=0,K=0;//flag_next_song用来记录切换到了那一首歌曲,i用来记录按键5按下的次数
    i = 0;
    time = 1;
    while(time)
    {
        key_scan();
       if(Singin_interface==0)
			 {
        //说明播放按钮按下了,执行相应的操作
        if(K_Value==1)
        {
            TR1=0;
            ET1=0;
            flag_play++;
            K_Value=0;
            if( flag_play==2)
            {
                flag_play=0;
                TR1=1;
                ET1=1;
            }
        }

        //切换到下一首歌曲
        else if(K_Value==2)
        {

            K_Value=0;
            //开启定时器
            TR1=0;
            ET1=0;
            flag_next_song++;

            if(flag_next_song>7)//当到最后一首歌时循环到第一首
            {

                flag_next_song=0;
            }
            Number=flag_next_song;

            //在1602上显示歌曲名字
            switch(Number)
            {
            case 0:
                lcd_pos (0x43);
                lcd_word(Player_Number1);
                break;
            case 1:
                lcd_pos (0x43);
                lcd_word(Player_Number2);
                break;
            case 2:
                lcd_pos (0x43);
                lcd_word(Player_Number3);
                break;
            case 3:
                lcd_pos (0x43);
                lcd_word(Player_Number4);
                break;
            case 4:
                lcd_pos (0x43);
                lcd_word(Player_Number5);
                break;
            case 5:
                lcd_pos (0x43);
                lcd_word(Player_Number6);
                break;
            case 6:
                lcd_pos (0x43);
                lcd_word(Player_Number7);
                break;
						case 7:
							  lcd_pos (0x43);
                lcd_word(Player_Number8);
						    break;
            default:
                break;
            }

            TR1=1;
            ET1=1;

            break;
        }

        //切换到上一首歌
        else if(K_Value==3)
        {

            K_Value=0;
            //开启定时器
            TR1=0;
            ET1=0;
            flag_next_song--;
            if(flag_next_song<0)//当到最后一首歌时循环到第一首
            {
                flag_next_song=7;
            }
            Number=flag_next_song;

            //在1602上显示歌曲名字
            switch(Number)
            {
            case 0:
                lcd_pos (0x43);
                lcd_word(Player_Number1);
                break;
            case 1:
                lcd_pos (0x43);
                lcd_word(Player_Number2);
                break;
            case 2:
                lcd_pos (0x43);
                lcd_word(Player_Number3);
                break;
            case 3:
                lcd_pos (0x43);
                lcd_word(Player_Number4);
                break;
            case 4:
                lcd_pos (0x43);
                lcd_word(Player_Number5);
                break;
            case 5:
                lcd_pos (0x43);
                lcd_word(Player_Number6);
                break;
            case 6:
                lcd_pos (0x43);
                lcd_word(Player_Number7);
                break;
						case 7:
							  lcd_pos (0x43);
                lcd_word(Player_Number8);
						    break;
            default:
                break;
            }
            TR1=1;
            ET1=1;
            break;
        }

        //表示按下了点歌按键
        else if(K_Value==4)
        {

            Singin_interface=1;//将标志为置为一表示处于点歌界面中
            TR1=0;
            ET1=0;
            K_Value=0;
            lcd_wcmd (0x01);//刷新屏幕
            lcd_pos(0x00);
            lcd_word("Hello!  Welcome!");
            lcd_pos(0x41);
            lcd_word("Start      Stop");

        }
			}
        else if(Singin_interface==1)//表示在点歌界面中
        {
            //选择点歌的开始按钮
            if(K_Value==5)
            {

                K_Value=0;
                K++;
                if(K==1)
                {
                    ET1=0;
                    TR1=0;
                    Start=1;
                    Stop=0;
                    lcd_pos(0x40);
                    lcd_wdat('*');
                    lcd_pos(0x4b);
                    lcd_wdat(' ');
                }
                else if(K==2)
                {
                    Start=0;
                    Stop=1;
                    lcd_pos(0x40);
                    lcd_wdat(' ');
                    lcd_pos(0x4b);
                    lcd_wdat('*');
                    K=0;
                    Delay100ms();//100MS开始播放选中的歌曲
                    ET1=1;
                    TR1=1;
                    Song(Number);
                }
            }

            else if(K_Value==1)//当处于点个界面时按下上一首按键时退出点歌界面
            {
							
							  K_Value=0;
                Singin_interface=0;//点歌界面标志位清零
                Start=0;
                Stop=0;
                Number=0;
							  flag_next_song=0;
                //先关闭定时器
                ET1=0;
                TR1=0;
                //Number=0;
                //进行主界面显示
                lcd_wcmd(0x01);
							  Delay100ms();
                //再1602上显示自定义字符							
                //显示字符的前四位
                lcd_pos (0x0c);
                for( LCD_i=0; LCD_i<4; LCD_i++)
                {
                    lcd_wdat(LCD_i);
                }
								 
                //显示自定义字符的后四位
                lcd_pos (0x4c);
                for( LCD_i=4;  LCD_i<8;  LCD_i++)
                {
                    lcd_wdat( LCD_i);
                }

                //在1602上显示自定义布局
                lcd_pos(0x00);
                lcd_word("T:");
                //温度
                lcd_pos(0x04);
                lcd_wdat(0xdF);
                lcd_wdat('C');
                //湿度
                lcd_pos(0x07);
                lcd_word("H:");
                lcd_pos(0x0b);
                lcd_wdat('%');


                lcd_pos (0x40);
                lcd_word("Sg:");
                lcd_word(Player_Number1);
                ET1=1;
                TR1=1;
								Song(Number);
            }
        }

        k = music[j][i] + 7 * music[j][i + 1] - 1;                            //第i个是音符, 第i+1个是第几个八度
        timer1h = FREQH[k];      //从数据表中读出频率数值
        timer1l = FREQL[k];      //实际上, 是定时的时间长度
        time = music[j][i + 2];   //读出时间长度数值
        i += 3;
        song();                  //发出一个音符
    }
}

2018-11-20 21:38:52 xfgryujk 阅读数 12049
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2489 人正在学习 去看看 朱有鹏

本文完整源码

原理

蜂鸣器

蜂鸣器分为有(震动)源的和无源的,有源的无法控制频率,所以用无源的才能播放音乐。无源蜂鸣器需要自己控制输入变化的信号才能发声,最简单的就是输入方波信号了,通过单片机控制方波的频率就能发出不同音调的声音

这是发出50%占空比方波的代码:

int i;
while (1) {
	for (i = 0; i < 10; ++i); // 改变循环次数可以改变方波频率
	P1_0 = 1;
	for (i = 0; i < 10; ++i);
	P1_0 = 0;
}

乐谱转成循环次数

首先要有蜂鸣器乐谱,就是用频率和持续时间表示一个音符的乐谱,至于如何获取蜂鸣器乐谱可以看我上一篇文章。由于单片机的运行速度很慢,如果在单片机里计算循环次数会浪费很多时间,导致输出的音乐断断续续的,所以我尽量在电脑上完成计算,单片机直接读取循环次数就行了

已知单片机使用的晶振频率(我用的11059200Hz),机器主频是晶振频率的12分频,所以一个机器周期是 1/(/12)1 / (晶振频率 / 12) 秒。还要知道for循环一次需要多少个机器周期,我是在keil仿真调试时开启定时器测出来的,是38个周期。一次循环时间就是 1000机器周期 * 1000 * 每次循环机器周期数 毫秒,然后可以算出发出相应频率的方波需要几次循环

下面是把蜂鸣器乐谱转成循环次数的Python脚本:

import json

# 晶振频率(Hz)
CRYSTAL_FREQUENCY = 11059200
# 计数周期(机器周期)(s)
COUNT_PERIOD = 1 / (CRYSTAL_FREQUENCY / 12)
# 一次循环几个机器周期,通过定时器实验得到
COUNT_PER_LOOP = 38
# 一次循环时间(ms)
MS_PER_LOOP = COUNT_PERIOD * 1000 * COUNT_PER_LOOP


def tone_to_loop_count(notes, output_path):
    res = []
    for frequency, duration in notes:
        if frequency == 0:
            # 延时
            loop_count = 65535
            period_count = round(duration / MS_PER_LOOP)
        else:
            period = 1000 / frequency
            loop_count = round(period / 2 / MS_PER_LOOP)
            period_count = round(duration / period)
        # 最低频率0.185,loop_count = 65534
        assert 0 <= loop_count <= 65535, f'frequency = {frequency}, loop_count = {loop_count},不在unsigned int范围内'
        # 把一个时间过长的音符拆成多个音符
        while period_count > 65535:
            res.append((loop_count, 65535))
            period_count -= 65535
        res.append((loop_count, period_count))

    with open(output_path + '.h', 'w') as f:
        f.write(f"""#define DELAY_COUNT 65535
#define NOTES_LEN {len(res)}
extern const unsigned int code notes[][2];
""")

    with open(output_path + '.c', 'w') as f:
        f.write('const unsigned int code notes[][2] = {\n')
        for i in range(0, len(res), 6):
            f.write('\t')
            for loop_count, period_count in res[i: i + 6]:
                f.write(f'{{{loop_count}, {period_count}}}, ')
            f.write('\n')
        f.write('};\n')


def main():
    with open('beep.json') as f:
        notes = json.load(f)
    tone_to_loop_count(notes, '../beep/music_data')


if __name__ == '__main__':
    main()

单片机代码

单片机代码就很简单了,读取循环次数然后循环发出方波

#include <AT89X51.H>

#include "music_data.h"

// 引脚定义
#define beepOut P1_0

int main() {
	beepOut = 0;
	while (1) {
		unsigned int i, j, k;
		for (i = 0; i < NOTES_LEN; ++i) {
			if (notes[i][0] == DELAY_COUNT) // 延时
				for (j = 0; j < notes[i][1]; ++j);
			else {
				for (j = 0; j < notes[i][1]; ++j) {
					for (k = 0; k < notes[i][0]; ++k);
					beepOut = 1;
					for (k = 0; k < notes[i][0]; ++k);
					beepOut = 0;
				}
			}
		}
	}
	
	return 0;
}

仿真电路图

其实就是单片机最小系统把P1.0口接到蜂鸣器。顺带一提Proteus中buzzer是有源蜂鸣器,sounder是发声器,只接受数字信号,speaker是扬声器,接受模拟信号,这里用的是sounder。实际中可能单片机最大电流不够驱动蜂鸣器,这时要加个三极管来驱动

电路图

2015-11-28 14:58:42 snyanglq 阅读数 6162
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2489 人正在学习 去看看 朱有鹏
一、什么是蜂鸣器

蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。在单片机电路中一般作为报警、提示或者播放音乐用。下面放张实物图


二、蜂鸣器的分类
按结构原理分蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。
按工作方式分,蜂鸣器主要分为有源和无源。


三、有源蜂鸣器和无源蜂鸣器的识别
从上图看,两种蜂鸣器好像一样,但仔细看,两者的高度略有区别,有源蜂鸣器,高度为9mm,
而无源蜂鸣器的高度为8mm。如将两种蜂鸣器的引脚都朝上放置时,可以看出有绿色电路板的一
种是无源蜂鸣器,没有电路板而用黑胶封闭的一种是有源蜂鸣器。进一步判断有源蜂鸣器和无源
蜂鸣器,还可以用万用表电阻档Rxl档测试:用黑表笔接蜂鸣器 "-"引脚,红表笔在另一引脚上来
回碰触,如果触发出咔、咔声的且电阻只有8Ω(或16Ω)的是无源蜂鸣器;如果能发出持续声音的,
且电阻在几百欧以上的,是有源蜂鸣器。
有源蜂鸣器直接接上额定电源(新的蜂鸣器在标签上都有注明)就可连续发声;
而无源蜂鸣器则和电磁扬声器一样,需要接在音频输出电路中才能发声。

注意:这里的""不是指电源,而是指震荡源。也就是说,有源蜂鸣器内部带震荡源,
所以只要一通电就会叫;而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。
必须用2K-5K方波去驱动它,所以有源蜂鸣器往往比无源,就是因为里面多个震荡电路。


四、蜂鸣器驱动电路

我在网上找了几个驱动电路,基本上单片机的驱动电路都是这样搭建的,大家参考下吧






五、蜂鸣器驱动程序

由于本人的驱动电路是焊接的是有源蜂鸣器,所以驱动程序如下:

<span style="font-size:18px;">#include <reg52.h>
sbit beep=P0^1;
void main(void)
{
 beep=1;
}</span>

若是无源蜂鸣器

<span style="font-size:18px;">#include <reg52.h>
sbit beep=P0^1;

//延时约250us
void delay(void)
{
	unsigned char i;
	for(i=0; i<125; i++)
	{
		;
	}
}
void main(void)
{
	while(1)
	{
		beep=1;
		delay();
		beep=0;
		delay();
	}
}</span>



没有更多推荐了,返回首页