【防疫居家學習】用DSI5168自製上、下課鐘聲播放器

作者:楊俊益

Ameba是由國內晶片IC設計大廠瑞昱半導體(Realtek)所推出,針對 IoT物聯網應用開發而設計的一套開發板解決方案。 它提供了相容於Arduino IDE開發的環境,除了非常適合入門者學習實作外,同時能滿足產業開發的要求,具備低功耗、安全性等特性,並支援IAR、mbed等工程師熟悉的開發環境,能讓創意的原型快速滿足市場量產需求,所以相當受到研發者的喜愛。 它也是值得學習的物聯網開發平台,能讓您以Wi-Fi透過 MQTT與 IoT 的整合運用功能,達成萬物聯網的機制。

資策會數位服務創新研究所(簡稱『服創所』),為了協助創新應用開發端經由開發板設計物聯網應用產品,催生「DSI5168」這塊量產導向的物聯網國產開發板。 該開發板以Ameba系列的主晶片  — RTL8711為核心,因此具備上述Ameba的開發優勢:完全兼容Arduino開發特性,還一舉整合MCU、Wi-Fi、Ethernet及豐富的3rd party設備與感測元件,搭配提供標準化的Arduino函式庫,可謂完整的物聯網方案。

資策會服創所開發基於Ameba RTL711AM WiFi 晶片的Arduino 相容開發板DSI5168

DSI5168 特色

DSI5168開發板支援Wi-Fi、GPIO、I2C、UART、PWM、ADC等多項傳輸方式。其精簡的I/O接腳設計,可順利與光照度、大氣壓力、開關、距離感測、溫濕度計、PIR感測、紅外線溫度感測、PM2.5 空氣品質感測等感測元件結合,快速將各項感測資料透過Wi-Fi上傳至雲端平台 ( IDEASChain 數據平台) 。 以DSI5168開發板所開發的各項物聯網應用,與坊間 ESP系列晶片類似,但卻提供更穩定物聯網相關運用。

DSI5168硬體介紹

為了滿足物聯網創新應用商品的小型化設計需求,特別簡化了晶片接腳,僅留下SPI、I2C、PWM、ADC、UART、GPIO等應用上常用的必要接腳,如下圖:

DSI5168物聯網開發板接腳圖(圖片來源:ideasHatch 官網)

DSI5168物聯網開發板硬體功能表(圖片來源:ideasHatch 官網)

DSI5168 物聯網開發板外觀,紅色框框部分為Micro USB接頭,藍色框框部分為Reset 按鈕。(圖片來源:資策會服創所)

DSI5168 Arduino IDE開發環境介紹

DSI5168 對Maker來說是非常方便運用於物聯網產品設計的開發板工具。DSI5168開發板繼承了Ameba系列Arduino 相容開發環境SDK Library,讓新創團隊及開發者可以非常快速使用這款國產小型物聯網開發板,更可利用Arduino 豐富的開源函式庫結合各種感測器與擴充模組,開發出許多的創新互動的應用。 以下為大家介紹在Windows 作業系統下安裝DSI5168 Arduino IDE開發環境:

1.至以下網址下載Arduino IDE軟體

https://www.arduino.cc/en/Main/Software

在Arduino 網站點選紅色框框 “JUST DOWNLOAD”(圖片來源:益師傅)

2.安裝Windows Ameba USB驅動程式

由於DSI5168是基於Ameba ARM MBED Free RTOS SoC硬體開發系統,在Windows下無法自動辨識 USB Driver,需至以下網址下載ARM MBED CMSIS DAP driver:https://os.mbed.com/handbook/Windows-serial-configuration

點選紅色框框 “Download latest driver”下載(圖片來源:益師傅)

將 MicroUSB 連接 DSI5168插在 Windows的電腦上,執行mbedWinSerial_16466 .exe驅動程式,即可看到 MBED 的磁碟及新增的Com Port 序列埠,即表示已完成安裝USB驅動程式 ( 如下圖呈現 ):

出現MBED虛擬磁碟機(圖片來源:益師傅)

