UID: 1737
zzz.jpg (182.28 KB, 下载次数: 1303)
下载附件 保存到相册
2025-5-23 16:35 上传
创作许可协议
本项目采用 None(不开放任何权利,保留所有权利) 进行许可。
相关推荐
这个项目很有挑战性,需要大量的微调,所以要做好试错的准备。如果我错过了什么或不清楚的地方,请随时联系我们——我很乐意提供帮助!
此外,我怎么强调激光的危险性都不为过。高功率激光会导致严重的眼睛受伤,甚至灼伤表面,因此请认真对待安全。在使用激光器之前,请确保您佩戴了经过认证的安全眼镜并了解风险。花点时间对激光安全和正确的处理技术进行自我教育。
本指南仅供参考和教育之用,不能取代专业建议。如果您有任何疑问或具体的技术问题,请咨询专家。我不是专业的工程师或机械师,本指南基于我的个人经验。始终使用您的最佳判断力,遵守当地法律和安全准则,并了解所涉及的任何风险。
使用这些说明,您将承担可能发生的任何伤害、损害或事故的所有风险和责任,并同意放弃和免除我的任何和所有索赔或责任。请谨慎行事,自行研究,并对您的结果承担全部责任。
附录
项目链接:https://www.instructables.com/Laser-Harp-2/
项目作者:保加利亚 Cybercraftics
项目视频:https://www.youtube.com/watch?v=c5HmCTt6hQ4
项目代码:https://github.com/cybercraftics/laser_harp
3D打印文件:https://content.instructables.co ... FPLB4E5M88SFWC9.stl
由于 Arduino Leonardo 可以充当人机接口设备 (HID),您只需将其插入计算机并启动您最喜欢的 DAW 软件即可。我正在使用 GarageBand,但它也适用于 Logic Pro、Ableton Live 和其他流行的 DAW。
从那里,您可以选择自己喜欢的声音并开始摇滚!
此外,您需要一台烟雾机来使光束可见并创建标志性的激光竖琴效果。
第 18 步:进一步的改进步骤
进一步的改进步骤
振镜扫描仪: 用振镜扫描仪代替步进电机将使竖琴有更多的琴弦和更亮的光束,因为由于步进电机的速度和精度,目前的设置仅限于七根琴弦。振镜扫描仪将能够更快、更精确地定位光束。
传感器高度:将传感器安装在更高的位置将进一步提高其检测光束中断的能力,并更好地跟踪手的高度。这将产生更准确的音符触发,并可能对声音进行更动态的控制。如果你仔细观察让-米歇尔的竖琴,你会注意到传感器的位置要高得多。
这些升级可以显着提高竖琴的性能和可玩性,使其反应灵敏和通用性更高。
您可以在此存储库中找到这两个 Arduino 的代码。
每当您需要更新 Arduino UNO(控制激光器和电机)的代码时,
我建议遵循这个例程:
关闭 12v 电源。
断开 Arduino Leonardo 与计算机的连接。
将 Arduino 代码上传到 Arduino Uno。
上传后断开 Arduino Uno 与计算机的连接。
打开 12v 电源。
将 Arduino Leonardo 重新连接到计算机。
每当您需要更新 Arduino Leonardo(控制超声波传感器并生成 MIDI 音符)的代码时,您可以在 harp 运行时上传新代码,而不会注意到任何中断,因为 Arduino UNO 同时处理电机和激光器。
[code]#include
#include "MIDIUSB.h"
#include
SoftwareSerial ultrasonicSensor1(8, 9);
struct BeamState {
bool playing;
int currentPitch;
unsigned long offStartTime;
};
const int NUM_BEAMS = 7;
BeamState beamStates[NUM_BEAMS];
const int baseNotes[NUM_BEAMS] = {36, 38, 40, 41, 43, 45, 47};
const unsigned long OFF_DEBOUNCE_MS = 10;
unsigned int sensor1Distance = 0;
unsigned int sensor2Distance = 0;
unsigned int lastSensor1Filtered = 0;
unsigned int lastSensor2Filtered = 0;
unsigned int HighByte = 0;
unsigned int LowByte = 0;
unsigned int Len = 0;
volatile byte receivedData = 0;
unsigned long lastSensorCheck = 0;
const unsigned long sensorCheckRate = 20;
int oldOffsetChannel0 = -1;
int oldOffsetChannel1 = -1;
void triggerAndReadUltrasonics();
int getOctaveOffset(unsigned int distanceMM);
int stabilizeOffset(int newOffset, int &oldOffset);
void noteOn(byte channel, byte pitch, byte velocity);
void noteOff(byte channel, byte pitch, byte velocity);
void receiveEvent(int bytesReceived);
void setup() {
Serial.begin(9600);
ultrasonicSensor1.begin(9600);
Serial1.begin(9600);
for (int i = 0; i < NUM_BEAMS; i++) {
beamStates[i].playing = false;
beamStates[i].currentPitch = 0;
beamStates[i].offStartTime = 0;
}
Wire.begin(0x08);
Wire.onReceive(receiveEvent);
}
void loop() {
unsigned long now = millis();
if (now - lastSensorCheck >= sensorCheckRate) {
lastSensorCheck = now;
triggerAndReadUltrasonics();
}
byte stableData = 0;
int firstOn = -1;
for (int i = 0; i < NUM_BEAMS; i++) {
if (receivedData & (1 << i)) {
if (firstOn < 0) {
firstOn = i;
}
}
}
if (firstOn >= 0) {
stableData |= (1 << firstOn);
}
for (int i = 0; i < NUM_BEAMS; i++) {
bool beamIsInterrupted = (stableData & (1 << i)) != 0;
if (beamIsInterrupted) {
beamStates[i].offStartTime = 0;
byte channel = (i < 4) ? 0 : 1;
unsigned int dist = (i < 4) ? sensor1Distance : sensor2Distance;
int rawOffset = getOctaveOffset(dist);
int stableOffset = (channel == 0)
? stabilizeOffset(rawOffset, oldOffsetChannel0)
: stabilizeOffset(rawOffset, oldOffsetChannel1);
int newPitch = baseNotes[i] + stableOffset;
if (!beamStates[i].playing) {
if (channel == 0) {
oldOffsetChannel0 = -1;
} else {
oldOffsetChannel1 = -1;
}
beamStates[i].playing = true;
beamStates[i].currentPitch = newPitch;
noteOn(channel, newPitch, 100);
} else {
if (beamStates[i].currentPitch != newPitch) {
noteOff(channel, beamStates[i].currentPitch, 0x40);
beamStates[i].currentPitch = newPitch;
noteOn(channel, newPitch, 100);
}
}
} else {
if (beamStates[i].playing) {
if (beamStates[i].offStartTime == 0) {
beamStates[i].offStartTime = now;
} else {
if (now - beamStates[i].offStartTime >= OFF_DEBOUNCE_MS) {
byte channel = (i < 4) ? 0 : 1;
noteOff(channel, beamStates[i].currentPitch, 0x40);
beamStates[i].playing = false;
if (channel == 0) {
oldOffsetChannel0 = -1;
} else {
oldOffsetChannel1 = -1;
}
}
}
}
}
}
MidiUSB.flush();
}
void receiveEvent(int bytesReceived) {
while (Wire.available()) {
receivedData = Wire.read();
}
}
void triggerAndReadUltrasonics() {
ultrasonicSensor1.write(0x55);
delay(50);
if (ultrasonicSensor1.available() >= 2) {
HighByte = ultrasonicSensor1.read();
LowByte = ultrasonicSensor1.read();
sensor1Distance = (HighByte << 8) + LowByte;
if (sensor1Distance < 2 || sensor1Distance > 1800) {
sensor1Distance = 0;
}
if (abs((int)sensor1Distance - (int)lastSensor1Filtered) > 20) {
sensor1Distance = lastSensor1Filtered;
} else {
lastSensor1Filtered = sensor1Distance;
}
}
Serial1.write(0x55);
delay(50);
if (Serial1.available() >= 2) {
HighByte = Serial1.read();
LowByte = Serial1.read();
sensor2Distance = (HighByte << 8) + LowByte;
if (sensor2Distance < 2 || sensor2Distance > 1800) {
sensor2Distance = 0;
}
if (abs((int)sensor2Distance - (int)lastSensor2Filtered) > 20) {
sensor2Distance = lastSensor2Filtered;
} else {
lastSensor2Filtered = sensor2Distance;
}
}
}
int getOctaveOffset(unsigned int distanceMM) {
int offset = map(distanceMM, 0, 1500, 24, 0);
return constrain(offset, 0, 24);
}
int stabilizeOffset(int newOffset, int &oldOffset) {
if (oldOffset < 0) {
oldOffset = newOffset;
return newOffset;
}
if (abs(newOffset - oldOffset) <= 1) {
return oldOffset;
}
oldOffset = newOffset;
return newOffset;
}
void noteOn(byte channel, byte pitch, byte velocity) {
midiEventPacket_t noteOnPacket = {
0x09,
(byte)(0x90 | channel),
pitch,
velocity
};
MidiUSB.sendMIDI(noteOnPacket);
}
void noteOff(byte channel, byte pitch, byte velocity) {
midiEventPacket_t noteOffPacket = {
0x08,
(byte)(0x80 | channel),
pitch,
velocity
};
MidiUSB.sendMIDI(noteOffPacket);
}[/code]
这种附件显着提高了竖琴的性能,并增加了横梁可以中断的高度。
插入四个光传感器,并将它们焊接到通向另一侧扩展板的电线上。
用一些热胶固定声音传感器。
装上盖子。
您可以使用一根末端带有白色油漆的细丝将其穿过外壳的孔,并在木板上标记钻孔点。然后,钻孔并用螺丝将电路板固定到外壳上。
电机支架和激光器的位置必须居中,以与顶部的分体对齐。Arduino 板必须与左侧的分割对齐,以确保其输出公开。其余组件可以根据图片大致定位。
您可以使用双面胶带将它们临时固定在板上,确保在用螺丝固定它们之前一切正常。然后,用笔标记孔,取下临时胶带,并预先钻孔。
剪下 2 个与 Arduino 尺寸相匹配的穿孔板。
按照此处提供的 ULN2003 dirver 和 here) 的 A4988 驱动程序的原理图来构建扩展板。
将屏蔽层到组件的电缆保留足够长的时间,并在稍后安装时将其修剪成合适的尺寸。
在继续焊接之前,我建议按照此处的ULN2003驱动器和 A4988 驱动器的原理图,使用原型板测试设置。
我建议在焊接元件之前,先用低功率激光器(约100-200 mW)对电路进行实验。它对眼睛还是很危险的,但是 3W 激光器在全功率运行时甚至可以灼伤黑暗的表面,想象一下它会对你的眼睛造成什么影响!使用低功率激光,您可以进行调整,直到看到七个投影点(如视频所示)。我不建议使用高功率激光器开发竖琴——使用较弱的激光器更安全、更容易排除故障。请记住始终佩戴经过认证且评级适当的安全眼
镜!
Add a Relay Between the Laser and the 12V Power Supply
作为一项安全措施,需要继电器。当 Arduino 不运行或重启时,它不会向激光器发送 PWM 信号,导致它在本应关闭时以全功率运行。为防止这种情况,应连接继电器,使其默认处于关闭状态,并且 Arduino 只有在完成电机归位程序后才能将其打开。
将 Diodes 后面的线路安装到支架上
用热胶将两个二极管固定到孔中。
从模块中拆焊二极管。
使用足够长的电线以到达电机支架,并将二极管焊接到它们上。
在支架上添加螺纹嵌件(此步骤是可选的 - 您也可以使用标准螺钉代替)。
用一些胶水将镜子连接到镜子架上。
将支架安装到电机上。
我对镜子做了很多实验。镜子应尽可能薄;否则,它会反射未聚焦的光束。我发现效果最好的是旧硬盘中的磁盘。我用 Dremel 把它剪下来并塑造它。在照片中,您可以看到镜子和硬盘之间反射光束的差异。
将步进电机安装到其支架上
如果您已经有信心控制步进电机,请继续跳过此步骤。
***
步进电机是竖琴的第二大重要部件,仅次于激光器,但事实证明,要做好它是最具挑战性的。因为有这么多不同类型的步进电机可用,所以每一种都需要自己独特的设置和控制方法。
最受欢迎的类型是:单极、双极和混合步进电机。根据电机的类型,它们需要不同的驱动器(控制它们的硬件单元)和编程。
DroneBot Workshop 上有一个很好的介绍视频,它可能会为您提供足够的背景知识来使用步进电机。我找到的另一个关于布线的有用视频是这个。
***
电机要求为 1.8° 步距角(即每转 200 步)和紧凑的尺寸,以保持较低的重量,这有助于在两个方向上快速、精确地运动。
在我的视频中,您可以看到我使用了两种类型的电机和两种不同的驱动器。
NEMA 17 (42SHDC3025-24B) 与 A4988 驱动器结合使用。
NEMA 16 (39BYG53322) 步进电机与 ULN2003 驱动器配对。(这是更好的选择,因为转子更轻、更短,可以实现更快、更灵敏的运动。
我最终坚持使用混合动力 NEMA 16 (39BYG53322),不幸的是它非常不受欢迎。但是,正如我所说,尺寸比电机的类型更重要,因此可以使用任何其他类型的 NEMA 16。
任何具有指定尺寸的木板都可以使用。它提供比 3D 打印更好的稳定性,并有助于抑制电机的振动。
Fusion 360 源文件也可用于根据您的需要进行修改。