2022年8月27日 星期六

Linux 指令:sed

動機:我想了解 sed 指令的原因

最早讓我想了解 sed 指令的原因,是在某次的操作過程中,需要查看目前的 $PATH,到底包含了哪些目錄,所以我下了一道指令:

echo $PATH

結果出現了這樣的答案:

面對這麼一長串的文字,我就想如果可以依照字符:來做分割並換行顯示,那我需要的資訊更容易閱讀。於是就 google 了一下:

然後就看到了這個答案:

於是就開始了我對 sed 指令的探索之旅。

sed 指令簡介

sed 全名為 Stream EDitor,是一個使用簡單緊湊的程式語言來解析和轉換文字 Unix 實用程式,是為命令列處理資料檔案而構建的早期 Unix 指令之一。

sed 和 awk 這兩大工具時常被拿來相提並論,因二者一樣強大和對正規表示法有良好的支援。也各自有自己專屬的腳本語言(script language),sed 主要功能為自動化的修改文字檔,而 awk 可想像為超輕量級的 C 直譯語言(Interpreted language)屬通用,偏向統計和需輸出重新排版的應用。

man page 是給已熟悉的人參考用的,用 man page 學 sed 和 awk 就好像用英文字典學英文會話。基本上若直接用 man page 來覺習 sed,這相當有難度,所以若能借用他人的使用心得與範例,相信一定是一個更好的方式。

sed 主要功能:檔案字串修改

grep 雖可利用功能強大的正規表示法搜尋檔案中的字串,但沒辦法對搜尋到的字串進行刪除,取代或插入等編輯動作。補足 grep 編輯功能的工具就是 sed,更甚者 sed 可程式化的特性常用來自動化的修改文字檔。

雖然 vi 也可用來搜尋/修改檔案內容,但要人工把檔案打開,改完再存檔人力要介入很深,如熟悉 sed 的操作這些都可自動化的完成。例如公司搬家,有舊公司地址的表格文件一堆,如善用 sed 就可有效率的自動把所有文件表格上的舊地址改新地址。

sed 用法有點抽象,故對基本用法和每一參數分別說明。

sed 基本用法

sed 和其他功能強大的指令一樣,通常功能愈強語法就愈複雜和抽象,sed 也是如此。如有常自動化的修改文字檔,是值得一學的好工具。

sed 基本的用法如下:

sed [-OPTION] [ADD1][,ADD2] [COMMAND] [/PATTERN][/REPLACEMENT]/[FLAG] [FILE]

但光看其晦澀不明的語法是不太可能會使用的,故先舉個例子,一例解千文。

範例一:

我要把檔案 "MyFile.txt" 的 1~8 行中的 "The" 或 "the" 改為大寫的 "THE" 可如下:

sed -e '1,8 s/[Tt]he/THE/g' MyFile.txt

其中:
  -e:為預設選項,一般的狀況可省略,如沒加任何選項預設是以是 sed -e 選項來執行。
  s/[Tt]he/THE/g:sed 最基本的命令,代表搜尋樣板並取代。
     s:代表搜尋樣板並取代。
     [Tt]he:代表要搜尋樣板文字。
     THE:代表將用來取代搜尋到的樣板文字。
     g:指令最後的 g 代表搜尋全部範圍。

因每一欄參數都可能複雜和抽象,有時 sed 會解讀錯誤,故一般除檔案和選項以外的參數都會用單引號 ' 把其括起來。

範例二:

如欲搜尋和取代的樣板為單字(word),可在樣板和取代字串前加一空隔(因單字間有空隔),例如:有一句子為 "This is a book",我只想把單字 "is" 變大寫,但單字 "This" 也有子串 "is",處理不好輸出會變 "ThIS IS a book"。

echo 'This is a book' | sed 's/is/IS/g'

將輸出文字:

ThIS IS a book

所以正確的指令應該下為:

echo 'This is a book' | sed 's/ is/ IS/g'

sed 進階用法

Delimiter 分隔符號:

sed 每個參數之間預設分隔符號(delimiter)是用 "/" 來區別如 sed 's/OLD/NEW/g' flie,但如要搜尋樣板有 "/" 會和分隔符號混在一起如再加上跳脫字元 "\" 會變得好像火星文看不懂。

例如:
要把 Linux 路徑 "/abc/wxy" 改為 WINDOWS 路徑表示法的 "\abc\wxy",sed 的寫法為

sed 's/\//\\/g'
這一串類火星文,真的不易一眼看出什麼是什麼,所以故 sed 允許我們可以用除了空白 ' '、換行 '\n' 以外的字元(英文字母或數字或符號皆可)來當分隔符號,只要前後一致即可。

例如:用 : 當作分隔字元:

echo "This ia an apple" | sed 's:ap:Ap:g'

例如:Linux 路徑改為 WINDOWS 路徑表示法,用 # 當作分隔字元:

echo "/home/tmps07" | sed 's#/#\\#g'

ADDRESS 位址範圍:

sed 位址的表示法可為行號或合法正規表示法的樣板,可為有起始和結束二個位址的範圍或只有單一位址(如第幾行或指定的樣板)。

有些 COMMAND 一定要配合單一位址,大部分的 COMMAND 位址的表示法為範圍,有些 COOMAND 如省略位址表示是全部(如上例搜尋並取代的 COMMAND "s")。

位址範圍的用法各如下:

位址表示法
為一個起始位址加上一個結束位址,兩者間以 "," 分隔。如只有一個位址則為固定單一位址。

$ sed '1,5 s/ [aA]/ one/g'  file # 將 1~5 行 "a" 或 "A" 改 "one"
$ sed '5 s/ [aA]/ one/g' file # 只將第 5 行 "a" 或 "A" 改 "one"
$ sed 's/ [aA]/ one/g' file # 省略位址, 整個檔案 "a" 或 "A" 改 "one"

檔案的開頭我們都知道是第一行,但檔案的結尾往往不知是第幾行,此時可用 "$" 來代表最後一行(和 vi 用法一樣,"$" 代表最後一行)。

$ sed '3,$ s/^can/CAN/g' file # 將 3 行到最後一行開頭為 "can" 改大寫

樣板表示法
有起始樣板和結束樣板,每一樣板以成對的 "/" 括起來,起始樣板和結束樣板間以 "," 分隔。

例:位址範圍從有 "The" 到 "When" 之間的行,將字串 "can" 改為 "CAN"

$ sed '/The/,/Whe/ s/ can/ CAN/g' < re.txt

例:將符合 "[Cc]an$" pattern 的那行 "a" 改 "A"

$ sed '/[Cc]an$/ s/a/A/g' < re.txt

混搭表示法
sed 的位址表示法和樣板表示法是可混合使用的。

例:第二行開始到符合 "The" 那行之間任何數字改為字串 "#"

$ sed '2,/The/ s/[0-9]/#/g' < re.txt

例:從有 "google" 那行到最後一行的 "a" 改大寫

$ sed '/google/,$ s/a/A/g' < re.txt

OPTION 選項:

了解 OPTION 選項時先要了解 sed 指令的一些術語,才不會不知所云。sed 的動作為一次只讀一行並去掉結尾的換行(EOL)到暫時的緩衝區(buffer)中,此暫時緩衝區稱為 "pattern space",接著處理完成後會把 pattern space 的內容送往螢幕後清空 pattern space 再去處理下一行,這樣不斷重複直到檔案結束。

如有內容符合搜尋的樣板之 pattern space 叫 "current pattern space",而 COMMAND "p" 或 "P" 可令 sed 再輸出 current pattern space 到螢幕,其基本的動作流程如下。

sed 主要的選項如下:

語法:
sed [-OPTION] [ADD1][,ADD2] [COMMAND] [/PATTERN][/REPLACEMENT]/[FLAG] [FILE]]
指令名稱/功能/命令使用者 選項 功能  
sed/(stream editor)檔案字串修改/Any -e 執行 sed 的 script 語法


-f 選用外部的 script 檔來執行  
-n 不輸出 pattern space 到螢幕  
-l # 和 COMMAND l 一起使用時
l 可以指定每一行的長度
#


-r 使用延伸正規表示  
--help 指令自帶說明  

sed 有些選項可獨力運作,但有些選項單獨使用是沒什意義的,如 -n 或 -l 要配合其他 sed COMMAND 才有意義。各選項說明如下:

  • -e:執行 sed 的 script 語法
    sed 本來就是執行自己專屬的 script 腳本語言,故如都沒選項此為預設選項可省略。但如要搜尋和取代的樣板是多重的,或許多 COMMAND 合併使用,就一定要用 "-e" 選項。

    例如:將 "t" 改為 "T" and "pen" 改為 "pencil"
    $ echo 'this is a pen' | sed -e 's/t/T/' -e 's/pen/&cil/'

    例如:許多 sed COMMAND 要使用,就一定要用 "-e" 選項
    $ echo 'this is a pen' | sed -e 's/t/T/' -e 's/pen/&cil/'
  • -f:選用外部的 script 檔來執行
    如 sed 要處理的任務是多重的,此時可把毎一任務一一寫在一起存成一外部 script 檔,再用選項 "-f" 來指定此外部 script 檔即可。這樣做的好處是規則改變時只要改此外部 script 檔即可,或寫許多 script 檔來處理不同的需求。
    例如:有一外部 script 檔 "sed_scr" 功能為把 a~d 改大寫
    $ cat sed_scr
    s/a/A/g
    s/b/B/g
    s/c/C/g
    s/d/D/g
    $ echo 'abcdefg' | sed -f sed_scr
    ABCDefg
  • -n:不輸出 pattern space 到螢幕
    因 sed 處理完成一行後會把 pattern space 的內容送往螢幕後清空,故如單獨使用選項 "-n" 連 pattern space 也不輸往螢幕故輸出什麼也沒有,故選項 "-n" 一般要配合其他的 sed FLAG 使用才有意義。
    一般是配合 sed FLAG "p" 只列出 current pattern space(符合樣板的 pattern),才不會不符合的 pattern 也往螢幕送,此時 sed 的行為可取代 grep。
    例如:選項 "-n" 配合 FLAG "p" 只列出符合樣板的行(類似指令 "grep")
    $ ls -d /etc/* | sed -n '/[A-Z][0-9]/ p'
  • -l:指定每一行的長度
    選項 "-l" (小寫的 L)這選項是配合 COMMAND "l" (小寫的 L)使用,因 sed COMMAND "l" 為把一些 ASCII 控制字元以 "\a"、"\b"、"\t",方式列出,但 ASCII 的控制字元大部分是用來文字定位或排縮,把定位功能取消列印出來可能會影響文字行的長度,故用選項 "-l" 來指定輸出到螢幕的長度。 例如:指定換行的長度=10
    $ echo -e 'This\tIs\tA\tDog' | sed -nl 10 'l'
  • -r: 使用延伸正規表示法解讀
    預設的情形下,sed 的樣板(pattern)只解讀正規表示法,加選項 "-r" 才會去以延伸正規表示法去解讀樣板。此時如樣板內有如 "?","+" 等符號會被認為是延伸正規表示法的 meta-charaters(表示字元)。不同的解讀結果可是差很多。
    例如:正常狀態下,將字串 "99?" 改為 "88"
    $ echo 'Why an apple $9.99?' | sed 's/99?/88/g'
    Why an apple $9.88
    例如:加選項 "-r" 用延伸正規表示法,將字串 "99?" 改為 "88"
    $ echo 'Why an apple $9.99?' | sed -r 's/99?/88/g'
    Why an apple $88.88?

FLAG 旗幟:

sed FLAG 主要為進一步的控制取代樣板(pattern)的行為,前面我們已用過 FLAG 中的 "g" (global replacement)和 "p" (print)。這兩個 FLAG 是最常使用的,其他的 FLAG 並不常用,了解一下即可,全部的 FLAG 用途如下:

Sed Flags
[g][ 數字] 全部取代或指定取代第幾個
I 忽略 pattern 大小寫
p 列印
w 寫入檔案
  • g:全部取代
    預設 sed 只取代搜尋/取代到第一個樣板此行就停止而去處理下一行,如有g FLAG 則全部搜尋和取代。
    例如:"is" → "IS",但預設只取代搜尋/取代到第一個
    $ echo 'this is an issue' | sed 's/is/IS/'
    thIS is an issue
    例如:加 flag "g" 全部取代
    $ echo 'this is an issue' | sed 's/is/IS/g'
    thIS IS an ISsue
    例如:第 3 個符合的樣本才取代
    $ echo 'aaaaaa aaaaaa' | sed 's/a/A/3'
    aaAaa aaaaa
    例如:第 3 個符合的樣本才取代,並重複到結束
    $ echo 'aaaaaa aaaaaa' | sed 's/a/A/3g'
    aaAAA AAAAA
  • I:忽略 pattern 大小寫
    此 FLAG 主要對樣板的大小寫一視同仁
    例如:
    $ echo 'this is an apple' | sed 's/APPLE/banana/I'
    this is an banana
  • p:列印 current pattern space
    如沒此 FLAG,sed 只輸出 pattern space。如加了 "p",FLAG 會再輸出 current pattern space。一般此 FLAG 是和選項 "-n" 合作只輸出符合的 pattern (current pattern space)。
    例如:
    
            
  • w:

    例如:
    
            

Regular Expression -正規表示式的簡易通則

正規表示式 Part 1

正規
表示式
說明及範例 比對不成立
之字串
/a/ 含字母 “a” 的字串。例如 “ab”,“bac”,“cba” “xyz”
/a./ 含字母 “a” 以及其後任一個字元的字串。例如 “ab”,“bac”。若要比對.,請使用 \. “a”,“ba”
/^xy/ 以 “xy” 開始的字串,例如 “xyz”,“xyab”。若要比對 ^,請使用 \^ “axy”,“bxy”
/xy$/ 以 “xy” 結尾的字串,例如 “axy”,“abxy”。若要比對 $,請使用 \$ “a”,“ba”
[13579] 包含 “1” 或 “3” 或 “5” 或 “7” 或 “9” 的字串,例如:”a3b”,“1xy” “y2k”
[0-9] 含數字之字串 不含數字之字串
[a-z0-9] 含數字或小寫字母之字串 不含數字及小寫字母之字串
[a-zA-Z0-9] 含數字或字母之字串 不含數字及字母之字串
b[aeiou]t “bat”,“bet”,“bit”,“bot”,“but” “bxt”,“bzt”
[^0-9] 不含數字之字串。若要比對 ^,請使用 \^ 含數字之字串
[^aeiouAEIOU] 不含母音之字串。若要比對 ^,請使用 \^ 含母音之字串
[^\^] 不含 “^” 之字串,例如 “xyz”,“abc” “xy^”,“a^bc”

正規表示式 Part 2

正規表示式的特定字元 說明 等效的正規表示式
\d 數字 [0-9]
\D 非數字 [^0-9]
\w 數字、字母、底線 [a-zA-Z0-9_]
\W 非 \w [^a-zA-Z0-9_]
\s 空白字元 [ \r\t\n\f]
\S 非空白字元 [^ \r\t\n\f]

正規表示式 Part 3

正規表示式 說明
/a?/ 零或一個 a。若要比對 ? 字元,請使用 \?
/a+/ 一或多個 a。若要比對 + 字元,請使用 \+
/a*/ 零或多個 a。若要比對 * 字元,請使用 \*
/a{4}/ 四個 a
/a{5,10}/ 五至十個 a
/a{5,}/ 至少五個 a
/a{,3}/ 至多三個 a
/a.{5}b/ a 和 b 中間夾五個(非換行)字元

正規表示式 Part 4

字元 說明 簡單範例
\ 避開特殊字元 /A\*/ 可用於比對 “A*”,其中 * 是一個特殊字元,為避開其特殊意義,所以必須加上 “\”
^ 比對輸入列的啟始位置 /^A/ 可比對 “Abcd” 中的 “A”,但不可比對 “aAb”
$ 比對輸入列的結束位置 /A$/ 可比對 “bcdA” 中的 “A”,但不可比對 “aAb”
* 比對前一個字元零次或更多次 /bo*/ 可比對 “Good boook” 中的 “booo”,亦可比對 “Good bk” 中的 “b”
+ 比對前一個字元一次或更多次,等效於 {1,} /a+/ 可比對 “caaandy” 中的 “aaa”,但不可比對 “cndy”
? 比對前一個字元零次或一次 /e?l/ 可比對 “angel” 中的 “el”,也可以比對 “angle” 中的 “l”
. 比對任何一個字元(但換行符號不算) /.n/ 可比對 “nay,an apple is on the tree” 中的 “an” 和 “on”,但不可比對 “nay”
(x) 比對 x 並將符合的部分存入一個變數 /(a*) and (b*)/ 可比對 “aaa and bb” 中的 “aaa” 和 “bb”,並將這兩個比對得到的字串設定至變數 RegExp.$1 和 RegExp.$2。
xy 比對 x 或 y /a*b*/g 可比對 “aaa and bb” 中的 “aaa” 和 “bb”
{n} 比對前一個字元 n 次,n 為一個正整數 /a{3}/ 可比對 “lllaaalaa” 其中的 “aaa”,但不可比對 “aa”
{n,} 比對前一個字元至少 n 次,n 為一個正整數 /a{3,}/ 可比對 “aa aaa aaaa” 其中的 “aaa” 及 “aaaa”,但不可比對 “aa”
{n,m} 比對前一個字元至少 n 次,至多 m 次,m、n 均為正整數 /a{3,4}/ 可比對 “aa aaa aaaa aaaaa” 其中的 “aaa” 及 “aaaa”,但不可比對 “aa” 及 “aaaaa”
[xyz] 比對中括弧內的任一個字元 /[ecm]/ 可比對 “welcome” 中的 “e” 或 “c” 或 “m”
[^xyz] 比對不在中括弧內出現的任一個字元 /[^ecm]/ 可比對 “welcome” 中的 “w”、”l”、”o”,可見出其與 [xyz] 功能相反。(同時請注意 /^/ 與 [^] 之間功能的不同。)
[\b] 比對退位字元(Backspace character) 可以比對一個 backspace ,也請注意 [\b] 與 \b 之間的差別
\b 比對英文字的邊界,例如空格 例如 /\bn\w/ 可以比對 “noonday” 中的 "no":/\wy\b/ 可比對 “possibly yesterday.” 中的 "ly"
\B 比對非「英文字的邊界」 例如,/\w\Bn/ 可以比對 “noonday” 中的 "on",另外 /y\B\w/ 可以比對 “possibly yesterday.” 中的 "ye"
\cX 比對控制字元(Control character),其中 X 是一個控制字元 /\cM/ 可以比對 一個字串中的 control-M
\d 比對任一個數字,等效於 [0-9] /[\d]/ 可比對 由 “0” 至 “9” 的任一數字 但其餘如字母等就不可比對
\D 比對任一個非數字,等效於 [^0-9] /[\D]/ 可比對 “w” “a”… 但不可比對如 “7” “1” 等數字
\f 比對 form-feed 若是在文字中有發生 “換頁” 的行為 則可以比對成功
\n 比對換行符號 若是在文字中有發生 “換行” 的行為 則可以比對成功
\r 比對 carriage return  
\s 比對任一個空白字元(White space character),等效於 [ \f\n\r\t\v] /\s\w*/ 可比對 “A b” 中的 “b”
\S 比對任一個非空白字元,等效於 [^ \f\n\r\t\v] /\S/\w* 可比對 “A b” 中的 “A”
\t 比對定位字元(Tab)  
\v 比對垂直定位字元(Vertical tab)  
\w 比對數字字母字元(Alphanumerical characters)或底線字母(”_”),等效於 [A-Za-z0-9_] /\w/ 可比對 “.A _!9” 中的 “A”、”_”、”9”
\W 比對非「數字字母字元或底線字母」,等效於 [^A-Za-z0-9_] /\W/ 可比對 “.A _!9” 中的 “.”、” “、”!”,可見其功能與 /\w/ 恰好相反
\ooctal 比對八進位,其中octal是八進位數目 /\oocetal123/ 可比對 與 八進位的ASCII中 “123” 所相對應的字元值
\xhex 比對十六進位,其中hex是十六進位數目 /\xhex38/ 可比對 與 16進位的ASCII中 “38” 所相對應的字元

2022年8月23日 星期二

Linux IO 輸入與輸出重新導向

I/O 的重新導向是 Linux 系統中很重要的一個特性,它可以讓我們任意串接各種程式的輸入與輸出、將資料導入檔案或從檔案中導出資料,結合多種 Linux 指令,組成任意的「指令管線」(command pipeline)。

輸入與輸出的重新導向

一般的 Linux 指令在執行時,會有三個輸入與輸出的資料流,分別為:

  1. 標準輸入(standard input,代碼為 0):程式執行所需要的輸入資料。
  2. 標準輸出(standard output,代碼為 1):程式正常執行所產生的輸出資料。
  3. 標準錯誤輸出(standard error output,代碼為 2):程式出錯時通知使用者用的訊息,或是呈現程式狀態用的訊息。

Linux 程式執行時的狀況就像這樣:

而重新導向的作用就是改變這些資料的流向,讓使用者可以非常彈性的組合各種程式,以下我們以範例來說明重新導向的用法。

標準輸出

最典型的程式會將程式執行的結果輸出在螢幕上(也就是說標準輸出預設就是螢幕),而我們可以使用 > 這個重新導向的運算子,將程式的標準輸出導向檔案,這樣輸出的訊息內容就會被儲存在檔案中,其用法為:

N > FILE

其中 N 是要設定重新導向的檔案代碼,若省略的話預設值為標準輸出的檔案代碼(即 1),而 FILE 就是要儲存輸出資料的檔案名稱。

以下是一個簡單的例子:

ls > output.txt

這樣就會把 ls 指令的輸出儲存至 output.txt 檔案中,而執行這行指令時,螢幕上就不會有其他的輸出了。

在執行上面這行指令時,如果 output.txt 這個檔案不存在的話,就會自動建立這個檔案,並把資料寫入其中。但若是這個檔案已經存在了,系統會把它的內容先清空,再將 ls 的輸出儲存進去,所以如果原本 output.txt 檔案中存在有舊的資料,就會全部被清掉。

如果想要以附加的方式把程式的輸出放在原本的檔案內容之後,可以使用 >>,其用法也是非常類似:

N >> FILE

以下為實際範例:

date >> output.txt
這樣一來,他就會在 output.txt 檔案中,於原本的 ls 輸出之後,再加上一行日期資訊。

標準錯誤輸出

若程式發生錯誤時,錯誤訊息預設也是會輸出在螢幕上(標準錯誤輸出預設為螢幕),例如:

ls non_exist > output.txt

這裡我們使用 ls 查看一個不存在的檔案,讓它產生錯誤,執行之後會在螢幕上看到一行錯誤訊息:

ls: non_exist: No such file or directory

這一行就是來自於 ls 標準錯誤輸出的訊息,而 output.txt 這個輸出檔案也會被建立,不過它的內容是空的(因為程式沒有產生任何正常的輸出)。

如果我們想要把程式的錯誤訊息導入檔案,可以使用 > 運算子,把標準錯誤輸出(2)導至指定的檔案:

ls non_exist > output.txt 2 > error.txt

這行指令就會將 ls 的標準錯誤輸出導入 error.txt 檔案,而正常的輸出則一樣導入 output.txt。

這是以附加方式寫入 error.txt 的例子:

ls non_exist > output.txt 2 >> error.txt

如果想要把正常的輸出以及錯誤的輸出都一起導入同一個 output.txt,可以加上 2 > &1

ls non_exist > output.txt 2 > &1

2 > &1 就是把標準錯誤輸出(2)導入標準輸出(1)的意思,然後再靠著 > 把所有的資料全部導入 output.txt,這樣所有的輸出訊息就會一起存入 outpupt.txt 中了。

這類重新導向的語法可以自己變化,例如我們也可以把資料都導向標準錯誤輸出,然後再導向檔案,結果是一樣的:

ls non_exist 2 > output.txt 1 > &2

另外還有一種寫法也可以將標準輸出與標準錯誤輸出都導向至同一個檔案:

ls non_exist &> output.txt

或是這樣也可以:

ls non_exist >& output.txt

標準輸入

一般需要輸入資料的 Linux 程式如果執行時沒有給他資料的話,預設就會從鍵盤讀取資料(也就是標準輸入預設是鍵盤),例如:

cat

直接執行 cat 時,他就會等待使用者從鍵盤輸入資料,並將接收到的資料輸出在螢幕上。

我們可以使用 < 運算子,將指定的檔案設定為程式的標準輸入,這樣他就會從檔案中讀取資料,用法如下:

cat < input.txt

這樣 cat 就可以取得 input.txt 檔案中的資料,並且顯示在螢幕上。

管線

前面介紹過的重新導向都是用於程式與檔案之間的資料流,而如果要把兩個程式的輸入與輸出串接起來,就可以使用管線(pipe)。

管線的寫法是 |,以下是一個例子:

ls | nl

nl 這個指令會把每一行資料加上行號,這一指令會將 ls 的輸出導給 nl,讓每個檔案名稱加上行號後,輸出在螢幕上。

管線與重新導向可以混用:

ls | nl > output 2 > &1

串接多個程式的狀況也是很常見的:

ls | grep keyword | nl | head -n 5

這個例子是在 ls 的輸出中,以 grep 篩選出有 keyword 的檔名,交給 nl 加上行號,最後交給 head 輸出前 5 行資料。

Linux 特殊檔案

在 Linux 系統上有一些特殊的檔案,善用這些特殊檔案可以讓我們在撰寫 shell 指令稿時更方便。

/dev/null

/dev/null 是 Linux 系統的空裝置,所有寫入這個檔案的資料都會被直接丟棄,而如果從這個檔案讀取資料,則會像讀取空檔案一樣,立即得到一個 EOF。

最常見的用法就是將沒有用的程式輸出導引至 /dev/null,這樣就不會讓沒用的輸出干擾螢幕畫面:

ls > /dev/null

上面這個例子是將 ls 的輸出導向至 /dev/null,這樣螢幕上就不會出現任何訊息。

以下是一個實務上比較常用的例子,假設我們想要檢查 Linux 系統上特定的程式有沒有在執行,我們可以在 ps 指令的輸出中,尋找特定的關鍵字,執行完之後,以 $? 查看最後一個 grep 執行的結果:

KEYWORD=chromium
ps aux | grep $KEYWORD | grep -v grep > /dev/null
echo $?

這我們要找的是 Chromium 瀏覽器的行程,看看系統上是不是有 Chromium 瀏覽器在執行,若有找到的話,$? 的值就會是 0,若沒有找到的話 $? 就會是 1。

在這個例子中,我們只需要 grep 最後的傳回值,而不需要看它輸出,所以我們將其標準輸出直接導向至 /dev/null 丟棄。

另外一個典型的例子就是把程式的任何輸出都丟棄,也就是把標準輸出與標準錯誤輸出都導向至 /dev/null,這個用法在執行自己撰寫的指令稿時很有用:

/path/to/script.sh > /dev/null 2 > &1

如果我們將自己的指令稿放在 crontab 中執行時,通常都會以這個方式丟棄所有的輸出,因為如果輸出太多訊息,會造成系統管理者有收不完的回報信件。

/dev/zero

/dev/zero 是一個特殊檔案,當我們從這個檔案讀取資料時,會讀出無限個 NULL 字元(0x00)資料,這個檔案可以用於清除硬碟資料:

# 清除硬碟(不可執行)
dd if=/dev/zero of=/dev/sda1

或是建立內容都是 0x00 的檔案:

dd if=/dev/zero of=file.dat count=1024 bs=1024

請參閱:dd - 備份與回覆資料的小工具

/dev/random 與 /dev/urandom

/dev/random 與 /dev/urandom 是 UNIX/Linux 系統的隨機資料來源,從這兩個檔案讀取出來的資料是隨機產生的,這兩個檔案功能相同,差別在於資料產生速度與品質。

/dev/random 的資料產生速度比較慢,但是品質比較好(比較接近隨機),如果從中讀取大量資料,超過系統所能產生的隨機量時,就會出現阻塞(block)的狀況。

/dev/urandom 在產生隨機資料時,若系統所能產生的隨機量不足,就會根據其他的方式生成隨機的資料,不會產生阻塞的狀況,缺點就是資料沒有那麼隨機。

以下是從 /dev/urandom 讀取資料,交給 cksum 產生隨機亂數的例子:

head -200 /dev/urandom | cksum

/dev/full

/dev/full 是一個常滿裝置,不管寫入什麼資料,都會因為空間不足而無法寫入,這個檔案通常被用來測試磁碟無剩餘空間時,程式所產生的行為。

ls > /dev/full

ls: write error: No space left on device

2022年2月13日 星期日

常用微控制板接腳圖 (Pin Layout)

Arduino UNO
Arduino Leonardo
Arduino NANO
Arduino Pro mini
Arduino Pro micro
ESP8266 GPIO
NodeMCU 1.0 (ESP8266)
WeMos D1 mini (ESP8266)
NodeOLED (ESP8266)
ESP-01 (ESP8266)
ESP32 DevBoard (ESP32)
Node ESP-32S (ESP32)
Ai-Thinker ESP12K (ESP32-S2)
Mediatek Linkit 7697 Nano
Mediatek Linkit 7697 Nano Board
Mediatek Linkit 7697 Bit
Microbit

2022年2月11日 星期五

自訂 Linux 的 Bash Shell 命令提示字串 Prompt

如何在 Linux 或 Unix 操作系統下更改 shell 提示的顏色?如何在 Linux、macOS 或類 Unix 系統上自定義和著色我的 Bash 提示符 PS1

我們可以更改 shell 提示符的顏色,或者在命令提示符下讓工作變得更輕鬆。BASH shell 是 Linux 和 Apple OS X 下的預設的設置。現在讓我們看看在 Linux 或 Unix 系統上使用 bash 時如何更改 shell 提示的顏色。

基本設定方法

Linux 的" Bash Shell 命令提示字串可以透過 PS1 這個環境變數來設定,通常他都是寫在 ~/.bashrc 或是 ~/.bash_profile 這些 Bash 的設定檔中,通常預設的設定會類似這樣:

PS1="\u@\h \W$ "

PS1 的設定中,若以反斜線加上一個特定字母,都有一些特殊意義:

  • \u:表使用者的帳號名稱。
  • \h:主機名稱。
  • \W:目前的工作目錄名稱。

顯示出來的結果會像這樣:

通常剛裝好的 Linux 系統,命令提示字串大概就是像這樣,沒有什麼特別,但是其實它的功能很強大,可以有很多的變化,以下是一些常用的功能介紹。

顯示時間

PS1 環境變數中,您可以使用 $(linux_command) 的方式,直接執行任何的 Linux 指令,下面是一個執行 $(date) 來顯示目前時間的例子。

PS1="\u@\h [$(date +%k:%M:%S)]> "

結果會像這樣:

另一種方式是直接使用 \t,則可以顯示 hh:mm:ss 格式的時間,例如:

PS1="[\t] \u@$(pwd)> "

而 \@ 則是可以顯示 12 小時制的時間:

PS1="[\@] \u@$(pwd)> "

任意指令輸出

如果您對於 shell 的程式設計很熟悉的話,可以在 PS1 插入任何的指令輸出或是變數:

kernel_version=$(uname -r)
PS1="\!|\h|$kernel_version|$? > "

文字顏色

命令提示字串也可以使用彩色的文字:

PS1="\e[0;34m\u@\h \w\e[0m > "

這裡文字的色彩是靠 ANSI escape code 來指定的,\e[0;34m 是顏色指定的開始控制碼,結束是 \e[0m,而在放這中間的所有文字都會是有顏色的,而文字的顏色則是由開始控制碼中的數值來決定:

  • 0 與 1:0 代表正常亮度,1 代表高亮度。
  • 30 與 37:30 + x 所得到的數值可指定前景顏色(x 值與顏色的對應請參考下面的對應表)。
  • 40 與 47:40 + x 所得到的數值可指定背景顏色(x 值與顏色的對應請參考下面的對應表)。
  • 多個數值之間以分號(;)隔開,像這裡的 \e[0;34m 就是指定正常亮度(0),顏色為藍色(34 = 30 + 4),結果會像這樣:

    這是使用高亮度(1)的狀況:

    PS1="\e[1;34m\u@\h \w\e[0m > "

    加上背景的顏色:

    PS1="\e[0;34;47m\u@\h \w\e[0m > "

    使用多種顏色:

    PS1='\e[1;37m\t\e[0m\n\e[1;32m\u\e[1;33m@\e[1;32m\h\e[33m:\e[1;36m$(pwd)\e[1;33m\$\e[0m '

    PS1 特殊字元

    以下這些是在 PS1 中可以使用的特殊字元,您可以用運這些設計自己的命令提示字串。

    • \a:ASCII bell 字元(07)。
    • \d:格式為 Weekday Month Date 的日期(例如 Tue May 26)。
    • \D{format}:將 format 傳給 strftime(3),然後將輸出的結果放進命令提示字串中,如果 format 是空字串,就會使用目前語系的預設的格式,其中的大括號不可以省略。
    • \e:ASCII 跳脫字元(escape character,033)。
    • \h:機器的簡短主機名稱(hostname),只顯示到第一個句點之前。
    • \H:機器的完整主機名稱(hostname)。
    • \j:目前的 shell 所掌控的 jobs 數量。
    • \l:the basename of the shell’s terminal device name。
    • \n:換行。
    • \r:carriage return。
    • \s:the name of the shell, the basename of $0 (the portion following the final slash)。
    • \t:現在時間,24 小時制(HH:MM:SS)。
    • \T:現在時間,12 小時制(HH:MM:SS)。
    • \@:現在時間,12 小時制(HH:MM AM/PM)。
    • \A:現在時間,24 小時制(HH:MM)。
    • \u:目前使用者的使用者名稱(username)。
    • \v:目前的 bash shell 版本(如 2.00)。
    • \V:目前的 bash shell 詳細版本(如 2.00.0)。
    • \w:目前的工作目錄完整路徑,若在 $HOME 中,則以 ~ 顯示。
    • \W:目前的工作目錄名稱,若在 $HOME 中,則以 ~ 顯示。
    • \!:目前指令的歷史紀錄編號。
    • \#:目前指令的編號。
    • \$:如果是 root 管理者,則顯示 #,否則顯示 $。
    • \n:以八進位表示字元,例如 33。the character corresponding to the octal number nnn。
    • \\:反斜線。
    • [ 與 ]:當 PS1 參雜一些無法顯示的字元時,就要用這兩個特殊字元包起來,這樣顯示才會正常,例如所有控制顏色或是格式的控制碼,都要加上這兩個特殊字元,這樣可以避免 bash 在計算提示字元長度時出錯。

    只要善用這些特殊字元,其實就可以讓自己的命令提示字串有許多的變化。

    2022年2月4日 星期五

    讓 U8G2 OLED 函式庫在 Arduino 中活用

    快速連結

    什麼是 u8g2?

    大家也知道,OLED 規格相當多,常見的就有 SSD1306 和 SH1106,LCD 更不用說了,如果為了這些不同的顯示器去找對映的函式庫,有時還真的蠻累人的,而且還有 ESP8266 或 ESP32 支援的問題。

    u8g2是嵌入式設備的單色圖形庫,一句話簡單明瞭。

    還好有 u8g2 函式庫,它算是 u8glib 的新版本,它對顯示器的支援超強大的!舉凡常見的 OLED,像是 SSD1306 和 SH1106,或是其它各種 TFT LCD,都在它的支援範圍,真的是學一招就無敵了!

    u8g2 支援單色 OLED 和 LCD,包括以下控制器:
    SSD1305,SSD1306,SSD1309,SSD1322,SSD1325,SSD1327,SSD1329,SSD1606,SSD1607,SH1106,SH1107,SH1108,SH1122,T6963,RA8835,LC7981,PCD8544,PCF8812,HX1230 ,UC1601,UC1604,UC1608,UC1610,UC1611,UC1701,ST7565,ST7567,ST7588,ST75256,NT7534,IST3020,ST7920,LD7032,KS0108,SED1520,SBN1661,IL3820,MAX7219。
    有關完整列表,請參見連結

    安裝、測試 u8g2 函式庫

    先安裝 u8g2 函式庫,在程式庫管理員就能找到。

    下載完畢,測試一下庫是否安裝成功:

    #include <U8g2lib.h>
        void setup() {
        // put your setup code here, to run once:
        }
        
        void loop() {
        // put your main code here, to run repeatedly:
        }

    若編譯成功,證明已經加載了 u8g2 函式庫。這裡是 u8g2 官方提供的各種顯示器的測試圖與宣告語法,我挑選幾種手邊有的顯示器,看看 u8g2 的顯示效果與宣告語法:

    ESP32 and SSD1306 OLED

    SW I2C and HW I2C with pin-remapping will work:

    U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R2, /* clock=*/ 16, /* data=*/ 17, /* reset=*/ U8X8_PIN_NONE);   // ESP32 Thing, pure SW emulated I2C
    U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 16, /* data=*/ 17);   // ESP32 Thing, HW I2C with pin remapping
    MAX7219 32x8 LED Matrix

    The MAX7219 is a LED driver for one 8x8 LED matrix. Multiple 8x8 blocks can be connected together. Here added support for a 32x8 LED matrix.

    U8x8 API is not supported, but the U8x8 fonts are also available for U8g2. The MAX7219 does not require a dc input signal, it has to be set to U8X8_PIN_NONE. The LOAD input line of the MAX7219 has to be connected as cs.

    U8G2_MAX7219_32X8_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 11, /* data=*/ 12, /* cs=*/ 10, /* dc=*/ U8X8_PIN_NONE, /* reset=*/ U8X8_PIN_NONE);
    WEMOS D1 (ESP8266) OLED Shield

    The 64x48 OLED Shield for the WEMOS ESP8266 boards. It features a SSD1306 driver and works with no special modification using the I2C bus (hardware or software):

    U8G2_SSD1306_64X48_ER_F_HW_I2C u8g2(U8G2_R0); // hardware
    U8G2_SSD1306_64X48_ER_F_SW_I2C u8g2(U8G2_R0, D1, D2); // software

    u8g2 庫函數

    u8g2 庫函數可以分爲四大類:

    • 基本函數
    • 繪製相關函數
    • 顯示配置相關函數
    • 緩存相關函數

    基本函數

    u8g2.begin() —— 建構 u8g2

    /**
    * 初始化 u8g2 庫
    * @Note 關聯方法 initDisplay clearDisplay setPowerSave
    */
    bool U8G2::begin(void)
    
    bool begin(void) {
        /* note: call to u8x8_utf8_init is not required here, this is done in the setup procedures before */
        initDisplay();   //初始化顯示器
        clearDisplay();  // 重置清屏
        setPowerSave(0); //喚醒屏幕
        return 1;
    }

    u8g2.beginSimple() —— 建構 u8g2

    /**
    * 初始化 u8g2 庫
    * @Note 關聯方法 initDisplay clearDisplay setPowerSave
    */
    bool U8G2::beginSimple(void)
    
    bool beginSimple(void) {
        /* does not clear the display and does not wake up the display */
        /* user is responsible for calling clearDisplay() and setPowerSave(0) */
        initDisplay();   //初始化顯示器
    }

    註:
    可以看到和 begin() 函數的區別,需要用戶自行控制初始化過程,給了一定的自由度,不過建議還是直接用 begin 函數吧。

    u8g2.clearDisplay() —— 清除屏幕內容

    /**
    * 清除屏幕內容
    */
    void U8G2::clearDisplay(void)

    註:
    ① 這個方法不需要我們單獨調用,會在 begin 函數主動調用一次,我們主要理解即可。
    ② 不要在 firstPage 和 nextPage 函數之間調用該方法。

    u8g2.setPowerSave() —— 是否開啓省電模式

    /**
    * 清除顯示緩衝區
    * @param is_enable
    *        1 表示啓用顯示器的省電模式,屏幕上看不到任何東西
    *        0 表示禁用省電模式
    */
    void U8G2::setPowerSave(uint8_t is_enable)

    註:
    ① 不管是啓用還是禁用,顯示器需要的內存消耗是不會變的,說到底就是爲了關閉屏幕,做到省電。
    ② 所以這裏就可以理解爲什麼初始化需要 setPowerSave(0); 。

    u8g2.clear() —— 清除操作

    /**
    * 清除屏幕顯示,清除緩衝區,光標回到左上角原點位置(0,0)
    * @Note 關聯方法 home clearDisplay clearBuffer
    */
    void U8G2::clear(void)
    
    void clear(void) { 
        home();         //回到原點
        clearDisplay(); //清除屏幕上的顯示
        clearBuffer();  //清除緩衝區
    }

    u8g2.clearBuffer() —— 清除緩衝區

    /**
    * 清除內存中數據緩衝區
    */
    void U8G2::clearBuffer(void)

    註:
    一般這個函數是與 sendBuffer 函數配對使用,通常用法如下:

    void loop(void) {
        u8g2.clearBuffer();
        // ... write something to the buffer 
        u8g2.sendBuffer();
        delay(1000);
    }

    u8g2.disableUTF8Print() —— 禁用 UTF8 字型顯示

    /**
    * 禁用 Arduino 平臺下輸出 UTF8 字符集,默認是開啓
    */
    void U8G2::disableUTF8Print(void)

    u8g2.enableUTF8Print() —— 啟用 UTF8 字型顯示

    /**
    * 開啟 Arduino 平臺下輸出 UTF8 字符集
    */
    void U8G2::enableUTF8Print(void)

    註:
    我們的中文字符就是 UTF8。常見範例如下:

    void setup(void) {
        u8g2.begin();
        u8g2.enableUTF8Print();
        // enable UTF8 support for the Arduino print() function
    }
    
    void loop(void) {
        u8g2.setFont(u8g2_font_unifont_t_chinese2); 
        u8g2.firstPage();
        do {
            u8g2.setCursor(0, 40);
            u8g2.print("你好世界");
        } while ( u8g2.nextPage() );
        delay(1000);
    }

    u8g2.home() —— 重置顯示游標的位置

    /**
    * 重置顯示游標的位置,回到原點(0,0)
    * @Note 關聯方法 print clear
    */
    void U8G2::home(void)
    繪製相關函數

    u8g2.drawBox() —— 畫實心方形

    /**
    * 畫實心方形,左上角座標爲 (x,y), 寬度爲 w,高度爲 h
    * @param x 左上角的 x 座標
    * @param y 左上角的 y 座標
    * @param w 方形的寬度
    * @param h 方形的高度
    * @Note 關聯方法 drawFrame setDrawColor
    */
    void U8G2::drawBox(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h)

    註:
    如果支援繪製顏色(也就是不是單色顯示器),那麼由 setDrawColor 設置,請看下例:

    u8g2.drawBox(3,7,25,15);

    u8g2.drawCircle() —— 畫空心圓

    /**
    * 畫空心圓,圓心座標爲 (x0,y0), 半徑爲 rad
    * @param x0 圓點的 x 座標
    * @param y0 圓點的 y 座標
    * @param rad 圓形的半徑
    * @param opt 圓形選項
    *        U8G_DRAW_ALL 整個圓
    *        U8G2_DRAW_UPPER_RIGHT 右上部分的圓弧
    *        U8G2_DRAW_UPPER_LEFT  左上部分的圓弧
    *        U8G2_DRAW_LOWER_LEFT  左下部分的圓弧
    *        U8G2_DRAW_LOWER_RIGHT 右下部分的圓弧
    *        選項可以通過 | 操作符來組合
    * @Note 關聯方法 drawDisc setDrawColor
    */
    void U8G2::drawCircle(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t opt = U8G2_DRAW_ALL)

    註:
    ① 如果支持繪製顏色(也就是不是單色顯示器),那麼由 setDrawColor 設置。
    ② 直徑等於 2 × rad + 1,範例如下:

    u8g2.drawCircle(20, 25, 10, U8G2_DRAW_ALL);

    u8g2.drawDisc() —— 畫實心圓

    /**
    * 畫空心圓,圓心座標爲 (x0,y0), 半徑爲 rad
    * @param x0 圓點的 x 座標
    * @param y0 圓點的 y 座標
    * @param rad 圓形的半徑
    * @param opt 圓形選項
    *        U8G_DRAW_ALL 整個圓
    *        U8G2_DRAW_UPPER_RIGHT 右上部分的圓弧
    *        U8G2_DRAW_UPPER_LEFT  左上部分的圓弧
    *        U8G2_DRAW_LOWER_LEFT  左下部分的圓弧
    *        U8G2_DRAW_LOWER_RIGHT 右下部分的圓弧
    *        選項可以通過 | 操作符來組合
    * @Note 關聯方法 drawDisc setDrawColor
    */
    void U8G2::drawDisc(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t opt = U8G2_DRAW_ALL)

    u8g2.drawEllipse() —— 畫空心橢圓

    /**
    * 畫空心圓,圓心座標爲 (x0,y0), 半徑爲 rad
    * @param x0 圓點的 x 座標
    * @param y0 圓點的 y 座標
    * @param rx 橢圓形水平 x 方向的半徑
    * @param ry 橢圓形豎直 y 方向的半徑
    * @param opt 圓形選項
    *        U8G_DRAW_ALL 整個圓
    *        U8G2_DRAW_UPPER_RIGHT 右上部分的圓弧
    *        U8G2_DRAW_UPPER_LEFT  左上部分的圓弧
    *        U8G2_DRAW_LOWER_LEFT  左下部分的圓弧
    *        U8G2_DRAW_LOWER_RIGHT 右下部分的圓弧
    *        選項可以通過 | 操作符來組合
    * @Note 關聯方法 drawDisc setDrawColor
    */
    void U8G2::drawEllipse()(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rx, u8g2_uint_t ry, uint8_t opt = U8G2_DRAW_ALL)

    註:
    ① rx × ry 在 8 位模式的 u8g2 必須小於 512(目前暫沒有理解為什麼)。
    ② 範例如下:

    u8g2.drawEllipse(20, 25, 15, 10, U8G2_DRAW_ALL);

    u8g2.drawFrame() —— 畫空心方形

    /**
    * 畫實心方形,左上角座標爲 (x,y), 寬度爲 w,高度爲 h
    * @param x 左上角的 x 座標
    * @param y 左上角的 y 座標
    * @param w 方形的寬度
    * @param h 方形的高度
    * @Note 關聯方法 setDrawColor
    */
    void U8G2::drawFrame(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h)

    註:
    如果支援繪製顏色(也就是不是單色顯示器),那麼由 setDrawColor 設置,請看下例:

    u8g2.drawFrame(3,7,25,15);

    u8g2.drawGlyph() —— 繪製字體字集的符號

    /**
    * 繪製字體字集裏面定義的符號
    * @param x 左上角的 x 座標
    * @param y 左上角的 y 座標
    * @param encoding 字符的 unicode 值
    * @Note 關聯方法 setFont
    */
    void U8G2::drawGlyph(u8g2_uint_t x, u8g2_uint_t y, uint16_t encoding)

    註:
    ① u8g2 支持 16 位以內的 unicode 字符集,也就是說 encoding 的範圍爲 0-65535,drawGlyph 方法只能
      繪製存在於所使用的字體字集中的 unicode 值。
    ② 這個繪製方法依賴於當前的字體模式和繪製顏色。

    u8g2.drawHLine() —— 繪製水平線

    /**
    * 繪製水平線
    * @param x 左上角的 x 座標
    * @param y 左上角的 y 座標
    * @param w 水平線的長度
    * @Note 關聯方法 setDrawColor
    */
    void U8G2::drawHLine(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w)

    註:
    如果支持繪製顏色(也就是不是單色顯示器),那麼由 setDrawColor 設置。

    u8g2.drawLine() —— 兩點之間繪製線

    /**
    * 繪製線,從座標 (x0,y0) 到 (x1,y1)
    * @param x0 端點 0 的 x 座標
    * @param y0 端點 0 的 y 座標
    * @param x1 端點 1 的 x 座標
    * @param y1 端點 1 的 y 座標
    * @Note 關聯方法 setDrawColor
    */
    void U8G2::drawLine(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t x1, u8g2_uint_t y1)

    註:
    如果支持繪製顏色(也就是不是單色顯示器),那麼由 setDrawColor 設置。請見範例:

    u8g2.drawLine(20, 5, 5, 32);

    u8g2.drawPixel() —— 繪製像素點

    /**
    * 繪製像素點,座標 (x,y)
    * @param x 像素點的 x 座標
    * @param y 像素點的 y 座標
    * @Note 關聯方法 setDrawColor
    */
    void U8G2::drawPixel(u8g2_uint_t x, u8g2_uint_t y)

    註:
    ① 如果支持繪製顏色(也就是不是單色顯示器),那麼由 setDrawColor 設置。
    ② 你會發現很多繪製方法的底層都是調用drawPixel,畢竟像素屬於最小顆粒度。
    ③ 我們可以利用這個繪製方法自定義自己的圖形顯示。

    u8g2.drawRBox() —— 繪製圓角實心方形

    /**
    * 繪製圓角實心方形,左上角座標爲 (x,y), 寬度爲 w,高度爲 h,圓角半徑爲 r
    * @param x 左上角的 x 座標
    * @param y 左上角的 y 座標
    * @param w 方形的寬度
    * @param h 方形的高度
    * @param r 圓角半徑
    */
    void U8G2::drawRBox(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, u8g2_uint_t r)

    註:
    ① 如果支持繪製顏色(也就是不是單色顯示器),那麼由 setDrawColor 設置。
    ② 要求,w >= 2 × (r+1) 並且 h >= 2 × (r+1),這是顯而易見的限制。

    u8g2.drawRFrame() —— 繪製圓角空心方形

    /**
    * 繪製圓角實心方形,左上角座標爲 (x,y), 寬度爲 w,高度爲 h,圓角半徑爲 r
    * @param x 左上角的 x 座標
    * @param y 左上角的 y 座標
    * @param w 方形的寬度
    * @param h 方形的高度
    * @param r 圓角半徑
    */
    void U8G2::drawRFrame(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, u8g2_uint_t r)

    註:
    ① 如果支持繪製顏色(也就是不是單色顯示器),那麼由 setDrawColor 設置。
    ② 要求,w >= 2 × (r+1) 並且 h >= 2 × (r+1),這是顯而易見的限制。請見以下範例:

    u8g2.drawRFrame(20,15,30,22,7);

    u8g2.drawStr() —— 繪製字符串

    /**
    * 繪製字符串
    * @param x 左上角的 x 座標
    * @param y 左上角的 y 座標
    * @param s 繪製字符串內容
    * @return 字符串的長度
    */
    u8g2_uint_t U8g2::drawStr(u8g2_uint_t x, u8g2_uint_t y, const char *s) 

    註:
    ① 需要先設置字體,調用 setFont 方法。
    ② 這個方法不能繪製 encoding 超過 256 的,超過 256 需要用 drawUTF8 或者 drawGlyph;說白了就是一般
      用來顯示英文字符。
    ③ x,y 屬於字符串左下角的座標。請見範例:

    u8g2.setFont(u8g2_font_ncenB14_tr);
    u8g2.drawStr(0,15,"Hello World!");

    u8g2.drawTriangle() —— 繪製實心三角形

    /**
    * 繪製實心三角形,定點座標分別爲 (x0,y0), (x1,y1), (x2,y2)
    */
    void U8G2::drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2) 

    請見範例:

    u8g2.drawTriangle(20,5, 27,50, 5,32);

    u8g2.drawUTF8() —— 繪製UTF8編碼的字符

    /**
    * 繪製UTF8編碼的字符串
    * @param x 字符串在屏幕上的左下角 x 座標
    * @param y 字符串在屏幕上的左下角 y 座標
    * @param s 需要繪製的 UTF-8 編碼字符串
    * @return 返回字符串的長度
    */
    u8g2_uint_t U8g2::drawUTF8(u8g2_uint_t x, u8g2_uint_t y, const char *s)

    註:
    ① 使用該方法,有兩個前提。首先是你的編譯器需要支持 UTF-8 編碼,對於絕大部分 Arduino 板子已經支持。
    ② 其次,顯示的字符串需要存爲 “UTF-8” 編碼,Arduino IDE 上默認支援。
    ③ 該方法需要依賴於 fontMode(setFont)以及 drawing Color,也就是說如果你傳進來的字符串編碼必須
      在 font 定義裏面。請見範例:

    u8g2.setFont(u8g2_font_unifont_t_symbols);
    u8g2.drawUTF8(5, 20, "Snowman: ☃");

    u8g2.drawVLine() —— 繪製豎直線

    /**
    * 繪製豎直線
    * @param x 左上角座標 x
    * @param y 左上角座標 y
    * @param h 高度
    */
    void U8G2::drawVLine(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t h)

    u8g2.drawXBM()/drawXBMP() —— 繪製圖像

    /**
    * 繪製圖像
    * @param x 左上角座標 x
    * @param y 左上角座標 y
    * @param w 圖形寬度
    * @param h 圖形高度
    * @param bitmap 圖形內容
    * @Note 關聯方法 setBitmapMode
    */
    void U8G2::drawXBM(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, const uint8_t *bitmap)
    void U8G2::drawXBMP(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, const uint8_t *bitmap)

    註:
    drawXBM 和 drawXBMP 區別在於 XBMP 支持 PROGMEM。

    u8g2.firstPage() / nextPage() —— 繪製命令

    /**
    * 繪製圖像
    */
    void U8G2::firstPage(void)
    uint8_t U8G2::nextPage(void)

    註:
    ① firstPage 方法會把當前頁碼位置變成 0。
    ② 修改內容處於 firstPage 和 nextPage 之間,每次都是重新渲染所有內容。
    ③ 該方法消耗的 ram 空間,比 sendBuffer 消耗的 ram 空間要少。請見範例:

    u8g2.firstPage();
    do {
        /* all graphics commands have to appear within the loop body. */
        u8g2.setFont(u8g2_font_ncenB14_tr);
        u8g2.drawStr(0,20,"Hello World!");
    } while ( u8g2.nextPage() );

    程式碼解析:

    void u8g2_FirstPage(u8g2_t *u8g2)
    {
        if ( u8g2->is_auto_page_clear )
        {
            // 清除緩衝區
            u8g2_ClearBuffer(u8g2);
        }
        // 設置當前緩衝區的 Tile Row 一個 Tile 等於 8 個像素點的高度
        u8g2_SetBufferCurrTileRow(u8g2, 0);
    }
                    
    uint8_t u8g2_NextPage(u8g2_t *u8g2)
    {
        uint8_t row;
        u8g2_send_buffer(u8g2);
        row = u8g2->tile_curr_row;
        row += u8g2->tile_buf_height;
        if ( row >= u8g2_GetU8x8(u8g2)->display_info->tile_height )
        {
            // 如果 row 已經到達最後一行,觸發 refreshDisplay 調用,表示整個頁面已經刷完了
            u8x8_RefreshDisplay( u8g2_GetU8x8(u8g2) );
            return 0;
        }
        if ( u8g2->is_auto_page_clear )
        {
            // 清除緩衝區
            u8g2_ClearBuffer(u8g2);
        }
        // 不斷更新 TileRow 這是非常關鍵的一步
        u8g2_SetBufferCurrTileRow(u8g2, row);
        return 1;
    }

    u8g2.print() —— 繪製內容

    /**
    * 繪製內容
    * @Note 關聯方法  setFont setCursor enableUTF8Print
    */
    void U8G2::print(...)

    請見範例:

    u8g2.setFont(u8g2_font_ncenB14_tr);
    u8g2.setCursor(0, 15);
    u8g2.print("Hello World!");

    u8g2.sendBuffer() —— 繪製緩衝區的內容

    /**
    * 繪製緩衝區的內容
    * @Note 關聯方法  clearBuffer
    */
    void U8G2::sendBuffer(void)

    註:
    ① sendBuffer 的 RAM 佔用空間大,需要結合構造器的 buffer 選項(請繼續往下看,先有個概念)使用。
    ② 不管是 fistPage、nextPage 還是 sendBuffer,都涉及到一個叫做 current page position 的概念。
    ③ 程式碼解析:

    void u8g2_SendBuffer(u8g2_t *u8g2)
    {
        u8g2_send_buffer(u8g2);
        u8x8_RefreshDisplay( u8g2_GetU8x8(u8g2) );  
    }
                    
    static void u8g2_send_tile_row(u8g2_t *u8g2, uint8_t src_tile_row, uint8_t dest_tile_row)
    {
        uint8_t *ptr;
        uint16_t offset;
        uint8_t w;
                      
        w = u8g2_GetU8x8(u8g2)->display_info->tile_width;
        offset = src_tile_row;
        ptr = u8g2->tile_buf_ptr;
        offset *= w;
        offset *= 8;
        ptr += offset;
        u8x8_DrawTile(u8g2_GetU8x8(u8g2), 0, dest_tile_row, w, ptr);
    }
                    
    /* 
        write the buffer to the display RAM. 
        For most displays, this will make the content visible to the user.
        Some displays (like the SSD1606) require a u8x8_RefreshDisplay()
    */
    static void u8g2_send_buffer(u8g2_t *u8g2) U8X8_NOINLINE;
    static void u8g2_send_buffer(u8g2_t *u8g2)
    {
        uint8_t src_row;
        uint8_t src_max;
        uint8_t dest_row;
        uint8_t dest_max;
                    
        src_row = 0;
        src_max = u8g2->tile_buf_height;
        dest_row = u8g2->tile_curr_row;
        dest_max = u8g2_GetU8x8(u8g2)->display_info->tile_height;
                      
        do
        {
            u8g2_send_tile_row(u8g2, src_row, dest_row);
            src_row++;
            dest_row++;
        } while ( src_row < src_max && dest_row < dest_max );
    }

    請見範例:

    void loop(void) {
        u8g2.clearBuffer();
        // ... write something to the buffer 
        u8g2.sendBuffer();
        delay(1000);
    }
    顯示配置相關函數

    u8g2.getAscent() —— 獲取基準線以上的高度

    /**
    * 獲取基準線以上的高度
    * @return 返回高度值
    * @Note 關聯方法  setFont getDescent setFontRefHeightAll
    */
    int8_t U8G2::getAscent(void)

    註:
    ① 跟字體有關(setFont)。
    ② 請看範例,下面例子,ascent 是 18:

    u8g2.getDescent() —— 獲取基準線以下的高度

    /**
    * 獲取基準線以上的高度
    * @return 返回高度值
    * @Note 關聯方法  setFont getDescent setFontRefHeightAll
    */
    int8_t U8G2::getDescent(void)

    註:
    ① 跟字體有關(setFont)。
    ② 請看範例,下面例子,descent 是 -5:

    u8g2.getDisplayHeight() —— 獲取顯示器的高度

    /**
    * 獲取顯示器的高度
    * @return 返回高度值
    */
    u8g2_uint_t getDisplayHeight(void)

    u8g2.getDisplayWidth() —— 獲取顯示器的寬度

    /**
    * 獲取顯示器的寬度
    * @return 返回寬度值
    */
    u8g2_uint_t getDisplayWidth(void)

    u8g2.getMaxCharHeight() —— 獲取當前字體裏的最大字符的高度

    /**
    * 獲取當前字體裏的最大字符的高度
    * @return 返回高度值
    * @Note 關聯方法 setFont
    */
    u8g2_uint_t getMaxCharHeight(void)

    註:
    每一個字符在 font 字集中都是一個位圖,位圖有高度和寬度。

    u8g2.getMaxCharWidth() —— 獲取當前字體裏的最大字符的寬度

    /**
    * 獲取當前字體裏的最大字符的寬度
    * @return 返回寬度值
    * @Note 關聯方法 setFont
    */
    u8g2_uint_t getMaxCharWidth(void)

    註:
    每一個字符在 font 字集中都是一個位圖,位圖有高度和寬度。

    u8g2.getStrWidth() —— 獲取字符串的像素寬度

    /**
    * 獲取字符串的像素寬度
    * @param s 繪製字符串
    * @return 返回字符串的像素寬度值
    * @Note 關聯方法 setFont drawStr
    */
    u8g2_uint_t U8G2::getStrWidth(const char *s)

    註:
    像素寬度和當前 font 字體有關。

    u8g2.getUTF8Width() —— 獲取 UTF-8 字符串的像素寬度

    /**
    * 獲取 UTF-8 字符串的像素寬度
    * @param s 繪製字符串
    * @return 返回字符串的像素寬度值
    * @Note 關聯方法 setFont drawStr
    */
    u8g2_uint_t U8G2::getUTF8Width(const char *s)

    註:
    像素寬度和當前 font 字體有關。

    u8g2.setAutoPageClear() —— 設置自動清除緩衝區

    /**
    * 是否自動清除緩衝區
    * @param mode 0 表示關閉
    *             1 表示開啓,默認是開啓
    */
    void U8G2::setAutoPageClear(uint8_t mode)

    註:
    ① 用於 firstPage 和 nextPage(看上面的程式碼解析)。
    ② 建議方法保持默認就好,如果用戶禁止了,那麼需要自己維護緩衝區的狀態或者手動調用 clearBuffer。

    u8g2.setBitmapMode() —— 設置位圖模式

    /**
    * 設置位圖模式(定義 drawXBM 方法是否繪製背景顏色)
    * @param is_transparent
    *         0 繪製背景顏色,不透明,默認是該值
    *         1 不繪製背景顏色,透明
    * @Note 關聯方法 drawXBM
    */
    void U8G2::setBitmapMode(uint8_t is_transparent)

    請看範例一:

    u8g2.setDrawColor(1);
    u8g2.setBitmapMode(0);
    u8g2.drawXBM(4,3, u8g2_logo_97x51_width, u8g2_logo_97x51_height,  u8g2_logo_97x51_bits);
    u8g2.drawXBM(12,11, u8g2_logo_97x51_width, u8g2_logo_97x51_height,  u8g2_logo_97x51_bits);

    請看範例二:

    u8g2.setDrawColor(1);
    u8g2.setBitmapMode(1);
    u8g2.drawXBM(4,3, u8g2_logo_97x51_width, u8g2_logo_97x51_height,  u8g2_logo_97x51_bits);
    u8g2.drawXBM(12,11, u8g2_logo_97x51_width, u8g2_logo_97x51_height,  u8g2_logo_97x51_bits);

    u8g2.setBusClock() —— 設置 I2C、SPI 頻率 (clock speed in HZ)

    /**
    * 設置總線時鐘 (I2C、SPI)
    * @param mode clock_speed 總線時鐘頻率(Hz)
    * @Note 關聯方法 begin
    */
    void U8G2::setBusClock(uint32_t clock_speed);

    註:
    ① 僅僅 Arduino 平臺支援。
    ② 必須在 u8g2.begin() 或者 u8g2.initDisplay() 之前呼叫。

    u8g2.setClipWindow() —— 設置顯示窗口大小

    /**
    * 設置顯示窗口,窗口範圍從左上角 (x0,y0) 到右下角 (x1,y1)
    * 也就是我們繪製的內容只能在規範範圍內顯示
    * @param x0 左上角 x 座標
    * @param y0 左上角 y 座標
    * @param x1 右上角 x 座標
    * @param y1 右上角 y 座標
    * @Note 關聯方法 begin
    */
    void U8G2::setClipWindow(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t x1, u8g2_uint_t y1 );

    註:
    可以通過 setMaxClipWindow 去掉該限制。

    void U8G2::setMaxClipWindow(void)

    請看範例:

    u8g2.setClipWindow(10, 10, 85, 30);
    u8g2.setDrawColor(1);
    u8g2.drawStr(3, 32, "U8g2");

    u8g2.setCursor() —— 設置繪製游標位置

    /**
    * 設置繪製游標位置 (x,y)
    * @Note 關聯方法 print
    */
    void U8G2::setCursor(u8g2_uint_t x, u8g2_uint_t y)

    請看範例:

    u8g2.setFont(u8g2_font_ncenB14_tr);
    u8g2.setCursor(0, 15);
    u8g2.print("Hello World!");

    u8g2.setDisplayRotation() —— 設置顯示器的旋轉角度

    /**
    * 設置顯示器的旋轉角度
    * @param u8g2_cb 旋轉選項
    *        U8G2_R0 不做旋轉 水平
    *        U8G2_R1 旋轉 90 度
    *        U8G2_R2 旋轉 180 度
    *        U8G2_R3 旋轉 270 度
    *        U8G2_MIRROR 不做旋轉 水平,顯示內容是鏡像的,暫時不理解
    */
    void setDisplayRotation(const u8g2_cb_t *u8g2_cb)

    u8g2.setDrawColor() —— 設置繪製顏色

    /**
    * 設置繪製顏色(暫時還沒有具體去了解用法)
    */
    void U8G2::setDrawColor(uint8_t color)

    u8g2.setFont() —— 設置使用字體

    /**
    * 設置字體集(字體集用於字符串繪製方法或者 glyph 繪製方法)
    * @param font 具體的字體集
    * @Note 關聯方法  drawUTF8 drawStr drawGlyph print
    */
    void U8G2::setFont(const uint8_t *font)

    Font 會根據像素點高度做了很多區分,具體 font 請參考:連結

    如果我們需要用到中文字符,可以在上面的連結裏面搜索一下 chinese,你就會發現很多中文 font,比如:(以下字型均支援 UTF-8 或者 GB2312 編碼)

    u8g2_font_wqy15_t_chinese1
    u8g2_font_wqy15_t_chinese2
    u8g2_font_wqy15_t_chinese3
    u8g2_font_wqy12_t_gb2312
    u8g2_font_wqy12_t_gb2312a
    ......

    註:
    中文字符集消耗記憶體大,請謹慎使用,可以用在 Arduino 101 等 RAM 空間比較大的板子上。

    我們看看 Font 的命名規則:

    <prefix> '_font_' <name> '_' <purpose> <char set>

    例如:

    u8g2.setFont( u8g2_font_5x7_tr );
    u8ge.setFont( u8g2_font_pressstart2p_8u );

    其中:

    • prefix 基本上都是 u8g2
    • name 一般會掛鉤上字符像素使用量,比如 5X7
    • purpose
      t:Transparent font, Do not use a background color.
      h:All glyphs have common height (所有的圖形有通用的高度).
      m:All glyphs have common height and width (monospace).
      8:All glyphs fit into a 8x8 pixel box.
    • char set
      f:The font includes up to 256 glyphs.
      r:Only glyphs on the range of the ASCII codes 32 to 127 are
         included in the font.
      u:Only glyphs on the range of the ASCII codes 32 to 95 (uppercase
         hars) are included in the font.
      n:Only numbers and extra glyphs for writing date and time strings
         are included in the font.
      :Other custom character list.

    註:
    u8g2 庫提供的 font 非常多,各位也暫時消化不了太多。如果我們使用中文的話,就去看看中文 font 就好。

    u8g2.setFontDirection() —— 設置字體方向

    /**
    * 定義字符串繪製或者圖形繪製的方向
    * @param dir 方向
    * @param 關聯方法 drawStr
    */
    void U8G2::setFontDirection(uint8_t dir)

    dir 的參數說明

    ArgumentString RotationDescription
    00 degree Left to right
    190 degreeTop to down
    2180 degreeRight to left
    3270 degreeDown to top

    請看範例:

    u8g2.setFont(u8g2_font_ncenB14_tfå);
    u8g2.setFontDirection(0);
    u8g2.drawStr(15, 20, "Abc");
    u8g2.setFontDirection(1);
    u8g2.drawStr(15, 20, "Abc");
    緩存相關函數

    緩存相關函數,一般不會去操作,瞭解即可。

    u8g2.getBufferPtr() —— 獲取緩存空間的地址

    /**
    * 獲取緩存空間的地址
    * @return 返回緩存空間起始地址
    * @Note 關聯方法 getBufferTileHeight, getBufferTileWidth, clearBuffer
    */
    uint8_t *U8G2::getBufferPtr(void)

    註:
    緩存大小等於 8 × u8g2.getBufferTileHeight() × u8g2.getBufferTileWidth()。

    u8g2.getBufferTileHeight() —— 獲取緩衝區的 Tile 高度

    /**
    * 獲取緩衝區的 Tile 高度
    * @return 返回高度值
    */
    uint8_t U8G2::getBufferTileHeight(void)

    註:
    一個 tile 等於 8 個像素點。

    u8g2.getBufferTileWidth() —— 獲取緩衝區的 Tile 寬度

    /**
    * 獲取緩衝區的 Tile 寬度
    * @return 返回寬度值
    */
    uint8_t U8G2::getBufferTileWidth(void)

    註:
    一個 tile 等於 8 個像素點。

    u8g2.getBufferCurrTileRow() —— 獲取緩衝區的當前 Tile row

    /**
    * 獲取緩衝區的當前 Tile row 行數
    * @return 返回當前的 tilerow
    */
    uint8_t U8G2::getBufferCurrTileRow(void)

    註:
    這個方法跟我們上面說到的 page position 相關。

    u8g2.setBufferCurrTileRow() —— 設置緩衝區的當前 Tile row

    /**
    * 設置緩衝區的當前 Tile row
    * @param 當前的 Tile row
    */
    uint8_t U8G2::setBufferCurrTileRow(uint8_t row)

    註:
    在 firstPage/nextPage 循環時,由於底層調用了 setBufferCurrTileRow,所以儘量不要自己手動調用該方法。
    請看範例:

    u8g2.setBufferCurrTileRow(0);
    // let y=0 be the topmost row of the buffer
    u8g2.clearBuffer();
    u8g2.setFont(u8g2_font_helvB08_tr);
    u8g2.drawStr(2, 8, "abcdefg");
                    
    u8g2.setBufferCurrTileRow(2);
    // write the buffer to tile row 2 (y=16) on the display
    u8g2.sendBuffer();
    u8g2.setBufferCurrTileRow(4);
    // write the same buffer to tile row 4 (y=32) on the display
    u8g2.sendBuffer();

    如何運用 u8g2 函式庫

    前面介紹到 u8g2 適配了絕大部分的 OLED,那麼我們如何構建具體的 OLED 驅動呢?可分爲以下幾個順序步驟:

    1. 區分顯示器:

      首先,你需要知道 OLED 顯示器的控制器型號以及屏幕大小。舉個例子,我手上有一塊 SSD1306 128×64 的 OLED,那麼它的控制器就是 SSD1306,螢幕大小是 128×64。

      其次,你所選擇的 OLED 必須在 u8g2 庫所支持的 OLED 列表中,具體可參考:這個網址

    2. 確認物理連線介面:

      圖像信息是通過物理連接線傳送給 OLED 顯示器。通常,我們的連謝種類包括:

      • 3SPI,3-wire SPI:串行外圍接口,依靠三個控制信號,Clock、Data、CS
      • 4SPI, 4-Wire SPI:跟 3SPI 一樣,只是多了一條數據命令線,經常叫做 D/C
      • I2C, IIC or TWI:SCL、SDA
      • 8080:A 8-Bit bus which requires 8 data lines
      • 6800:Another 8-Bit bus, but with a different protocol

      具體的 OLED 使用怎樣的連線,我們需要查閱設備手冊。比如,SSD1306 就是 I2C。

    3. 確認連線引腳:

      知道了物理連線模式之後,我們一般都是把 OLED 連接到 Arduino Board 的輸出引腳,也就是軟件模擬通訊協議。當然,如果有現成的物理連線介面那就更好了。
      如:Arduino 的 A4 即為 I2C 的 SDA、A5 即為 I2C 的 SCL

    4. u8g2 初始化:

      經歷以上三步之後,我們就可以開始初始化出具體的 OLED 驅動了。比如,IIC SSD1306 128X64 的 OLED,就可以用以下初始化建構子(Constrator):

      U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);
      // All Boards without Reset of the Display

      當然 SSD1306 還有其他的建構子,如:

      OLED : SSD1306 / 128×64
      U8G2_SSD1306_128X64_NONAME_1_4W_SW_SPI(rotation, clock, data, cs, dc [, reset])
      page buffer size : 128 bytes
      U8G2_SSD1306_128X64_NONAME_2_4W_SW_SPI(rotation, clock, data, cs, dc [, reset])
      page buffer size : 256 bytes
      U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI(rotation, clock, data, cs, dc [, reset])
      page buffer size : 1024 bytes
      U8G2_SSD1306_128X64_NONAME_1_4W_HW_SPI(rotation, cs, dc [, reset])
      page buffer size : 128 bytes

      看到這裡,我們需要重點講述一下建構子(Constrator)的規則。建構子的名字由以下幾方面組合而成,它們之間使用 "_" 連接起來。

      NoDescriptionExample
      1PrefixU8G2
      2Display ControllerSSD1306
      3Display Name128X64_NONAME
      4Buffer Size
      1:保持一頁的緩衝區,用於 firstPage/nextPage 的 PageMode
      2:保持兩頁的緩衝區,用於 firstPage/nextPage 的 PageMode
      F:獲取整個屏幕的緩衝區,RAM 消耗大,一般用在 RAM 空間
          比較大的 arduino 板子
      5Communication
      4W_SW_SPI
      4-wire (clock, data, cs and dc) software emulated SPI
      4W_HW_SPI
      4-wire (clock, data, cs and dc) hardware SPI (based on Arduino SPI library)
      2ND_4W_HW_SPI
      If supported, second 4-wire hardware SPI (based on Arduino SPI library)
      3W_SW_SPI
      3-wire (clock, data and cs) software emulated SPI
      SW_I2C
      Software emulated I2C/TWI
      HW_I2C
      Hardware I2C based on the Arduino Wire library
      2ND_HW_I2C
      If supported, use second hardware I2C (Arduino Wire lib)
      6800
      8-bit parallel interface, 6800 protocol
      8080
      8-bit parallel interface, 8080 protocol

      建構子參數的第一個項目為 Rotation,其意義是指定整個螢幕是否要在旋轉的狀態下顯示內容,其詳細定義是:

      Rotation / MorrorDescriptiomn
      U8G2_R0No rotation, landscape
      U8G2_R190 degree clockwise rotation
      U8G2_R2180 degree clockwise rotation
      U8G2_R3 270 degree clockwise rotation
      U8G2_MIRRORNo rotation, landscape, display content is mirrored (v2.6.x)

      所以,一個完整的例子爲:

      #include <Arduino.h>
      #include <U8g2lib.h>
      #include <SPI.h>
      #include <Wire.h>
                      
      U8G2_ST7920_128X64_1_SW_SPI u8g2(U8G2_R0, 13, 11, 10, 8);
                      
      void setup(void) {
          u8g2.begin();
      }
                      
      void loop(void) {
          u8g2.firstPage();
          do {
          u8g2.setFont(u8g2_font_ncenB14_tr);
          u8g2.drawStr0,24,"Hello World!");
          } while ( u8g2.nextPage() );
      }
    5. u8g2 繪製模式:

      u8g2 支援三種繪製模式:

      1. Full screen buffer mode(全屏緩存模式)

        特點:繪製速度快、所有的繪製方法都可以使用、需要大量的 RAM 空間。

        建構子:必須帶有 F,比如:

        U8G2_ST7920_128X64_F_SW_SPI(rotation, clock, data, cs [, reset])

        用法:

        1. 清除緩衝區 u8g2.clearBuffer()
        2. 呼叫繪製方法
        3. 發送緩衝區的內容到顯示器 u8g2.sendBuffer()

        範例程式:

        void setup(void) {
            u8g2.begin();
        }
                              
        void loop(void) {
            u8g2.clearBuffer();
            u8g2.setFont(u8g2_font_ncenB14_tr);
            u8g2.drawStr(0,20,"Hello World!");
            u8g2.sendBuffer();
        }

      2. Page mode(分頁模式:This is the U8glib picture loop)

        特點:繪製速度慢、所有的繪製方法都可以使用、需要少量的 RAM 空間。

        建構子:必須帶有 1 或 2,比如:

        U8G2_ST7920_128X64_1_SW_SPI(rotation, clock, data, cs [, reset])

        用法:

        1. 呼叫 u8g2.firstPage()
        2. 開始一個 do while 循環
        3. 在循環內部呼叫繪製方法
        4. 不斷判斷 u8g2.nextPage()

        範例程式:

        void setup(void) {
            u8g2.begin();
        }
                              
        void loop(void) {
            u8g2.firstPage();
            do {
                u8g2.setFont(u8g2_font_ncenB14_tr);
                u8g2.drawStr(0,24,"Hello World!");
            } while ( u8g2.nextPage() );
        }

      3. U8x8 character only mode(僅僅支援普通字符)

        特點:繪製速度快、並不是對所有的顯示器都有效、圖形繪製不可用、不需要 RAM 空間。

        建構子:使用 U8X8 建構子,比如:

        U8X8_ST7565_EA_DOGM128_4W_SW_SPI(clock, data, cs, dc [, reset])

        範例程式:

        void setup(void) {
            u8x8.begin();
        }
                              
        void loop(void) {
            u8x8.setFont(u8x8_font_chroma48medium8_r);
            u8x8.drawString(0,1,"Hello World!");
        }