arduino 官網 arduino uno

Arduino 物聯網應用 - 上課教材

動畫互動網頁程式設計 > 課程內容 > 第 06 章 - 使用 LCD 顯示

第 06 章 - 使用 LCD 顯示

上次更新日期 2022/10/19

一般來說,物聯網的各項感測器,不可能直接連接到電腦上面,因此,我們上課透過的 USB 連接埠口, 理論上應該是不可能存在的!取而代之的,很可能就是一些轉接頭搭配電池之類的供電方式。那如果我需要知道 arduino 上面的數據該如何是好?此時就得要有面板的支援了!最簡單方式,當然就是透過一組 LCD 的小型界面來處理! 這樣就能即時獲得 arduino 上面的各項資訊。

學習目標:

  1. 了解 I2C 裝置,並由 arduino 掃描到 I2C 的裝置位址
  2. 了解 LCD 小螢幕的結構
  3. 讓 arduino 可以傳送訊號給 LCD 進行展示

6.1: 連接 I2C 界面的 LCD 小螢幕

液晶顯示器 (Liquid Crystal Display, LCD) 是很常用的顯示裝置,許多有顯示螢幕的設備,大部分都是配備 LCD 的! 用在小型的物聯網上面,常見的有 16字 x 2列、也有 20字及 40字的,本課程使用 16字 x 2列的 LCD 做介紹。而要將資料顯示到 LCD 上面, 就需要一個支援的傳輸模組 (LCD module, LCM),目前市面上釋出的 LCD,大部分都已經焊上 LCM 了!另外, 一般 LCM 上面會有 16 個針腳,這些針腳真的連接到 arduino 上面的話,整個接線會非常非常麻煩。因此,許多公司開發出另一個小界面, 透過 I2C 的功能,直接將許多的腳位整合,最終剩下四隻針腳就可以提供大部分的功能了!

本章使用的是 QAPASS 開發的 LCD 裝置與相關的模組,主要是透過 I2C 的環境,I2C 可以將資料整合後透過 I2C 的匯流排輸出, 因此,接收 I2C 的匯流排資料,就得要支援 I2C 的針腳~所以,不能像以前一樣,我們可以隨意接在 arduino 上面的任意腳位上, 而是需要了解 arduino 上面的 I2C 針腳後,才有辦法進行連線喔!

  • I2C 與 arduino 腳位

一般 I2C 的腳位共有四隻,分別是:

  • GND:接地
  • VCC:5V供電
  • SDA:串列資料線 (serial data)
  • SCL:串列時脈線 (serial clock)

arduino 上面有接地與 5V 的腳位,這個沒問題~那 SDA 與 SCL 要接在哪裡呢?在 arduino nano 上面,這兩個針腳, 需要接在 I2C 的位置上喔~主要接腳是這樣的 (不同的 arduino 板子其接腳都不相同!連接之前請特別搜尋一下喔!):

  • SDA: 接在 A4 上
  • SCL: 接在 A5 上
  • arduino 的 I2C 模組

為了方便使用者的操作, arduino 釋出名為 Wire 的模組,某部份就是用來支援 I2C 的!因此,你要使用到 A4, A5 的類比腳位, 就得要在 IDE 程式碼裡面,先使用『 #include <Wire.h> 』這個模組才可以!另外,如果你要輸出資料到 LCD 上面, 也得要匯入 <LiquidCrystal_I2C.h> 模組才行!這兩個模組,基本上在 IDE 的範例當中都有存在!只是 arduino IDE 環境提供的是原始的 LCD 腳位,跟 I2C 的腳位並不相同~因此,不能直接呼叫範例程式碼就直接使用喔! 得要做些修改才可以!

例題 6.1.1:點亮 LCD 的連線方式測試:
  1. 首先,將 LCD 的四個腳位 (GND, VCC, SDA, SCL) 直接插在小麵包板上面。
  2. 拿出四條杜邦線,依據上述腳位的麵包板位置,直接連接到 arduino 上面。要注意 SDA 連到 A4 而 SCL 連到 A5 喔!
