电机音乐 | Music with stepper motor

效果演示 | Demo

B站视频

上位机 | Code

使用python的mido库读取midi文件,按照文件中记录的音高和节拍将命令通过pyserial库以串口方式发送给下位机,代码如下:

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
49
50
51
52
53
54
55
56
57
import mido
from mido import MidiFile
import serial
import time

# Open Serial port
port = 'COM15'
ser = serial.Serial(port, 115200, timeout=0.5)
time.sleep(2)
print("Serial port {} opened".format(port))

# Midisetting
mid = MidiFile("../yoimiya.mid")
tempo = mido.bpm2tempo(131)

# note(60代表中央C): 音高
# velocity(0-127): 音量
# time(tick, 480 ticks/beat): 消息所在时间
# tempo: 乐曲速度,500000表示一拍为500000ns = 0.5s, bpm = (6*10^7)/tempo

def sendNote(channel, note):
ser.write((str(channel) + " " + str(note) + '\n').encode())
return

motStatus = [0, 0, 0, 0]

# Iterate among tracks
for i, track in enumerate(mid.tracks):
print('Track {}: {}'.format(i, track.name))
passed_time = 0

# Iterate among massages
for msg in track:
ab_time = mido.tick2second(msg.time, mid.ticks_per_beat, tempo)
real_time = ab_time + passed_time
passed_time += ab_time

# Delay
if (msg.type == "note_on" or msg.type == "note_off") and msg.time != 0:
time.sleep(ab_time)

# Status of motors
print(motStatus)

# Send control message to motor
if msg.type == "note_on":
targetIndex = motStatus.index(0)
sendNote(targetIndex, msg.note)
motStatus[targetIndex] = msg.note
elif msg.type == "note_off":
targetIndex = motStatus.index(msg.note)
sendNote(targetIndex, 0)
motStatus[targetIndex] = 0

# Shutdown all motors
for i in range(4):
sendNote(i, 0)

硬件 | Hardware

  • Arduino UNO 开发板
  • Arduino CNC Shield 扩展板
  • DRV8825 电机驱动模块
  • 57 步进电机

下位机 | Arduino

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#define MOT_EN  8  
#define X_DIR 5
#define X_STEP 2
#define Y_DIR 6
#define Y_STEP 3
#define Z_DIR 7
#define Z_STEP 4
#define A_DIR 13
#define A_STEP 12

float x_freq = 0;
float y_freq = 0;
float z_freq = 0;
float a_freq = 0;

unsigned int x_cnt = 0;
unsigned int y_cnt = 0;
unsigned int z_cnt = 0;
unsigned int a_cnt = 0;

unsigned int x_cnt_target = 0;
unsigned int y_cnt_target = 0;
unsigned int z_cnt_target = 0;
unsigned int a_cnt_target = 0;

float freqList[128] = {
0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
32.7, 34.6, 36.7, 38.9, 41.2, 43.7, 46.2,
49, 51.9, 55, 58.3, 61.7,
65.4, 69.3, 73.4, 77.8, 82.4, 87.3, 92.5,
98, 103.8, 110, 116.5, 123.5,
130.8, 138.6, 146.8, 155.6, 164.8, 174.6, 185,
196, 207.7, 220, 233.1, 246.9,
261.6, 277.2, 293.7, 311.1, 329.6, 349.2, 370,
392, 415.3, 440, 466.2, 493.9,
523.3, 554.4, 587.3, 622.3, 659.3, 698.5, 740,
784, 830.6, 880, 932.3, 987.8,
1047, 1109, 1175, 1245, 1319, 1397, 1480,
1568, 1661.2, 1760, 1864.7, 1975.5,
2093, 2217, 2349, 2489, 2637, 2794, 2960,
3136, 3322.4, 3520, 3729.3, 3951.1,
4186, 4434.9, 4698.6, 4978, 5274, 5587.7, 5919.9,
6271.9, 6644.9, 7040, 7458.6, 7902.1, 8372, 8869.8,
9397.3, 9956.1, 10548.1,11175.3,11839.8,12543.9
};

void setup() {
Serial.begin(115200);

// Motor
pinMode(MOT_EN, OUTPUT);
pinMode(X_DIR, OUTPUT);
pinMode(X_STEP, OUTPUT);
pinMode(Y_DIR, OUTPUT);
pinMode(Y_STEP, OUTPUT);
pinMode(Z_DIR, OUTPUT);
pinMode(Z_STEP, OUTPUT);
pinMode(A_DIR, OUTPUT);
pinMode(A_STEP, OUTPUT);
digitalWrite(X_DIR, HIGH);
digitalWrite(Y_DIR, HIGH);
digitalWrite(Z_DIR, HIGH);
digitalWrite(A_DIR, HIGH);
digitalWrite(MOT_EN, LOW);

// 定时器寄存器设置,中断频率为 16000000/64/1=250000Hz
noInterrupts(); // Disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 4;
TCCR1B |= (1 << WGM12); // CTC mode
TCCR1B |= (1 << CS10) | (1 << CS11); // Prescaler 64
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts(); // enable all interrupts
}


ISR(TIMER1_COMPA_vect) // timer interrupt
{
if (x_cnt_target != 0) {
if (++x_cnt >= x_cnt_target) {
x_cnt = 0;
bitRead(PORTD, X_STEP) ? bitClear(PORTD, X_STEP) : bitSet(PORTD, X_STEP);
}
}
if (y_cnt_target != 0) {
if (++y_cnt >= y_cnt_target) {
y_cnt = 0;
bitRead(PORTD, Y_STEP) ? bitClear(PORTD, Y_STEP) : bitSet(PORTD, Y_STEP);
}
}
if (z_cnt_target != 0) {
if (++z_cnt >= z_cnt_target) {
z_cnt = 0;
bitRead(PORTD, Z_STEP) ? bitClear(PORTD, Z_STEP) : bitSet(PORTD, Z_STEP);
}
}
if (a_cnt_target != 0) {
if (++a_cnt >= a_cnt_target) {
a_cnt = 0;
bitRead(PORTD, A_STEP) ? bitClear(PORTD, A_STEP) : bitSet(PORTD, A_STEP);
}
}
}

void loop() {
while (Serial.available() > 0) {
int channel = Serial.parseInt();
int note = Serial.parseInt();
if (Serial.read() == '\n') {
switch(channel) {
case 0:
note == 0 ? x_freq = 0 : x_freq = freqList[note];
x_cnt_target = 25000 / x_freq;
break;
case 1:
note == 0 ? y_freq = 0 : y_freq = freqList[note];
y_cnt_target = 25000 / y_freq;
break;
case 2:
note == 0 ? z_freq = 0 : z_freq = freqList[note];
z_cnt_target = 25000 / z_freq;
break;
case 3:
note == 0 ? a_freq = 0 : a_freq = freqList[note];
a_cnt_target = 25000 / a_freq;
break;
}
}
}
}

参考资料 | Reference

MIDI文件来源: https://www.midishow.com/
B站专栏《电机在唱歌》:https://www.bilibili.com/read/cv9458045?spm_id_from=333.999.0.0