【 Arduino 动手做】 激光竖琴

在这里插入图片描述

《Arduino 手册(思路与案例)》栏目介绍:
在电子制作与智能控制的应用领域:广泛涉及了Arduino BLDC、Arduino CNC、Arduino ESP32 SPP、Arduino FreeRTOS、Arduino FOC、Arduino GRBL、Arduino HTTP、Arduino HUB75、Arduino IoT Cloud、Arduino JSON、Arduino LCD、Arduino OLED、Arduino LVGL、Arduino PID 及 Arduino TFT 等方面的相关拓展思路和众多参考案例。本专栏目前博客近2300篇。
https://blog.csdn.net/weixin_41659040/category_12422453.html

Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用Arduino IDE(集成开发环境)来编写、编译和上传代码到Arduino板上。Arduino还有一个丰富的库和社区,你可以利用它们来扩展Arduino的功能和学习Arduino的知识。

Arduino的特点是:
1、开放源码:Arduino的硬件和软件都是开放源码的,你可以自由地修改、复制和分享它们。
2、易用:Arduino的硬件和软件都是为初学者和非专业人士设计的,你可以轻松地上手和使用它们。
3、便宜:Arduino的硬件和软件都是非常经济的,你可以用很低的成本来实现你的想法。
4、多样:Arduino有多种型号和版本,你可以根据你的需要和喜好来选择合适的Arduino板。
5、创新:Arduino可以让你用电子的方式来表达你的创意和想象,你可以用Arduino来制作各种有趣和有用的项目,如机器人、智能家居、艺术装置等。

在这里插入图片描述

经过 3 个月的反复试验、调整电机、微调激光器并克服了不少挫折,我终于让它工作起来了。让我告诉你——这太不可思议了!

在本指南中,我将向您介绍我如何从头开始构建自己的无框激光竖琴。它类似于传奇人物让-米歇尔·雅尔 (Jean-Michel Jarre) 使用的标志性激光竖琴。我仍然记得小时候在电视上看它,问我爸爸它是如何运作的——建造一个是我毕生的梦想。

它完全由 Arduino 控制,并使用电机扫描激光束,并搭配巧妙的传感器设置来检测光束中断。通过执行这些步骤,您可以创建一个功能齐全的激光竖琴,它不仅看起来令人难以置信,而且可以播放真实的音乐。

观看视频以了解其背后的概念,并了解我是如何构建它的。

⚠️ 重要安全警告 ⚠️

在这个项目中,我使用了一台强大的 3W 激光器。如果处理不当,这种类型的激光会导致严重和永久性的眼睛或皮肤损伤。永远不要低估危险 - 即使是短暂的暴露或间接反射也可能导致永久性的眼睛损伤。

在开始使用强大的激光器之前,我强烈建议您先熟悉低功率激光器,例如 5mW 激光指示器,以安全地学习激光器处理基础知识、对准和安全协议。全面了解激光分类、护目镜、光束反射风险和安全作程序。

网上有很多关于激光安全的资源:

https://makezine.com/article/digital-fabrication/laser-cutting-digital-fabrication/laser-eye-safety-in-digital-fabrication-protecting-your-vision/

https://ehs.mit.edu/wp-content/uploads/Laser_Safety_Guide.pdf

舒适后,小心地使用更强大的激光,始终确保您使用专门针对您的激光波长的认证激光安全护目镜。在受控环境中工作,尽量减少反射表面,清楚地标记激光器的路径,并在进行调整或不积极实验时始终关闭激光器。

这种激光竖琴特别危险,因为激光束向上指向您的眼睛,大大增加了意外暴露的危险。

激光可以帮助创建像这款激光竖琴这样的惊人项目,但前提是要负责任地处理。安全第一,创意先行!

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

用品与材料

1 个 Arduino Leonardo

1 个 Arduino Uno

4 x 光敏电阻 LDR

2 x 超声波传感器 US-100

1 x 步进电机驱动器 ULN2003

1 x NEMA 16 39MM 1.8 度 2 相 4 线混合薄型步进电机

警告:

我正在使用一个 39BYG53322 步进电机,这是我从旧的 3D 打印机中抢救出来的。不幸的是,我在亚马逊或全球速卖通上找不到它。这是一款 NEMA 16、39 毫米、1.8 度、2 相、5 线混合步进电机。

1 x 继电器模块

1 x 12v 电源 - 我使用类似于这个的电源:https://amzn.to/3RaaXPv

1 x 激光 - 3W TTL

4 x10Ohm 电阻器

1 x TCRT5000 红外反射传感器

第 1 步:打印 3D 模型

Fusion 360 源文件也可用于根据您的需要进行修改。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

步骤 2:准备底板

任何具有指定尺寸的木板都可以使用。它提供比 3D 打印更好的稳定性,并有助于抑制电机的振动。

在这里插入图片描述

第 3 步:学习控制步进电机

如果您已经有信心控制步进电机,请继续跳过此步骤。


