知乎 - 知乎

如何製作深受兒童喜愛的木製收音機

來自專欄 DF創客社區4 人贊同了文章

這個項目的初心是,製作一個適合您孩子使用的小巧方便的收音機。讓他們播放自己喜歡的歌曲或童話故事。這一切都集中在木盒子裡面!

使用硬體

  • Arduino Nano R3
  • DFPlayer Mini 播放器模塊

起源

當我第一眼看到Horbert收音機時,我就立刻想為我2歲的女兒製作出類似的東西。但是我希望讓它變得更加小巧和方便。也許需要在那裡放置一些燈。我們確實也是這麼做的。收音機在您手中可以變得更加小巧。它包含7個顏色按鈕、小巧而強大的揚聲器,以及MP3播放器,能夠播放SD卡中的歌曲或童話故事。最後但同樣重要的是,它在錶殼周圍設有彩虹LED,以創造出漂亮的環境光,能在睡前童話故事中作為夜燈使用。

木箱

我希望讓箱子變成木製的。它看起來不錯,我喜歡用木頭工作。幸運的是,同時,我的朋友給我帶來了一把激光切割機切割膠合板。它太棒了!各種形狀都能夠在幾秒鐘內切割完畢,而且精確度極高!圖形文件附在本文底部。

尺寸:90x140x35mm

錶殼採用4毫米膠合板並切割成8個部分。前後板、四個側板、電池和揚聲器蓋。首先,我將四個側板粘在一起形成整個框架。我還使用了其它一些木頭對角落進行加固,因為我希望它們在最後是圓形的,所以我需要在那裡增加一些材料才能將角落向下鑽一點。

在我粘貼前面部分前,我在前板和框架之間嵌入一層薄薄的有機玻璃。這將允許我稍後連接LED,以便在收音機周圍創建漂亮的閃亮環。某些額外的設計觸感觸動了我。我使用4mm有機玻璃,將其切成粗糙的形狀以適應整個框架。有機玻璃使用超級膠水粘在框架和前板上。不必擔心重疊的有機玻璃,它將最終打磨成型。

環境彩虹燈

為了在黑暗環境下增加額外的炫酷效果,我在錶殼周圍增加了4個RGB LED。先前插入的有機玻璃將允許發射的光在殼體周圍擴散。我使用了菊鏈式標準可定址WS2818 RGB LED。木箱的每個角落都分布了一個LED燈。

首先,使用萬能膠將這些微型LED粘合到有機玻璃頂部角落。不要忘記標記引腳1,否則您將無法正確連接它們-請參閱永久標記所做的微型黑色標記。其次,將它們粘連在一起。我採用的工具是0.3mm銅線和透明絕緣材料。

VCC到VCC

GND至GND

OUT到下一個LED的IN

完成木箱製作

在將背板粘貼到位之前,在框架一側切割小孔以適合Arduino Nano USB埠、MP3播放器SD卡插槽和電源開關。我決定將它們放置在收音機左側。我使用小刀和方形文件,切割出精確的孔洞。保證這些孔洞盡量少,並且儘可能地隱藏。

現在是時候將背板粘合到位,並打造收音機最終形狀了。利用萬能膠套裝後,我使用台式砂光機塑造圓角,並對所有表面進行清理。為了保護邊緣,我還給它們打了一點點萬能膠。保護從RGB LED出來的電線,以防止在打磨時損壞絕緣層。

最後一步是將揚聲器蓋環粘在揚聲器孔上。它將使揚聲器保持在原位。作為最後的保護措施,我已經塗了4層清漆

揚聲器蓋網

為了避免人員好奇損壞揚聲器,我希望在揚聲器孔上放置一些網狀物。但是由於我沒有3D列印機,而且我沒有其他任何合適的工具,我只好通過將1mm黃銅棒焊接成網狀物來創建這種網狀物。

事實證明它很完美。它非常堅固。沒有什麼能夠穿透這層網,而且它也看起來很漂亮。為了隱藏焊點,我噴了白色漆。使用萬能膠將揚聲器蓋粘在收音機中。