可以從裝置管理員看到新增的 mbed Serial Port(圖片來源:益師傅)

3.設定DSI5168在Arduino IDE的執行環境

開啟Arduino程式,偏好設定中加入: https://github.com/Ameba8195/Arduino/raw/master/release/package_realtek.com_ameba_index.json

(圖片來源:益師傅)

在Arduino功能列選擇 “工具”(tools) 底下 →“開發版” (Boards)→“開發版管理者”(Boards manager) 輸入realtek,在畫面即可看到 Realtek Ameba Boards (32-bits ARM Cortex-M3) 等字樣,可選擇最新版 “2.06”按下其右下角的安裝 (install)。

安裝DS5168在Arduino IDE 開發SDK(圖片來源:益師傅)

點選“工具” (tools)  開發版 (Boards),往下來即可選擇“Ameba RTL8195A”,大功告成。

(圖片來源:益師傅)

IDEASChain 數據平台傳送資料設定

Ideaschain 網站的平台設定與 API 使用教學 請參考下列網址 : https://iforum.ideaschain.com.tw/iforum/devtool/board.do?board=3

步驟1:新增裝置

步驟2:產生在 IDEASChain新增裝置後的權杖,以便後續在 Arduino IDE 中使用

Arduino IDE 程式:

DSI 5168 上下課鐘聲播放器

功能說明

因疫情關係,雙北緊急通知大學以下的學生在家自主學習或是線上上學,由於事發突然(隔天就要上線使用) ,急忙且粗糙做了照表操課播放上下課鐘聲與語音提醒的系統,讓家中小孩能依學校上下課時間播報鐘聲,於不同時段中按下 “上課報到按鈕”,便能完成上課報到程序,若上課時間逾時5分鐘未按按鈕,便會透過 MQTT 將缺課哪一堂資料傳送至 IDEASChain 數據平台上,若父母不在家時也能透過接收 MQTT訊息或是到數據平台上得知是否有正常完成報到程序。

設計過程

為了要模擬上下課鐘聲,所以到網路上尋找上下課鐘聲 mp3 檔案,再依設定的時間透過DFPlayer mini MP3 player來播放。也運用Sound of Text網站將預計播放語音提醒功能預先轉換成 mp3,一樣依既定時程播放所需的音檔。

播報鐘聲及報到裝置電路圖

 

實體成品圖

這是一個簡易的提醒功能系統,有幾個注意要點:

  1. 跑 NTP 服務取得正確時間
  2. 先預錄文字轉MP3檔案放至記憶卡中 DFPlayer Mini MP3 Player 購買資訊: https://pse.is/3kaxh3 文字轉MP3音檔連結: https://soundoftext.com/
  3. 記憶卡不得大於 4 GB
  4. 程式與語音檔連結 https://pse.is/3k2b8v

有興趣的朋友可參考程式碼如下:

參考程式碼

#include #include #include #include #include #include #include #include “SoftwareSerial.h” #include “DFRobotDFPlayerMini.h” #include #include #include #include

#ifdef U8X8_HAVE_HW_SPI #include #endif #ifdef U8X8_HAVE_HW_I2C #include #endif

int weatherKind; U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

//—————————— WPA/WPA2 SSID and password—————- char ssid[] = “Pos”; // your network SSID (name) char pass[] = “0222435183”; // your network password int status = WL_IDLE_STATUS; // the Wifi radio’s status

//—————————– openweathermap———————————- //open weather map api key String apiKey = “b3a1fd4de2403e47a564135a5be59821”; //the city you want the weather for String location = “Taipei,TW”; // Tainan,Kaohsiung,Taoyuan,Hsinchu,Taichung,Keelung char server[] = “api.openweathermap.org”;

//—————————–Ifttt Webhoke to LINE ————————— const char* iftttHost = “maker.ifttt.com”; //Ifttt主機 const char* eventName = “msg2LINE”; //Ifttt觸發條件名稱 const char* key = “dOQDNJd1otLw8pUWs4YCF6Cd8mw7FJf0cgFiIKXdVUJ”; //Ifttt授權碼

