高通台灣AI黑客松|競賽說明會
|

【Tutorial】自動語音導覽系統實作介紹

   

作者:Ryan Hu

先來分享為什麼會做這個自動語音導覽的小專案好了。這個故事是這樣,之前在做 NTP 時鐘的時候有陣子真的滿挫敗的 (一直鬼打牆解決不了問題),當時想說要尋找同溫層或許可以能有幫助,也想知道自己現在到底 capable of what,我就決定出去參加一些 maker 的活動(eg. MakerPRO)。

在過程中,我意外看到 十年前他帶學生親手打造太陽能車,如今門生遍佈Tesla、Gogoro 這篇文章,看完覺得很興奮,覺得台灣已經沒有幾個教授願意帶著學生實作,我就毅然決然寄信給文中主角台大機械系鄭榮和教授,詢問教授我是否能加入他的團隊?一起把手弄髒,完成一些腦中的想法。

原本抱持著教授怎麼可能會理我這個菜鳥,可能信也就石沈大海了,沒想到隔天一早起床就看教授回信,教授不僅很歡迎我加入團隊,還特地約了個時間要我去找他聊聊,當然我興喜若狂答應赴約,也就這樣進了團隊。

而自動語音導覽系統就是在這裡做的第一個小專案。這個東西是為了台大最近那台電動自駕巴士,初步要規劃用在校園導覽時的語音導覽功能,而語音導覽就是到了某個位置開始播放介紹那個位置的語音檔案,所以我就想說我要用我可能會的東西解決這個問題。

解決方式 (組件介紹)

  1. Adafruit Ultimate GPS x1
  2. Arduino nano (一開始測試我使用 Arduino UNO)x1
  3. JQ8900 (mp3 解碼播放) x1
  4. speaker x1

Adafruit Ultimate GPS

一開始我拿到這顆 GPS module 的時候,用了 4 個 pin 腳位 (VIN, GND, TX, RX),(各個腳位的應用與解釋可以參考以上連結),連接 USB 轉 TTL 的元件,用 coolterm (下圖就是 coolterm 視窗) 去看 GPS 抓到的東西到底是什麼?

