当前位置:   article > 正文

笔记:C51单片机——音乐播放,模拟钢琴键。_c51音乐

c51音乐

首先知道单片机的蜂鸣器是有源蜂鸣器,可以利用修改频率来对其蜂鸣器改变发出不同的声音频率。

但是修改频率这个怎么修改呢。可以通过延迟修改,但是如果有一个钢琴乐谱在这里,就不知道呢个延迟对应着哪个按键。而且一个个延迟可能会导致代码太多,复用性也不好。

所以就可以通过定时器来代替延迟函数,而且这样子也省去了,占用运行内存。但是利用定时器也是需要模拟钢琴按键的。模拟钢琴按键怎么模拟呢,下面讲到就是首先知道钢琴按键有

低音,低半,中音,中半音,高音,高半音。半音其实就是黑色的那个就是按键就是对应着它旁边的半音键。

可以看一下这个钢琴谱,这里可以明显的看到低音的是数字底下有黑点,中音没有,高音的是数字头上有黑点,其实钢琴键有很多个键,但是音都是比原来的音高或者低。所以模拟按键只需要用到普遍的D介就可以了。可以看到就是没有黑点开始左边和右边其实只是比中介这个高或低。

还有在低音那里的黑色的键就是对应旁边的键的半音键,意思就是只发出半个声音。其它的都是依次这样的。

认识到了这个琴谱接下来就是怎么识别这个琴谱的频率了。这个频率其实也是有公式的,只需要知道一个琴谱的频率,其它的都可以知道。公式就是知道前一个音的频率。这个频率的后一个音等于前一个音乘2的12分之一次方(前一个频率*2^(1/12))同理知道后一个频率前一个频率等于后一个除以2的12分之一次方(后一个频率/2^(1/12))。但是知道频率之后还不能直接用,还有利用单片机的频率周期然后计算重装载值,所以得到频率之后,计算周期,周期等于=((1/频率*1000000)/2),重装载值就是定时器的65536-周期。所以最后的出一张琴谱模拟频率重装载值就可以直接用到定时器。

 

 上面有个数字旁边的#号就是半音的意思。这个琴谱频率其实就是对应着第一张图片左往有。

认识到这里就大致了解这个琴谱频率怎么转换了。但是转换完了之后,对于乐谱又要看这个音调了。接下来就是怎么看乐谱对应着那个按键了。

先随便符一张琴谱的图片,这个其实怎么看呢。很简单的。只需要看着下面的数字就可以知道是那个按键了。就比如第一个6 7 底下有一行的就是音乐播放时间减半。而且这个6旁边没有点就是中介音的6音时长减原来的一半,然后7也是同理。在过去就是1这个1头顶有小点的就是高介的1而且右边有点就是把播放时长加原来的一半。在在过去的7就是中介时长减半。然后 1 3头顶有点就高介,正常的时长。这里没有减半和加半。再过去就可以看到7有边有这个符号— ,这个就是加时长的意思,而且是加一个正常时长 比如说正常是4 那么就是4+4+4因为自身正常是4然后有两个个—所以就是4 +4+4。再下去第二行可以看到有一个#号这个意思就是说的升半音的意思,#4 —在底下意思就是4中音升半介然后时间减半。还有第二行后面头顶有个半括号的意思就是延音键,就是把按键的时长加长,而且是在一次按下的意思,而不是分开两次按。两个7 7头顶一个括号)。就是中音一个时长减半所以剩下2+4,后面的7没有减半。

看着上面有点乱,所以下面总结了一下。

(1)#是升半音 而且在同一个小节的音调同样的音调都要升,同一个小节的意思就是用  |   分开为一个小节

(2)b是减半音

(3)一个—在有右边是加一个自身时长

(4).在右边加原来的2分之一时长

(5)底下加—就是减半时长

