【Arduino和高中通用技术】——八、一个“科技小制作”

        这是之前制作的一个小作业——音乐喷泉。喷泉的高低和喷射时间随着音符的高低长短而变化,配以随机的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的基本用法。

你可能感兴趣的:(Arduino,Arduino和高中通用技术)