這個東西很有趣 (一開始不知道是什麼的時候只覺得很酷),詢問 Bird 之後才發現原來這個是一種叫做 NMEA 的通訊傳輸資料的 protocol,裡面的每個 data 都有它所代表的意義,我這裡不細項介紹 GGA 或 RMC 所代表的東西,而是直接分享我可以從 GGA 或 RMC 裡面獲得什麼資訊。(參考資料:The NMEA 0183 Protocol

其實我要的東西不外乎經緯度,畢竟我一開始的規劃是要做,假設到了 「某個位置」就要 trigger 「某件事情」(但發現沒有這麼容易),而這兩個 longitude and latitude 都可以從 GGA 或是 RMC 裡面找到 (上面 GPS 沒有定位當然還看不到,真正定位到的樣子我留到底下,用 Arduino UNO 當 MCU 的時候說明)。

而現在 coolterm 裡面從 RMC 可以得知訊息像是有「接近原子鐘精度級別的時間信息」,「移動速度」,「日月年 (dd/mm/yy)(從這裡可以看到其實我是剛剛才截圖的,因為當時做的時候忘記拍下來)」。

Arduino console 裡面的資訊 & 撈出我需要的資料

知道這些 data 所代表的意義後,我就將 USB 轉 TTL 收起來,改用 Arduino UNO 來作為主要發號司令者,之所以要 Arduino UNO 作為發號司令者是為了達成我的目的:定位到 「某個位置」 ,我要 trigger 「某個事件」。第一步得把 GPS module 身上的資料不僅僅是顯示在 console 而已,我要用 console 裡面的資料做到我要 trigger 某件事件的動作。

那這個撈資料的動作怎麼完成?Bird 以前常說要站在巨人的肩膀上,因此我 google 了一下,果不其然真的有有人寫好的 library 可以供 user 使用,library 下載連結:library

看完這個 library 大致上了解他的運作方式後,比如說跟 GPS 說我想要獲得什麼資料 (eg. RMC GGA),多久 update 一次 (頻率幾赫茲),以及發現原來也是用 interrupt 的方式去抓資料下來 (所以 for loop 裡面就可以做 trigger 某件事件的動作),在寫好我需要的 funciton 後,真正 GPS module 定位到是長這個樣子 (如圖):

測試版硬體接線實體圖,LED 是我用來trigger 後就會亮(圖片來源

上圖可以看到我要的東西有四個,分別是緯度、經度、連線到的衛星數,以及 HDOP(等等會說明),所以我當時所在的位置的定位即 latitude 為 2502.58,longitude 為 12133.83(這個單位跟 google map 所用的單位不同,稍後分享),連接到的衛星數為 3 顆衛星,HDOP (Horizontal Dilution of precision)為 3.76。

而可以在 console 裡面看到這些資訊最重要的一點是,我成功利用一些 functions,透過 Serial.print 的方式把 GGA 或是 RMC這些 data 中,需要的資訊給正確的抓了出來,離我想達成的目標更近一步。

DOP (Dilution of precision)

回到剛剛提到的 HDOP,這其實是一個精度準確度的判斷值,而 HDOP 只是其中一種 DOP 而已(即水平也就是所謂的二維),還有 GDOP (三維座標與時間的精度準確度)、PDOP(三維位置)、TDOP(時間)、HDOP(水平二維)、VDOP(高程及高度)等等,之間的關係以及意義不多做說明,有興趣的人可以參考:GPS 觀測中的的參數「DOP值」是什麽概念?

我在這裡要分享的東西是 HDOP(水平二維)所代表的誤差值,利用 GPS 定位最少要有三顆衛星連線,數字越低代表越準確,如果看到 1.00 表示非常非常準(也可以從連接到的衛星數看出端倪,連接到的衛星數和 HDOP 的值大致成反比,1.00 通常都 7 顆 8 顆了),越高代表所得到的經緯度資訊越不可靠,也就是說 DOP 這個東西是告訴我們獲得資料的可靠性。

但可靠性要怎麼應用又是另外一個故事,畢竟我要做的事情是「定位到某個位置 trigger 某件事件」。也就是說,我所獲得的定位資訊的可信度要夠高,我才能 trigger 某件事件(一開始我還蠢蠢的以為,HDOP 是可以直接拿來和經緯度 「加減」一個正負 range 的概念),所以我設定 HDOP 在小於 3 的情況下才能 trigger 某事件(當然還有別的條件,只是這是第一個)。至於為何只要 HDOP?因為我不需要用到海拔高度,所以用水平二維的資訊即可。

longitude and latitude 資訊 convert & 大地座標系

資料正確抓出來,以及了解 DOP 的問題後,接下來就是要清楚地知道抓出來的資料的意義是什麼?從上面的圖片可以看到,latitude 為 2502.58,longitude 為 12133.83。好奇如我,立馬把這個經緯度複製貼往 google map,看看定位在哪裡?(我當然預期在 AppWorks 附近)

但失敗了,根本沒有這個地方。後來觀察經緯度的數字才發現,原來我所獲得的經緯度的表示方式和 google map 所用的經緯度是不同的,我的這個是 Decimal Degrees(十進位表示法),而 google map 用的是 Degrees, Minutes & Seconds(度分秒表示法),而這之間的換算可以輕鬆地做到,有興趣的這裡有連結:GPS converter

做好換算後再貼到 Google Map 一次,發現成功了!的確有顯示位置,但顯示的位置卻在饒河夜市附近。後來詢問 Bird 才知道,原來是因為可能所使用的大地座標系不同而導致這樣的結果覺得博大精深,連做這種小 project 都可以牽涉到這麼廣的知識)。

不過沒關係,最後成品出來後,我可以直接去台大定位,取得這個 GPS module 自已本身的數字也不需要 converter 去做換算了。但其實 Google Map 對我的 project 還是有它的價值存在,雖然沒有辦法定在正確的位置上,我還是可以用 Google Map 來做與我獲得的經緯度和 HDOP 之間的關係。意思是說,在某個數值的 HDOP 的情況下(其實不是定值,但變化相對小),我把每次獲得的經緯度都丟到 Google Map 上看在哪裡(比如說抓 10 次, 10 個點的分佈狀況),就可以大概知道可信度與資料間的關係(比如說 HDOP 為 1.73 時,10 次位置大概是怎麼分佈)。

在 Adafruit Ultimate GPS module 的背後,其實有一個可以裝上電池的地方。(圖片來源

上圖可以看到已經有電池裝在上面了(因為還沒裝的時候,甚至還沒將電池外殼焊上去的時候又忘記拍照了……)。這顆電池叫CR1220(圖中右上方有這個電池的型號),很不常見,便利商店也買不到。最後 Bird 說公司對面打鑰匙的店鋪可能會有,果然在那裡找到了這個型號的電池 (一顆 100 超貴…)。

而這裡要分享的重點是這顆電池對於 GPS 的作用為何?原以為這顆電池是用來供電,不外接任何電源的情況下,使 GPS 仍然能夠正常運作 (即定位),後來聽到 Bird 說這顆電池可以撐個一兩年才發現自己大錯特錯。

原來這顆電池是用來記憶的,所謂記憶的意思是,假設我現在定位成功,我獲得的資訊(像是經緯度,HDOP 等等的資料),因為有了 CR1220 這顆電池的存在,所以能在斷電後(即拔掉 USB 電源)將斷電前的資訊紀錄在 GPS module 的記憶體中,等到下一次重新接電時,因為有上一次的記憶(在 GPS module 的記憶體中),若要定位的地點和上次雷同,就可以快速地連接上衛星而獲得資訊,這也就是所謂的 Warm start。

而與 Warm start 相對應的即是 Cold start。Cold start 與 Warm start 的差異,就在於 Cold  start 沒有辦法拿到前一次斷電前的資料,每一次重新插電就是全新的一次,也就是說只要一斷電,任何收到的資訊都會被洗掉歸零。所以說有了這顆電池,往後需要重新連接衛星時,可以較快成功定位。

JQ8900(mp3 player shield)

搞定所有 GPS 的部分,接著要處理的即是 mp3 player shield 的部分。JQ8900 這個東西其實不難,講白了他就是一個可以解碼 WAV、mp3 檔案,且含有一個 4.2 MB 的記憶體可供存放 mp3 檔案的元件。至於怎麼使用也不是件太困難的事情,如下圖可以看到各個 pin 腳:

這裡我會分享我有用到的腳位,對其他腳位有興趣的朋友可以參考:JQ8900-16P語音模塊。IO1 ~ IO7 是 7 隻對地觸發(腳位的 state 為 LOW 時才運作)的 pin 腳位,也就是說在 JQ8900 中可以放 7 首 mp3 檔案。GND 不用多做解釋,仍是地地相連到天邊的概念。DC-5V 其實就是常常看到的 VIN。SPK- & SPK+ 就分別接到 speaker 的兩端。

了解以上的事情後,我現在要做的,就是把這幾個已經分別解決處理好的小模組,把他們拼裝起來並且能夠互相溝通。

成為硬漢的第一步

現在手邊的東西已經有:Arduino UNO、Adafruit Ultimate GPS module 還有 JQ8900 這三樣東西,Bird 問我打算怎麼把他們搞在一起?總不可能成品仍然掛著長長的跳線吧!為了這個問題,我在腦中想出了各種奇形怪狀的方式,事實上解法其實很單純:設計電路

設計電路前,要解決的問題是我要怎麼把東西都搞在同一張板子上(洞洞板)。在經過 Bird 提點後,決定將 Arduino UNO 改成 Arduino nano,問題便迎刃而解(nano 版很小等等有圖片 demo)。如圖(尚未焊接,只是先想想要怎麼排比較合適):

其實上圖的狀況,已經是 Bird 要我先把連接各 module 形式,以及接線都先畫下來給他看後,我才拼裝的樣子(畢竟我這個菜鳥是第一次自己設計這種東西)。

設計完整體形式後,接下來就是焊接。而要成爲一名硬漢,最重要的武器就是「烙鐵」 、「各式鉗子」、「銲錫」等等工具,這也是我第一次自己動手焊接電路與元件(真的有難度,Bird 總是可以焊到跟工廠出品的一樣,我也不知道為什麼…)。

前後練習了好幾次,終於看起來有點樣子(其實還是很醜,手很抖),最後我把元件固定在洞洞板上的點,以及 module 之間應該要相連的 pin 腳用 OK 線焊接好,如圖:

(真的滿醜的,被 Bird 嫌棄哈哈,不過能用且功能正常,至於焊接技術只能再接再厲)再附一張焊接好後接電的樣子,如圖:

目前看起來沒有什麼問題,成爲硬漢的第一步成功,接下來要做的就是在 nano 裡面 program,讓我達成 「到某個位置 trigger 某個事件」這個目標。回顧一下,因為 GPS 是利用 interrupt 的形式,以每秒一次 (可以自己挑)的頻率向衛星收資料,並顯示在 console 裡面,所以說我的 for loop 就可以用來做我想做的事(即到某個位置 trigger 某個事件)

在這次的成品需求下,我只需要用到 JQ8900-16P 的 5 隻 IO 腳,分別接到 nano GPIO2 – GPIO6,由於 JQ8900 的 IO 腳是接地觸發,所以 nano GPIO2 – GPIO6 pin 腳位的 initial state(即 digitalWrite())當然就要設為 HIGH。

此外,另外一件重要的事情是,一旦到了某個特定位置(eg. latitude : 2502.56 longitude : 12133.89)觸發了某個事件(eg. GPIO2 state 為 LOW 又變回 HIGH→音樂播放)後,我要在一段時間間隔內不再觸發,不然我要一秒收衛星資料一次,如果還在我設的範圍內,豈不是又會再觸發一次(即重播)?所以我得設定時間間隔避免這樣的悲劇發生。

程式碼

#define pinNUM 5
int location[5] = {2, 3, 4, 5, 6};
unsigned long triggerTime;
unsigned long lastTriggerTime[pinNUM] = {};
void setup()
{
for (int i = 0; i < 5; i++)
{
pinMode(location[i], OUTPUT);
digitalWrite(location[i], HIGH);
}
GPS.begin(9600);
//.
//.
//.
//there are lots of code above and it’s about GPS, but not the point so skip it
}
//here also has interrupt function for GPS to grab the info
void loop()
{
triggerTime = millis();
if (GPS.newNMEAreceived())
{
GPS.parse(GPS.lastNMEA());
if (GPS.fix)
{
Serial.print(Latitude: );
Serial.println(GPS.latitude);
Serial.print(Longitude: );
Serial.println(GPS.longitude);
Serial.print(numebr of satellities: );
Serial.println(GPS.satellites);
Serial.print(HDOP: );
Serial.println(GPS.HDOP);
if (GPS.HDOP < 3)
{
if (triggerTime – lastTriggerTime[0] > 15000)// longitude and latitude of the first location will add up later
{
lastTriggerTime[0] = triggerTime;
digitalWrite(location[0], LOW);
delay(1000);
digitalWrite(location[0], HIGH);
Serial.println(song1 started!);
}
}
}
}
}
view rawGPS_nano_mp3.cpp hosted with ❤ by GitHub

最後分享這次成品的 demo 影片:

(本文轉載於PROJECTPLUS原文連結作者部落格連結;責任編輯:周政毅)

Ryan Hu

訂閱MakerPRO知識充電報

與40000位開發者一同掌握科技創新的技術資訊!

Author: Ryan Hu

對於 IOT 軟硬整合相關有極大興趣,鍾愛無人機,目前獨自完成的專案像是瓦力號、GPS 自動語音導覽系統、NTP 網路自動校時時鐘,喜歡流浪。

Share This Post On
468 ad

2 Comments

    • 好的,來Push作者開課,台北OK?

      Post a Reply

發佈回覆給「陳生」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *