戰(zhàn)解析:I2C 擴(kuò)展 16 路 GPIO 的硬件連接與驅(qū)動調(diào)試)
簡介TCA6416A 是一款常見的 I2C 接口 GPIO 擴(kuò)展芯片該驅(qū)動資源面向嵌入式開發(fā)者與電子制作愛好者提供完整控制接口幫助快速實(shí)現(xiàn)芯片配置、IO 讀寫以及極性設(shè)置等操作能有效縮短開發(fā)周期并減少重復(fù)查手冊的時間。壓縮包內(nèi)共 2 個文件包含 1 個頭文件與 1 個源文件總體積僅 2KB結(jié)構(gòu)極為精簡可直接添加到 I2C 通信相關(guān)工程中方便在不同硬件平臺上移植使用。驅(qū)動實(shí)現(xiàn)了 TCA6416AWriteConfig 寄存器配置、TCA6416AWriteOutput 輸出控制、TCA6416AWritePolarity 極性設(shè)置、TCA6416AInitDefault 默認(rèn)初始化以及 TCA6416AInitI2CReg I2C 寄存器初始化五個函數(shù)覆蓋芯片初始化與基本 IO 操作場景代碼量小、可讀性好便于二次開發(fā)學(xué)習(xí)。目前已有 387 人學(xué)習(xí)下載適合需要快速上手 TCA6416A 的硬件工程師或嵌入式學(xué)習(xí)者參考并復(fù)用。 做硬件這幾年群里、網(wǎng)盤里、同事的共享目錄里TCA6416A.rar這種文件見得太多了。文件名簡潔到不像話內(nèi)容卻可能天差地別——有的打開就是個數(shù)據(jù)手冊有的則是原理圖庫、封裝、驅(qū)動代碼、調(diào)試筆記一股腦塞進(jìn)去。這個編號對應(yīng)的是 TI 的 16 位 I2C GPIO 擴(kuò)展器 TCA6416A常在 MCU 引腳不夠用、或者需要跨電平域控制 IO 的場景里出現(xiàn)。這篇就借這個壓縮包說開去把 TCA6416A 從硬件連接到驅(qū)動調(diào)試的關(guān)鍵環(huán)節(jié)掰開揉碎講清楚正在做擴(kuò)展 IO、按鍵矩陣、LED 指示項(xiàng)目的工程師可以省不少事。1. 從壓縮包說起TCA6416A.rar里通常裝著什么1.1 不要把它當(dāng)成一個普通的數(shù)據(jù)手冊包先聊聊“拿到包之后怎么判斷它值不值得信任”。我只用 TCA6416A 以來的經(jīng)驗(yàn)是這芯片本身不復(fù)雜復(fù)雜的是你拿到的資料質(zhì)量。同一個型號的壓縮包放在十年前可能是老工程師整理的一整套項(xiàng)目備份里面可能包含 PCB 工程、固件源碼甚至是產(chǎn)線測試腳本現(xiàn)在很多包里就孤零零一個 PDF頂多加一個封裝庫。所以第一件事不是解壓而是看文件名字和目錄結(jié)構(gòu)。命名帶版本號、日期、作者信息的包質(zhì)量一般比較靠譜裸奔的“新建文件夾”就要多留個心眼。TCA6416A 本質(zhì)是個按鍵開關(guān)干的是“把 I2C 總線的兩根線變成 16 路 IO”這種活。項(xiàng)目里拿它接按鍵、撥碼開關(guān)、LED、繼電器狀態(tài)反饋、傳感器報警輸入都很常見。很多 MCU 的 GPIO 不夠用或者信號要從 3.3V 域跨到 5V 域普通 IO 直接連會出電平問題用這顆芯片反而順手。1.2 解壓之后優(yōu)先看哪幾樣文件我拿到一個壓縮包會按這個順序翻原理圖符號庫核對引腳名尤其是 A0-A2、VCCI、VCCP、INT、RESET 這幾個功能腳有沒有標(biāo)反。PCB 封裝TCA6416A 常見 TSSOP-24 和 VQFN-24。VQFN 底部有 exposed pad接地和散熱都靠它新手容易把封裝畫錯。數(shù)據(jù)手冊重點(diǎn)翻寄存器表別只盯著“絕對最大額定值”看。驅(qū)動代碼區(qū)分是裸機(jī) MCU 的例程還是 Linux 下的設(shè)備樹片段。兩者套路完全不同。README 或調(diào)試筆記有這玩意的包價值翻倍往往記錄了別人踩過的坑。如果你發(fā)現(xiàn)解壓后原理圖庫和封裝庫是從別處抄來的那強(qiáng)烈建議對照數(shù)據(jù)手冊里的機(jī)械尺寸圖重新確認(rèn)一遍。封裝錯一個引腳打樣回來就只能飛線成本比花五分鐘核對高得多。2. TCA6416A的本事I2C擴(kuò)展16路IO的核心邏輯2.1 地址、引腳和端口分配I2C 設(shè)備先得有地址。TCA6416A 上有 A0、A1、A2 三個地址引腳分別接 GND 或 VCCI能組合出 8 個不同地址7 位地址范圍是 0x20 到 0x27。實(shí)戰(zhàn)里記一個規(guī)律就夠A0-A2 全接地就是 0x20A0 接高變 0x21A1 接高變 0x22依此類推。這里有一個特別容易栽跟頭的點(diǎn)Linux 的 i2c-tools 掃出來的是 7 位地址比如 0x20但部分芯片手冊里表頭寫的是“寫地址/讀地址”那是把 7 位地址左移一位補(bǔ)上讀寫位之后的值。比如 TCA6416A 在 A0-A2 全接地時8 位寫地址是 0x40。你要是拿 0x40 去 i2cget系統(tǒng)會告訴你找不到設(shè)備其實(shí)芯片就在那。遇到地址不對先做一次除法再找。端口方面芯片分 P0 和 P1 兩組每組 8 位P00-P07 和 P10-P17總共 16 路。每一路都可以獨(dú)立設(shè)為輸入或輸出。需要注意的是TCA6416A 內(nèi)部沒有可編程上拉和下拉電阻外部電路需要自己補(bǔ)。這和某些帶內(nèi)部上拉的擴(kuò)展器不一樣選型時看清楚。2.2 四類寄存器決定一切TCA6416A 的寄存器模型很直白用“命令字節(jié) 數(shù)據(jù)字節(jié)”的方式訪問。跟大多數(shù) I2C 外設(shè)一樣先發(fā)一個 8 位命令字節(jié)選擇寄存器然后讀寫數(shù)據(jù)。核心寄存器一共四對寄存器地址寄存器名讀寫屬性復(fù)位值0x00輸入端口0P0只讀外部電平實(shí)時值0x01輸入端口1P1只讀外部電平實(shí)時值0x02輸出端口0讀寫0xFF0x03輸出端口1讀寫0xFF0x04極性反轉(zhuǎn)0讀寫0x000x05極性反轉(zhuǎn)1讀寫0x000x06配置端口0讀寫0xFF0x07配置端口1讀寫0xFF用一句話解釋配置寄存器某一位寫 0對應(yīng)引腳就是輸出寫 1 就是輸入。復(fù)位值全是 1意味著上電后 16 個引腳默認(rèn)全部是輸入這一點(diǎn)坑過不少人。極性反轉(zhuǎn)寄存器寫 1讀回的電平就會取反做“按鍵按下為低電平”這種場景特別省事軟件里不用再額外取反。很多驅(qū)動框架把寄存器操作封裝成了現(xiàn)成的 GPIO 讀寫不掌握這張表也能把燈點(diǎn)亮。但一旦遇到回讀異常、中斷亂觸發(fā)不理解寄存器流的話就只能瞎試。把 0x00-0x07 這張表記住九成調(diào)試問題都有方向。3. 硬件連接要點(diǎn)上拉、中斷、電平匹配不能憑感覺3.1 I2C上拉阻值怎么算I2C 是開漏結(jié)構(gòu)SCL 和 SDA 必須外接上拉電阻阻值不能拍腦袋。上拉能力太強(qiáng)電阻太小會把電平拉得過低邏輯判斷出錯太弱電阻太大上升沿會變圓400kHz 快速模式要求上升時間不超過 300ns。估算用 RC 充電模型上升時間 tr ≈ 0.8473 × R × C。假設(shè)總線上等效電容約 100pF一個 TCA6416A 加 MCU、PCB 走線差不多就這個量級。要求 tr ≤ 300ns算出來 R ≤ 300ns / (0.8473 × 100pF)約等于 3.54kΩ。所以 2.2kΩ、2.7kΩ、3.3kΩ 都是安全范圍。關(guān)鍵是要把整個 I2C 總線上的設(shè)備都算進(jìn)去所有從設(shè)備輸入電容是并聯(lián)的。如果總線上掛了 3、4 個器件加上走線長200pF 很正常這時候 2.2kΩ 更穩(wěn)。有一回我在掛滿設(shè)備的總線上用了 4.7kΩ波形的上升沿明顯變圓直接跑不到 400kHz換成 2.2kΩ 才正常。3.2 INT引腳和RESET引腳的狀態(tài)處理TCA6416A 有一個 INT 輸出腳開漏、低電平有效。當(dāng)輸入引腳電平發(fā)生變化時INT 會被拉低。這個腳要正常工作必須外接上拉到 VCCI然后接到 MCU 的外部中斷或者 GPIO 輪詢。開漏輸出不會自己推高懸空的話永遠(yuǎn)讀不到高電平。INT 的清除時機(jī)是硬件行為I2C 主機(jī)讀取輸入端口寄存器0x00 或 0x01之后INT 自動恢復(fù)高電平。所以用中斷方式時ISR 里第一件事就是讀輸入寄存器否則中斷會一直掛在低電平反復(fù)觸發(fā)。RESET 腳同樣低有效是異步復(fù)位輸入。如果板上不用它千萬別懸空接上拉到 VCCI 或者由 MCU 控制。懸空時一旦受到干擾誤復(fù)位寄存器全部回到復(fù)位值 0xFF引腳全部變輸入I2C 地址還不變排查起來非常頭疼。3.3 VCCI與VCCP的分工電平匹配的關(guān)鍵TCA6416A 有兩組電源腳這是它設(shè)計上很聰明的地方。VCCI 給內(nèi)部邏輯和 I2C 接口供電范圍大概 1.65V 到 3.6VVCCP 給端口 P0/P1 供電范圍更寬最高能到 5.5V。換句話說I2C 側(cè)可以一直是 3.3V端口側(cè)卻獨(dú)立工作在 5V。要驅(qū)動 5V 的繼電器模塊、讀 5V 傳感器輸出把 VCCP 接 5V、端口電平就是 5V而 I2C 總線還是 3.3V兩邊邏輯互不干擾。這個特性比普通單電源擴(kuò)展器好用太多。但要記住一個前提VCCI 必須高于或等于 I2C 總線的上拉電壓否則芯片內(nèi)部的保護(hù)二極管會導(dǎo)通長期工作會不穩(wěn)定。我習(xí)慣 VCCI 和 MCU 的 IO 電源共用一路VCCP 再根據(jù)端口負(fù)載單獨(dú)選擇。還有芯片底部的大焊盤如果是 GND 相關(guān)引腳一定焊牢別只靠一側(cè)引腳接地。4. 驅(qū)動與調(diào)試從寄存器到總線的完整套路4.1 先拿i2c-tools手動驗(yàn)證硬件通路拿到板子不要一頭扎進(jìn)驅(qū)動開發(fā)。先用 i2c-tools 把硬件通路確認(rèn)下來這一步能省幾個小時。以嵌入式 Linux 為例# 掃描 I2C 總線 1 上的設(shè)備 i2cdetect -y 1掃描結(jié)果里能看到 0x20說明芯片地址正確、電源和上拉基本沒大問題。掃不到先查 A0-A2 的接法、VCCI/VCCP 是否供電、上拉電阻是否焊接別急著懷疑芯片壞了。然后手動操作寄存器。把 P0 全部配成輸出i2cset -y 1 0x20 0x06 0x00這里的參數(shù)分別是總線號、設(shè)備地址、命令字節(jié)、數(shù)據(jù)。0x06 是配置端口0寄存器0x00 表示 P0.0-P0.7 全部設(shè)為輸出。再往輸出寄存器 0x02 寫 0x55i2cset -y 1 0x20 0x02 0x55拿萬用表量 P00-P07能看到 5V/0V 交替出現(xiàn)說明輸出鏈路完全正常。讀輸入也一樣i2cget -y 1 0x20 0x00這會把 P0 端口的實(shí)時輸入電平讀回來。先手動驗(yàn)證的好處是后面驅(qū)動出問題你能迅速分清是硬件還是軟件的鍋。4.2 一個最小驅(qū)動程序的代碼骨架裸機(jī) MCU 環(huán)境下TCA6416A 驅(qū)動流程非常固定初始化 I2C發(fā)命令字節(jié)選擇寄存器再讀寫數(shù)據(jù)。代碼骨架大致這樣#define TCA6416A_ADDR 0x20 // 寫一個寄存器先發(fā)命令字節(jié)再發(fā)數(shù)據(jù) void tca6416a_write_reg(uint8_t reg, uint8_t value) { uint8_t buf[2] { reg, value }; i2c_master_write(TCA6416A_ADDR, buf, 2); } // 讀一個寄存器先選寄存器再讀數(shù)據(jù) uint8_t tca6416a_read_reg(uint8_t reg) { i2c_master_write(TCA6416A_ADDR, reg, 1); uint8_t value; i2c_master_read(TCA6416A_ADDR, value, 1); return value; } // 使用示例P0 低四位輸出高四位輸入 tca6416a_write_reg(0x06, 0xF0); // 寫輸出P0.0 拉高 tca6416a_write_reg(0x02, 0x01); // 讀輸入讀 P0 端口 uint8_t in tca6416a_read_reg(0x00);注意讀操作中先發(fā)設(shè)備地址寫模式選寄存器再發(fā)重復(fù)起始位切到讀模式最后連續(xù)讀回數(shù)據(jù)。大多數(shù) MCU 的 HAL 庫都封裝了類似接口直接調(diào)用即可。如果用中斷方式讀輸入寄存器會清掉 INTISR 里最好把需要讀的端口一次讀完再退出避免漏掉狀態(tài)變化。4.3 嵌入式Linux下的設(shè)備樹寫法Linux 下有個好消息內(nèi)核的gpio-pca953x驅(qū)動直接支持 TCA6416A不用自己寫內(nèi)核驅(qū)動。設(shè)備樹節(jié)點(diǎn)大概長這樣i2c1 { tca6416a: gpio-expander20 { compatible ti,tca6416; reg 0x20; gpio-controller; #gpio-cells 2; interrupt-parent gpio0; interrupts 10 IRQ_TYPE_EDGE_FALLING; vcc-supply reg_3v3; }; };地址 0x20 要和 A0-A2 的接線一致interrupts指定 MCU 側(cè)的 GPIO 和觸發(fā)方式。設(shè)備樹加載成功后就能通過 libgpiod 或/sys/class/gpio控制擴(kuò)展出來的引腳驅(qū)動成熟度很高絕大多數(shù)應(yīng)用不用碰內(nèi)核代碼。5. 實(shí)測排障記錄三個容易踩的坑5.1 i2cdetect掃不到地址先懷疑地址引腳和7位/8位表示有次同事拿板子過來說 i2cdetect 掃整個地址范圍0x20 到 0x27 全部沒有但示波器上能看到 SDA 波形里明明有 ACK 響應(yīng)。我第一反應(yīng)也是芯片壞了后來冷靜下來查手冊才發(fā)現(xiàn)是 7 位地址和 8 位地址混用了。Linux 內(nèi)核和 i2c-tools 統(tǒng)一使用 7 位地址而部分芯片手冊表格里寫的是“寫地址/讀地址”那是左移一位補(bǔ)上讀寫位之后的結(jié)果。比如 A0-A2 全接地時7 位地址是 0x208 位寫地址是 0x40。你按 0x40 去讀取內(nèi)核會去找地址 0x40 的設(shè)備自然找不到。遇到掃描不到的情況先把地址口徑統(tǒng)一一下再查。如果地址表示沒問題還是掃描不到那就量 SCL 和 SDA 的靜態(tài)電平。正常狀態(tài)下兩根線都應(yīng)該是高電平。如果有一根被拉低查總線上別的設(shè)備是不是在發(fā)表錯誤或者某個上拉電阻虛焊了。5.2 寫入寄存器后回讀全是FF配置寄存器沒先解鎖另一個高頻現(xiàn)象用 i2cset 往輸出寄存器 0x02 寫 0x55再用 i2cget 讀回結(jié)果全是 0xFF。很多人會懷疑芯片壞了其實(shí)罪魁禍?zhǔn)资桥渲眉拇嫫鞯膹?fù)位值。TCA6416A 上電后配置寄存器是 0xFF也就是所有引腳默認(rèn)輸入。在輸入模式下輸出寄存器的值并不會體現(xiàn)在引腳上讀回輸出寄存器時還是復(fù)位值。正確的操作順序是先往 0x06/0x07 配置寄存器寫方向再往 0x02/0x03 寫數(shù)據(jù)。很多從 PCF8574 移植過來的代碼沒有這個步驟直接照搬到 TCA6416A 就掉坑。這個坑的本質(zhì)是寄存器初始化順序和 I2C 時序沒半點(diǎn)關(guān)系。遇到回讀異常先想想配置寄存器有沒有寫對再懷疑總線波形。5.3 INT腳亂觸發(fā)或完全不觸發(fā)別忽略讀取清除機(jī)制INT 的問題分兩種極端。一是 INT 腳一直處于低電平MCU 外部中斷不斷進(jìn) ISR系統(tǒng)被中斷風(fēng)暴拖死。原因多半是配置觸發(fā)方式時用了電平觸發(fā)而讀取輸入寄存器之前 INT 一直有效。處理辦法是 MCU 端設(shè)成下降沿觸發(fā)ISR 里盡快讀一次 P0/P1 輸入寄存器把狀態(tài)存到全局緩存再退出。二是按了按鍵 INT 卻紋絲不動。這種情況先拿萬用表量 INT 腳有沒有上拉到 VCCI開漏輸出如果連上拉電阻都沒焊電平就懸空永遠(yuǎn)看不到低電平。補(bǔ)上上拉再看是否觸發(fā)。另外提醒一下如果同一個 I2C 總線上還掛了別的擴(kuò)展器讀自己的輸入寄存器時總線上的其他通信也可能干擾 INT 狀態(tài)判斷。設(shè)計階段把 INT 腳引到獨(dú)立 MCU 引腳上別和其他設(shè)備共用。6. 拿到TCA6416A.rar之后我習(xí)慣先做這幾件事最后說點(diǎn)個人習(xí)慣算是一個相對穩(wěn)妥的落地流程。不管壓縮包從哪來我不會解壓后直接開始畫板子。先把解壓內(nèi)容按“01_硬件 / 02_軟件 / 03_文檔 / 04_參考”分類歸檔然后打開數(shù)據(jù)手冊的寄存器表通讀一遍重點(diǎn)看復(fù)位值和默認(rèn)狀態(tài)接著核對原理圖庫和 PCB 封裝是否符合機(jī)械尺寸再用 i2c-tools 或者最小測試固件把芯片實(shí)際跑通最后才把這些東西集成到正式項(xiàng)目里。這流程看著多實(shí)際耗不了多少時間卻能省掉后面幾天的返工。特別是來源不明的包文件可能是舊版本的寄存器地址或引腳定義萬一有出入直接復(fù)用代碼容易出問題。把“驗(yàn)證”放在“集成”前面后續(xù)出問題的概率小很多。說到根上TCA6416A 不是個難搞的芯片復(fù)雜的是外圍細(xì)節(jié)地址引腳怎么接、上拉電阻取多大、INT 怎么清、配置寄存器有沒有先寫。這四個點(diǎn)控制住這個芯片基本就能穩(wěn)定工作在項(xiàng)目里。也建議你拿到包以后先別急著調(diào)業(yè)務(wù)邏輯先把這些基本功過一遍后面會順很多。本文還有配套的精品資源點(diǎn)擊獲取