2019年11月12日 星期二

Arduino 解析 I2C 訊號控制 HD44780 點陣文字LCD熒幕模組

I2C (實際是 I2C) 或 IIC 全名是 Inter-Integrated Circuit
I2C 只需要 一支 序列時脈 (Serial Clock (SCL)) 及 一支 序列資料 (Serial Data (SDA)) 便可以處理其他 集成電路 的 輸入 或 輸出
因此是一種在 集成電路 與 控制界面 的 內部集成電路
究竟 I2C 有甚麼作用?

Arduino Starter Kit 套件附送 焊接在 HD44780 2x16 LCD Screen 背後的 PCF8574 for HD44780 LCD backpack 就是運用 I2C 技術
PCF8574 for HD44780 LCD backpack 是專為 1x16 引腳排序的 HD44780 LCD Screen 整合的 I2C
I2C 將原本需要 16支引腳 來控制的 HD44780 ,只需要使用 4支引腳 便可以控制

見下文
見下文
PCF8574 for HD44780 LCD backpack 的側面

見下文
PCF8574 for HD44780 LCD backpack 的正面

見下文
PCF8574 for HD44780 LCD backpack 的背面

見下文
SMDPCF8574T晶片

見下文
PCF8574 for HD44780 LCD backpack 使用 電位器 來控制 對比度
3362 電位器 需要使用 螺絲批 轉動來調整
轉動到左邊寫著 1 的方向電壓越低,轉動到右邊寫著 3 的方向電壓越高

見下文
PCF8574 for HD44780 LCD backpack 使用 跳線 (Jumper) 來控制 背光開關 ,但 不能微調光度

引腳功能

引腳16引腳15引腳14引腳13引腳12引腳11引腳10引腳9
引腳1引腳2引腳3引腳4引腳5引腳6引腳7引腳8
描述
(缺口在左邊)
VCCSDASCLINTP7P6P5P4
A0A1A2P0P1P2P3GND

引腳編號描述方向用途
1A0自訂地置,第2位元;實體連接或中斷
2A1自訂地置,第1位元;實體連接或中斷
3A2自訂地置,第0位元;實體連接或中斷
4P0資料,第7位元;連接到目標裝置引腳
5P1資料,第6位元;連接到目標裝置引腳
6P2資料,第5位元;連接到目標裝置引腳
7P3資料,第4位元;連接到目標裝置引腳
8GND接地
9P4資料,第3位元;連接到目標裝置引腳
10P5資料,第2位元;連接到目標裝置引腳
11P6資料,第1位元;連接到目標裝置引腳
12P7資料,第0位元;連接到目標裝置引腳
13INT輸出資料傳送或接收時,中斷連接
14SCL輸出序列時脈;連接到控制器
15SDA輸入/輸出序列資料;連接到控制器
16VCC電源

訊號

發送指令
I2C 啟動後,會以每組 8位元資料 及 1位元確認 發送資料
啟動
(必須)
指派Slaver地址
(必須)
發送資料 週期
(選用、可重覆)
結束
(必須)
7位元
Slaver地址
1位元
模式
1位元
確認
8位元
資料
1位元
確認
  1. 啟動 I2C
  2. 指派Slaver地址
    1. 7位元 Slaver地址
    2. 1位元 模式
    3. 1位元 確認
  3. 發送資料 週期 (可重覆)
    1. 8位元 資料
    2. 1位元 確認
  4. 終止 I2C

啟動 I2C
啟動 I2C 需要讓 序列時脈 保持 高電壓,並將 序列資料 由 高電壓 下降到 低電壓
引腳啟動
Time延遲250微秒延遲250微秒
SCL
SDA

訊號波紋時序圖
SCLSDA

Arduino AVR-C 例子
digitalWrite(sclPin, HIGH);
digitalWrite(sdaPin, HIGH);
delayMicroseconds(250);
digitalWrite(sdaPin, LOW);