收音機裡面

收音機核心是我最喜歡的Arduino Nano板。它能夠讀取7個按鈕的狀態,控制DFPlayer迷你MP3播放器,以及可定址RGB LED。DFPlayer是一款小巧的MP3播放器,內置SD卡插槽和聲音放大器,能夠對高達3W的揚聲器進行處理。我使用了一個功率為0.5W的8歐揚聲器

按鈕

首先,創建按鈕平台。收音機包含7個可編程按鈕,總共焊接到一小塊穿孔板上。最初我希望每個按鈕都採用不同的顏色,以播放7種不同的歌曲。但是它看起來並不好看,所以我決定用一個黑色按鈕來改變音量,用黃色、藍色和紅色對來選擇3組歌曲的上一首或下一首歌。每個按鈕連接到Arduino的一個數字引腳,它們都通過1Kohm電阻接地。

按鈕的穿孔板間距並非標準和友好,因此我不得不將它們放入殼體的孔洞中,並使用自由形式技術將它們與一塊黃銅棒焊接在一起。現在當按鈕布局固定後,是時候將它倒掛到穿孔板上,並為每個按鈕添加7根電線。最後,我為共同的接地點焊接了第8根電線。

整體組裝

現在,當您已經完成所有零件製作後,就可以結束這項工作了。我首先焊接了Arduino Nano、DFPlayer、按鈕、揚聲器、RGB LED、電池和收音機外部的所有電線,因為一旦插入它們,機箱內就沒有足夠空間。它還允許我在所有組件不可逆地粘在內部之前測試外殼電子設備。

首先,我使用Mamut Glue將Arduino Nano、DFPlayer和開/關開關插入並粘貼到錶殼中。 Mamut Glue讓我擁有更多的時間,精確地將板材放在裡面,並且將來會更好地保持它們,因為它是靈活可調的。接下來是揚聲器蓋網底部再次安裝Mamut Glue的揚聲器。最後的環節是按鈕和電池組。電池組位於背面孔洞中,方便輕鬆更換電池。後來我用一節9V電池更換了4xAA電池。它的重量更輕

大功告成!我們來編程吧!

播放歌曲!

我只是使用了DFPlayer網站的示例,並添加了一些代碼來讀取按鈕,並對我的Ever Blooming Mechanical Tulip(「永不凋謝的機械鬱金香」)項目的LED進行控制。代碼非常簡單。這簡直不像是我做出來的,但是它卻非常有效。

它要求SD卡包含3個名為01、02、03的文件夾,以對應每種顏色。每個文件夾中最多可包含255首歌曲,名為001.mp3到255.mp3。我寧願將文件夾命名為紅色、藍色和黃色,但DFPlayer不支持。

DFPlayer使用非常方便。它通過串列通信,並具有超級棒的音樂庫,具有各種功能讀取和控制播放器的狀態。我只會添加對人可讀的文件夾和文件名的功能支持。

代碼

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <Adafruit_NeoPixel.h>
#define BUTTON_1 A2 // yellow left
#define BUTTON_2 A1 // yellow right
#define BUTTON_3 A3 // red left
#define BUTTON_4 A0 // red right
#define BUTTON_5 A4 // blue left
#define BUTTON_6 2 // blue right
#define BUTTON_7 3 // black
#define BUTTONS_COUNT 7
#define BUTTON_RELEASED 0
#define BUTTON_PRESSED 1
#define BUTTON_HANDLED 2
byte buttonPins[] = {BUTTON_1, BUTTON_2, BUTTON_3, BUTTON_4, BUTTON_5, BUTTON_6, BUTTON_7};
byte buttonState[] = {0, 0, 0, 0, 0, 0, 0};
#define NEOPIXEL_PIN 9
#define RED 0
#define GREEN 1
#define BLUE 2
float currentRGB[] = {0, 0, 0};
float changeRGB[] = {0, 0, 0};
byte newRGB[] = {0, 0, 0};
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(4, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ400);
SoftwareSerial playerSerial(7, 6); // RX, TX
DFRobotDFPlayerMini player;
#define FOLDER_RED 0
#define FOLDER_BLUE 1
#define FOLDER_YELLOW 2
byte currentSong[] = {0, 0, 0}; // 1 to size of folder
byte folderSizes[] = {0, 0, 0};
byte volume = 5;
byte lightIntensity = 0;
void setup() {
randomSeed(analogRead(A7));
playerSerial.begin(9600);
Serial.begin(115200);
pixels.begin();
pixelsUnifiedColor(0);
for (int i = 0; i < BUTTONS_COUNT; i++) {
pinMode(buttonPins, INPUT_PULLUP);
buttonState = BUTTON_RELEASED;
}

Serial.println();
Serial.println(F("DFRobot DFPlayer Mini Demo"));
Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));

