这是之前制作的一个小作业——音乐喷泉。喷泉的高低和喷射时间随着音符的高低长短而变化,配以随机的RGB LED灯光效果对于一个科技小制作来说还是很好的。
一、硬件准备
1、Arduino uno
2、12V水泵(即使你有5V的也要使用另外的MOS管模块等进行驱动,请查看Arduino各个引脚的最大电流)
3、三色LED灯(一般购买Arduino学习套件时都会带一个)
4、PWM开关控制模块(MOS管模块,当时手头没有,所以我使用的是L298N电机驱动模块)
5、蜂鸣器
6、超声波测距模块
7、杜邦线若干
8、12V电源
二、知识准备
1、阅读各模块的使用说明书
2、全部GND连接在一起(Arduino的各个GND本身就是相通的)
三、编码
// toneConst.h
#ifndef _TONECONST_h
#define _TONECONST_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "arduino.h"
#else
#include "WProgram.h"
#endif
//节拍强度
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
//旋律
const int melody[] = {
NOTE_G4,//5
NOTE_G4,//5
NOTE_A4,//6
NOTE_G4,//5
NOTE_C5,//1
NOTE_B4,//7
0,
NOTE_G4,//5
NOTE_G4,//5
NOTE_A4,//6
NOTE_G4,//5
NOTE_D5,//2
NOTE_C5,//1
0,
NOTE_G4,//5
NOTE_G4,//5
NOTE_G5,//5
NOTE_E5,//3
NOTE_C5,//1
NOTE_B4,//7
NOTE_A4,//6
0,
NOTE_F5,//4
NOTE_F5,//4
NOTE_E5,//3
NOTE_C5,//1
NOTE_D5,//2
NOTE_C5,//1
0,
};
//音符持续时间
const int noteDurations[] = {
8, 8, 4, 4, 4, 4,
4,
8, 8, 4, 4, 4, 4,
4,
8, 8, 4, 4, 4, 4, 2,
8,
8, 8, 4, 4, 4, 2,
4,
};
#endif
这是一个头文件,它包含了很多音符的定义以及我们的歌曲——节拍和持续时间。
#include "toneConst.h"
const int TrigPin = 5; //超声波测距模块控制(输出)
const int EchoPin = 6; //超声波测距模块输入
const int GNDPin = 7; //GND插满了,用一个引脚当GND。超声波测距模块电流只有2mA,小于引脚40mA。
const int tonePin = 11; //无源蜂鸣器VCC
const int motorVCCPin = 9; //水泵VDD(L298N in3),该引脚输出PWM控制水泵功率,从而控制喷水高度.
const int LedPin[3] = { A0,A1,A2 }; //RGB发光管GND
const int motorBasicPWM = 128; //水泵最低PWM,维持水刚刚喷出喷头
const int motormaxPWM = 255; //水泵最大PWM,决定水的最大喷出高度
const int melodyMaxNOTE = NOTE_G5; //最高音值,用于将音符值映射到PWM,这里最高音是NOTE_G5;
int curRGB[3] = { 0,0,0 }; //RGB发光管发出的颜色
const float LedVelRatio[3] = { 1.9 / 3.3,3.3 / 3.3,3.3 / 3.3 }; //RGB发光管各管脚电压(共阳极发光管,阳极接在3.3V上,用PWM输出)
void setup() {
//设置超声波测距模块引脚
pinMode(TrigPin, OUTPUT);
pinMode(EchoPin, INPUT);
pinMode(GNDPin, OUTPUT);
digitalWrite(GNDPin, LOW);
//设置蜂鸣器初始引脚
pinMode(tonePin, OUTPUT);
//初始化LED输出引脚并拉高引脚值(拉高负极电压导致LED正负极电压相等,不发光)
for (int i = 0; i < 3; i++) {
pinMode(LedPin[i], OUTPUT);
digitalWrite(LedPin[i], HIGH);
}
//设置水泵正负极引脚
pinMode(motorVCCPin, OUTPUT);
}
void loop() {
if (TestDistance() < 100) {
//当100厘米内有物体时播放一遍音乐,等待5秒以冷却L298N电机驱动。
PlaySound();
delay(5000);
}
else {
//每隔1秒检测一次。
delay(1000);
}
}
void PlaySound() {
//每次播放前,将水泵正极(L298N in3)拉高1秒使水流达到出水口.否则,播放音乐时前几个音符不会出水.
analogWrite(motorVCCPin, motormaxPWM);
delay(50);
analogWrite(motorVCCPin, motorBasicPWM);
delay(1000);
//循环每个音符
for (int thisNote = 0; thisNote < 29; thisNote++) {
//节拍持续时间,这里节拍被统一拉长了2倍.
int noteDuration = 2000 / noteDurations[thisNote];
//将节拍强弱映射为水流增高的高度[0,128)
int notePWM = (float)melody[thisNote] / melodyMaxNOTE*(motormaxPWM - motorBasicPWM);
//若不是空白,先输出一个50毫秒使得水流先增高一定高度,这样看起来就不会落后节拍很多.
if (melody[thisNote] != 0) {
analogWrite(motorVCCPin, motorBasicPWM + notePWM);
delay(50);
}
//更改LED颜色
SetColor(notePWM);
//播放当前节拍
tone(tonePin, melody[thisNote], noteDuration);
//若不是空白,输出水流增高的高度
if (melody[thisNote] != 0) {
analogWrite(motorVCCPin, motorBasicPWM + notePWM);
}
//延时达到节拍(去掉之前的50毫秒预输出)
delay(noteDuration - 50);
//维持水流与出水口同高
analogWrite(motorVCCPin, motorBasicPWM);
//关闭蜂鸣器
noTone(tonePin);
//节拍间的空白
delay(noteDuration / 4);
}
//播放完一次之后,关闭水泵
analogWrite(motorVCCPin, 0);
//播放完一次之后,关闭LED
CloseLed();
}
void CloseLed() {
//遍历LED每个管脚
for (int i = 0; i < 3; i++) {
digitalWrite(LedPin[i], HIGH);
}
}
void SetColor(int notePWM) {
//遍历LED每个管脚
for (int i = 0; i < 3; i++) {
//将水流增高高度随机化映射为颜色变化
curRGB[i] +=(float)random(0,255)/255 * notePWM;
//截断高位使之保持在255以内
curRGB[i] &= 0xff;
//将[0,255]PWM即[0,5V]电压映射到[0,vMax]以免烧毁LED,并在管脚上输出
analogWrite(LedPin[i], LedVelRatio[i]*(255 - curRGB[i]));
}
}
unsigned long TestDistance() {
//产生一个10us的高脉冲去触发TrigPin
digitalWrite(TrigPin, LOW);
delayMicroseconds(2);
digitalWrite(TrigPin, HIGH);
delayMicroseconds(10);
digitalWrite(TrigPin, LOW);
//检测脉冲宽度,并计算出距离
return pulseIn(EchoPin, HIGH) / 58.30;
}
这就是整个的代码,这里涉及到一些问题需要说明一下:
1、因为没有进行转接所以GND不够用,此时模块的电流非常小所以“非常规”的使用了一个引脚作为GND,这并不规范
2、水泵并不是加电就会喷水,实际上电机也是这样的,当从静止开始加电需要PWM达到一定值才会转动,解决方法可以实测这个PWM作为最低值每次都从这个最低值启动,也可以每次启动时先以最大PWM短暂工作几十毫秒先让电机转动起来。
3、RGB发光管的电压是有限制的,实际上我们常用的LED都有一定的电压限制,并不是5V的,也不一定是3.3V,为了避免烧坏器件,请阅读你的说明书。
4、除非使用库进行编程,否则应在setup函数中初始化我们所使用的引脚。
5、在loop函数中,不要写太具体的函数内容——将功能模块化(这里就是封装成函数)而后进行调用,这样可以使得代码思路更清晰,这是一个初学者尤为需要注意的问题。
6、我所使用的驱动模块是L298N,它对PWM的相应并不十分出色,所以代码中的参数你都需要实际验证和调整。
7、距离测试函数结合超声模块的说明书进行阅读,也可以使用封装好的库去驱动超声模块。
最后,关于外壳,在制作外壳时,我是用SOLIDWORKS 2016 来设计模型,而后3D打印机进行打印。下一篇我们简单的介绍一下SOLIDWORKS 2016的基本用法。