終止 I2C
終止 I2C 需要讓 序列時脈 保持 高電壓,並將 序列資料 由 低電壓 上升到 高電壓
引腳終止
Time延遲250微秒延遲250微秒
SCL
SDA

訊號波紋時序圖
SCLSDA

Arduino AVR-C 例子
digitalWrite(sclPin, HIGH);
digitalWrite(sdaPin, LOW);
delayMicroseconds(250);
digitalWrite(sdaPin, HIGH);

Slaver地址

啟動 I2C 後,指派執行指令的 Slaver地址
指派 Slaver 需要發出
  1. 7位元 為 地址 (Address)
    • PCF8574*版本 為 0100XXX(二進制)
    • PCF8574A*版本 為 0111XXX(二進制)
    • 指派次序必須由 最高有效位元 (Most Significant Bit (MSB)) (第6位元) 開始設定
  2. 1位元 為 模式 (Mode)
    • 寫入為 0
    • 讀取為 1
  3. 1位元 為 確認 (Acknowledge),必須為 0

見下文
Slaver地址 當中的 A0, A1, A2 的設定類似 整合驅動電子 (Integrated Drive Electronics (IDE))
使用 跳線 將 IDE裝置 區分 Master 及 Slaver
I2C 同樣使用類似的方式來區分不同的 Slaver
在 I2C 的情況下,發送指令為 Master (例如 Arduino),接收指令為 Slaver (例如 PCF8574)
由於 I2C 使用 7位元地址,所以理論上能夠區分 128個Slaver

見下文
PCF8574 使用 3個開關制 來設定 Slaver地址
不接地狀態為 1 ;接地狀態為 0
A2A1A0數值PCF8574*PCF8574A*
二進制十六進制二進制十六進制
接地接地接地001000000x2001110000x38
接地接地不接地101000010x2101110010x39
接地不接地接地201000100x2201110100x3A
接地不接地不接地301000110x2301110110x3B
不接地接地接地401001000x2401111000x3C
不接地接地不接地501001010x2501111010x3D
不接地不接地接地601001100x2601111100x3E
不接地不接地不接地701001110x2701111110x3F
雖然 PCF8574 Slaver地址 的 前4位元不能任意改動,但 3個開關掣 再配合 2種PCF8574,總共有 16種組合
即是使用 PCF8574 來實作 I2C 只使用 2支引腳 便可以區分 16個 PCF8574 Slaver 來控制 16個裝置

指派 Slaver地址
先將 序列時脈 下降至 低電壓,並將 序列資料 調整為 高電壓 或 低電壓 ,然後將 序列時脈 上升至 高電壓 ,便完成 1位元 資料
由於指派 Slaver地址 總共需要 9個位元資料,因此需要重覆以上程序 9次

PCF8574*版本
引腳第6位元第5位元第4位元第3位元A2A1A0RWACK
Time每次執行後,延遲250微秒
SCL
SDA高/低高/低高/低高/低

訊號波紋時序圖
SCLSDA

PCF8574A*版本
引腳第6位元第5位元第4位元第3位元A2A1A0RWACK
Time每次執行後,延遲250微秒
SCL
SDA高/低高/低高/低高/低

訊號波紋時序圖
SCLSDA

發送資料

配對對應裝置後,便可以通過 I2C 指示目標裝置 發出 寫入指令 或 讀取指令
指令會以 P7 至 P0 次序發出
引腳P7P6P5P4P3P2P1P0ACK
Time每次執行後,延遲250微秒
SCL
SDA高/低高/低高/低高/低高/低高/低高/低高/低

訊號波紋時序圖
SCLSDA

PCF8574 for HD44780 LCD backpack 是 PCF8574T 屬於 PCF8574* 版本
指派 Slaver地址 與,發出指令是相同
Arduino AVR-C 例子
void writeBit(bool enabled) {
    digitalWrite(sclPin, LOW);
    digitalWrite(sdaPin, ((enabled) ? HIGH : LOW));
    delayMicroseconds(250);
    digitalWrite(sclPin, HIGH);
}

