加载中…
个人资料
谢作如
谢作如
  • 博客等级:
  • 博客积分:0
  • 博客访问:1,106,896
  • 关注人气:1,732
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

陈益漳的作品:用步进电机搭建MIDI播放设备

(2016-12-04 15:35:07)
标签:

互动媒体

创客作品

创客教育

分类: 温州中学创客空间

      本文的作品视频地址为:http://www.bilibili.com/video/av4596330/


      一个高中生的作品,不想受到了很多人的关注。本文发表于《无线电》,这里有删节。

缘起

有一天我在逛论坛的时候突然发现了一个帖子。具体讲的是使用步进电机播放MIDI音乐,同时再加上一些其他的部件。这个贴子[1]从播放MIDI音乐的角度出发,基本完成了播放MIDI音乐的目标。但是原文的作者似乎没有看到使用较老的ArduinoMIDI实现方式在简易性和兼容性上的缺陷,即使用时要更改大量的参数、在各种平台下支持能力较差。很早之前我就有过类似步进电机制作MIDI设备的想法——主要是因为工作环境里的3D打印机相当吵,而且声音非常有节奏感——要拿步进电机做点什么鬼畜(一种混音作品形式)。看到这个帖子之后我受到了启发,立刻开始了制作步进电机乐队的工作。

准备

首先我们需要了解一些我们乐队的主唱和伴奏:步进电机。步进电机是将电脉冲信号转变为角位移或线位移的开环控制元步进电机件。在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响,当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度,称为步距角,它的旋转是以固定的角度一步一步运行的。可以通过控制脉冲个数来控制角位移量,从而达到准确定位的目的;同时可以通过控制脉冲频率来控制电机转动的速度和加速度,从而达到调速的目的。[2]

一般的步进电机一般会使用4个引脚,两个是电源(一正一负),一个是转动方向(低电平和高电平方向不同,视型号而定),一个是接受脉冲的引脚(由低到高驱动电机走一步)。

我们可以在步进电机的工作过程中发现,步进电机的运转时发出的噪音具有一定的规律,其音高与其当前转速有直接关联。因此如果我们给一个步进电机合适频率的脉冲,步进电机就能够按照一定的音高发出我们需要的声音。

经过反复实验和参考资料,我发现直接以标准A440Hz)的频率发送脉冲,并按照半音频率之间相差陈益漳的作品:用步进电机搭建MIDI播放设备的规律(也就是升一个八度频率翻一倍),就可以发出对应的音。同样的方式也适用于蜂鸣器等可以发出声音的元件。

 

怎么让步进电机和MIDI输出连起来

我使用的是Arduino LeonardoDFrobot出品的双路步进电机驱动板和步进电机。

陈益漳的作品:用步进电机搭建MIDI播放设备



设计的结构示意图

 

很早之前Arduino官方就在IDE中包含了一个有关MIDI的样例。而这个样例基于一个开源软件Hairless MIDI(或者其他有类似功能的软件)。这个软件在我的Windows 10系统下无法找到对应的MIDI端口,因此我转移到了Ubuntu上。这恐怕也是我一开始看到的帖子使用的环境是Ubuntu的原因。Hairless MIDI官网上的介绍说这个小程序可以在LinuxWindowsMac OS的环境下运行。但是我亲自测试后发现这个软件并没有官方所说的那么好的兼容性。

Hairless MIDI的主要作用就是将我们使用的编曲软件等带有的MIDI输出端口转移到串口上,以各种Arduino常用的波特率发送MIDI信号。这部分的实现十分简单。而早期的Arduino IDE无法使用9600倍数外的波特率。不幸的是,MIDI设备使用的标准波特率是31250。更糟的是,因为波特率的不同,常常会出现由于过多指令发送给Leonardo而导致串口传输速度跟不上,电机声音不同步。加上Arduino串口在过高的波特率下容易丢失数据、换一台电脑就要多装一个Hairless MIDI,我最终放弃了使用软件转接MIDI信号的方式。

