键盘乐器

2020-01-237910
AI 快速预览详细 收起
本文介绍了一个使用Arduino和MIDI模块制作简易电子琴的项目。通过将USB键盘作为乐器按键,结合VS1053B芯片的MIDI功能,实现了键盘按键与MIDI音符的映射。文章详细说明了硬件选择、连接方式及代码实现。适合零基础的新手,无需音乐基础也能完成。
上小学的时候,音乐课对于五音不全的我来说是痛苦的存在。好在老师并不好意思给一个勤奋听话的孩子不及格,所以我音乐课期末成绩通常都是60分。三年级的一天,老师宣布要期末考试,内容很简单,随机抽取教科书上的一个简谱唱出来即可。经过认真的思考,我认为这是一个能够攫取满分的机会。于是在考试前,我找了一个会唱的小伙伴,付出了几个大大泡泡糖的代价后,他教了我考试范围中的一首歌曲。回去后,我又在那首歌曲的简谱上用“多来米发 拉西”将每个音符重新标记,随后背了下来。在另一个阳光灿烂的音乐课上,同学们排队到老师面前抽签,我盯住前面同学抽中的这首歌曲,然后等到我之后以迅雷不及掩耳之势抽出来那张,在老师狐疑的目光中假装在看谱,然后背诵了出来…….如果说有时候一种声音,可以把人带回真实的过去。那么对于我来说,这个声音就是《小星星》-----那首二十多年前死记硬背下来的歌曲。
相比小号笛子之类的乐器,电子琴这样的键盘发生设备是更容易理解和操作的设备。如果将USB的键盘作为乐器按键,那么我们就能得到一个最简单的键盘乐器。为了达到这个目标,需要解决如下2个问题:
1.     发声的问题
2.     USB键盘数据的解析;
首先需要一种能够发出乐器声音的模块。自然而然想到的是各种录音播放模块,但是很多时候会同时演奏出多个不同声音(可以理解为按下2个钢琴键实现同时发声)。对于这种情况下录音模块是无能为力的。接下来目光投向 目前已有的MIDI方案。MIDI MusicalInstrument Digital Interface(乐器数字接口)的简称。有时候中文会翻译成“迷笛”。这种技术是20世纪80 年代初为解决电声乐器之间的通信问题而提出的。简单的说这种方案规定了一种乐器通讯机制,比如当前按下了钢琴某个键,力度是多少。当音源设备收到这个消息后会根据内容播放实现准备好的声音。显而易见,这样的方案相比直接录音要节省大量空间,比如:十分钟左右的钢琴曲只有几十Kbytes的大小,WAV至少要十几Mbytes。经过比较,确定选用VS1053b 芯片的模块,这个芯片是单片OggVorbis/MP3/AAC/WMA/MIDI音频解码器,当它收到MIDI消息后会将乐器的声音直接发送出去。
选择的是微雪的 Music Shield模块,是通过 SPI 接口和 Arduino 进行通讯。特别的我在阅读资料的时候特别看到,VS1053B芯片支持一种实时Midi模式,上电之后可以从RXPin 接受 MIDI 数据来发生。但是这种模式需要HW设置VS1053B 引脚。市面上的模块并没有支持和预留。因此,这次的 Music Shield仍然使用 SPI 接口,MIDI数据也是从SPI送到芯片中的。
键盘乐器图1

对于第二个问题,很容易想到使用之前多次使用的USBHost Shield来完成。但是它存在占用 SPI 接口,代码复杂的缺点。经过比较最终确定使用的是南京沁恒微电子股份有限公司生产的USB键盘鼠标转串口通讯控制芯片 CH9350。南京沁恒是一家国产芯片公司,它设计了一些很有意思的USB 相关芯片,比如,市面上大量使用的廉价CH340 系列的USB转串口方案就是他们家的产品。这次使用的CH9350芯片是键盘鼠标远距离传输的方案。
键盘乐器图2
例如,右侧的CH9350芯片解析操作者的USB键盘鼠标条码枪的数据,然后转换为TTL 串口信号(TXD,RXD)。这样可以用RS485或者网络发送到远端,再用另外的 CH3950芯片接收还原为一个USB设备从而实现远程操作的目的。同样的方法还可以实现一套键盘鼠标控制多台机器的目的。