連線完備,將 arduino 安插到 USB 之後,就可以看到 LCD 被點亮才對!目前應該沒有任何資料存在。

我們得要確認這個 LCD 的 I2C 位址才行!否則,arduino 會無法傳輸資料到 LCD 上面去。你可以依據底下的方式來取得你 LCD 的 I2C 位址。

例題 6.1.2: 掃描 I2C 裝置的存取位址,請先啟動 IDE 環境
  1. 從 IDE 的『檔案』、『範例』、『Wire』當中,取得『i2c_scanner』的範例資料。
  2. 直接編譯並上傳到 arduino 上
  3. 打開序列埠,你會發現類似如下的資料,那就是你的 I2C 位址。
    13:57:54.514 -> 
    13:57:54.514 -> I2C Scanner
    13:57:54.514 -> Scanning...
    13:57:54.514 -> I2C device found at address 0x27  !
    13:57:54.559 -> done
    

裝好之後並且開始傳輸資料之前,先得要安裝 LCD 的 I2C 模組,預設 IDE 是沒安裝該模組的!因此,請依據底下的說明,安裝起來吧:

例題 6.1.3:安裝 LiquidCrystal_I2C 模組,請先啟動 IDE 環境
  1. 前往『草稿碼』、『匯入程式庫』、『管理程式庫』之後就會出現程式庫管理員的畫面
  2. 在搜尋欄位裡面輸入『 LiquidCrystal_I2C 』關鍵字,往下找會有一個名為 LiquidCrystal I2C 的標題, 由 Macro Schwartz 所撰寫,安裝時版本為 1.1.2 版,按下安裝即可。
  3. 安裝完畢請關閉程式庫管理員的畫面。
  4. 由『檔案』、『範例』找到最下方的『LiquidCrystall I2C』之後,開啟 Hello World 範例檔。
  5. 將檔名更改為 code-hello-01,因為裡面有些資料需要變更才行。

這樣你的 LCD 應該就準備要開始展示資訊了!接下來,讓我們來繼續操作 code-hello-01 的內容吧! 在 LCD 上面展示一下 Hello World 看看!請將程式碼修改一下,大致上會像這樣,基本上,最重要的是 0x27 那個位址的項目, 以及我們使用的是 16行 x 2列 的項目喔!要注意!要注意!

// 這個是 code-hello-01 的內容,有修改部份資料喔!
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,16,2); 

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.print("Hello, world!");
}

void loop() {
}

你要特別注意的是,仔細觀察 LCD 的背面,其實 I2C 模組上有一個旋鈕,這個旋鈕其實是 LCD 的光強度! 如果你沒有看到 LCD 上面有字體產生的話,那應該很可能就是背板的光亮度不足。此時,請使用小螺絲起子 (一字或十字都可以), 順時針或逆時針轉動一下,直到順利出現字體為止~那應該就可以順利的展示出你的 LCD 資訊了。

6.2: LCD 小螢幕的相關函數

剛剛你看到 code-hello-01 程式裡面有一個很重要的變數,那就是 lcd.init(), lcd.print("字串") 等,這個 lcd 物件裡面的函數, 就需要來了解一下才行。而我們使用的是 16行 x 2列 的螢幕。一般來說,我們講『直行橫列』,所以,算 X 軸就是幾行,直行英文是 column (簡寫為 col)。至於 Y 軸就是算列,橫列英文是 row, 所以,用底下的表格來說,最左上角顯示的是 (0,0),最右下角顯示的是 (15,1) 這樣。亦即座標是 (cols, rows)。 要注意喔,起始點座標是 (0,0) 喔!編號從 0 號開始的!

0 1 2 3 4 5 6 7 8 9 0 11 12 13 14 15
第 0 列 ->
第 1 列 ->
  • 常用於 LCD 的相關函數

