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


这是一个显示倒计时的非接触式洗手计时器,还可以播放 20 秒的循环歌曲。你可以随便添加自己喜欢的提示音!
背景
一直以来,洗手就是疾病预防和整体健康的重要武器,伴随着新型冠状病毒大流行蔓延,洗手也成为遏制病毒传播的重要预防措施。
CDC 良好洗手指南规定,理想情况下,人们必须洗手 20 秒。
在这个洗手计时器的简化版中,我们的音乐洗手计时器通过在超声波传感器前挥动我们的手来激活,并在 7 段显示屏上显示倒计时。为了使它更有趣,它还从预编程的歌曲列表中播放 20 秒的歌曲。您可以通过转录您拥有的任何乐谱符号轻松添加自己的音乐!
我编写了 4 个提示音:
第 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,如涉及侵权,可联系删除。
基于Arduino UNO的采样范围和频率计数器
2022-03-22
利用 Arduino Nano 自制迷你示波器
2022-04-01
自制随机数模拟器
2022-01-04
基于Arduino UNO的爬楼机器人
2022-03-11
基于Arduino Nano的点焊机
2022-03-24
讨论