本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

非接触式音乐洗手定时器

发布时间:2022-06-10
分享到:

非接触式音乐洗手定时器

发布时间:2022-06-10
分享到:

这是一个显示倒计时的非接触式洗手计时器,还可以播放 20 秒的循环歌曲。你可以随便添加自己喜欢的提示音!

背景

一直以来,洗手就是疾病预防和整体健康的重要武器,伴随着新型冠状病毒大流行蔓延,洗手也成为遏制病毒传播的重要预防措施。

CDC 良好洗手指南规定,理想情况下,人们必须洗手 20 秒。

在这个洗手计时器的简化版中,我们的音乐洗手计时器通过在超声波传感器前挥动我们的手来激活,并在 7 段显示屏上显示倒计时。为了使它更有趣,它还从预编程的歌曲列表中播放 20 秒的歌曲。您可以通过转录您拥有的任何乐谱符号轻松添加自己的音乐!

我编写了 4 个提示音:

  • 生日快乐
  • Do-Re-Mi (Sound of Music)
  • We Will Rock You (Queen)
  • Jeopardy Theme Music

第 1 步 - 示意图
该项目使用 Arduino Uno、7 段 LED 背包 (I2C)、HC-SR04 超声波传感器和压电蜂鸣器。请参阅下面的示意图:

第 2 步 - 规划和设置
除了原理图和编程之外,我还想考虑“成品”以及如何使其可用——因此将其放入外壳中并为此进行规划。

我使用了 ProtoStax 外壳——它们是可堆叠的,并且支持 Arduino、Raspberry Pi 和 Breadboard。由于我使用了带有面包板电路的 Arduino,因此我为 Arduino 选择了 ProtoStax 外壳,为面包板/定制板选择了 ProtoStax 外壳。我还希望可以从外部访问超声波传感器,因此将其固定在外壳上 - 我使用了用于超声波传感器 HC-SR04 的 ProtoStax 套件。

我开始使用水平堆叠连接器水平堆叠 Arduino 和 Breadboard 基础平台以促进原型设计:

一旦我有了原型平台,我就可以开始按照原理图填充组件。我将 HC-SR04 超声波传感器连接到用于超声波传感器 HC-SR04 的 ProtoStax 套件的侧壁上,以便在原型完成后将外壳组装在一起后可以使用它。带有传感器的侧壁进入底座平台的插槽,如图所示:

第 3 步 - 编程和测试
现在我有了一个可以工作的原型平台,我可以着手为它开发代码了。我将在下面的单独部分中深入研究代码功能和布局。确认一切正常后,我继续添加侧壁和剩余的连接器和顶部以完成我的外壳:

了解代码
初始化组件:

我们使用 LED Backpack 库中的 Adafruit_7segment 类来初始化我们的 7 段显示器并与之通信。

Adafruit_7segment matrix = Adafruit_7segment();

我们还将 HC-SR04 上的 trig 和 echo 引脚分别初始化为输出和输入

 pinMode(trigPin, OUTPUT);
 pinMode(echoPin, INPUT);
 matrix.begin(0x70);

在主循环中,这是在高层次上完成的:

1) 检查超声波传感器距离读数,看是否触发了洗手定时器。