void writeByte(byte data, bool ack) {
    for (byte i = 0; i < 8; i++) {
        // 由第7位元開始發出指令
        writeBit((data & 0x80) > 0);
        // 向左移動1位元
        data <<= 1;
    }
    // 發出 Acknowledge 指令
    writeBit(ack);
}

void setup() {
    // do something
    // 如果 Slaver 是 PCF8574 ,A2, A1, A0 都沒有接地,地址便是 0100111
    // 0100111 = 0x27
    // 0x27 << 1 = 0x4E 將地址向左偏移1位,以讓往後可以設定 寫入 或 讀取 模式
    // 0x4E | 0 = 指派 01001110 地址 為 寫入模式
    writeByte(0x4E, false);
}

見下文
在下理解 PCF8574 for HD44780 LCD backpack 的線路圖

雖然 PCF8574 for HD44780 LCD backpack 的 A0, A1, A2 引腳 實際沒有 開關掣
不過使用者仍然可以根據需要自行焊接導電體連接接地來切換地址
在下亦設定 A引腳 為 開關掣 取代原本的 跳線 (因為沒有相似元件圖示)
PCF8574 8支P引腳 分別接駁到 HD44780 的特定引腳,讓 PCF8574 向 HD44780 發出指令
PCF8574 引腳HD44780 引腳
P7D7
P6D6
P5D5
P4D4
P3A
P2En
P1RW
P0RS
閣下會發現 PCF8574 8支P引腳 沒有接駁到 HD44780 所有引腳
由於 HD44780 可以使用 4位元模式 ,因此不接駁到 HD44780 的 D0 至 D3 引腳,便能夠讓 PCF8574 向 HD44780 發出足夠指令

見下文
PCF8574 for HD44780 LCD backpack 模組的接駁方式
其中有 4支引腳 接駁到 HD44780 D0 至 D3 實際上沒有線路只是固定用途

見下文
PCF8574 for HD44780 LCD backpack 的 4支屈曲引腳 在 HD44780 左邊伸出

見下文
PCF8574 for HD44780 LCD backpack 的 另外16支引腳 焊接到 HD44780

見下文
PCF8574 for HD44780 LCD backpack 焊接到 HD44780 背後

見下文
Fritzing HD44780 with PCF8574 組模圖示

Arduino AVR-C 例子
const byte GND_PIN = A0;
const byte VCC_PIN = A1;
const byte SDA_PIN = A2;
const byte SCL_PIN = A3;

