I 2 C 只需要 一支 序列時脈 (Serial Clock (SCL)) 及 一支 序列資料 (Serial Data (SDA)) 便可以處理其他 集成電路 的 輸入 或 輸出
因此是一種在 集成電路 與 控制界面 的 內部集成電路
究竟 I 2 C 有甚麼作用?
Arduino Starter Kit 套件附送 焊接在 HD44780 2x16 LCD Screen 背後的 PCF8574 for HD44780 LCD backpack 就是運用 I 2 C 技術
PCF8574 for HD44780 LCD backpack 是專為 1x16 引腳排序的 HD44780 LCD Screen 整合的 I 2 C
I 2 C 將原本需要 16支引腳 來控制的 HD44780 ,只需要使用 4支引腳 便可以控制
PCF8574 for HD44780 LCD backpack 的側面
PCF8574 for HD44780 LCD backpack 的正面
PCF8574 for HD44780 LCD backpack 的背面
SMD 的 PCF8574T晶片
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 |
| 引腳 (缺口在左邊) | VCC | SDA | SCL | INT | P7 | P6 | P5 | P4 |
| A0 | A1 | A2 | P0 | P1 | P2 | P3 | GND |
| 編號 | 引腳 | 方向 | 用途 |
|---|---|---|---|
| 1 | A0 | 自訂地置,第2位元;實體連接或中斷 | |
| 2 | A1 | 自訂地置,第1位元;實體連接或中斷 | |
| 3 | A2 | 自訂地置,第0位元;實體連接或中斷 | |
| 4 | P0 | 輸出 | 資料,第7位元;連接到目標裝置引腳 |
| 5 | P1 | 輸出 | 資料,第6位元;連接到目標裝置引腳 |
| 6 | P2 | 輸出 | 資料,第5位元;連接到目標裝置引腳 |
| 7 | P3 | 輸出 | 資料,第4位元;連接到目標裝置引腳 |
| 8 | GND | 接地 | |
| 9 | P4 | 輸出 | 資料,第3位元;連接到目標裝置引腳 |
| 10 | P5 | 輸出 | 資料,第2位元;連接到目標裝置引腳 |
| 11 | P6 | 輸出 | 資料,第1位元;連接到目標裝置引腳 |
| 12 | P7 | 輸出 | 資料,第0位元;連接到目標裝置引腳 |
| 13 | INT | 輸入 | 資料傳送或接收時,中斷連接 |
| 14 | SCL | 輸入 | 序列時脈;連接到控制器 |
| 15 | SDA | 輸入輸出 | 序列資料;連接到控制器 |
| 16 | VCC | 電源 |
訊號
發送指令
I 2 C 啟動後,會以每組 8位元資料 及 1位元確認 發送資料| 啟動 (必須) | 指派Slaver地址 (必須) | 發送資料 週期 (選用、可重覆) | 結束 (必須) | ||||||
|---|---|---|---|---|---|---|---|---|---|
| 7位元 Slaver地址 | 1位元 模式 | 1位元 確認 | 8位元 資料 | 1位元 確認 | |||||
- 啟動 I 2 C
- 指派Slaver地址
- 7位元 Slaver地址
- 1位元 模式
- 1位元 確認
- 發送資料 週期 (可重覆)
- 8位元 資料
- 1位元 確認
- 終止 I 2 C
啟動 I 2 C
啟動 I 2 C 需要讓 序列時脈 保持 高電壓,並將 序列資料 由 高電壓 下降到 低電壓| 引腳 | 啟動 | |
|---|---|---|
| Time | 延遲250微秒 | 延遲250微秒 |
| SCL | 高 | |
| SDA | 高 | 低 |
訊號波紋時序圖
Arduino AVR-C 例子
digitalWrite(sclPin, HIGH); digitalWrite(sdaPin, HIGH); delayMicroseconds(250); digitalWrite(sdaPin, LOW);
終止 I 2 C
終止 I 2 C 需要讓 序列時脈 保持 高電壓,並將 序列資料 由 低電壓 上升到 高電壓| 引腳 | 終止 | |
|---|---|---|
| Time | 延遲250微秒 | 延遲250微秒 |
| SCL | 高 | |
| SDA | 低 | 高 |
訊號波紋時序圖
Arduino AVR-C 例子
digitalWrite(sclPin, HIGH); digitalWrite(sdaPin, LOW); delayMicroseconds(250); digitalWrite(sdaPin, HIGH);
Slaver地址
啟動 I 2 C 後,指派執行指令的 Slaver地址指派 Slaver 需要發出
- 7位元 為 地址 (Address)
- PCF8574*版本 為 0100XXX(二進制)
- PCF8574A*版本 為 0111XXX(二進制)
- 指派次序必須由 最高有效位元 (Most Significant Bit (MSB)) (第6位元) 開始設定
- 1位元 為 模式 (Mode)
- 寫入為 0
- 讀取為 1
- 1位元 為 確認 (Acknowledge) ,必須為 0
Slaver地址 當中的 A0, A1, A2 的設定類似 整合驅動電子 (Integrated Drive Electronics (IDE))
使用 跳線 將 IDE裝置 區分 Master 及 Slaver
I 2 C 同樣使用類似的方式來區分不同的 Slaver
在 I 2 C 的情況下,發送指令為 Master (例如 Arduino),接收指令為 Slaver (例如 PCF8574)
由於 I 2 C 使用 7位元地址,所以理論上能夠區分 128個Slaver
PCF8574 使用 3個開關制 來設定 Slaver地址
不接地狀態為 1 ;接地狀態為 0
| A2 | A1 | A0 | 數值 | PCF8574* | PCF8574A* | ||
|---|---|---|---|---|---|---|---|
| 二進制 | 十六進制 | 二進制 | 十六進制 | ||||
| 接地 | 接地 | 接地 | 0 | 0100000 | 0x20 | 0111000 | 0x38 |
| 接地 | 接地 | 不接地 | 1 | 0100001 | 0x21 | 0111001 | 0x39 |
| 接地 | 不接地 | 接地 | 2 | 0100010 | 0x22 | 0111010 | 0x3A |
| 接地 | 不接地 | 不接地 | 3 | 0100011 | 0x23 | 0111011 | 0x3B |
| 不接地 | 接地 | 接地 | 4 | 0100100 | 0x24 | 0111100 | 0x3C |
| 不接地 | 接地 | 不接地 | 5 | 0100101 | 0x25 | 0111101 | 0x3D |
| 不接地 | 不接地 | 接地 | 6 | 0100110 | 0x26 | 0111110 | 0x3E |
| 不接地 | 不接地 | 不接地 | 7 | 0100111 | 0x27 | 0111111 | 0x3F |
即是使用 PCF8574 來實作 I 2 C 只使用 2支引腳 便可以區分 16個 PCF8574 Slaver 來控制 16個裝置
指派 Slaver地址
先將 序列時脈 下降至 低電壓,並將 序列資料 調整為 高電壓 或 低電壓 ,然後將 序列時脈 上升至 高電壓 ,便完成 1位元 資料由於指派 Slaver地址 總共需要 9個位元資料,因此需要重覆以上程序 9次
PCF8574*版本
| 引腳 | 第6位元 | 第5位元 | 第4位元 | 第3位元 | A2 | A1 | A0 | RW | ACK | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Time | 每次執行後,延遲250微秒 | |||||||||||||||||
| SCL | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 |
| SDA | 低 | 高 | 低 | 低 | 高/低 | 高/低 | 高/低 | 高/低 | 低 | |||||||||
訊號波紋時序圖
PCF8574A*版本
| 引腳 | 第6位元 | 第5位元 | 第4位元 | 第3位元 | A2 | A1 | A0 | RW | ACK | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Time | 每次執行後,延遲250微秒 | |||||||||||||||||
| SCL | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 |
| SDA | 低 | 高 | 高 | 高 | 高/低 | 高/低 | 高/低 | 高/低 | 低 | |||||||||
訊號波紋時序圖
發送資料
配對對應裝置後,便可以通過 I 2 C 指示目標裝置 發出 寫入指令 或 讀取指令指令會以 P7 至 P0 次序發出
| 引腳 | P7 | P6 | P5 | P4 | P3 | P2 | P1 | P0 | ACK | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Time | 每次執行後,延遲250微秒 | |||||||||||||||||
| SCL | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 | 低 | 高 |
| SDA | 高/低 | 高/低 | 高/低 | 高/低 | 高/低 | 高/低 | 高/低 | 高/低 | 低 | |||||||||
訊號波紋時序圖
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 引腳 |
|---|---|
| P7 | D7 |
| P6 | D6 |
| P5 | D5 |
| P4 | D4 |
| P3 | A |
| P2 | En |
| P1 | RW |
| P0 | RS |
由於 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 上使用 I 2 C 控制 HD44780 LCD Screen 都會使用 LiquidCrystal_I2C.h(可以到 https://www.arduinolibraries.info/libraries/liquid-crystal-i2-c 下載函式庫)
在 LiquidCrystal_I2C.h 的原始碼中會發現使用 Wire.h 函式庫,是 Arduino 附帶的函式庫
在開發角度上,使用已經製成、穩定的函式庫,可以避免出錯、加快開發速度、節省時間
在下想了解 I 2 C 的運作原理,因此由零開始查看文件,但完全不明白文件描述的運作方式,便到網上尋找資料
不過很多資料都是使用 LiquidCrystal_I2C.h 並沒有詳細解釋 I 2 C 運作原理,亦沒有不使用 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 設定時出現問題
因此在下唯有自行編寫 I 2 C 程式
亦已經上載到 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
學習沒有捷徑