2) 如果是,则记下当前时间,初始化倒计时计时器(我将其设置为 20,持续 20 秒),并选择下一个要播放的音乐叮当声。我首先使用 random() 来选择随机旋律,但我将其更改为在旋律数组上的“循环”(循环回到第一个),并将 startMusic 设置为 1(设置倒计时和音乐播放.

 if (distance < 10 && !startMusic) {
   startMusic = 1;
   // initializeTimer1();
   countDown = 20;
   currentTime = millis();
   melodyNum = (melodyNum+1)%(NUM_MELODIES(melodies));
 }

在这里,我们同时做两件事——我们要定期更新倒计时时钟,显示还剩多少秒可以洗手。我们还想处理叮当声并及时正确播放。

因此,我们不能使用 delay()

播放音乐的典型示例使用 Tone 库的tone() 函数,并在继续播放下一个音符之前等待适当的延迟。那是行不通的,因为我们仍然想更新倒计时时钟!

tone() 是一个非阻塞调用。它使用 Timer2 在指定的时间长度内发送信号,这意味着在此期间,我们可以自由地进行其他处理。

我们使用 millis() 和局部变量来计算已经过去了多少时间,而不是使用 delay(),并且可以在此期间继续进行其他检查并执行其他操作。

轻松转录音乐 - 全音符、四分音符等
我们想演奏给定的旋律,也想让转录更多的旋律变得容易。Arduino 音乐示例通常存储两个不同的数组,一个用于音符,一个用于音符持续时间(以毫秒为单位)。

为了简化事情,我创建了一个结构来关联音符及其给定的持续时间。我没有使用绝对持续时间,而是使用了我为#defines 创建的相对持续时间

typedef struct Note {
 int frequency;
 float duration;   
} Note;

#define NOTE_WHOLE 1
#define NOTE_HALF 0.5f
#define NOTE_QUARTER 0.25f
#define NOTE_EIGHTH 0.125f
#define NOTE_SIXTEENTH 0.0625f
#define DOTTED(X) (X * 1.5f) 

以生日快乐为例,如果您看不懂乐谱,只需找到要使用的实际音符。但是学习阅读乐谱始终是一项巧妙的技能,而且您不必非常擅长,只要知道音符是什么就可以让您做必要的转换音乐到您的项目!

// Happy Birthday
Note melody[] = { 
 {NOTE_G6, DOTTED(NOTE_EIGHTH)}, {NOTE_G6, NOTE_SIXTEENTH}, {NOTE_A6, NOTE_QUARTER}, {NOTE_G6, NOTE_QUARTER}, {NOTE_C7, NOTE_QUARTER}, {NOTE_B6, NOTE_HALF}, 
 {NOTE_G6, DOTTED(NOTE_EIGHTH)}, {NOTE_G6, NOTE_SIXTEENTH}, {NOTE_A6, NOTE_QUARTER}, {NOTE_G6, NOTE_QUARTER}, {NOTE_D7, NOTE_QUARTER}, {NOTE_C7, NOTE_HALF},
 {NOTE_G6, DOTTED(NOTE_EIGHTH)}, {NOTE_G6, NOTE_SIXTEENTH}, {NOTE_E7, NOTE_QUARTER}, {NOTE_D7, NOTE_QUARTER}, {NOTE_C7, NOTE_QUARTER}, {NOTE_B6, NOTE_QUARTER}, {NOTE_A6, NOTE_HALF}, 
 {NOTE_F7, DOTTED(NOTE_EIGHTH)}, {NOTE_F7, NOTE_SIXTEENTH}, {NOTE_E7, NOTE_QUARTER}, {NOTE_C7, NOTE_QUARTER}, {NOTE_D7, NOTE_QUARTER}, {NOTE_C7, NOTE_HALF},      
};
注意:我在这里没有使用任何实际的持续时间,我将音符的相对持续时间指定为四分音符、八分音符、十六分音符等。我什至有一个 DOTTED() 宏来表示一个点音符(1.5 x 前面任何音符的持续时间)。

旋律本身包含这个数组,以及关于整个音符应该代表什么持续时间的附加信息。

typedef struct Melody {
 Note *notes;
 int numNotes;
 int wholeNoteDurationMs;  
} Melody;

由于无法使用指向数组的指针来调整 C 数组的大小,因此我将 numNotes 添加为 Note 数组的大小。这可以使用 MELODY_LENGTH 宏轻松初始化 - 因此您不必担心在转录您最喜欢的歌曲时在 Note 数组中创建了多少音符!

然后我定义了一个这样的旋律数组以在我的程序中使用。

Melody melodies[] = {
 {melody, MELODY_LENGTH(melody), 1250}, {melody3, MELODY_LENGTH(melody3), 1000}, {melody4, MELODY_LENGTH(melody4), 1000}
};

在循环中,当启动倒数计时器和音乐时,我使用上面的音符信息、相对持续时间和整个音符的实际持续时间来计算如何播放音乐。在播放音乐之间,我还检查并更新倒计时计时器并在 7 段显示屏上显示数字。

因为我想人们想听完整首歌曲,所以我继续播放歌曲直到它结束,即使 20 秒结束(倒计时将变为负数,直到歌曲结束)。叮当声结束后,它将停止,直到再次在超声波传感器前挥手进一步触发。如果叮当声太短,那么它会再次播放,直到 20 秒过去并且音乐播放完毕!

 if (startMusic) {
   // Pick the melody to play
   Melody mel = melodies[melodyNum];
   Note *m = mel.notes;
   int mSize = mel.numNotes;
   // speedUp is an easy way to speed up the playing of the notes. The best way would be to 
   // set the wholeNoteDurationMs appropriately. 
   int speedUp = 1; 
   noTone(TONE_PIN); // Start with a clean slate
   for (int thisNote = 0; thisNote < mSize; thisNote++) {
     // to calculate the note duration, take the duration of the whole note and multiply by the note type.
     //e.g. quarter note = wholeNoteDurationMs * 0.25, eighth note = wholeNoteDurationMs * 1/8 (0.125), etc.
     // reduce the duration by the speedup factor to increase the speed of playing 
     // by an appropriate amount
     int noteDuration = mel.wholeNoteDurationMs * m[thisNote].duration / speedUp;
     unsigned long noteTime = millis();
     tone(TONE_PIN, m[thisNote].frequency, noteDuration);
     // to distinguish the notes, set a minimum time between them.
     // the note's duration + 30% seems to work well:
     int pauseBetweenNotes = noteDuration * 1.30;
     unsigned long nowTime = millis();
     countDown = 20 - (int)((nowTime - currentTime)/1000);
     // Look Ma No delay()!
     // Don't use delay(), as we still want to update the countDown timer 
     // and update the display
     while(millis() - noteTime <= pauseBetweenNotes) {
       countDown = 20 - (int)((millis() - currentTime)/1000);
       matrix.print(countDown);
       matrix.writeDisplay();  
       delay(10);      
     }
     noTone(TONE_PIN);
     matrix.print(countDown);
     matrix.writeDisplay();
   }
整个代码可以在文章下方找到,并且包含到存储库的链接。我建议从那里获取代码,而不是从这里复制和粘贴代码。

进一步推进项目
一旦您熟悉了代码示例并理解了代码,尝试通过做更多事情来扩展您的学习总是很好的。

以下是有关如何推进该项目的一些建议:

1)你可以找到你最喜欢的曲子,然后使用我上面描述的NOTE和NOTE持续时间宏来转录它。只要记住注释掉已经定义的一个或多个其他叮当声,以降低内存使用率。

2) 旋律占用SRAM中的空间,很快就会耗尽可用内存。例如,我转录了 4 首旋律(生日快乐、Do-Re-Mi、We Will Rock You 和 Jeopardy!)。然而,这些将 SRAM 的使用率推到了 96%,没有为 7 段显示库的功能留下足够的空间,并且没有正确更新!我必须从 Melody 数组中排除其中一个旋律才能正常工作。

Arduino Uno 带有 2k 的 SRAM,但有 32k 的闪存(程序所在的位置)。如果您可以将一些全局变量移动到闪存中,您不仅可以为程序的其余部分腾出 SRAM,还可以有更多的空间来存储更多的歌曲!尝试通过将 Note 和 Melody 数组定义为 PROGMEM 将它们移动到 Flash 中。

为了让您了解这些差异,这个程序(有 3 个旋律)占用了 Uno 上 31% 的程序存储和 76% 的动态内存。使用 PROGMEM 中定义的上述变量,它占用了 32% 的程序空间(这只是闪存使用量的轻微增加,还有更多可用空间)和动态内存的 22%(低于 76%)!这意味着一旦您将内容移至 PROGMEM ,您就可以轻松地将大量歌曲添加到这款非接触式音乐洗手计时器!

本文中所用到的一些代码

如果您对此项目有任何想法、意见或问题,请在下方留言。

以上内容翻译自网络,原作者:Sridhar Rajagopal,如涉及侵权,可联系删除。

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论