LCD 的函數其實不少,底下幾個是常應用的項目,提供給大家參考看看:

  • 使用 lcd.begin(cols, rows) 初始化螢幕大小
    因為 LCD 螢幕大小有許多不同的形式,例如有 4 橫列的以及 20 個直行的,因此,許多時候,你可能需要指定螢幕的大小。 不過我們這邊使用的模組當中,其實已經指定了螢幕大小,因此這個 lcd.begin 就不用存在!在其他的模組當中, 可能就得要使用這個函數來指定 LCD 的顯示大小才行。
  • 使用 .clear() 清除螢幕
    將原本的資料清除而已,游標預設會回到座標 (0, 0) 的位置上。
  • 使用 .home() 回到座標 (0, 0)
    只是讓游標移動回原本的 (0, 0) 而已。
  • 使用 .setCursor(col, row) 定位座標
    讓游標跑到正確的位置上!注意喔,最小是左上角的 (0, 0),最大是右下角的 (15, 1) 喔!再次強調!
  • 使用 .print("字串") 印出內容
    就是將資料印出來而已
  • 使用 .write(byte) 印出 byte 代表的文字
    LCD 有些特殊的字型碼,這些字型碼主要以 byte 類型存在,而想要將這種字型從 LCD 上面列出來,就得要使用 .write 函數了。舉例來說,如果要列出 ° 這種特殊的上標,可能要參照文末的 LCM 字型碼對照表,會找到 B11011111 這個編碼, 所以使用 lcd.write(B11011111) 這樣的格式,才能夠印出來!
  • 使用 .cursor() 與 .noCursor() 指定是否需要有游標
    預設不會有游標在螢幕上,你可以設定顯示游標,那你就能看到目前展示的位置了!
  • 使用 .blink(), noBlink() 指定是否需要讓游標閃爍
    跟上面的游標有關,除了出現游標之外,還可以讓游標閃爍喔!
  • 使用 .scrollDisplayLeft(), .scrollDisplayRight() 左右移動
    LCD 螢幕的動畫滑動處理這也太簡單了!直接使用上面這兩個函數,就可以讓文字以跑馬燈的形式持續出現在 LCD 上面!相當有趣喔!
  • 使用 .leftToRight(), .rightToLeft() 控制文字出現的方向
    文字預設都是從左到右 (.leftToRight()),如果要改過來,就可以使用 .rightToLeft() 函數的控制了!
  • 列出會左右跑動的 LCD 文字

現在,讓我們透過上面介紹的函數來處理一下 LCD 的展示效果。我們剛剛已經做過處理 hello world 的項目, 讓我們繼續來處理一下,如果要讓 "HELLO!" 這 6 個字元在第一列上面左右碰撞移動時,該如何進行呢? 你得要思考一下:

  • 假設一開始 6 個字元是緊靠右側,所以,左邊會有 10 個空白。
  • 你得要讓這 6 個字元向左移動 10 格 (從 0 到 9,所以是 10 次)
  • 接下來,又開始往右移動 10 格。

處理流程應該也不是太困難,我們就直接來玩一玩!先來測試一下,如何讓文字放在我們想要它出現的地方~

例題 6.2.1: 設計特定的字串放在特定的 LCD 位置上,檔名請取名為 code-lcd-01 即可
  1. 先匯入需要的函式庫,最上方的三行是一定要存在的:
    #include <Wire.h>
    #include <LiquidCrystal_I2C.h>
    LiquidCrystal_I2C lcd(0x27,16,2);
    
  2. 在 setup() 函數裡面進行:
    • 使用 lcd.init() 初始化
    • 使用 lcd.backlight() 讓 LCD 的背板亮起來,這才有資訊
    • 不要有游標,並且將畫面清空
    • 設定游標到 (10,0) 的座標上,亦即第一個字元會在第 11 行第 1 列的意思
    • 印出 HELLO! 的字樣即可
  3. 先編譯測試,然後燒錄到 arduino 上看看情況如何。

看起來目前已經可以順利輸出 HELLO! 了,那麼開始進行移動的處理方式吧!記得是左移 10 次,再右移 10 次,這樣反覆移動即可!