void setup() {
    // 在下使用其中2支引腳作為 電源線 及 接地線
    pinMode(GND_PIN, OUTPUT);
    digitalWrite(GND_PIN, LOW);
    pinMode(VCC_PIN, OUTPUT);
    digitalWrite(VCC_PIN, HIGH);
    // 設定 序列時脈 及 序列資料 為 OUTPUT
    pinMode(SDA_PIN, OUTPUT);
    pinMode(SCL_PIN, OUTPUT);
    // 啟動 I2C
    start();
    // 設定 Slaver地址為 0100111 為 寫入模式 = 0x4E
    writeByte(0x4E, false);
    // 設定 使用4位元模式
    writeByte(0x2C, false); // 設定 指令 為 0010 , A, EN 為 高電壓 , RW, RS 為 低電壓
    writeByte(0x28, false); // 設定 指令 為 0010 , A 為 高電壓 , EN, RW, RS 為 低電壓
    // 設定 使用4位元模式、使用2行、使用5x8點陣 (二進制數值 00101000)
    // 分開為 0010 及 1000 2組4位元資料
    writeByte(0x2C, false); // 設定 指令 為 0010 , A, EN 為 高電壓 , RW, RS 為 低電壓
    writeByte(0x28, false); // 設定 指令 為 0010 , A 為 高電壓 , EN, RW, RS 為 低電壓
    writeByte(0x8C, false); // 設定 指令 為 1000 , A, EN 為 高電壓 , RW, RS 為 低電壓
    writeByte(0x88, false); // 設定 指令 為 1000 , A 為 高電壓 , EN, RW, RS 為 低電壓
    // 設定使用 顯示熒幕、顯示游標、顯示閃爍 (二進制數值 00001111)
    // 分開為 0000 及 1111 2組4位元資料
    writeByte(0x0C, false); // 設定 指令 為 0000 , A, EN 為 高電壓 , RW, RS 為 低電壓
    writeByte(0x08, false); // 設定 指令 為 0000 , A 為 高電壓 , EN, RW, RS 為 低電壓
    writeByte(0xFC, false); // 設定 指令 為 1111 , A, EN 為 高電壓 , RW, RS 為 低電壓
    writeByte(0xF8, false); // 設定 指令 為 1111 , A 為 高電壓 , EN, RW, RS 為 低電壓
    // 清除熒幕資料 (二進制數值 00000001)
    // 分開為 0000 及 0001 2組4位元資料
    writeByte(0x0C, false); // 設定 指令 為 0000 , A, EN 為 高電壓 , RW, RS 為 低電壓
    writeByte(0x08, false); // 設定 指令 為 0000 , A 為 高電壓 , EN, RW, RS 為 低電壓
    writeByte(0x1C, false); // 設定 指令 為 0001 , A, EN 為 高電壓 , RW, RS 為 低電壓
    writeByte(0x18, false); // 設定 指令 為 0001 , A 為 高電壓 , EN, RW, RS 為 低電壓
    // 在熒幕顯示 A (二進制數值 01000001)
    // 分開為 0100 及 0001 2組4位元資料
    writeByte(0x4D, false); // 設定 指令 為 0100 , A, EN, RS 為 高電壓 , RW 為 低電壓
    writeByte(0x49, false); // 設定 指令 為 0100 , A, RS 為 高電壓 , EN, RW 為 低電壓
    writeByte(0x1D, false); // 設定 指令 為 0001 , A, EN, RS 為 高電壓 , RW 為 低電壓
    writeByte(0x19, false); // 設定 指令 為 0001 , A, RS 為 高電壓 , EN, RW 為 低電壓
    // 結束 I2C
    stop();
}

void loop() {
}

void start() {
    digitalWrite(SCL_PIN, HIGH);
    digitalWrite(SDA_PIN, HIGH);
    delayMicroseconds(250);
    digitalWrite(SDA_PIN, LOW);
}

void stop() {
    digitalWrite(SCL_PIN, HIGH);
    digitalWrite(SDA_PIN, LOW);
    delayMicroseconds(250);
    digitalWrite(SDA_PIN, HIGH);
}

void writeBit(bool enabled) {
    digitalWrite(SCL_PIN, LOW);
    digitalWrite(SDA_PIN, ((enabled) ? HIGH : LOW));
    delayMicroseconds(250);
    digitalWrite(SCL_PIN, HIGH);
}

void writeByte(byte data, bool ack) {
    for (byte i = 0; i < 8; i++) {
        writeBit((data & 0x80) > 0);
        data <<= 1;
    }
    writeBit(ack);
}

見下文
見下文
見下文
執行效果
(在下使用比 Arduino UNO R3 細小的 Arduino Nano)
(在下同樣懶惰關係,直接將 PCF8574 安置到 麵包板 上)

同時控制多個Slaver


見下文
在下將其中一塊 PCF8574 for HD44780 LCD backpack 的 A0 焊接
當 A0 接駁到接地後, Slaver地址 會調整成 0100110 = 0x26

見下文
然後接駁兩塊 PCF8574 for HD44780 LCD backpack 的 序列時脈 及 序列資料