基于一个MIDIUSB的库就可以直接将Arduino板变成一个USB连接的MIDI设备,直接接到任何一个可以接受MIDI设备的PC或者其他MIDI输出设备上。从原理上说,这种实现方式可以在所有的输出MIDI的设备上使用。这个库中封装了一个名称为MidiUSB的类,它可以直接用MidiUSB.read()读取MIDI信号,并返回一个midiEventPacket_t类的变量。库中midiEventPacket_t定义如下:

1

2

3

4

5

6

7

typedef struct

{

uint8_t header;

uint8_t byte1;

uint8_t byte2;

uint8_t byte3;

}midiEventPacket_t;

可以看到这个类(实际上只是结构体)中只存了3个字节的内容(header实际上是byte1的前4位,即第一字节的高位,表示当前指令)。之后我们需要的就是对读取的MIDI信号进行处理。处理MIDI的部分本着不重新发明轮子的原则,我在一个国外的论坛上找到了MIDI处理的具体代码。[3]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

// A very simple MIDI synth.

// Greg Kennedy 2011

//Avoid other code about defining and so on

void loop () {

 static byte note;

 static byte lastCommand = MIDI_IGNORE;

 static byte state;

 static byte lastByte;

 

 while (Serial.available()) {

 

   // read the incoming byte:

   byte incomingByte = Serial.read();

 

   // Command byte?

   if (incomingByte & 0b10000000) {

     if (respondAllChannels ||

            (incomingByte & 0x0F) == myChannel) { // See if this is our channel

       lastCommand = incomingByte & 0xF0;

     } else { // Not our channel.  Ignore command.

       lastCommand = MIDI_IGNORE;

     }

     state = MIDI_STATE_BYTE1; // Reset our state to byte1.

   } else if (state == MIDI_STATE_BYTE1) { // process first data byte

     if ( lastCommand==MIDI_CMD_NOTE_OFF )

     { // if we received a "note off", make sure that is what is currently playing

       if (note == incomingByte) noTone(tonePin);

       state = MIDI_STATE_BYTE2; // expect to receive a velocity byte

     } else if ( lastCommand == MIDI_CMD_NOTE_ON ){ 

// if we received a "note on", we wait for the note (databyte)

       lastByte=incomingByte;    // save the current note

       state = MIDI_STATE_BYTE2; // expect to receive a velocity byte

     }

     // implement whatever further commands you want here

   } else { // process second data byte

     if (lastCommand == MIDI_CMD_NOTE_ON) {

       if (incomingByte != 0) {

         note = lastByte;

         tone(tonePin,(unsigned int)pgm_read_word(&frequency[note]));

       } else if (note == lastByte) {

         noTone(tonePin);

       }

     }

     state = MIDI_STATE_BYTE1; // message data complete

                                // This should be changed for SysEx

   }

 }

}

从这段代码我们可以看到,在处理MIDI信号时要针对状态字节(第一字节)的具体指令进行分类操作。实际上这段代码只涉及了两个我们会用到的操作:发声和停止发声。而一个完整的MIDI设备应该还能够处理其他的指令,如滑音等特效。同时因为每个读入的字节都无法确定第二字节还是第三字节,作者在写这段代码时特别使用了一个state变量来表示当前状态(读到了第几个字节)。

这段代码的年代比较久远,使用的仍是Hairless的实现方法。但是作者使用了一个我没见过的函数tonetone在英语中原意是音调,在Arduino中的作用就是对一个端口发送一定频率的脉冲。原型为tone(pin, frequency[, duration]),即(在duration的时间内)向pin端口发送频率为frequency Hz的脉冲。与之对应的还有停止发送脉冲的noTone()。这个函数的出场率实在不高,很多国内的教程都会忽略。但tone函数的的缺点在于不能同时对一个以上的端口发送信号。而采用分时系统(我称之为伪多线程)就无法及时停止脉冲,会导致不同步,这可能与Leonardo的性能和分时系统本身的缺陷有关。

那么动手吧