例題 6.2.2: 將剛剛 code-lcd-01 複製成為 code-lcd-02,然後在 loop() 裡面,進行如下的動作即可:
  1. 使用一個 for 迴圈,設定變數 i 初始值 0 限制值為 < 10,步階是 1。裡面只需要進行 LCD 畫面左移的函數,並且延遲 0.5 秒即可。
  2. 上面的 for 迴圈複製另一個,只是裡面的函數變成向右移即可。
測試編譯後,沒問題燒錄上傳,就會發現 HELLO! 在左右移動了。
  • 利用 LCD 做 10 秒倒數計時器

如果你想要拿 arduino 做一個固定時間的倒數計時器,倒是可以的!例如模擬密室逃脫的時限功能等等。 實做的方法很簡單啊!就每隔 1 秒 (delay) 展示一次數字到 LCD 上面即可!讓我們來測試看看:

例題 6.2.3: 將剛剛 code-lcd-02 複製成為 code-lcd-03,之後如此進行:
  1. 設定 count = 10 為全域變數的整數型態,這個就是即將要倒數的秒數了!
  2. 在 setup() 函數中,在座標為 (0,0) 的位置,列印出『 down count... 』的字串
  3. 在 loop() 裡面,進行如下的動作:
    • 設定座標在 (0,1) 的位置上
    • 獨立的 if,當 count 小於 100 時,輸出空白到 LCD 上
    • 獨立的 if,當 count 小於 10 時,輸出空白到 LCD 上
    • 直接印出 count 這個資料到 LCD 上
    • 判斷,如果 count 為 0 時,就 (1)在座標 (0,0) 印出『 Program STOP.... 』的字樣, (2)然後一直執行 while ,內容為 delay 5 秒,等於是結束程式的意思。
    • 讓 count 減 1
    • 延遲 1 秒
測試編譯後,沒問題燒錄上傳,等待 10 秒鐘~這就是倒數計時器囉!

不過,只有秒數,沒有小數點,好像不是太有趣!因為 arduino 對浮點數的資料輸出,通常就是到小數點第二位, 所以,我們可以每次延遲 0.01 秒,這樣就好像可以實作出有小數點下秒數的倒數!看起來是挺有趣!雖然不見得實用...

例題 6.2.4: 將剛剛 code-lcd-03 複製成為 code-lcd-04,之後如此進行:
  1. 修改 count = 10.0 為全域變數的『浮點數』型態!
  2. 在 loop() 裡面的修改為:
    • 將所有的整數 (如100, 10等),全部改成浮點數 (如 100.0)
    • 讓 count 減 0.01
    • 延遲 0.01 秒
測試編譯後,沒問題燒錄上傳,等待 10 秒鐘~這就是倒數計時器囉!

如果需要重複倒數,那就按下 arduino 上面的 reset 按鈕即可!

  • 利用 arduino 內建運作時間鐘

你應該要知道,其實使用 delay 是很不準的...因為 delay 只是延遲時間,而其他程式碼也會消耗時間,所以,短時間是沒問題, 差異不大,如果是長時間,那誤差就會變得很大!arduino 其實有個內建的時間,該時間會記憶 arduino 整體運作的時間! 最長時間大概可以紀錄到 50 天左右。先讓我們寫個小程式,將目前 arduino 開機開始運作的時間印到 LCD 上!

例題 6.2.5: 將剛剛 code-lcd-04 複製成為 code-lcd-05,之後如此進行:
  1. 設定一個名為 mytime 的浮點數全域變數
  2. 在 setup 函數內,在座標 (0,0) 處,印出『 arduino run... 』的字樣
  3. 在 loop() 裡面進行一點點程式即可:
    • 設定 mytime 為 millis() / 1000.0 (因為是浮點數,所以最好加上小數點)
    • 在座標 (0,1) 處,印出 mytime 即可
    • 延遲 0.1 秒
測試編譯後,沒問題燒錄上傳,你會發現時間一直往上跑!而且這個時間會比較準確!因為是抓 arduino 此次開機後的實際執行總時間, 總是比 delay 要來的準確!

透過這個時間鐘,基本上,就不會有延遲造成的時間誤差效應!

6.3: LCD 特殊字型與自訂圖示