見下文
可以根據 Slaver地址 控制不同 PCF8574 for HD44780 LCD backpack 讓 HD44780 顯示不同資料而不需要接駁太多線路

補充資料

除了 PCF8574 for HD44780 LCD backpack
還有具備 3-Pin DIP Switch 的 PCF8574(Red)版本

見下文
見下文
PCF8574(Red) 的前側面

見下文
PCF8574(Red) 的後側面

見下文
PCF8574(Red) 的正面

見下文
PCF8574(Red) 的 3-Pin DIP Switch 可以免焊接來調整 A0, A1, A2 的 Slaver地址
切換到 ON 方向為 1 ,反之為 0

見下文
同樣使用 SMD 的 PCF8574T晶片

見下文
見下文
見下文
見下文
將 HD44780 接駁到 PCF8574(Red)

見下文
見下文
見下文
見下文
見下文
再將 HD44780 接駁到 Arduino Nano
要留意 PCF8574 for HD44780 LCD backpack 及 PCF8574(Red) 的引腳次序不同
以正面擺放、4支引腳向左為準,引腳次序由下至上
PCF8574 for HD44780 LCD backpack 為 GND, VCC, SDA, SCL
PCF8574(Red) 為 SCL, SDA, GND, VCC

見下文
測試效果

其他資料

由於在下不太喜歡小型模組板的引腳在正面方向,因此將引腳除錫,再重新焊在組模板背面

見下文
見下文
重新焊接引腳的 PCF8574(Red) 正面

見下文
重新焊接引腳的 PCF8574(Red) 背面

見下文
在下認為引腳焊接在背面可以方便安置在 麵包板 上接駁線路

總結

通常在 Arduino 上使用 I2C 控制 HD44780 LCD Screen 都會使用 LiquidCrystal_I2C.h
(可以到 https://www.arduinolibraries.info/libraries/liquid-crystal-i2-c 下載函式庫)
在 LiquidCrystal_I2C.h 的原始碼中會發現使用 Wire.h 函式庫,是 Arduino 附帶的函式庫
在開發角度上,使用已經製成、穩定的函式庫,可以避免出錯、加快開發速度、節省時間

在下想了解 I2C 的運作原理,因此由零開始查看文件,但完全不明白文件描述的運作方式,便到網上尋找資料
不過很多資料都是使用 LiquidCrystal_I2C.h 並沒有詳細解釋 I2C 運作原理,亦沒有不使用 Wire.h 實作效果
因為函式庫是基於 Arduino AVR-C 編寫,在下嘗試尋找不是使用 Arduino AVR-C 的例子,希望有類似原始碼的程式可以參考
在下找到 Assembly 及 C 的程式,複製程式碼再修改成在下需要的效果,但結果都是失敗;最後還是只能認真、仔細、慢慢理解文件

除了理解文件,在下不想受到 Wire.h 的 序列時脈 及 序列資料 的引腳限制
有些使用 LiquidCrystal_I2C.h 的例子可以更改 序列時脈 及 序列資料 ,但在下查看原始碼發現不能使用
(可能有很多不同版本,或自行修改版本)

見下文
Arduino 允許使用者自行修改原始碼,將 SCL 及 SDA 修改成需要的引腳
(不同 Arduino 的 SCL 及 SDA 引腳會有不同,可參考 https://www.arduino.cc/en/reference/wire)
但修改後亦可能導致其他函式庫需要使用原來的 SCL 及 SDA 設定時出現問題
因此在下唯有自行編寫 I2C 程式
亦已經上載到 https://bitbucket.org/hkgoldenmra/hd44780i2cmodule
https://create.arduino.cc/editor/hkgoldenmra/34c270b4-8443-406f-8149-eb68011b9bcc/preview
可以在 Terminal 輸入
git clone "https://bitbucket.org/hkgoldenmra/hd44780i2cmodule.git" --depth=1

學習沒有捷徑

沒有留言 :

張貼留言