步进电机是竖琴的第二大重要部件,仅次于激光器,但事实证明,要做好它是最具挑战性的。因为有这么多不同类型的步进电机可用,所以每一种都需要自己独特的设置和控制方法。

最受欢迎的类型是:单极、双极和混合步进电机。根据电机的类型,它们需要不同的驱动器(控制它们的硬件单元)和编程。

DroneBot Workshop 上有一个很好的介绍视频,它可能会为您提供足够的背景知识来使用步进电机。我找到的另一个关于布线的有用视频是这个。


电机要求为 1.8° 步距角(即每转 200 步)和紧凑的尺寸,以保持较低的重量,这有助于在两个方向上快速、精确地运动。

在我的视频中,您可以看到我使用了两种类型的电机和两种不同的驱动器。

NEMA 17 (42SHDC3025-24B) 与 A4988 驱动器结合使用。
NEMA 16 (39BYG53322) 步进电机与 ULN2003 驱动器配对。(这是更好的选择,因为转子更轻、更短,可以实现更快、更灵敏的运动。
我最终坚持使用混合动力 NEMA 16 (39BYG53322),不幸的是它非常不受欢迎。但是,正如我所说,尺寸比电机的类型更重要,因此可以使用任何其他类型的 NEMA 16。

第 4 步:将步进电机安装到支架上

将步进电机安装到其支架上

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 5 步:剪出一个镜子

我对镜子做了很多实验。镜子应尽可能薄;否则,它会反射未聚焦的光束。我发现效果最好的是旧硬盘中的磁盘。我用 Dremel 把它剪下来并塑造它。在照片中,您可以看到镜子和硬盘之间反射光束的差异。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 6 步:组装镜架

在支架上添加螺纹嵌件(此步骤是可选的 - 您也可以使用标准螺钉代替)。
用一些胶水将镜子连接到镜子架上。
将支架安装到电机上。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 7 步:准备环回传感器

从模块中拆焊二极管。
使用足够长的电线以到达电机支架,并将二极管焊接到它们上。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 8 步:将二极管后面的线路安装到支架上

将 Diodes 后面的线路安装到支架上
用热胶将两个二极管固定到孔中。

在这里插入图片描述
在这里插入图片描述

第 9 步:在激光器和 12V 电源之间添加继电器

Add a Relay Between the Laser and the 12V Power Supply
作为一项安全措施,需要继电器。当 Arduino 不运行或重启时,它不会向激光器发送 PWM 信号,导致它在本应关闭时以全功率运行。为防止这种情况,应连接继电器,使其默认处于关闭状态,并且 Arduino 只有在完成电机归位程序后才能将其打开。

在这里插入图片描述

第 10 步:使用原型板测试所有布线

在继续焊接之前,我建议按照此处的ULN2003驱动器和 A4988 驱动器的原理图,使用原型板测试设置。

我建议在焊接元件之前,先用低功率激光器(约100-200 mW)对电路进行实验。它对眼睛还是很危险的,但是 3W 激光器在全功率运行时甚至可以灼伤黑暗的表面,想象一下它会对你的眼睛造成什么影响!使用低功率激光,您可以进行调整,直到看到七个投影点(如视频所示)。我不建议使用高功率激光器开发竖琴——使用较弱的激光器更安全、更容易排除故障。请记住始终佩戴经过认证且评级适当的安全眼
镜!

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 11 步:创建 Arduino 扩展板

剪下 2 个与 Arduino 尺寸相匹配的穿孔板。
按照此处提供的 ULN2003 dirver 和 here) 的 A4988 驱动程序的原理图来构建扩展板。
将屏蔽层到组件的电缆保留足够长的时间,并在稍后安装时将其修剪成合适的尺寸。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 12 步:将组件固定到电路板上

电机支架和激光器的位置必须居中,以与顶部的分体对齐。Arduino 板必须与左侧的分割对齐,以确保其输出公开。其余组件可以根据图片大致定位。

您可以使用双面胶带将它们临时固定在板上,确保在用螺丝固定它们之前一切正常。然后,用笔标记孔,取下临时胶带,并预先钻孔。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 13 步:将木板固定到外壳上

您可以使用一根末端带有白色油漆的细丝将其穿过外壳的孔,并在木板上标记钻孔点。然后,钻孔并用螺丝将电路板固定到外壳上。

在这里插入图片描述
在这里插入图片描述

第 14 步:将传感器安装到外壳盖上

插入四个光传感器,并将它们焊接到通向另一侧扩展板的电线上。
用一些热胶固定声音传感器。
装上盖子。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 15 步:安装反光附件。

这种附件显着提高了竖琴的性能,并增加了横梁可以中断的高度。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第 16 步:将代码上传到 Arduino

您可以在此存储库中找到这两个 Arduino 的代码。

每当您需要更新 Arduino UNO(控制激光器和电机)的代码时,

我建议遵循这个例程:

#include <SoftwareSerial.h>
#include "MIDIUSB.h"
#include <Wire.h>

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);
}

关闭 12v 电源。
断开 Arduino Leonardo 与计算机的连接。
将 Arduino 代码上传到 Arduino Uno。
上传后断开 Arduino Uno 与计算机的连接。
打开 12v 电源。
将 Arduino Leonardo 重新连接到计算机。

每当您需要更新 Arduino Leonardo(控制超声波传感器并生成 MIDI 音符)的代码时,您可以在 harp 运行时上传新代码,而不会注意到任何中断,因为 Arduino UNO 同时处理电机和激光器。

第 17 步:用竖琴演奏音乐

由于 Arduino Leonardo 可以充当人机接口设备 (HID),您只需将其插入计算机并启动您最喜欢的 DAW 软件即可。我正在使用 GarageBand,但它也适用于 Logic Pro、Ableton Live 和其他流行的 DAW。

从那里,您可以选择自己喜欢的声音并开始摇滚!

此外,您需要一台烟雾机来使光束可见并创建标志性的激光竖琴效果。

第 18 步:进一步的改进步骤

进一步的改进步骤
振镜扫描仪: 用振镜扫描仪代替步进电机将使竖琴有更多的琴弦和更亮的光束,因为由于步进电机的速度和精度,目前的设置仅限于七根琴弦。振镜扫描仪将能够更快、更精确地定位光束。

传感器高度:将传感器安装在更高的位置将进一步提高其检测光束中断的能力,并更好地跟踪手的高度。这将产生更准确的音符触发,并可能对声音进行更动态的控制。如果你仔细观察让-米歇尔的竖琴,你会注意到传感器的位置要高得多。

这些升级可以显着提高竖琴的性能和可玩性,使其反应灵敏和通用性更高。

在这里插入图片描述

第 19 步:最后的话

这个项目很有挑战性,需要大量的微调,所以要做好试错的准备。如果我错过了什么或不清楚的地方,请随时联系我们——我很乐意提供帮助!

此外,我怎么强调激光的危险性都不为过。高功率激光会导致严重的眼睛受伤,甚至灼伤表面,因此请认真对待安全。在使用激光器之前,请确保您佩戴了经过认证的安全眼镜并了解风险。花点时间对激光安全和正确的处理技术进行自我教育。

本指南仅供参考和教育之用,不能取代专业建议。如果您有任何疑问或具体的技术问题,请咨询专家。我不是专业的工程师或机械师,本指南基于我的个人经验。始终使用您的最佳判断力,遵守当地法律和安全准则,并了解所涉及的任何风险。

使用这些说明,您将承担可能发生的任何伤害、损害或事故的所有风险和责任,并同意放弃和免除我的任何和所有索赔或责任。请谨慎行事,自行研究,并对您的结果承担全部责任。

在这里插入图片描述

### 解决PyCharm无法加载Conda虚拟环境的方法 #### 配置设置 为了使 PyCharm 能够成功识别并使用 Conda 创建的虚拟环境,需确保 Anaconda 的路径已正确添加至系统的环境变量中[^1]。这一步骤至关重要,因为只有当 Python 解释器及其关联工具被加入 PATH 后,IDE 才能顺利找到它们。 对于 Windows 用户而言,在安装 Anaconda 时,默认情况下会询问是否将它添加到系统路径里;如果当时选择了否,则现在应该手动完成此操作。具体做法是在“高级系统设置”的“环境变量”选项内编辑 `Path` 变量,追加 Anaconda 安装目录下的 Scripts 文件夹位置。 另外,建议每次新建项目前都通过命令行先激活目标 conda env: ```bash conda activate myenvname ``` 接着再启动 IDE 进入工作区,这样有助于减少兼容性方面的问题发生概率。 #### 常见错误及修复方法 ##### 错误一:未发现任何解释器 症状表现为打开 PyCharm 新建工程向导页面找不到由 Conda 构建出来的 interpreter 列表项。此时应前往 Preferences/Settings -> Project:...->Python Interpreter 下方点击齿轮图标选择 Add...按钮来指定自定义的位置。按照提示浏览定位到对应版本 python.exe 的绝对地址即可解决问题。 ##### 错误二:权限不足导致 DLL 加载失败 有时即使指定了正确的解释器路径,仍可能遇到由于缺乏适当的操作系统级许可而引发的功能缺失现象。特别是涉及到调用某些特定类型的动态链接库 (Dynamic Link Library, .dll) 时尤为明显。因此拥有管理员身份执行相关动作显得尤为重要——无论是从终端还是图形界面触发创建新 venv 流程均如此处理能够有效规避此类隐患。 ##### 错误三:网络连接异常引起依赖下载超时 部分开发者反馈过因网速慢或者其他因素造成 pip install 操作中途断开进而影响整个项目的初始化进度条卡住的情况。对此可尝试调整镜像源加速获取速度或是离线模式预先准备好所需资源包后再继续后续步骤。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

驴友花雕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值