你會發現,LCD 預設是沒有中文字型的!要有中文實在是太難了!尤其我們這種小型 LCD,根本就不支援中文字型的! 沒關係,基本上,我們可以使用類似的『圖形』來進行設計!嗄!可以設計 LCD 的圖示!沒錯啊!請你仔細看一下你的 LCD, 其實 LCD 共有 16x2 個字,每個字的組成,大概就是 5x8 (X*Y) 的大小,有點類似底下的展示成果!

☐☐☐☐☐
☐☐☐☐☐
☐☐☐☐☐
☐☐☐☐☐
☐☐☐☐☐
☐☐☐☐☐
☐☐☐☐☐
☐☐☐☐☐
  • 自訂字型的方式: 使用 .createChar(#,var) 設計

針對這 16x2 個字,每個字我們都可以透過類似陣列的方式,將圖片印出來到該位置上!舉例來說,文末有年月日的範本連結, 你可以參考一下~該連結很有趣啊!以年來說,你可以這樣進行輸入:

☒☐☐☐☐ B10000
☒☒☒☒☒ B11111
☐☐☐☒☐ B00010
☐☒☒☒☒ B01111
☐☒☐☒☐ B01010
☒☒☒☒☒ B11111
☐☐☐☒☐ B00010
☐☐☐☐☐ B00000

也就是說,透過 8 列每列有 5 個欄位的輸出 (前面有 B 開頭) 的資料,就可以設計出自訂圖示了!這個就是所謂的自建字型功能。 我們先來嘗試輸出年月日看看,假設檔名設為 code-lcd-11

byte year[8] =  {B10000,B11111,B00010,B01111,B01010,B11111,B00010,B00000};
byte month[8] = {B01111,B01001,B01111,B01001,B01111,B01001,B11101,B00000};
byte day[8]   = {B01111,B01001,B01001,B01111,B01001,B01001,B01111,B00000};
int yy = 2022;
int mm = 10;
int dd = 19;

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.noCursor();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.createChar(0,year);
  lcd.createChar(1,month);
  lcd.createChar(2,day);
}

void loop() {
  lcd.setCursor(0,0);
  lcd.print(yy);
  lcd.write(byte(0));
  lcd.print(mm);
  lcd.write(byte(1));
  lcd.print(dd);
  lcd.write(byte(2));
}

將上述程式碼燒錄到 arduino 上面,你就可以發現,螢幕出現小小的圖示~真的有點像中文字...

  • 使用 LCD 預設特殊字型

LCD 作為小型螢幕使用,很常用在 IoT 的小裝置上,這些裝置很可能會拿來作為感測器數據的輸出!而這些感測器的數據中, 比較有趣的大概就是溫度的 °C 那個小圖示吧!在 LCD 上面,其實有這個特殊字體~我們也不需要使用 .createChar 來建立它! 直接呼叫即可!該字體的輸出應該也要用 .write 才行!

byte deg = B11011111;
lcd.write(deg);
例題 6.3.1: 將感測器資料傳輸到 LCD 上,請依據 5.2 章節的說明,將 DHT11 溫溼度計安插到 arduino 上!另外, 你的 LCD 同樣要接到 arduino 上喔!因為 LCD 已經使用掉 5V 的電源,你的 DHT11 可以使用 3.3V 的電源,同樣是可以偵測到數據的。 然後,拿出 code-dht-1 的程式碼,修改 DHTPIN 的腳位號碼,搭配 code-lcd-11 的程式碼內容,將模組資料整理好!然後這樣做:
  1. 在全域變數的地方,除了整合 DHT-11 以及 LCD 的模組之外,增加 deg 變數,使用 byte 類型,內容就是 ° 的樣式
  2. 在 setup 內,將 LCD 初始化的程式碼丟進去
  3. 在 loop 內,先找到可以輸出溫度的地方,在第一列將溫度同步輸出到 LCD 上!
  4. 在 loop 內,先找到可以輸出濕度的地方,在第二列將濕度同步輸出到 LCD 上!

這真是簡單、有趣、又好玩!

6.4: 參考資料