硬件列表:
1.      1.CH9350 Mini 模块
2.      2.微雪MusicShield 模块
3.      3Arduino Proto Shield (用于固定CH9350 Mini,无任何元件)
4.      4.Arduino Proto Shield 扩展(用于插接Music Shield,无任何元件)
硬件连接(实际上我使用Shield,直接插上即可,并没有特别线路连接):
键盘乐器图3
需要特别注意的是 CH9350模块上的 RSV 需要接地,这样会使得芯片工作在状态3,每次自动将解析后的键盘鼠标数据通过串口发出:
键盘乐器图4
软件方面,首先因为选用的是 Arduino Uno,留下硬串口以便Debug,使用软串口来和 CH9350通讯。之后将键盘按键和MIDI 发生映射起来,使用的映射方法和Everyone Piano 相同:

键盘乐器图5
代码:

  1. #include <SoftwareSerial.h>
  2. #include <SPI.h>
  3. #include <MusicPlayer.h>
  4. MusicPlayer player;
  5. //实例化软串口,连接到 Music Shield
  6. SoftwareSerial USBKB(2, 3); // RX, TX
  7. byte KeyToNote(byte key)
  8. {
  9.   byte v=0;
  10.   switch (key) {
  11.      case 0x1D: //Z
  12.        v=36;
  13.        break;
  14.      case 0x1B: //X
  15.        v=38;
  16.        break;
  17.      case 0x06: //C
  18.        v=40;
  19.        break;
  20.      case 0x19: //V
  21.        v=41;
  22.        break;
  23.      case 0x05: //B
  24.        v=43;
  25.        break;
  26.      case 0x11: //N
  27.        v=45;
  28.        break;
  29.      case 0x10: //M
  30.        v=47;
  31.        break;
  32.       case 0x36: //,
  33.        v=48;
  34.        break;
  35.       case 0x37: //.
  36.        v=50;
  37.        break;
  38.      case 0x38: // /
  39.        v=52;
  40.        break;
  41.      
  42.      case 0x04: //A
  43.        v=48;
  44.        break;
  45.      case 0x16: //S
  46.        v=50;
  47.        break;
  48.      case 0x07: //D
  49.        v=52;
  50.        break;
  51.      case 0x09: //F
  52.        v=53;
  53.        break;
  54.      case 0x0A: //G
  55.        v=55;
  56.        break;
  57.      case 0x0B: //H
  58.        v=57;
  59.        break;
  60.      case 0x0D: //J
  61.        v=59;
  62.        break;
  63.      case 0x0E: //K
  64.        v=60;
  65.        break;
  66.      case 0x0F: //L
  67.        v=62;
  68.        break;
  69.      case 0x33: //;
  70.        v=64;
  71.        break;
  72.      case 0x34: //'
  73.        v=65;
  74.        break;
  75.       
  76.      case 0x14:  //Q
  77.        v=60;
  78.        break;
  79.      case 0x1A: //W
  80.        v=62;
  81.        break;
  82.      case 0x08:  //E
  83.        v=64;
  84.        break;
  85.      case 0x15: //R
  86.        v=65;
  87.        break;
  88.      case 0x17: //T
  89.        v=67;
  90.        break;
  91.      case 0x1C: //Y
  92.        v=69;
  93.        break;
  94.      case 0x18: //U
  95.        v=71;
  96.        break;
  97.      case 0x0C: //I
  98.        v=72;
  99.        break;
  100.      case 0x12: //O
  101.        v=74;
  102.        break;
  103.      case 0x13: //P
  104.        v=76;
  105.        break;
  106.      case 0x2F: //[
  107.        v=77;
  108.        break;      
  109.      case 0x30: //]
  110.        v=79;
  111.        break;      
  112.      case 0x1E:  //1
  113.        v=72;
  114.        break;
  115.      case 0x1F: //2
  116.        v=74;
  117.        break;
  118.      case 0x20:  //3
  119.        v=76;
  120.        break;
  121.      case 0x21: //4
  122.        v=77;
  123.        break;
  124.      case 0x22: //5
  125.        v=79;
  126.        break;
  127.      case 0x23: //6
  128.        v=81;
  129.        break;
  130.      case 0x24: //7
  131.        v=83;
  132.        break;
  133.      case 0x25: //8
  134.        v=84;
  135.        break;
  136.      case 0x26: //9
  137.        v=86;
  138.        break;
  139.      case 0x27: //0
  140.        v=88;
  141.        break;
  142.      case 0x28: //-
  143.        v=89;
  144.        break;      
  145.      case 0x29: //=
  146.        v=91;
  147.        break;            
  148.     default:
  149.      break;
  150.     }   
  151.     return v;      
  152. }
  153. void setup()
  154. {
  155.   //用于Arduino Debug 串口
  156.   Serial.begin(115200);
  157.   
  158.   //用于和CH3995通讯
  159.   USBKB.begin(38400);
  160.   //初始化 Midi 功能
  161.   player.beginMidi();
  162.   
  163.   delay(2000);
  164.   //设置主音量为最大
  165.   player.midiWriteData(0xB0, 0x07, 127);
  166.   
  167.    //player.midiWriteData(0xB0, 0, 0x00);    //Default bank GM1
  168.    
  169.    player.midiWriteData(0xC0, 1, 0);   
  170. }
  171. //每笔 CH9350 键盘数据长度
  172. #define CH9350KBLENGTH  11
  173. //存放上一次 CH9350 发送过来的数据
  174. char Last[CH9350KBLENGTH]={0,0,0,0,0,0,0,0,0,0,0};
  175. //存放当前 CH9350 发送过来的数据
  176. char Current[CH9350KBLENGTH];
  177. byte i;
  178. boolean Found;
  179. void loop()
  180. {
  181.   byte Index=0;
  182.   //取得 CH9350 送过来的当前按键信息
  183.   while (Index<CH9350KBLENGTH-1) {//不保存首位的 0x57 帧头,所以总共只有
  184.     if ((USBKB.available())&&(0x57==USBKB.read())) {
  185.         while (Index<CH9350KBLENGTH-1) {
  186.             if (USBKB.available()) {
  187.                 Current[Index++]=USBKB.read();
  188.               }
  189.           }
  190.       }
  191.   }
  192.   
  193.   for (int p=0;p<CH9350KBLENGTH;p++) {Serial.print((byte)Current[p],HEX);Serial.print(" "); }
  194.   Serial.print("\n");
  195.   //检查 Current[Index],如果Last[]没有的,那么需要发送出去
  196.   for (Index=4;Index<CH9350KBLENGTH;Index++) {
  197.     Found=false;
  198.     if (Current[Index]!=0) {
  199.       for (i=4;i<CH9350KBLENGTH;i++) {
  200.          if (Current[Index]==Last<i>) {
  201.                  Found=true;
  202.          }
  203.       }
  204.     if (Found==false) {
  205.       //Set instrument number. 0xC0 is a 1 data byte command
  206.       player.midiNoteOn(0, KeyToNote(Current[Index]), 127);
  207.       Serial.print("On ");
  208.       Serial.println(Current[Index],HEX);
  209.       Serial.print(" >> ");
  210.       Serial.println(KeyToNote(Current[Index]));
  211.     }
  212.     }
  213.   }
  214.   //检查 Last[Index],如果Current[]里面没有,那么需要发送NoteOff命令
  215.   for (Index=4;Index<CH9350KBLENGTH;Index++) {
  216.     Found=false;
  217.     if (Last[Index]!=0) {
  218.       for (i=4;i<CH9350KBLENGTH;i++) {
  219.          if (Last[Index]==Current<i>) {
  220.                  Found=true;
  221.          }
  222.       }
  223.       if (Found==false) {
  224.         player.midiNoteOff(0, KeyToNote(Current[Index]), 127);
  225.         Serial.print("Off ");
  226.         Serial.println(KeyToNote(Last[Index]),HEX);
  227.       }
  228.      }
  229.     }
  230.    for (Index=0;Index<10;Index++) {Last[Index]=Current[Index];}
  231. }</i></i>
复制代码

VS1053B 用来播放MIDI是很好的选择,这次使用的是微雪出品的Music Shield 价格在90左右,淘宝最低价格有35的,我觉得应该同样能够使用。


工作的视频

完整的代码和用的库

下载附件MusicShield.zip

代码
下载附件MusicKB.zip

创作许可协议

本项目采用 None(不开放任何权利,保留所有权利) 进行许可。

评论(5)
蟹蟹泳裤
蟹蟹泳裤
想问一下这个 可以接USB口的那种MIDI键盘演奏吗?还是要换别的芯片?
zoologist
zoologist
作者
回复蟹蟹泳裤
不可以的,这个没有做 USB MIDI 设备的解析
gada888
gada888
能脱离电脑播放么
zoologist
zoologist
作者
回复gada888
可以啊,这个就是不用电脑直接放出来声音的
kylinpoet
kylinpoet
楼主强大,多谢分享。
rzyzzxw
rzyzzxw
项目不错啊。
gada888
gada888
但实际演奏效果怎么,最好有视频
zoologist
zoologist
作者
回复gada888
- 没有更多了 -