利用arduino和超声波测距模块制作的MIDI控制器
bg8npk2013/12/09电子技术 IP:河南
本帖最后由 bg8npk 于 2013-12-9 13:00 编辑

前言:作为一个吉他手和伪电工以及穷苦人民,一直喜欢使用吉他软件效果器(如amplitube3),以及其他的DAW(数字音频工作站)软件 ,这些音乐软件都有一个十分普及的通讯协议--MIDI通讯协议,各种midi键盘、midi控制发出的midi信号可以对软件进行全方位的控制,midi外设在演出中特别重要

  
电吉他演奏中,常常需要对于效果器的参数进行控制,例如最常用的哇音踏板,就是通过脚踩踏板输出不同的音色效果。手头正好有一个超声波测距模块,再看了叉叉同志的超声波测距贴,就打算用超声波测量手的运动之后输出midi信号去控制效果器软件。
首先去研究了midi协议



MIDI(Musical Instrument Digital Interface)乐器数字接口 ,是20 世纪80 年代初为解决电声乐器之间的通信问题而提出的。MIDI 传输的不是声音信号, 而是音符、控制参数等指令, 它指示MIDI 设备要做什么,怎么做, 如演奏哪个音符、多大音量等。它们被统一表示成MIDI 消息(MIDI Message) 。传输时采用异步串行通信, 标准通信波特率为31.25×( 1±0.01) KBaud。

MIDI文件有很多信息构成的指令。一些信息,只由1字节构成,有些有2个字节,还有一些有3个字节。有一类的MIDI信息,甚至可以包含无限的字节数。所有的信息有一点是共同的,那就是第一个字节的信息是状态。
状态字节的0x80到0xef是可以在16个MIDI通道的任何一个出现的信息。正因为如此,这些是所谓的声音信息。这些状态字节有8位二进制数,可以把8个二进制位分成两个 4位,即一个高位和一个低位 。例如,一个状态字节的0x92可细分成9 (高位 )和2 (低位 ) 。高位告诉你是什么类型的MIDI信息,低位说明信息操作的MIDI通道序号。以下是所有可能的高位值,每个代表的声音信息类型:
8 =停止发声
9 =开始发声
a =轮指
b =改变控制器
c =改变音色
d =通道演奏压力(可近似认为是音量)
e =音高


看得出,midi只是一个简单的串行通讯而已,主要问题就是要解决传输数据是什么,在网上没有太多的信息,就直接使用了一个midi监视器,研究了一下我原来的一个midi控制器的midi数据
QQ截图20131208140923.jpg


第一个数据 B0   B是控制器,0代表通道,后一位数据 07 还是通道 最后data2 是控制器的值,在0到127之间,这里就打算使用超声波测距的数据送到data2的midi信号。

然后开始搞程序....

#define LED 13    // LED pin on Arduino board
#define switch1 10              // 1st Switch
#define switch2 6               // 2nd Switch
#define MIDI_COMMAND_CONTROL_CHANGE 0xB0
#define MIDI_COMMAND_NOTE_ON 0x90
#define MIDI_COMMAND_NOTE_OF 0x80

//Variables
int switch1LastState = 0;
int switch1CurrentState = 0;
int switch2LastState = 0;
int switch2CurrentState = 0;
                                                                     //照抄了叉叉同学的帖子

int ssgnd = 5; //首先为了程序看着方便,定义若干针脚。gnd是5, echo是4, trig 是3, vcc是2.
int ssecho = 4;
int sstrig = 3;
int ssvcc = 2;
//前缀ss是supersonic的意思,不加也可以。

void setup() {                
  pinMode(ssgnd,OUTPUT);
  pinMode(sstrig,OUTPUT);
  pinMode(ssecho,INPUT); //除了echo脚设成输入模式(INPUT),其他都设为输出模式。因为我们要检测echo脚的电平变化,所以设成输入模式。
  pinMode(ssvcc,OUTPUT);

  pinMode(LED, OUTPUT);
  pinMode(switch1,INPUT);
  pinMode(switch2, INPUT);
  XXXXXXXXXgin(31250);
  
  blinkLed(3);


  digitalWrite(ssvcc,HIGH);
  }
  
