作者:Bird
在上一篇文章【Maker 電子學】GPS 接收器的原理與應用—PART6 中,我們將 u-blox NEO-7M GPS 接收器模組透過 USB 接到 PC 上,用 terminal 程式直接觀察在真實環境中收到的訊號、解出來的資料,我們也說明了 $GPGSA、$GPGGA、$GPGLL、$GPRMC 等幾個最重要的定位訊息。
這一回我們要試著將 GPS 接收器透過 UART 連接到像 Arduino 這樣的小系統上,來使用 GPS 的訊號。
連接 ESP8266 #
我們選用 ESP8266 來當作這次實驗的主控晶片。除了 Arduino 原生的 ATmega328p 之外,ESP8266 可能是 Arduino 平台上數一數二流行的晶片,它有 Wi-Fi 的功能,又有足夠大的 flash 和 RAM,處理器的速度也夠快,可以做很多 ATmega328p 做不到或做起來很吃力的事。
不過 ESP8266 最大的缺點就是 I/O 接腳太少,雖然晶片本身提供了 17 支的 GPIO,但 GPIO 0、GPIO 2、GPIO 15 用來控制啟動狀態,使用上有一些限制,而 GPIO 6-11 則用來連接存放程式碼的 SPI flash memory,其中 GPIO 7-10 是 data bits;如果你讓 SPI flash 跑在 4-bit 模式下,這四支腳都要用到;如果你把 SPI flash 設成 1-bit 模式,就可以空出 GPIO 8-10 三支腳出來當 GPIO 使用。
ESP8266 晶片本人有兩組硬體的 UART,但嚴格來說只有一組半。UART 0 有完整的 RxD/TxD 接腳,甚至還有硬體 flow control 用的 RTS/CTS 接腳,但 UART 1 只有一支 TxD 接腳,也就是說 UART 1 只能發不能收。
UART0 的 RxD/TxD 分別在 GPIO 3 和 GPIO 1 上,而 UART 1 的 TxD 則是 GPIO 2。在很多 ESP8266 的開發板上,會把 GPIO13 和 GPIO15 標示成 UART2,這裡其實有點小小的誤會。GPIO13 和 GPIO15 其實是 UART0 的另一組可以設定的接腳,也就是說當你用了 GPIO13/15,GPIO1/GPIO3 就不能當 UART 用了。

(圖片來源:Bird 提供)
當我們在 Arduino 裡面使用 Serial.begin() 來起始 serial 物件時,使用的就是預設在 GPIO1/3 上面的 serial port。如果要用第二組接腳 GPIO13/15,只要呼叫 Serial.swap(),就可以把 UART0 的接腳換到 GPIO13/15 上,但這時 GPIO1/3 上就沒有 UART 訊號了,而一般的 ESP8266 開發板像 NodeMCU 或是 WeMos D1,都是將 UART-USB 橋接晶片接在 GPIO1/3 上,以便透過 USB 可以燒錄 firmware,因此如果將 UART0 的接腳移走,我們雖然可以在別的接腳上使用 UART,但就失去了透過 USB 來觀察 UART0 輸出的方便。
我們想要將 GPS 模組的輸出連接到 ESP8266 上,又希望可以繼續用 Serial.print() 等方便的工具來觀察 ESP8266 的工作狀態,但 UART1 又只有 TxD,無法接收 GPS 的資料。
該怎麼辦呢?
軟體 Serial 界面 #
Arduino 裡有一個叫做 SoftwareSerial 的函式庫,可以在任意的 GPIO 接腳上用軟體方式模擬 serial 界面。原來的這個函式庫只能給 AVR 架構的 Arduino 硬體使用,在 ESP8266 上直接使用 SoftwareSerial 會遭遇一些奇奇怪怪的問題。幸好後來有一個叫做 EspSoftwareSerial 的函式庫,是專門移植給 ESP8266/ESP32 使用的 SoftwareSerial。
我們可以將 SoftwareSerial 出來的 UART 界面拿來連接 GPS 接收器,而將原來的硬體 UART 保留給 debug 界面。
SoftwareSerial 用起來很簡單:
SoftwareSerial SWSerial (rxPin, txPin); // 初始化 SoftwareSerial 物件
SoftwareSerial 物件繼承了 Serial 物件大部分的 property 跟 method,因此用起來的方法就跟 SErial 物件一樣。
我們先來寫個小程式:
這個程式很簡單,它不斷地從 SoftwareSerial 模擬的 UART 界面上將資料讀進來,然後送到硬體的 UART 0 上。我們在程式中宣告的軟體 UART 接腳是 RxD 用 GPIO 13,TxD 用 GPIO 15。
我們以 ESP8266 的開發板 NodeMCU 爲例,用四根線將 NodeMCU 跟 Neo-7m GPS 接收器模組連接起來:

(圖片來源:Bird 提供)

(圖片來源:Bird 提供)
如果連接正確,當我們把 NodeMCU 的 USB 插上電腦,NodeMCU 就會對 Neo-7m 模組供電,我們就會看到 Neo-7m 板子上的紅色 LED 亮起來。
接下來,在 Arduino 環境中將上面那組程式碼燒到 ESP8266 裡面之後,再用 Arduino 內建的 Serial Monitor 觀察 ESP8266 所連接的 serial port,應該就會看到來自 Neo-7m 模組的訊息,像這樣:

(圖片來源:Bird 提供)
嗯我們又看到了 99.99 的 DOP 了,因爲我做這個實驗的時候是在室內,GPS 接收器收不到任何訊號,所以就如我們上一回所聊的,$GPGSA 後面有一大排逗點,一顆有效的衛星編號都沒有。
從 UART 拿到 GPS 接收器送來的 NMEA 資料後,接下來就可以去 parse 它,拿出我們需要用的數據來使用了。就如我們上次聊過的,如果你只需要經緯度跟時間,就可以抓 $GPGLL;如果需要經緯度跟速度,就要抓 $GPRMC;如果想要知道現在使用的衛星編號、定位精度,就要抓 $GPGSA…。
NMEA-0183 的格式要用 Arduino 的字串處理功能來 parse 並不難,因爲它的格式很單純:又特定字串開頭標示不同的資訊,而每一行內的資訊都以逗點分隔各欄位,所以如果你不需要計算 checksum,其實可以很容易用 strtok() 之類的函數將字串拆解後就拿到所需的資訊。
當然,寫程式如果能站在巨人的肩膀上,開發的速度往往會更快。Arduino 上已經有很多前人寫好的 GPS NMEA parser 函式庫可以使用,常用的像是 MicroNMEA、TinyGPS 等,只要宣告好物件、將有 NMEA 資料流的 UART 餵給 parser,就可以輕鬆拿出所需的特定欄位。這個部分就留給讀者當作進一步研究的題目了。
最準的時鐘 #
在結束這個系列之前,我想再提一件事。除了定位之外,GPS 還有一個異常強大的功能:時鐘。
GPS 接收器上的時鐘在捕獲 GPS 衛星訊號之後,會與 GPS 衛星上的原子鐘同步,因此在持續有 GPS 訊號存在的狀況下,它所輸出的時間精度就跟衛星上的原子鐘一樣。GPS 上的原子鐘有極高的精確度,誤差大概在數千億甚至上兆分之一的等級,也就是三千多年才會有一秒的誤差。
你只要透過 GPS 接收器捕獲 GPS 衛星,你就有一個跟 GPS 衛星上的原子鐘一樣精確的時鐘。更棒的是,不管你在地球的哪個角落,只要收得到 GPS 衛星訊號,這個時鐘就是同步的。在手機通訊的技術進入 3G 時代之後,由於 CDMA 調變技術需要在不同的基地台之間有精確的時間同步,基地台之間需要一個一致的、同步的時脈訊號,因此在 3G 時代乃至於後來的 4G、5G 系統,基地台都使用 GPS 訊號來同步。
在 Neo-7m GPS 接收器模組的排針上有一個訊號叫做 PPS,它的意思是「pulse per second」,也就是一秒一次的方波。接收器捕獲 GPS 訊號後,PPS 上就會開始輸出這個一秒一次的方波,不過這個 PPS 訊號的精度跟定位精度一樣,都會受到電磁波傳遞延遲、訊號衰減等不確定因素的影響,它的精確度大概在十幾 ns 甚至數百 ns 左右,但它的不準確是以「jitter」的形式存在,長期來看它的準確度仍然會接近原子鐘本身的精確度。
小結 #
我們花了七次的篇幅,從 GPS 的技術、原理開始講起,順便聊了一些展頻通訊的技術,以及 GPS 的編碼、導航電文的格式等,一直到示範如何使用實際的 GPS 接收器。希望這一次關於 GPS 的系列文章對於讀者們往後使用 GPS 時,能有一些幫助。
(責任編輯:賴佩萱)