試)
做嵌入式最怕的不是功能實現(xiàn)不了而是功能看起來實現(xiàn)了數(shù)據(jù)卻在悄悄騙你。前段時間做一塊多路環(huán)境監(jiān)測板需要采集32路模擬信號MCU內(nèi)部ADC只有3個通道最后選了CD74HC4067把單路ADC擴展成16路。板子跑起來之后采集值時靈時不靈通道之間還會“串味”加上一個藏得更深的上電初始化問題折騰了整整一個周末。這篇筆記把完整方案和兩個最折磨人的調(diào)試坑一次性說清楚給正在做多路模擬采集的朋友一個參考。先說結論CD74HC4067是模擬多路復用器4根地址線控制16路模擬開關的通斷把一路ADC物理上擴展成16路。方案本身不復雜但坑藏在信號建立時間和復位瞬間的引腳懸空里這兩件事數(shù)據(jù)手冊不會替你操心。1. 為什么我用CD74HC4067而不是換芯片重畫板1.1 通道不夠用時的三條路當時的情況是板子已經(jīng)畫完MCU選型已定3路ADC通道要覆蓋32路外部模擬量通道數(shù)完全不夠。擺在面前的無非三條路第一換一顆ADC通道更多的MCU。這是最徹底的方案但意味著重新選型、重畫PCB、重寫底層驅動項目周期直接多出兩到三周對于已經(jīng)定型的板子來說代價太大。第二外掛一顆多通道ADC芯片比如ADS11154路16位、ADS12568路24位這類。精度確實高還自帶PGA可編程增益放大器但需要I2C或SPI通信驅動代碼量大不少而且多路高速采樣時吞吐率受限用在低速傳感器采集場景有點殺雞用牛刀。第三就是本文的方案用CD74HC4067做模擬開關把MCU自帶的那一路ADC通過時分復用擴展到16路。每片不到兩塊錢接線不超過10根代碼只需要控制4個GPIO電平就能把一路ADC“變成”16路。我的選擇是第三種。原因很樸素改動最小邏輯最簡單用來采集NTC溫度、電池電壓、電位器位置這類更新率低于每秒幾十次的信號完全夠用。1.2 CD74HC4067在方案對比中的真實位置為了說清楚這個芯片的定位我做了一張選型對比表覆蓋當年糾結過的幾個候選方案方案通道數(shù)通信方式精度附加成本適合場景換高通道MCU視型號無高換料改板項目早期方案未定型ADS1115等外置ADC4~16I2C/SPI高帶PGA中通道少、要求高精度CD74HC4067模擬開關16/片并行GPIO控制受限于后級ADC低通道多、速度要求低多片4067級聯(lián)16×N并行GPIO片選受限于后級ADC低通道非常多、成本敏感4067的優(yōu)勢一句話就能概括它不是在“最高精度”方向上競爭而是在“用最少資源擴展最多通道”這個訴求上做到極致。它本身不參與模數(shù)轉換只是把16路模擬信號按順序“切”給后級的一路ADC轉換精度完全取決于MCU內(nèi)部ADC。1.3 一個關鍵邊界它不是萬能的同時必須說清楚4067的邊界否則后面調(diào)試起來容易誤判。首先它是單端模擬開關不是差分開關。每個被測信號都得有一個公共地參考如果信號是差分輸出或者浮地必須先把信號轉成單端再接進來。其次模擬輸入電壓范圍不能超過芯片電源范圍。CD74HC4067的VCC我用的是3.3V那所有通道輸入電壓必須落在GND到3.3V之間。如果通道上接了12V電平的信號內(nèi)部ESD保護二極管的漏電路徑會被導通輕則讀數(shù)嚴重偏移重則芯片損壞。再者它有導通電阻Ron典型值在幾十到一百多歐姆這會在信號鏈路里引入分壓誤差和建立時間延遲。處理辦法后面會詳細講但選型時就要有這個概念它適合連接輸出阻抗偏低的信號源不適合直接接高阻信號。2. 這塊芯片到底是怎么工作的2.1 內(nèi)部邏輯4根線控制16個開關CD74HC4067的內(nèi)部結構可以拆成兩部分一個4-16線譯碼器加上16個模擬開關MOSFET傳輸門。數(shù)字邏輯部分不參與模擬信號的路徑選擇它只負責把S0~S3這4根地址線的二進制編碼翻譯成“接通第幾號模擬開關”。比如S00、S10、S20、S30時COM端與C0通道導通S01、S10、S20、S30時COM端與C1通道導通。16個通道對應16種組合用4個GPIO的二進制編碼全部覆蓋這就是“4線控16路”的秘密。S3S2S1S0導通通道0000C00001C10010C2...............1111C15另外還有一個EN引腳低電平有效。EN接低電平芯片正常工作EN接高電平所有開關全部關斷COM端呈高阻態(tài)。這個引腳看似多余實際上是我第二個調(diào)試坑的救命稻草后面細說。有了這個邏輯結構采集16路信號的核心算法就很簡單把通道號拆成4個二進制位輸出到S0~S3等信號穩(wěn)定啟動一次ADC轉換讀回結果然后切下一路。2.2 從數(shù)據(jù)手冊里擠出來的關鍵參數(shù)有一說一CD74HC4067的數(shù)據(jù)手冊并不厚幾頁紙但參數(shù)全在字縫里得自己擠。以下是實際工作中最關鍵的幾個導通電阻RonVCC3.3V時實測在80~120Ω左右手冊給的是4.5V供電時典型70Ω。注意Ron會隨供電電壓下降而上升3.3V系統(tǒng)里按100Ω估算比較穩(wěn)妥。開關切換時間一般只有幾百納秒但這只是“開關動作完成”的時間不等于“信號穩(wěn)定”的時間。信號在開關閉合后還要經(jīng)過源阻抗、導線寄生電容、后級濾波電容組成的RC網(wǎng)絡慢慢爬升這個“穩(wěn)定時間”才是決定延時大小的關鍵我第一個調(diào)試坑就栽在這里。未選中通道的漏電流很小常溫下nA級別但懸空的未選中通道引腳就像一根小天線會通過寄生電容把噪聲耦合到COM端。所以硬件上最好給每路輸入一個到地的偏置電阻比如100kΩ讓懸空通道有個確定的電位防止噪聲累積。還有一點容易被忽略CD74HC4067是多路復用開關不是繼電器它不能隔離。開關斷開時是“高阻態(tài)”而不是真正“斷路”高阻態(tài)和斷路在絕大多數(shù)情況下效果一樣但耐壓、漏電特性完全不同。如果你用它來切換高壓信號務必確認斷開狀態(tài)下的耐壓等級足夠。2.3 關于導通電阻和懸空通道的認知誤區(qū)很多教程會告訴你4067有導通電阻建議選低阻信號源但沒說為什么。我來解釋一下假設某路信號源是電池電壓經(jīng)過100kΩ和100kΩ分壓得到的2.5V信號源等效內(nèi)阻就是兩個電阻的并聯(lián)值50kΩ。這個50kΩ串聯(lián)在信號路徑上和4067的100Ω導通電阻、后級ADC輸入阻抗構成分壓網(wǎng)絡。如果后級ADC輸入阻抗是1MΩ那ADC實際采到的電壓只有2.5V × 1MΩ / (50kΩ 100Ω 1MΩ) ≈ 2.38V直接偏了5%。更麻煩的不是分壓誤差而是建立時間。高內(nèi)阻信號源與后級寄生電容、濾波電容組成的一階RC低通時間常數(shù)大得驚人。如果你切換到這一路后立刻啟動ADC采到的根本不是一個穩(wěn)定值而是上一路電壓向當前電壓爬升過程中的一個中間值這就是“串擾”的根源。所以繞開認知誤區(qū)的做法有兩個一是降低信號源內(nèi)阻重要通道在進4067之前加一個運放電壓跟隨器二是軟件上留足穩(wěn)定延時讓RC網(wǎng)絡把電壓爬到足夠接近最終值再采樣。3. 硬件接線和采集時序的實際做法3.1 電路連接的最簡骨架先給出一份可以直接照抄的接線表MCU端我以STM32F103為例其他MCU同理CD74HC4067引腳接法C0~C1516路模擬輸入信號COMMCU的ADC輸入引腳如PA1S0~S3MCU任意4個GPIO如PA0~PA3ENMCU任意1個GPIO如PA4通過10kΩ上拉到3.3VVCC3.3V對地并聯(lián)10μF0.1μF去耦電容GND系統(tǒng)地幾點實際操作經(jīng)驗VCC和GND之間必須加去耦電容電源紋波會直接影響模擬開關的導通特性信號線盡量短遠離數(shù)字信號線避免數(shù)字邊沿通過寄生電容耦合到模擬通道如果需要采集的信號數(shù)量不到16路空閑通道用100kΩ電阻下拉到GND不要讓引腳懸空。3.2 軟件切換與ADC讀取的時序設計采集一路信號的軟件流程可以拆成四個步驟設置地址線電平、等待信號穩(wěn)定、啟動ADC轉換、讀取轉換結果。寫成流程串就是/* 偽代碼切換并讀取一路 */ void MUX_SelectChannel(uint8_t ch) { /* ch范圍0~15拆成二進制位輸出到地址線 */ HAL_GPIO_WritePin(GPIOA, MUX_S0_PIN, (ch 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MUX_S1_PIN, (ch 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MUX_S2_PIN, (ch 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MUX_S3_PIN, (ch 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); /* 留足穩(wěn)定時間后面細說這個值怎么來的 */ delay_us(5000); } static uint16_t ADC_ReadOnce(void) { HAL_ADC_Start(hadc); HAL_ADC_PollForConversion(hadc, 10); return (uint16_t)HAL_ADC_GetValue(hadc); }地址線電平設置順序沒有硬性要求但建議用位操作一次性賦值而不是逐根線修改因為逐根線修改會在切換過程中產(chǎn)生中間狀態(tài)。比如從通道3切換到通道5S0變化和S1變化之間可能有幾十納秒的時間差這時候芯片短暫選中了某個中間通道雖然時間極短但在高內(nèi)阻場景下可能引入毛刺。3.3 穩(wěn)定延時的計算思路穩(wěn)定延時到底設多少這個問題很多人直接抄教程寫個10μs實際上應該算出來。我當時的溫度采集通道NTC熱敏電阻和分壓電阻的等效內(nèi)阻約5kΩ4067導通電阻按100Ω計COM端對地有0.1μF濾波電容如果輸入端還加了RC濾波這個電容會更大。那么切換后信號的建立時間常數(shù)就是τ R_total × C_load (5kΩ 100Ω) × 0.1μF ≈ 510μsRC一階系統(tǒng)要穩(wěn)定到0.1%精度理論上需要約6.9倍時間常數(shù)也就是3.5ms。我當時軟件只給了100μs差了35倍采到的信號還處在指數(shù)爬升的早期階段數(shù)值自然不對。后面我把這個延時改成5ms同時把采樣結果連讀三次取中值問題立刻緩解了一大半。后來在硬件上給溫度通道加了運放跟隨器把源內(nèi)阻降到幾十歐整個時間常數(shù)縮小到幾十微秒級別延時改成200μs就綽綽有余了。所以延時不是一個“拍腦袋”的參數(shù)它取決于信號源等效內(nèi)阻、通道負載電容和你對精度的要求。嚴謹?shù)淖龇ㄊ悄檬静ㄆ骺床ㄐ瘟砍鰧嶋H穩(wěn)定時間再留出一到兩倍余量。4. 調(diào)試坑一通道串擾的完整排查鏈路4.1 現(xiàn)象13路里只有8路正常板子上用了兩片CD74HC4067擴展32路采集第一片接的是電池電壓、電流、環(huán)境溫度等信號工作正常第二片接了8路NTC溫度和幾路開關量電平結果這8路溫度普遍偏高且溫度顯示會隨著電池電壓的波動而上下跳動最高時偏了3度。一開始我還以為溫度傳感器本身有問題換了傳感器、重新標定、檢查分壓電阻都沒找到毛病。4.2 排查過程懷疑順序逐級排除第一個懷疑目標是ADC參考電壓。我往4067的第13通道接了一個標準1.5V電壓源讀回來的值是1.53V偏高但不離譜而且抖動明顯。我又把同一個1.5V直接接到MCU的ADC引腳繞過4067讀回來是穩(wěn)定的1.50V由此排除ADC和參考電壓的問題。第二個懷疑目標是芯片損壞。我把兩片4067對調(diào)了一下位置結果故障還是出現(xiàn)在原來的通道位置上也就是說這是“位置性”問題不是“芯片性”問題芯片本身是好的。第三個懷疑目標落到了信號源阻抗和建立時間上。我用示波器探到第13通道對應的COM引腳在切換瞬間能看到一條毫秒級的指數(shù)充放電曲線信號爬升的過程清晰可見。這時候才徹底明白問題出在“開關切換了但信號遠沒有穩(wěn)定”。4.3 根因拆解70Ω導通電阻和寄生電容組成了一階低通把這條鏈路展開看就一目了然NTC溫度傳感器的等效源內(nèi)阻約5kΩ加上4067的導通電阻約100Ω再碰上輸入端濾波電容和走線寄生電容構成的約0.1μF負載整個就是一個截止頻率僅幾百赫茲的低通濾波器。切換到該通道后信號不是瞬間跳變到目標電壓而是要按指數(shù)曲線慢慢爬升。我在爬升到三分之一的時候就啟動了ADC采樣讀到的自然是偏低且受前一路殘留電壓影響的錯誤值。至于為什么第一片4067上的8路都正常原因也很簡單那些通道的信號都經(jīng)過運放緩沖源內(nèi)阻只有幾十歐姆同樣100nF電容的時間常數(shù)只有幾微秒100μs的延時早就綽綽有余。同一個延時參數(shù)低阻信號源和高阻信號源的表現(xiàn)天差地別。4.4 解決與驗證硬件低阻化加上穩(wěn)定延時解決方案分兩步軟件硬件同時改第一步是軟件延時切換通道后的穩(wěn)定延時從100μs直接加到5ms并且對同一通道連采三次排序取中位數(shù)剔除ADC轉換中偶然出現(xiàn)的毛刺。這一步做完溫度讀數(shù)偏移從3度降到不到0.5度。第二步是硬件低阻化。在每路NTC溫度信號進入4067之前加一級運放電壓跟隨器把幾十kΩ的高阻信號轉成低阻信號。我用的是LMV321這一類的軌到軌單運放3.3V供電下輸出擺幅接近滿量程一片四路運放就能覆蓋四路溫度信號。硬件改完再把延時調(diào)回200μs全鏈路正常。項目修改前修改后切換延時100μs5ms硬件低阻化后可回到200μs采樣策略單次采樣三次取中值信號源內(nèi)阻約5kΩ約幾十Ω加跟隨器后溫度通道最大偏差約3度小于0.1度這個坑的價值在于它說明多路開關加高阻信號源是天然的“串擾放大器”只要源阻抗不降下來軟件調(diào)參只能是拆東墻補西墻。5. 調(diào)試坑二上電瞬間的隨機選通5.1 現(xiàn)象設備復位后偶發(fā)采集超限報警第二個坑比第一個更隱蔽它只在上電和復位的瞬間出現(xiàn)。設備每次上電后第一個采集周期偶爾會有通道讀數(shù)超過報警閾值系統(tǒng)誤報一次“超限”之后所有通道恢復正常再也抓不到異常。故障概率大概在20%到30%而且報警通道不固定今天報第3通道明天報第7通道完全沒有規(guī)律。這種偶發(fā)問題最麻煩因為它是“瞬態(tài)”的一旦系統(tǒng)跑起來一切又都正常常規(guī)打斷點、看日志的手段都很難定位。5.2 排查過程從觸發(fā)條件反推我先用串口調(diào)試助手把首周期16路的原始ADC值全部打印出來對比正常啟動和異常啟動的差異。結果發(fā)現(xiàn)異常啟動時首周期里會有一路ADC值明顯偏高幾乎是滿量程后面所有周期恢復正常。再結合“報警通道隨機”這個特征我判斷問題出在“上電瞬間4067選到了一個隨機通道”。但當時的硬件設計是EN引腳直接接地圖省事少用一個GPIOS0~S3由MCU的GPIO直連。問題就出在這里——MCU的GPIO在復位期間是浮空輸入沒有任何確定的電平輸出S0~S3上的電壓完全取決于上電順序和引腳內(nèi)部的寄生狀態(tài)4067就會隨機選通某個通道。如果恰好選到一直接著外部電壓的通道比如電池電壓分壓后的3V信號這個電平就直接通過COM端出現(xiàn)在了MCU的ADC引腳上觸發(fā)了首周期的超限報警。為了驗證這個推斷我用示波器同時抓MCU的3.3V電源軌和S0引腳的波形上電瞬間確實能看到S0在VCC爬升過程中出現(xiàn)了一段無規(guī)則抖動而不是干凈的0V或3.3V。這就是隨機選通的直接證據(jù)。5.3 根因GPIO復位態(tài)懸空4067隨機選通根因總結起來就是一句話MCU從上電復位到執(zhí)行main函數(shù)里GPIO初始化代碼之間存在幾十毫秒的時間窗口這個窗口內(nèi)GPIO寄存器還沒有配置引腳處于高阻浮空狀態(tài)。如果外設的“默認狀態(tài)”依賴MCU引腳來保證那這幾十毫秒就是失控窗口。CD74HC4067的EN如果直接接地芯片在上電瞬間就處于“使能且隨機選通”的狀態(tài)任何輸入信號都可能被直接串到ADC引腳上。更危險的是如果被隨機選中的通道恰好接的是高于3.3V的外部電源在MCU尚未配置好ADC引腳的過壓保護時這個電壓可能對引腳造成實質(zhì)性的過壓沖擊。我當時采樣的是分壓后的信號沒有超出3.3V所以只是誤報警沒有損壞芯片但隱患是實打實的。5.4 修復EN上拉、地址線下拉、軟件先置電平再操作對癥下藥硬件和軟件各改一處硬件上EN引腳加一顆10kΩ上拉電阻到3.3V保證芯片在上電和復位期間默認處于禁用狀態(tài)所有開關斷開COM端與任何通道都不導通。S0~S3每根地址線加一顆10kΩ下拉電阻到GND讓復位期間地址線被鉗位在低電平即使以后某次忘了配置GPIO芯片也只會“選擇通道0”而不是隨機選通。軟件上main函數(shù)最開頭、任何外設初始化之前就先配置4067相關的GPIO為推挽輸出并立即輸出一個確定電平S0~S3全部輸出低電平EN輸出高電平禁用狀態(tài)。全部配置完成后再把EN拉低使能芯片。這樣MCU代碼接管引腳的控制權之后芯片才被允許工作中間的失控窗口被完全覆蓋。改完后的驗證方式是連續(xù)上電20次、按復位鍵20次用串口監(jiān)控首周期采集值沒有再一次出現(xiàn)超限報警。示波器復測復位瞬間的S0~S3波形也已經(jīng)是干凈的低電平EN引腳在MCU接管之前始終為高。修改項修改前修改后EN引腳直接接地10kΩ上拉到3.3V由MCU控制S0~S3直連MCU GPIO無默認電平各加10kΩ下拉到GND軟件初始化順序先初始化ADC后操作GPIOmain函數(shù)最先強制GPIO輸出確定電平上電首周期誤報約20%~30%概率0這個坑的通用教訓是外設的默認態(tài)必須由硬件電阻保證不能依賴軟件的初始化速度。代碼執(zhí)行前那幾十毫秒所有懸空引腳都是隱患。6. 再往后可以做的幾個優(yōu)化6.1 高阻抗信號源前務必加緩沖第一個坑已經(jīng)充分說明了問題4067的導通電阻只是串擾的一部分真正的放大器是高阻抗信號源本身。凡是要接NTC、光敏電阻、電位器這類高阻傳感器設計階段就在前端加上運放跟隨器比事后調(diào)參省心一百倍。選運放時注意軌到軌輸出和足夠的帶寬LMV321、SGM8531這類微功耗軌到軌運放在3.3V系統(tǒng)里都很好用。6.2 輪詢策略與自檢通道16路通道輪詢時建議不要按固定順序從頭到尾刷而是把變化頻繁的重要通道比如電池電壓放在前面更新周期短的通道優(yōu)先采集。這樣即使系統(tǒng)忙到來不及刷新全部通道關鍵數(shù)據(jù)也不會過期太久。另一個實用技巧是留出一個空閑通道接一個精密基準電壓源比如TL431輸出的2.5V周期性采集一次并與標定值比對。如果偏差超過閾值就能提前發(fā)現(xiàn)4067的開關阻抗漂移、ADC參考電壓變化或焊點老化問題。這個小功能在長期運行的項目里價值極高用幾行代碼換一份可靠性保障。6.3 還能不能繼續(xù)擴展通道如果你覺得16路還不夠可以用多片4067做級聯(lián)。最簡單的做法是所有芯片的S0~S3并聯(lián)共用一個4位地址總線每個芯片的EN引腳由MCU的獨立GPIO控制。訪問時先選通對應芯片的EN再輸出通道地址。N片4067就能擴展成16×N路成本依然很低。如果想提高整體采集吞吐率還有一種思路MCU如果有兩個獨立的ADC可以用兩片4067分別接ADC1和ADC2兩路并行采集不同通道采樣周期縮短一半。這種改動只涉及硬件接線和軟件抽象層采集邏輯不需要大改。最后說一個我自己的調(diào)試習慣多路模擬開關電路出問題時示波器比萬用表重要得多。萬用表只能告訴你“現(xiàn)在電壓是多少”但示波器能告訴你“這個電壓是怎么爬上去的”也就是能看到開關切換的完整暫態(tài)過程。很多看似“玄學”的串擾問題波形一出來原因基本就擺在眼前了。希望這篇筆記里的兩個坑能幫你少走點彎路。