if (!player.begin(playerSerial)) { //Use softwareSerial to communicate with mp3.
Serial.println(F("Unable to begin:"));
Serial.println(F("1.Please recheck the connection!"));
Serial.println(F("2.Please insert the SD card!"));
while(true);
}
Serial.println(F("DFPlayer Mini online."));
//player.EQ(DFPLAYER_EQ_ROCK);
player.outputDevice(DFPLAYER_DEVICE_SD);
player.volume(volume);
//player.play(1);
folderSizes[FOLDER_RED] = player.readFileCountsInFolder(FOLDER_RED + 1);
folderSizes[FOLDER_BLUE] = player.readFileCountsInFolder(FOLDER_BLUE + 1);
folderSizes[FOLDER_YELLOW] = player.readFileCountsInFolder(FOLDER_YELLOW + 1);
Serial.println(F("Radio ready."));
}
void loop() {
if (player.available()) {
printDetail(player.readType(), player.read()); //Print the detail message from DFPlayer to handle different errors and states.
}
readButtons();
crossFade();
delay(15);
}
void readButtons() {
for (int i = 0; i < BUTTONS_COUNT; i++) {
if (digitalRead(buttonPins) == HIGH) {
buttonState = BUTTON_RELEASED;
}
else if (buttonState == BUTTON_RELEASED) {
buttonState = BUTTON_PRESSED;
}
}
if (buttonState[0] == BUTTON_PRESSED) { // yellow left
buttonState[0] = BUTTON_HANDLED;
playNextInFolder(FOLDER_YELLOW);
}
else if (buttonState[1] == BUTTON_PRESSED) { // yellow right
buttonState[1] = BUTTON_HANDLED;
playPreviousInFolder(FOLDER_YELLOW);
}
else if (buttonState[2] == BUTTON_PRESSED) { // red left
buttonState[2] = BUTTON_HANDLED;
playNextInFolder(FOLDER_RED);
}
else if (buttonState[3] == BUTTON_PRESSED) { // red right
buttonState[3] = BUTTON_HANDLED;
playPreviousInFolder(FOLDER_RED);
}
else if (buttonState[4] == BUTTON_PRESSED) { // blue left
buttonState[4] = BUTTON_HANDLED;
playNextInFolder(FOLDER_BLUE);
}
else if (buttonState[5] == BUTTON_PRESSED) { // blue right
buttonState[5] = BUTTON_HANDLED;
playPreviousInFolder(FOLDER_BLUE);
}
else if (buttonState[6] == BUTTON_PRESSED) { // black
volume = volume >= 15 ? 5 : volume + 5;
player.volume(volume);
buttonState[6] = BUTTON_HANDLED;
}
}
void playNextInFolder(byte folder) {
currentSong[folder]++;
if (currentSong[folder] > folderSizes[folder]) {
currentSong[folder] = 1;
}
player.playFolder(folder + 1, currentSong[folder]);
}
void playPreviousInFolder(byte folder) {
currentSong[folder]--;
if (currentSong[folder] <= 0) {
currentSong[folder] = folderSizes[folder];
}
player.playFolder(folder + 1, currentSong[folder]);
}
void printDetail(uint8_t type, int value){
switch (type) {
case TimeOut:
Serial.println(F("Time Out!"));
break;
case WrongStack:
Serial.println(F("Stack Wrong!"));
break;
case DFPlayerCardInserted:
Serial.println(F("Card Inserted!"));
break;
case DFPlayerCardRemoved:
Serial.println(F("Card Removed!"));
break;
case DFPlayerCardOnline:
Serial.println(F("Card Online!"));
break;
case DFPlayerPlayFinished:
Serial.print(F("Number:"));
Serial.print(value);
Serial.println(F(" Play Finished!"));
break;
case DFPlayerError:
Serial.print(F("DFPlayerError:"));
switch (value) {
case Busy:
Serial.println(F("Card not found"));
break;
case Sleeping:
Serial.println(F("Sleeping"));
break;
case SerialWrongStack:
Serial.println(F("Get Wrong Stack"));
break;
case CheckSumNotMatch:
Serial.println(F("Check Sum Not Match"));
break;
case FileIndexOut:
Serial.println(F("File Index Out of Bound"));
break;
case FileMismatch:
Serial.println(F("Cannot Find File"));
break;
case Advertise:
Serial.println(F("In Advertise"));
break;
default:
break;
}
break;
default:
break;
}
}
void prepareCrossFade(byte red, byte green, byte blue, unsigned int duration) {
float rchange = red - currentRGB[RED];
float gchange = green - currentRGB[GREEN];
float bchange = blue - currentRGB[BLUE];
changeRGB[RED] = rchange / (float) duration;
changeRGB[GREEN] = gchange / (float) duration;
changeRGB[BLUE] = bchange / (float) duration;
newRGB[RED] = red;
newRGB[GREEN] = green;
newRGB[BLUE] = blue;
Serial.print(newRGB[RED]);
Serial.print(" ");
Serial.print(newRGB[GREEN]);
Serial.print(" ");
Serial.print(newRGB[BLUE]);
Serial.print(" (");
Serial.print(changeRGB[RED]);
Serial.print(" ");
Serial.print(changeRGB[GREEN]);
Serial.print(" ");
Serial.print(changeRGB[BLUE]);
Serial.println(")");
}
boolean crossFade() {
if (currentRGB[RED] == newRGB[RED] && currentRGB[GREEN] == newRGB[GREEN] && currentRGB[BLUE] == newRGB[BLUE]) {
return true;
}
for (byte i = 0; i < 3; i++) {
if (changeRGB > 0 && currentRGB < newRGB) {
currentRGB = currentRGB + changeRGB;
}
else if (changeRGB < 0 && currentRGB > newRGB) {
currentRGB = currentRGB + changeRGB;
}
else {
currentRGB = newRGB;
}
}
pixelsUnifiedColor(pixels.Color(currentRGB[RED], currentRGB[GREEN], currentRGB[BLUE]));
return false;
}
void pixelsUnifiedColor(uint32_t color) {
for (unsigned int i = 0; i < pixels.numPixels(); i++) {
pixels.setPixelColor(i, color);
}
pixels.show();
}
void rainbow(int j) {
uint16_t i;
byte num = pixels.numPixels() - 1;
pixels.setPixelColor(pixels.numPixels() - 1, 100, 100, 100);
for (i = 0; i < num; i++) {
pixels.setPixelColor(i, colorWheel(((i * 256 / num) + j) & 255));
}
pixels.show();
}
uint32_t colorWheel(byte wheelPos) {
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
wheelPos = 255 - wheelPos;
if (wheelPos < 85) {
return pixels.Color(255 - wheelPos * 3, 0, wheelPos * 3);
}
if (wheelPos < 170) {
wheelPos -= 85;
return pixels.Color(0, wheelPos * 3, 255 - wheelPos * 3);
}
wheelPos -= 170;
return pixels.Color(wheelPos * 3, 255 - wheelPos * 3, 0);
}

原文鏈接:mc.dfrobot.com.cn/threa

附件請在原文下載

推薦閱讀:

相关文章