int supersonicread()
//这里我定义了一个函数,叫做supersonicread,顾名思义就是读取超声波传感器数值。
//函数前面的int表示这个函数返回一个整数。
{
   digitalWrite(sstrig,HIGH); // 将trig脚电压设为高
  digitalWrite(sstrig,LOW); //将trig脚电压设为低,这样就向模块发送了一个信号,让模块发射超声波。
  int echotime=pulseIn(ssecho,HIGH);
   return echotime; //return 命令 表示把一个数作为这个函数的返回值。我们把echotime,也就是回声时间,作为这个函数返回的数值。
}


// the format of the message to send Via serial
typedef union {
    struct {
uint8_t command;
uint8_t channel;
uint8_t data2;
uint8_t data3;
    } msg;
    uint8_t raw[4];
} t_midiMsg;


void blinkLed(byte num) {  // 当有midi数据发送的时候led闪烁
  for (byte i=0;i<num;i++) {
    digitalWrite(LED,HIGH);
    delay(50);
    digitalWrite(LED,LOW);
    delay(50);
  }
}


void loop() {
  int microseconds=supersonicread(); //定义一个叫microseconds变量,令它的值等于supersonicread函数的返回值。我们知道supersonicread的返回值,是超声波从发射到回声所经过的时间,单位是微秒。所以现在microseconds变量里面就保存了这个时间。
  
int value=microseconds*0.084;//定义了value变量,value在0-127之间,而超声波控制的距离打算在20cm左右,即1500毫秒,1500除以127既得0.084
  delay(100); //延迟100毫秒,然后循环。
t_midiMsg midiMsg1;  // MIDI message for Switch 1
t_midiMsg midiMsg2;  //MIDI message for Swtich 2
switch1CurrentState = digitalRead(switch1);
switch2CurrentState = digitalRead(switch2);

if (switch1CurrentState == 1){
    
                  XXXXXXXXXXXXXXXXmand = MIDI_COMMAND_CONTROL_CHANGE;
           XXXXXXXXXXXXXXXannel = 1;
           XXXXXXXXXXXg.data2   = value;
           XXXXXXXXXXXg.data3   = 0; /* Velocity */

           /* Send note on */
           Serial.write(midiMsg1.raw, sizeof(midiMsg1));                          
         blinkLed(2);          
                                 }
switch1LastState = switch1CurrentState;

if (switch2CurrentState == 1){
  
                  XXXXXXXXXXXXXXXXmand = MIDI_COMMAND_CONTROL_CHANGE;  //这里是打算做第二个脚踏
           XXXXXXXXXXXXXXXannel = 2;
           XXXXXXXXXXXg.data2   = 127;
           XXXXXXXXXXXg.data3   = 0; /* Velocity */

           /* Send note on */
           Serial.write(midiMsg2.raw, sizeof(midiMsg2)); //发送数据                          
         blinkLed(2);          
                                 }
    switch2LastState = switch2CurrentState;
}


实际图片
DSC04870.jpg
DSC04871.jpg
DSC04872.jpg
DSC04873.jpg


使用视频
XXXXXXXXXXXXXXXXXX/v_show/id_XXXXXXXXXXXXXXXXml


下一步?
下一步主要会提高整体的稳定性(舞台上对于稳定性的要求是很高的),制作一个符合人机工学的外壳(实用才是正道),以及制作无线midi接口等~~~
欢迎讨论~~

(不想当吉他手的贝司手不是好程序员)
+200  科创币    ry7740kptv    2013/12/09 很不错
+10  科创币    打不死的小超    2013/12/10 好贴!!!
来自:电子信息 / 电子技术
8
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
ry7740kptv
11年2个月前 IP:未同步
657163
我也贴一个我写的MIDI解码的算法代码,在ATmega8上用的:

#include <avr/io.h>
#include <avr/pgmspace.h>
#include "midifreq.h"

/*------宏定义------*/
#define uint unsigned int
#define uchar unsigned char
#define BIT(x) (1<<(x))

uchar k,config,midicode[3];
uint i,t0count,ontime,bps;

void enablepwm()
{
TCCR1A=0x23;
TCCR1B=0x1A;
}

void disablepwm()
{
TCCR1B=0x00;
TCCR1A=0x00;
}

void silent()
{
disablepwm();
TCNT1H=0x00;
TCNT1L=0x00;
}

void play()
{
silent();
OCR1A=pgm_read_byte(&midith[midicode[1]-21])*256+pgm_read_byte(&miditl[midicode[1]-21]);
OCR1B=((unsigned long)ontime*midicode[2])/127-1;
enablepwm();
}

//端口初始化
void port_init(void)
{
PORTB = 0x00;
DDRB  = 0x04;
PORTC = 0x24;
DDRC  = 0x3C;
PORTD = 0x6E;
DDRD  = 0x00;
}

//定时T1初始化
void timer1_init(void)
{
disablepwm(); //停止定时器
TCNT1H = 0x00;
TCNT1L = 0x00; //初始值
OCR1AH = 0x00;
OCR1AL = 0x00; //匹配A值
OCR1BH = 0x00;
OCR1BL = 0x00; //匹配B值
ICR1H  = 0xFF;
ICR1L  = 0xFF; //输入捕捉匹配值
}

//串口通信初始化
void usart_init(void)
{
UCSRB = 0x00;//禁止中断
UCSRA = 0x00;
UCSRC = BIT(URSEL) | 0x06;
UBRRL = 0x0F;
UBRRH = 0x00;
}

//串行接收结束中断服务程序
SIGNAL(SIG_UART_RECV)
{
if(k==0)
{
k=UDR&0xF0; //判断即将接收的是否为发声指令
if(!((k==0x80)||(k==0x90)))
{
k=0;
return;
}
else
k=0;
}
midicode[k]=UDR;
k++;
if(k==3) //判断指令是否接收完毕
{
k=0x80|(0x0F&config);
if(midicode[0]==k)
silent();
else
{
k=0x90|(0x0F&config);
if(midicode[0]==k)
play();
}
k=0;
}
}

void init_devices(void)
{
cli(); //禁止所有中断
port_init();
timer0_init();
timer1_init();
usart_init();
int_init();
sei(); //开全局中断
}

因为这原本是给TC灭弧器用的所以省略了一部大分代码,只把与解码相关的部分贴上来,需要用的请自行补全~(midifreq.h保存了各个音符对应频率所应该给定时器装的初值)

MIDI接口电路示例:
MIDI接线1.gif
MIDI接线2.gif
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
弹琴说i
11年2个月前 IP:未同步
657235
最近没事也在研究midi用单片机解码。到现在也没成功
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
a404041481
11年2个月前 IP:未同步
658234
是不是midi你的源码直邮控制一个量, 音量什么的?
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
a404041481
11年2个月前 IP:未同步
658237
视频里的是不是还有一个脚踏没有录进去但是实际是有用的?
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
莱茵蒙特
11年2个月前 IP:未同步
658265
飘过。。。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
cooledit2989
10年2个月前 IP:北京
734949
很想学习,太难了……
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
不死野人
9年10个月前 IP:江西
760818
引用 ry7740kptv:
我也贴一个我写的MIDI解码的算法代码,在ATmega8上用的:

#include <avr/io.h>
#include <avr/pgmspace.h>
#include "midifreq.h"

/*------宏定义...
求资源[s::lol]
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
不死野人
9年10个月前 IP:浙江
760819
楼主好人,问问硬件和软件怎么学习?
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
bg8npk
进士 老干部 学者 机友 笔友
文章
152
回复
1454
学术分
1
2011/08/19注册,2时1分前活动

设计师,音乐制作人 KC国际局,Live Soundman

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}