//—————————– NTP Server setup——————————— int keyIndex = 0; // your network key Index number (needed only for WEP) char timeServer[] = “time.stdtime.gov.tw”; const int timeZone = 8; // Taipei Time // A UDP instance to let us send and receive packets over UDP WiFiUDP Udp; unsigned int localPort = 2390; // local port to listen for UDP packets time_t getNtpTime(); void sendNTPpacket(IPAddress &address);

#define SCREEN_WIDTH 128 // OLED 寬度像素 #define SCREEN_HEIGHT 64 // OLED 高度像素 #define NUMFLAKES 10 #define XPOS 0 #define YPOS 1 #define DELTAY 2 #define LOGO16_GLCD_HEIGHT 16 #define LOGO16_GLCD_WIDTH 16 // 設定OLED #define OLED_RESET 0 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

SoftwareSerial mySoftwareSerial(0, 1); // RX, TX DFRobotDFPlayerMini myDFPlayer;

void printDetail(uint8_t type, int value);

WiFiClient wifiClient; PubSubClient client(wifiClient); WiFiSSLClient SSLclient;

unsigned long startTime, quarkStartTime, nowTime; // 程式起始時間與目前時間 unsigned long WifiConnectingTimeout; //連線逾時時間 char *weekdayStr[7] = {“Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”};

int alarmVoice = 0 , voiceNum; char now_Str[7]; char dayType = ‘1’, dayTypeR = ‘1’; int currentMp3 = 2 , playKey = 0 , playCounter = 1, playMp3Flag = 0 ; String weatherFlag; unsigned long weatherTimeoutStart; int weatherTimeoutCounter = 0; String weatherStr, nowTemp, maxTemp, minTemp;

//——————————json6 object————————————————————- StaticJsonDocument<5000> doc;

void setup() {

Serial.begin(9600); mySoftwareSerial.begin(9600);

Serial.println(“Connected to wifi”); delay(2000);

u8g2.begin(); u8g2.enableUTF8Print();

u8g2.setFont(u8g2_font_unifont_t_chinese1); //使用我們做好的字型 u8g2.clearBuffer(); u8g2.setCursor(10, 30); u8g2.print(“物聯網智造基地”); u8g2.setCursor(5, 50); u8g2.print(“IOT SERVICE HUB”); u8g2.sendBuffer();

if (!myDFPlayer.begin(mySoftwareSerial)) { //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) { delay(0); // Code to compatible with ESP8266 watch dog. } } Serial.println(F(“DFPlayer Mini online.”));

myDFPlayer.EQ(DFPLAYER_EQ_ROCK); myDFPlayer.volume(25); myDFPlayer.playMp3Folder(1); //系統設定中

if (WiFi.status() == WL_NO_SHIELD) { Serial.println(“WiFi shield not present”); // don’t continue: while (true); }

WifiConnectingTimeout = millis(); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { Serial.print(“Attempting to connect to SSID: “); Serial.println(ssid);

// Connect to WPA/WPA2 network. Change this line if using open or WEP network:

Serial.println(“Connect …”);

if ((millis() – WifiConnectingTimeout) > 10000) //等待超過 10秒 break; // wait 3 seconds for connection: delay(3000); }

Serial.println(WiFi.localIP());

if (WiFi.status() == WL_CONNECTED ) { myDFPlayer.playMp3Folder(2); //MP3資料夾下的 0002.mp3 連線完成 Serial.println(“waiting for sync”); Udp.begin(localPort); getNtpTime(); delay(2000); setSyncProvider(getNtpTime); //每120秒同步 NTP 時間 setSyncInterval(120);

int getLoop = 0; while ( nowTemp.toInt() == 0 ) { ++getLoop ; getWeather(); if ( getLoop > 5) { Serial.println(“getWeather error!!”); break ; } } } else { Serial.println(“no network !! “); myDFPlayer.playMp3Folder(3); //MP3資料夾下的 0003.mp3 系統連線失敗

}

displayInstruction(); myDFPlayer.playMp3Folder(4010); delay(9000);

myDFPlayer.playMp3Folder(4020); delay(2000);

startTime = millis(); }

void loop() {

if ((minute() == 29 || minute() == 0 ) && second() == 0 ) { getNtpTime(); getWeather(); while ( (nowTemp.toInt() == 0) && (WiFi.status() == WL_CONNECTED) ) getWeather(); } if ( weekday() != 1 || weekday() != 7) { if ( hour() == 8 && minute() == 45 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第一節上課時間:”); u8g2.setCursor(10, 36); u8g2.print(“8:45-9:25”); u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4021); delay(2000); myDFPlayer.playMp3Folder(4038); delay(16000); } if ( hour() == 9 && minute() == 25 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第一節下課時間:”); u8g2.setCursor(10, 36); u8g2.print(“9:25-9:35”);

u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4031); delay(2000); myDFPlayer.playMp3Folder(4039); delay(16000); } if ( hour() == 9 && minute() == 35 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第二節上課時間:”); u8g2.setCursor(10, 36); u8g2.print(“9:35-10:15”); u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4022); delay(2000); myDFPlayer.playMp3Folder(4038); delay(16000); } if ( hour() == 10 && minute() == 15 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第二節下課時間:”); u8g2.setCursor(10, 36); u8g2.print(“10:15-10:35”);