先将电机、驱动板、Leonardo组装在一起。因为步进电机驱动板需要超过8V的电压才能工作,所以我自己拿洞洞板焊了一个并联4个电机的部件。同时4Leonardo使用串口通信来传输信号,就把通信的线一起焊在洞洞板上了。(上图中左侧的布线是电机电源,右侧的是串口线)

陈益漳的作品:用步进电机搭建MIDI播放设备

MIDI处理的代码烧录到各个Leonardo上。但是一号板烧的代码里要使用MIDIUSB,其他的板就直接用串口(和原来一样)就行了。

那么试试看吧

我自己改编了一下黒うさP的千本樱,使得谱子适合电机演奏(单个轨道内没有和弦)。[4]听过的朋友居然还说挺好听。

实际上一开始,我的电机是直接放在桌上,声音相当轻。基本上只有在耳朵紧贴着的时候,才能听到。一开始我相当失落,以为是自己的电源(12V1A)不够强(虽然后来换了一个12V3A的),只好先凑合着用。有一天我将原来买电机时的电机包装盒拿来,把运转的电机放了进去,然后声音就提高了20db(估)。十分惊讶。十分欣喜。于是我就把两个电机放进盒子。而且飞机盒还可以调节开合程度来改变音色(只能在一个很小的范围内,还会因为电机的剧烈震动重新弹开)。

陈益漳的作品:用步进电机搭建MIDI播放设备

本来放在盒子里的两个电机准备做主旋律的。在组装到木板上时,我参照原来论坛上的帖子的方法,把剩下两个电机用扎带固定在木板上。工作后发现和木板共振的两个用扎带固定的电机声音居然比盒子里的还大。看来电机发声的音量和与之共振的物体大小有关。

可以改进的地方

完成这个步进电机的MIDI播放设备之后,我发现虽然这个实现的成果比原来论坛上的帖子的结果要好一些,但是还是存在如下几个问题:

1、不能够播放和弦

我之前学会MIDI音乐制作之后,一直都和同学开玩笑说我一把小提琴能同时拉响4根弦,一架钢琴能够弹88个和弦(小提琴只有4根弦,钢琴只有88个键。笑~)。但是我们现在制作的步进电机播放MIDI是无法播放同一条轨道上的和弦的。一开始我以为MIDI播放软件能够很机智用一种特别的算法将和弦的多个音合成一个别的音来代替和弦的所有的音。但是在实践后我发现实际上一只电机同时只能播放一个音,而且还会因为上一个音没有正确结束而出错。

我曾经试着使用将电机的当前状态记录下来,如果接受发声信息的时候对应电机正忙,把这个音符丢给其他电机。但是这么做就会导致别的电机在应该发它该发的音的时候无法正常发声。这个管理系统我没能完成,汗。

2、接线过多,装置很杂乱


陈益漳的作品:用步进电机搭建MIDI播放设备

这件事也没什么好说的,只好自己一个人分辨线的时候风中凌乱。而且比较丑。相比较而言论坛上的帖子因为使用了大量自己焊的电路而显得布局更合理。

3、易用性差

很多同学都在表示钦佩后问我能不能播放他们想要听的歌。我很无奈,因为他们根本不会画甚至拿不出、找不到MIDI谱子。同时由于MIDI软件小众而专业性过强,这些同学对MIDI毫无兴趣。本来我想制作一个基于FFT的音高识别的小工具在各种场合记录歌曲的MIDI。但是FFT不能够识别多种乐器(多个音轨),所以也没能成功。

 

文中引用的资料和代码出处:

[1]ChoirBot,桌子上的迷你机械乐队

http://www.dfrobot.com.cn/community/thread-14112-1-1.html

[2]步进电机_百度百科

http://baike.baidu.com/link?url=e6O45jcE2J1mb0n-yOxCr3sZv_HXz1uibWk7g2q-22xv_ccM8f7CughGI4AsDyYoOkvoK8kerpmrXyMGxCQNCq

[3]Tone() + MIDI = ToneMIDISynth

http://forum.arduino.cc/index.php?topic=79326.0

[4]【步进电机】千本樱

http://www.bilibili.com/video/av4596330/

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有