(6)头顶上( 是延音键 的意思就是两个音的时长相加在一个音上比如 高音2+4

(7).在头顶是高音

(8).在地下是低音

(9)无点是中音

(10)0就是休止的意思,就是停止按键的时长

 

接下来就是代码实现了

首先是定时器

  1. void Time_Init(void)
  2. {
  3. TMOD&=0xf0; //把TMOD低四位清0,高四位保持不变
  4. TMOD|=0x01; //把TMOd低四位置1,高四位保持不变
  5. TMOD=0X01; //设置定时器/计数器模式
  6. TL0=0x66; //1ms
  7. TH0=0xfc;
  8. TF0=0; //定时器中断要清零,意思是当我定时器设置的数到了,即将溢出我就要清零,开始执行设置的任务
  9. TR0=1; //定时器启动开始工作
  10. ET0=1; //中断允许控制位 128 64 32 16 8 4 2 1
  11. EA=1; //开启中断电路
  12. PT0=0; //接通外部中断源
  13. }

定时器中断代码,这里只有一个函数,其实就没到1ms就进入一次这个函数意思,执行这个函数里面的代码

  1. void time1_Init() interrupt 1 //这个是调用定时器模板每进入一次都记1ms
  2. {
  3. Beep_Loop();
  4. }

这个就是每次进入重装载值得时间了。

  1. void Beep_Loop(void)
  2. {
  3. if(Beep_frep[Freplect]) //用来休止符的,如果位置的符号是选择这个数组的 Beep_frep[] 第0个则不进入定时器计时
  4. {
  5. TL0=Beep_frep[Freplect]%256; //低位
  6. TH0=Beep_frep[Freplect]/256; //设置高位每次进入的初始值 因为这个没有设置停止位 只能每次溢出的时候才会重新进入,所以要找到定时器的时间只能设置初始值,就是在中间截取到溢出停止
  7. Beep1=!Beep1; //按照上面得频率蜂鸣器响
  8. }
  9. }

然后钢琴谱的代码也不是很难,只需要把上面的重装载值做成一个数组。就像这样,说一下为什么要用到重装载值呢,就是因为定时器要计算这个数的频率,但是这个定时器每次都是溢出它才会重新计算,但是不能再溢出阻止它的时长度,那么就可以在它进入的时间里控制,也是一样达到控制时长的。就好比本是1ms时间溢出,但是控制不可了到0.5ms溢出就停止,那么我可以在0.5ms的时候再让它进入,一样可以达到让它计算0.5ms 这样也是一样达到0.5ms的效果。这就是重装载值得意思。

  1. unsigned int code Beep_frep[]=
  2. { 0,63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64466,64528,
  3. 64580,646353,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
  4. 65058,65085,65110,65134,65158,65178,65198,65217,65235,65252,65268,65283
  5. };
  6. //这里是按照低音到高音排序的。
  7. //有一个0等下选键的时候要用到的。

定义完重装载值的数组,然后就是选择数组,选择的意思就是选择按那个音键。可以这样定义一个宏定义,这样可以达到复用的效果

  1. #define Stop 0 //这个0就是选择重装载值的第0个位置
  2. #define L1 1 //下面的一次是低音,然后中音,高音,也是按照琴谱的位置来放的
  3. #define L1_ 2
  4. #define L2 3
  5. #define L2_ 4
  6. #define L3 5
  7. #define L4 6
  8. #define L4_ 7
  9. #define L5 8
  10. #define L5_ 9
  11. #define L6 10
  12. #define L6_ 11
  13. #define L7 12
  14. //中音
  15. #define M1 13
  16. #define M1_ 14
  17. #define M2 15
  18. #define M2_ 16
  19. #define M3 17
  20. #define M4 18
  21. #define M4_ 19
  22. #define M5 20
  23. #define M5_ 21
  24. #define M6 22
  25. #define M6_ 23
  26. #define M7 24
  27. //高音
  28. #define H1 25
  29. #define H1_ 26
  30. #define H2 27
  31. #define H2_ 28
  32. #define H3 29
  33. #define H4 30
  34. #define H4_ 31
  35. #define H5 32
  36. #define H5_ 33
  37. #define H6 34
  38. #define H6_ 35
  39. #define H7 36

下面就是宏定义数组得代码了。这个数组就是用来存放需要按下得那个按键了。只要按照琴谱修改这个数组得定义,就可以得到一首新的曲子了。

  1. unsigned int code Beep_freplectMusic[]={
  2. //音符是钢琴谱C调的4/4 把1/16作为基准1 1/8则是2 1/4是4 1/2是8 1是16
  3. //0是休止符用于选中Beep_frep[]的第0位
  4. //1行
  5. //音频位选择 音频时长 里面都是写的宏定义得值,也就是重装载数组得第几个位置。后面得数字就是要播放得时长了。
  6. Stop,4,
  7. Stop,4,
  8. Stop,4,
  9. M6,2,
  10. M7,2,
  11. H1,4+2,
  12. M7,2,
  13. H1,4,
  14. H3,4,
  15. M7,4+4+4,
  16. M3,2,
  17. M3,2,
  18. //2行
  19. M6,4+2,
  20. M5,2,
  21. M6,4,
  22. H1,4,
  23. M5,4+4+4,
  24. M3,4,
  25. M4,4+2,
  26. M3,2,
  27. M4,4,
  28. H1,4,
  29. //3行
  30. M3,4+4,
  31. Stop,2,
  32. H1,2,
  33. H1,2,
  34. H1,2,
  35. M7,4+2,
  36. M4_,2,
  37. M4_,4,
  38. M7,2,
  39. M7,4+4,
  40. Stop,4,
  41. M6,2,
  42. M7,2,
  43. //4行
  44. H1,4+2,
  45. M7,2,
  46. H1,4,
  47. H3,4,
  48. M7,4+4+4,
  49. M3,2,
  50. M3,2,
  51. M6,4+2,
  52. M5,2,
  53. M6,4,
  54. H1,4,
  55. //5行
  56. M5,4+4+4,
  57. M2,2,
  58. M3,2,
  59. M4,4,
  60. H1,2,
  61. M7,2+2,
  62. H1,2+4,
  63. H2,2,
  64. H2,2,
  65. H3,2,
  66. H1,2+4+4,
  67. //6行
  68. H1,2,
  69. M7,2,
  70. M6,2,
  71. M6,2,
  72. M7,4,
  73. M5_,4,
  74. M6,4+4+4,
  75. H1,2,
  76. H2,2,
  77. H3,4+2,
  78. H2,4,
  79. H3,4,
  80. H5,2,
  81. //7行
  82. H2,4+4+4,
  83. M5,2,
  84. M5,2,
  85. H1,4+2,
  86. M7,2,
  87. H1,4,
  88. H3,4,
  89. H3,4+4+4+4,
  90. //8行
  91. M6,2,
  92. M7,2,
  93. H1,4,
  94. M7,4,
  95. H2,2,
  96. H2,2,
  97. H1,4+2,
  98. M5,2+4+4,
  99. H4,4,
  100. H3,4,
  101. H2,4,
  102. H1,4,
  103. //9行
  104. H3,4+4+4,
  105. H3,4,
  106. H6,4+4,
  107. H5,4,
  108. H5,4,
  109. H3,2,
  110. H2,2,
  111. H1,4+4,
  112. Stop,2,
  113. H1,2,
  114. //10行
  115. H2,4,
  116. H1,2,
  117. H2,2,
  118. H2,4,
  119. H5,4,
  120. H3,4+4+4,
  121. H3,4,
  122. H6,4+4,
  123. H5,4+4,
  124. //11行
  125. H3,2,
  126. H2,2,
  127. H1,4+4,
  128. Stop,2,
  129. H1,2,
  130. H2,4,
  131. H1,2,
  132. H2,2+4,
  133. M7,4,
  134. M6,4+4+4,
  135. M6,2,
  136. M7,2,
  137. 0xff //音乐停止符
  138. };

然后接下来就是主要得选择位置得代码了,不明白可以看注释的

  1. void Beep_music_Init(unsigned int SPEED)
  2. {
  3. if(Beep_freplectMusic[Musiclect]!=0xff) //播放音乐就停止 这个在宏定义数组的最后一个位置,只有把这个曲子播放一遍就会轮到后面的这个0xff
  4. {
  5. Freplect=Beep_freplectMusic[Musiclect]; //这里开始的是数组0值每次轮回都是双数 用来控制Beep_freplectMusic[双数位置]播放那个音乐的调 ,可以看到双数的位置都是调
  6. Musiclect++;
  7. delay(SPEED/4*Beep_freplectMusic[Musiclect]); //到这里是数组的1值,每次轮回都是单数是控制Beep_freplectMusic[单数位置]音乐每个频率的时长 ,可以看到单数位置都是播放的时长
  8. Musiclect++;
  9. TR0=0; //设置模拟钢琴抬手关闭在打开 ,如果没有这个延迟的定时器开关,听到声音就会很连贯,没有断点。
  10. delay(5);
  11. TR0=1;
  12. }
  13. else
  14. {
  15. TR0=0; //关闭定时器音乐就停止
  16. }
  17. }

上面的是封装好的代码。下面的是直接在一个main.c文件的代码

  1. #include <REGx52.H>
  2. #define Stop 0
  3. #define L1 1
  4. #define L1_ 2
  5. #define L2 3
  6. #define L2_ 4
  7. #define L3 5
  8. #define L4 6
  9. #define L4_ 7
  10. #define L5 8
  11. #define L5_ 9
  12. #define L6 10
  13. #define L6_ 11
  14. #define L7 12
  15. //中音
  16. #define M1 13
  17. #define M1_ 14
  18. #define M2 15
  19. #define M2_ 16
  20. #define M3 17
  21. #define M4 18
  22. #define M4_ 19
  23. #define M5 20
  24. #define M5_ 21
  25. #define M6 22
  26. #define M6_ 23
  27. #define M7 24
  28. //高音
  29. #define H1 25
  30. #define H1_ 26
  31. #define H2 27
  32. #define H2_ 28
  33. #define H3 29
  34. #define H4 30
  35. #define H4_ 31
  36. #define H5 32
  37. #define H5_ 33
  38. #define H6 34
  39. #define H6_ 35
  40. #define H7 36
  41. sbit Beep1=P1^5;
  42. unsigned int Freplect=0,Musiclect=0;
  43. void Time_Init(void);
  44. void Beep_Loop(void);
  45. void delay(unsigned int xms);
  46. unsigned int code Beep_frep[]=
  47. { 0,63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64466,64528,
  48. 64580,646353,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
  49. 65058,65085,65110,65134,65158,65178,65198,65217,65235,65252,65268,65283
  50. };
  51. //这个数组是用来选择Beep_frep[]数组的位置,以更好的确认音乐频率
  52. unsigned int code Beep_freplectMusic[]={
  53. //音符是钢琴谱C调的4/4 把1/16作为基准1 1/8则是2 1/4是4 1/2是8 1是16
  54. //0是休止符用于选中Beep_frep[]的第0位
  55. //音频位选择 音频时长
  56. Stop,4,
  57. Stop,4,
  58. Stop,4,
  59. M6,2,
  60. M7,2,
  61. H1,4+2,
  62. M7,2,
  63. H1,4,
  64. H3,4,
  65. M7,4+4+4,
  66. M3,2,
  67. M3,2,
  68. //2行
  69. M6,4+2,
  70. M5,2,
  71. M6,4,
  72. H1,4,
  73. M5,4+4+4,
  74. M3,4,
  75. M4,4+2,
  76. M3,2,
  77. M4,4,
  78. H1,4,
  79. //3行
  80. M3,4+4,
  81. Stop,2,
  82. H1,2,
  83. H1,2,
  84. H1,2,
  85. M7,4+2,
  86. M4_,2,
  87. M4_,4,
  88. M7,2,
  89. M7,4+4,
  90. Stop,4,
  91. M6,2,
  92. M7,2,
  93. //4行
  94. H1,4+2,
  95. M7,2,
  96. H1,4,
  97. H3,4,
  98. M7,4+4+4,
  99. M3,2,
  100. M3,2,
  101. M6,4+2,
  102. M5,2,
  103. M6,4,
  104. H1,4,
  105. //5行
  106. M5,4+4+4,
  107. M2,2,
  108. M3,2,
  109. M4,4,
  110. H1,2,
  111. M7,2+2,
  112. H1,2+4,
  113. H2,2,
  114. H2,2,
  115. H3,2,
  116. H1,2+4+4,
  117. //6行
  118. H1,2,
  119. M7,2,
  120. M6,2,
  121. M6,2,
  122. M7,4,
  123. M5_,4,
  124. M6,4+4+4,
  125. H1,2,
  126. H2,2,
  127. H3,4+2,
  128. H2,4,
  129. H3,4,
  130. H5,2,
  131. //7行
  132. H2,4+4+4,
  133. M5,2,
  134. M5,2,
  135. H1,4+2,
  136. M7,2,
  137. H1,4,
  138. H3,4,
  139. H3,4+4+4+4,
  140. //8行
  141. M6,2,
  142. M7,2,
  143. H1,4,
  144. M7,4,
  145. H2,2,
  146. H2,2,
  147. H1,4+2,
  148. M5,2+4+4,
  149. H4,4,
  150. H3,4,
  151. H2,4,
  152. H1,4,
  153. //9行
  154. H3,4+4+4,
  155. H3,4,
  156. H6,4+4,
  157. H5,4,
  158. H5,4,
  159. H3,2,
  160. H2,2,
  161. H1,4+4,
  162. Stop,2,
  163. H1,2,
  164. //10行
  165. H2,4,
  166. H1,2,
  167. H2,2,
  168. H2,4,
  169. H5,4,
  170. H3,4+4+4,
  171. H3,4,
  172. H6,4+4,
  173. H5,4+4,
  174. //11行
  175. H3,2,
  176. H2,2,
  177. H1,4+4,
  178. Stop,2,
  179. H1,2,
  180. H2,4,
  181. H1,2,
  182. H2,2+4,
  183. M7,4,
  184. M6,4+4+4,
  185. M6,2,
  186. M7,2,
  187. 0xff //音乐停止符
  188. };
  189. void Time_Init(void)
  190. {
  191. TMOD&=0xf0; //把TMOD低四位清0,高四位保持不变
  192. TMOD|=0x01; //把TMOd低四位置1,高四位保持不变
  193. TMOD=0X01; //设置定时器/计数器模式
  194. TL0=0x66; //1ms
  195. TH0=0xfc;
  196. TF0=0; //定时器中断要清零,意思是当我定时器设置的数到了,即将溢出我就要清零,开始执行设置的任务
  197. TR0=1; //定时器启动开始工作
  198. ET0=1; //中断允许控制位 128 64 32 16 8 4 2 1
  199. EA=1; //开启中断电路
  200. PT0=0; //接通外部中断源
  201. }
  202. void Beep_Loop(void)
  203. {
  204. if(Beep_frep[Freplect]) //用来休止符的,如果位置的符号是选择这个数组的 Beep_frep[] 第0个则不进入定时器计时
  205. {
  206. TL0=Beep_frep[Freplect]%256; //低位
  207. TH0=Beep_frep[Freplect]/256; //设置高位每次进入的初始值 因为这个没有设置停止位 只能每次溢出的时候才会重新进入,所以要找到定时器的时间只能设置初始值,就是在中间截取到溢出停止
  208. Beep1=!Beep1;
  209. }
  210. }
  211. void delay(unsigned int xms) //延迟函数 1表示1ms
  212. {
  213. unsigned char i, j;
  214. while(xms--){
  215. i = 2;
  216. j = 239;
  217. do
  218. {
  219. while (--j);
  220. } while (--i); }
  221. }
  222. void main(void)
  223. {
  224. if(Beep_freplectMusic[Musiclect]!=0xff) //播放音乐就停止
  225. {
  226. Freplect=Beep_freplectMusic[Musiclect]; //这里开始的是数组0值每次轮回都是双数 用来控制播放那个音乐的调
  227. Musiclect++;
  228. delay(100/4*Beep_freplectMusic[Musiclect]); //到这里是数组的1值,每次轮回都是单数是控制音乐每个频率的时长
  229. Musiclect++;
  230. TR0=0; //设置模拟钢琴抬手关闭在打开
  231. delay(5);
  232. TR0=1;
  233. }
  234. else
  235. {
  236. TR0=0; //关闭定时器音乐就停止
  237. }
  238. }
  239. void time1_Init() interrupt 1 //这个是调用定时器模板每进入一次都记1ms
  240. {
  241. Beep_Loop();
  242. }

总之就是一句话,上面的代码忘记了没事,只需要把unsigned int  code Beep_freplectMusic[]数组里面的钢琴键位置按照上面教的对应的钢琴谱来放置位置就可以得到一首新的曲子了。

也就是下面这个,看着谱子按照这个方式来改就没有多大问题。

(1)#是升半音 而且在同一个小节的音调同样的音调都要升,同一个小节的意思就是用  |   分开为一个小节

(2)b是减半音

(3)一个—在有右边是加一个自身时长

(4).在右边加原来的2分之一时长

(5)底下加—就是减半时长

(6)头顶上( 是延音键 的意思就是两个音的时长相加在一个音上比如 高音2+4

(7).在头顶是高音

(8).在地下是低音

(9)无点是中音

(10)0就是休止的意思,就是停止按键的时长

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/554305
推荐阅读
相关标签
  

闽ICP备14008679号