u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4032); delay(2000); myDFPlayer.playMp3Folder(4039); delay(16000); } if ( hour() == 10 && minute() == 35 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第三節上課時間:”); u8g2.setCursor(10, 36); u8g2.print(“10:35-11:15”); u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4023); delay(2000); myDFPlayer.playMp3Folder(4038); delay(16000); } if ( hour() == 11 && minute() == 15 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第三節下課時間:”); u8g2.setCursor(10, 36); u8g2.print(“11:15-11:25”);

u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4033); delay(2000); myDFPlayer.playMp3Folder(4039); delay(16000); } if ( hour() == 11 && minute() == 25 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第四節上課時間:”); u8g2.setCursor(10, 36); u8g2.print(“11:25-12:05”); u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4024); delay(2000); myDFPlayer.playMp3Folder(4038); delay(16000); } if ( hour() == 12 && minute() == 5 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第四節下課時間:”); u8g2.setCursor(10, 36); u8g2.print(“12:05-13:20”);

u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4034); delay(2000); myDFPlayer.playMp3Folder(4039); delay(16000); } if ( hour() == 13 && minute() == 30 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第五節上課時間:”); u8g2.setCursor(10, 36); u8g2.print(“13:30-14:10”); u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4025); delay(2000); myDFPlayer.playMp3Folder(4038); delay(16000); } if ( hour() == 14 && minute() == 10 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第五節下課時間:”); u8g2.setCursor(10, 36); u8g2.print(“14:10-14:20”);

u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4035); delay(2000); myDFPlayer.playMp3Folder(4039); delay(16000); } if ( hour() == 14 && minute() == 20 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第六節上課時間:”); u8g2.setCursor(10, 36); u8g2.print(“14:20-15:00”); u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4026); delay(2000); myDFPlayer.playMp3Folder(4038); delay(16000); } if ( hour() == 15 && minute() == 0 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第六節下課時間:”); u8g2.setCursor(10, 36); u8g2.print(“15:00-15:20”);

u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4036); delay(2000); myDFPlayer.playMp3Folder(4039); delay(16000); } if ( hour() == 15 && minute() == 20 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第七節上課時間:”); u8g2.setCursor(10, 36); u8g2.print(“15:20-16:00”); u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4026); delay(2000); myDFPlayer.playMp3Folder(4038); delay(16000); } if ( hour() == 16 && minute() == 0 && second() == 0 ) { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“第七節下課時間:”); u8g2.setCursor(10, 36); u8g2.print(“16:00-16:10”);

u8g2.sendBuffer(); myDFPlayer.playMp3Folder(4037); delay(2000); myDFPlayer.playMp3Folder(4039); delay(16000); } if ( ((hour() >= 16) || (hour() < 8)) ) { u8g2.clearBuffer(); u8g2.setCursor(10, 36); u8g2.print(“Take Break…”); u8g2.sendBuffer(); } } } void displayInstruction() //系統開始執行時,呈現的資訊 { u8g2.clearBuffer(); u8g2.setCursor(10, 16); u8g2.print(“這是個在家學習時”); u8g2.setCursor(10, 36); u8g2.print(“間管理系統,會按”); u8g2.setCursor(10, 56); u8g2.print(“時間播報上下課鐘聲”); u8g2.sendBuffer(); } /*——– NTP code ———-*/ const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets time_t getNtpTime() { Serial.println(“Transmit NTP Request”); Udp.setRecvTimeout(1500); sendNTPpacket(); if ( Udp.read(packetBuffer, NTP_PACKET_SIZE) > 0 ) { unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long)packetBuffer[40] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; return secsSince1900 – 2208988800UL + timeZone * SECS_PER_HOUR; } else { // 無法取得NTP,再重新聯網試試,讓網路隨時保持連線狀態;網路閒置2-3分鐘,便會進入省電或休眠模式,就會造成無法連網,雖然 WL_CONNECTED == Ture Serial.println(“No NTP Response :-(“); wdt_enable(8000); wdt_reset(); status = WiFi.begin(ssid, pass); delay(1000); wdt_reset(); wdt_disable(); return 0; // return 0 if unable to get the time } }

// send an NTP request to the time server at the given address // send an NTP request to the time server at the given address void sendNTPpacket() { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(timeServer, 123); //NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); }

time_t getWeather() { //取得氣象預報

String jsonTmp ; wifiClient.stop(); Serial.println(“nStarting connection to server…”); // if you get a connection, report back via serial: if (wifiClient.connect(server, 80)) { Serial.println(“connected to server”); // Make a HTTP request: wifiClient.print(“GET /data/2.5/weather?”); wifiClient.print(“q=” + location); wifiClient.print(“&appid=” + apiKey); wifiClient.print(“&cnt=3”); wifiClient.println(“&units=metric”); wifiClient.println(“Host: api.openweathermap.org”); wifiClient.println(“Connection: close”); wifiClient.println(); wifiClient.flush(); } else { Serial.println(“unable to connect”); }

delay(500); while (wifiClient.available()) {

String jsonData = wifiClient.readStringUntil(‘r’); //逐列讀取 Serial.println(jsonData); DeserializationError error = deserializeJson(doc, jsonData);

// Test if parsing succeeds. if (error) { Serial.print(F(“deserializeJson() failed: “)); Serial.println(error.c_str());

}

//JsonArray array = doc.as();

String Str1 = doc[“main”][“temp”]; String Str2 = doc[“main”][“temp_min”]; String Str3 = doc[“main”][“temp_max”]; String Str4 = doc[“weather”][0][“main”];

nowTemp = Str1; minTemp = Str2; maxTemp = Str3; weatherStr = Str4;

Serial.print(“目前溫度: “); Serial.println(nowTemp); Serial.print(“最低溫度: “); Serial.println(minTemp); Serial.print(“最高溫度: “); Serial.println(maxTemp); Serial.print(“天氣狀況: “); Serial.println(weatherStr); if ( weatherStr == “Clear” ) weatherKind = 0; if ( weatherStr == “Clouds” ) weatherKind = 2; if ( weatherStr == “Rain” ) weatherKind = 3; if ( weatherStr == “Drizzle” ) weatherKind = 3; if ( weatherStr == “Thunderstorm” ) weatherKind = 4 ;

} }


(本文經Ideas Hatch同意轉載;責任編輯:謝涵如)

「經濟部工業局廣告」

楊俊益

Author: 楊俊益

《MQTT 與 IoT 整合運用》臉書社團版主,是一位熱血Maker ,專長包含IoT及MQTT等,曾擔任國產IC開發板DSI 5168、2598種子講師,開發過智能居家防護警示系統設計等。

Share This Post On

發表

跳至工具列