展STM32 ADC通道:16路模擬采集的硬件設(shè)計與調(diào)試復(fù)盤)
這塊板子是去年底開始做的當(dāng)時的需求其實很簡單一塊主控板要同時采集12路電池電壓和4路溫度信號滿打滿算16路模擬量。手上這顆STM32雖然自帶ADC但可用通道數(shù)掰著手指頭數(shù)都不夠總不能為了幾路模擬量去換一顆引腳更多的型號畢竟改硬件封裝、改PCB布局的代價實在太大。于是我把目光放到了CD74HC4067這顆16通道模擬多路復(fù)用器上——用4根GPIO就能把單路ADC變成16路成本幾毛錢原理也直白。方案聽起來很美好實際調(diào)試的過程中卻踩了兩個不大不小的坑折騰了兩個晚上才徹底搞明白。這篇筆記把整個設(shè)計思路、接線方式、驅(qū)動代碼以及那兩個坑的完整復(fù)盤寫下來給后面打算用同一顆芯片做ADC通道擴(kuò)展的朋友做個參考。整篇內(nèi)容適合正在做嵌入式采集電路、想用最小成本擴(kuò)展ADC通道的開發(fā)者也適合第一次用模擬多路復(fù)用器的新手。我會盡量把硬件選型、軟件時序和調(diào)試過程串成一條線來講盡量說人話碰到關(guān)鍵參數(shù)會直接給出計算過程和實測數(shù)據(jù)。1. 為什么選CD74HC4067擴(kuò)展ADC通道的三種思路對比1.1 需求場景復(fù)盤看似簡單實則尷尬的16路采集很多項目都有這種尷尬階段模擬量通道數(shù)量不上不下比MCU內(nèi)置ADC通道多幾個但又沒多到需要專門上一顆高精度采集芯片。我這個項目就是典型——12路電池電壓監(jiān)測加4路NTC溫度總共16路而主流STM32的ADC外設(shè)一般是10到16個外部通道去掉被占用的引腳真正能自由使用的可能只有6到8路。這個時候擺在面前的方案有三條換MCU、加外部ADC芯片、加模擬多路開關(guān)。換MCU意味著SDK、工程、PCB幾乎全要動而且為了多幾路ADC去選一顆高引腳封裝成本和時間都不劃算項目迭代的時候尤其忌諱這種傷筋動骨的做法。加外部ADC芯片倒是正經(jīng)路子但I(xiàn)2C接口的ADS1115一次轉(zhuǎn)換時間動輒幾毫秒SPI接口的高速ADC又貴還得額外處理參考電壓、轉(zhuǎn)換時序?qū)χ恍枰?2位精度的場景來說有點殺雞用牛刀。剩下就是模擬多路開關(guān)配合內(nèi)置ADC的方案。用MCU本身的ADC只是在ADC前端加一個開關(guān)分時選通16路信號每路輪流采一遍就當(dāng)16個ADC通道用。CD74HC4067就是干這個事的芯片16選1模擬開關(guān)導(dǎo)通電阻幾十歐姆開關(guān)時間幾十納秒級別的量級足夠配STM32這類內(nèi)置ADC使用。最終我選了這條路線理由很簡單改動最小成本最低原理最簡單。1.2 方案對比不同擴(kuò)展路線各自的取舍把三條路線放在一張表里看優(yōu)缺點就非常直觀了方案成本額外MCU引腳采樣率改動范圍適合場景換更多引腳MCU高0高原生多路ADC大原理圖和PCB都要改新項目設(shè)計階段I2C外部ADC如ADS1115中2I2C低幾百Hz中新增I2C從機(jī)低速高精度采集16位分辨率SPI外部ADC高4~5SPICS高M(jìn)Hz級別中要求高速同步采樣的場景CD74HC4067模擬開關(guān)極低5S0-S3EN中每通道幾kHz小前置開關(guān)通道多但單路速度要求不高這個表里的采樣率一欄需要特別說明。分時復(fù)用方案本質(zhì)上是用時間換通道16個通道輪流走同一個ADC每個通道實際能分到的采樣率大致是“單通道轉(zhuǎn)換速度除以16”。我這邊每路信號基本都是靜態(tài)電壓和緩變溫度單通道10kHz都算綽綽有余所以分時復(fù)用沒有任何問題。反過來如果你要采的是交流信號或者振動波形每通道有效采樣率低于20kHz就會明顯感到吃力這種情況下還是老老實實上真正的多ADC同步采集。1.3 CD74HC4067的工作原理與關(guān)鍵參數(shù)CD74HC4067本質(zhì)上是一個16選1的模擬開關(guān)矩陣屬于CMOS 4000系列邏輯家族的HC版本。片上有16個模擬開關(guān)輸入/輸出引腳編號CH0到CH15一個公共端通常標(biāo)注為COM還有4根地址選擇線S0、S1、S2、S3外加一根使能/禁止線EN有些手冊標(biāo)注為INH。S0到S3組成4位二進(jìn)制數(shù)0000到1111分別選中CH0到CH15EN為低電平時開關(guān)正常選通EN為高電平時所有通道全部斷開。關(guān)鍵參數(shù)里Design上最需要關(guān)注的是導(dǎo)通電阻Ron和開關(guān)時間。CD74HC4067在4.5V電源下Ron典型值約70Ω這個阻值在模擬信號鏈路里并不算小——如果傳感器輸出阻抗本身很高比如達(dá)到幾十千歐那么70Ω的Ron幾乎可以忽略但如果你直接接的是一個低阻抗源比如運放輸出或者穩(wěn)壓器輸出Ron就會和負(fù)載構(gòu)成分壓產(chǎn)生誤差。開關(guān)時間通常在幾十納秒級別芯片本身的“切換速度”不是瓶頸真正拖慢系統(tǒng)的是后面的外部RC充電時間這個稍后在坑1里面詳細(xì)展開。還有一個細(xì)節(jié)很多人一開始會忽略CD74HC4067的輸入模擬電壓范圍只能在VCC和VEE這個項目里VEE就是GND之間超過VCC或低于GND都會讓芯片內(nèi)部寄生的保護(hù)二極管正向?qū)ㄝp則讀數(shù)錯誤重則損傷芯片。所以當(dāng)你的信號本身有負(fù)電壓或者超過電源電壓的情況必須先做電平搬移或保護(hù)處理不能直接懟到芯片上。2. 硬件設(shè)計接線不是把芯片放上去就行2.1 引腳功能與最小接線方式CD74HC4067的封裝常見是SOIC-24和DIP-24管腳排列非常規(guī)律左邊12根右邊12根S0、S1、S2、S3和EN在芯片的一側(cè)COM在另一側(cè)的中部。給它通電特別簡單VCC接正電源GND接負(fù)電源模擬信號掛在CH0到CH15上公共端COM輸出接到MCU的ADC輸入引腳4根地址線由MCU的GPIO控制。我在畫原理圖時習(xí)慣給每個通道名標(biāo)成X1、X2這種絲印標(biāo)識比如CH0接BATT_1到BATT_12CH12到CH15接NTC1到NTC4這樣后期查線、寫驅(qū)動、看打印都方便。這個做法聽起來很基礎(chǔ)實際調(diào)試的時候能省不少事——尤其當(dāng)你用邏輯分析儀或者示波器抓波形時能一眼看出某根通道線確實是接到對應(yīng)傳感器上了。2.2 電源、邏輯電平和去耦電容的幾個細(xì)節(jié)電源和邏輯電平這兩個問題是我特別想提醒所有第一次用這顆芯片的人注意的。CD74HC4067的VCC范圍是2V到6V很多人想都不想直接接5V。如果你的MCU是5V系統(tǒng)這沒問題但如果你用的是3.3V的STM32事情就變得微妙起來。HC系列的輸入高電平門檻典型值是0.7×VCC在VCC5V時就是3.5V而3.3V單片機(jī)GPIO的高電平輸出通常只有3.0V到3.3V可能達(dá)不到這個門檻就會導(dǎo)致S0-S3的電平識別不穩(wěn)定開關(guān)通道偶發(fā)錯亂。我自己在調(diào)試初期就碰到過通道選擇的偶發(fā)跳變后來查數(shù)據(jù)手冊才反應(yīng)過來。解決辦法有兩個一是把CD74HC4067的VCC直接接到3.3V代價是模擬電壓輸入范圍也跟著變成0到3.3V并且Ron會略微增大到一百多歐姆二是換用CD74HCT4067HCT版本的輸入門檻是0.8V左右TTL兼容3.3V驅(qū)動5V供電毫無壓力。我在這個項目里最終采用了后者HCT版本選型完全兼容不用擔(dān)心電平不匹配。去耦電容這事雖然老生常談但確實見過有人直接跳過。VCC引腳旁邊必須放一個0.1μF的瓷片電容盡量貼近芯片放置高頻噪聲和通道切換帶來的電源毛刺都有明顯的抑制作用。模擬信號線和地址線在PCB上盡量短避免長距離平行走線造成通道間串?dāng)_這個是后面坑2的物理根源之一。2.3 輸入信號調(diào)理與阻抗匹配CD74HC4067不是神它只是個開關(guān)模擬信號是什么樣進(jìn)就是什么樣出不會幫你改善信號質(zhì)量。如果你的傳感器輸出的是高阻抗信號比如幾kΩ到幾十kΩ的分壓網(wǎng)絡(luò)建議在進(jìn)入多路開關(guān)之前加一個運放跟隨器把源阻抗降到很低。這樣有兩個好處一是降低外部阻容與被采信號的交互讓開關(guān)建立時間大幅縮短二是讓ADC采樣電容充電時有個低阻來源減少采樣誤差。我在實際電路上做了一層統(tǒng)一調(diào)理所有電池電壓先用精密電阻分壓到3.0V以內(nèi)再過一個運放跟隨器輸出直接給CD74HC4067的通道腳NTC電阻信號則先經(jīng)過一個固定電阻分壓成0到3.3V的電壓同樣經(jīng)跟隨器后進(jìn)入開關(guān)。成本增加不多但信號鏈路的穩(wěn)定性和一致性好了不止一個檔次。如果源阻抗實在降不下來軟件上就要用更長的采樣周期和更長的切換等待時間后面章節(jié)的時序分析會說明這個trade-off。3. 軟件驅(qū)動分時復(fù)用ADC的完整實現(xiàn)3.1 外設(shè)初始化GPIO和ADC的配置方式以STM32F103加HAL庫為例先初始化控制多路開關(guān)的GPIO。S0到S3加上EN一共5根線全部配置成推挽輸出、下拉EN初始化為低電平使能void Mux_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_PULLDOWN; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); /* EN拉低讓多路開關(guān)處于使能狀態(tài) */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_RESET); }ADC部分按常規(guī)配置即可我這里選擇PA1作為ADC12_IN1輸入單次轉(zhuǎn)換模式12位分辨率。因為信號是分時復(fù)用的同一時刻只轉(zhuǎn)換一路不需要開啟DMA用最樸素的輪詢讀取就好。DMA在多通道連續(xù)采集的語境下確實更高效但對分時復(fù)用的場景幫助有限還白白增加緩存管理的復(fù)雜度。3.2 通道切換與采樣代碼一個輪詢式的完整實現(xiàn)通道選擇的核心就是通過GPIO輸出4位二進(jìn)制地址void Mux_SelectChannel(uint8_t ch) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, (ch 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, (ch 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, (ch 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, (ch 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); }讀取任意一個通道的電壓值時先切換通道然后延時等待開關(guān)和外部RC電路穩(wěn)定再啟動ADC轉(zhuǎn)換。基礎(chǔ)版讀取函數(shù)如下uint16_t Mux_ReadChannel(uint8_t ch) { Mux_SelectChannel(ch); delay_us(50); /* 等待4067輸出穩(wěn)定 */ HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t value HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return value; }這個函數(shù)看起來沒毛病但如果你真這么寫大概率會踩到坑1——切換后延時50μs其實還不夠穩(wěn)或者說在某些輸入條件下讀到的值仍然不對。具體問題下面專門用一整節(jié)來講先在這里留個懸念。3.3 采樣率與延時平衡一個必須算清楚的賬軟件上能直接配置的參數(shù)主要是兩個切換后的等待延時以及ADC自身的采樣周期。這兩個值都會直接影響系統(tǒng)能跑多快而采樣周期長短又會帶來精度差異所以必須把賬算明白。以STM32F103為例ADC時鐘來自PCLK2經(jīng)過預(yù)分頻后的時鐘通常配置為12MHz。如果設(shè)置采樣周期為1.5個ADC時鐘一次12位轉(zhuǎn)換需要1.512.514個周期也就是約1.17μs如果采樣周期設(shè)為28.5個周期一次轉(zhuǎn)換需要41個周期約3.4μs。在高阻抗信號源場景下1.5周期的采樣時間通常不夠因為內(nèi)部采樣電容來不及充到目標(biāo)電壓實際采集值會偏低所以常規(guī)做法是用28.5周期以上。加上多路開關(guān)的切換延時單通道耗時大約就是“等待延時ADC轉(zhuǎn)換時間”。下面這張表按不同等待延時計算了16通道一輪掃描的周期方便直觀理解切換后等待延時單通道耗時采樣28.5周期16通道一輪耗時等效每通道采樣率0μs理論上3.4μs54.4μs18.4kHz5μs8.4μs134.4μs7.4kHz10μs13.4μs214.4μs4.7kHz20μs23.4μs374.4μs2.7kHz50μs53.4μs854.4μs1.2kHz對于我的電池電壓和NTC溫度采集項目每通道1kHz以上完全夠用所以延時給20μs到50μs都很寬裕。如果你的源阻抗很低跟隨器輸出通常小于1Ω那么延時可以大膽壓到5μs甚至更短換取更高的掃周期。最關(guān)鍵的一點是先看信號源阻抗再決定等待延時而不是隨便拍一個值。3.4 采樣濾波先剔除壞值再做均值模擬開關(guān)分時復(fù)用有一個先天弱點通道切換瞬間信號鏈路上會有毛刺和瞬態(tài)電壓。哪怕等待時間足夠長ADC轉(zhuǎn)換結(jié)果偶爾也會跳出幾個離譜的野值。直接拿這些值參與控制會很危險尤其電池電壓監(jiān)測這種場景一個錯誤的高電壓值可能觸發(fā)保護(hù)邏輯誤動作。常見的處理辦法是“去極值平均濾波”每通道連續(xù)采樣7次去掉一個最大值和一個最小值剩下的5個值做算術(shù)平均。這個算法實現(xiàn)很簡單卻能同時濾掉脈沖噪聲和隨機(jī)噪聲uint16_t Mux_ReadChannelFiltered(uint8_t ch) { uint16_t buf[7]; uint16_t min_val, max_val; uint32_t sum 0; int i; Mux_SelectChannel(ch); delay_us(20); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); (void)HAL_ADC_GetValue(); /* 第一次丟棄 */ HAL_ADC_Stop(hadc1); for (i 0; i 7; i) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); buf[i] HAL_ADC_GetValue(); HAL_ADC_Stop(hadc1); delay_us(2); } min_val buf[0]; max_val buf[0]; for (i 1; i 7; i) { if (buf[i] min_val) min_val buf[i]; if (buf[i] max_val) max_val buf[i]; } sum 0; for (i 0; i 7; i) { if (buf[i] ! min_val buf[i] ! max_val) sum buf[i]; } return (uint16_t)(sum / 5); }這里有幾處細(xì)節(jié)值得說明。第一切換通道后我先做了一次ADC轉(zhuǎn)換并丟棄結(jié)果目的就是讓信號鏈路完成一次完整的充放電過程第二每次轉(zhuǎn)換之間加2μs左右的間隔給ADC內(nèi)部采樣保持電容一個再充電的時間第三去極值平均只適合信號變化速度遠(yuǎn)低于掃描周期的場景如果采樣的是快速變化的波形建議改成滑動平均或者卡爾曼濾波否則會明顯鈍化信號響應(yīng)。4. 兩個調(diào)試坑的完整復(fù)盤4.1 坑1切換通道后立即采樣讀回的是上一個通道殘留電壓這個坑的項目現(xiàn)場表現(xiàn)實在太典型了必須詳細(xì)寫出來。我第一版驅(qū)動程序?qū)懲暌院笤陂_發(fā)板上做了個簡單測試把16個通道分別接不同的分壓電壓然后依次掃描打印。打印出來的數(shù)據(jù)讓我一頭霧水——每個通道讀數(shù)都接近上一個通道的電壓比如上一個是3.3V下一個明明是0V第一次讀到的還是接近3.3V的值要連讀幾次才慢慢降下來。一開始我懷疑是S0到S3的接線順序錯了因為如果地址線接反通道映射就會錯亂讀到的也會是“錯誤通道”的值。用萬用表一根根測過GPIO和芯片引腳之間的通斷確認(rèn)沒有虛焊和短路。又用邏輯分析儀抓了S0-S3的波形每一輪掃描的地址碼完全正確該是哪一路就是哪一路。接著我懷疑是不是ADC配置的問題比如是不是用了DMA緩存導(dǎo)致數(shù)據(jù)錯位或者轉(zhuǎn)換模式配成了連續(xù)轉(zhuǎn)換。翻代碼檢查全部都是單次轉(zhuǎn)換這個方向也不對。后來我直接把示波器探頭接到了CD74HC4067的COM輸出引腳上在GPIO切換通道的那一刻盯著波形看謎底一下就揭開了。示波器上的現(xiàn)象是切換地址線之后COM引腳上的電壓并沒有立刻跳到目標(biāo)電壓而是從舊電壓開始沿著一條RC充電曲線緩緩爬向新電壓。爬的過程大約要幾十微秒才能穩(wěn)定到目標(biāo)值附近。這個現(xiàn)象根本原因是CD74HC4067內(nèi)部不是理想開關(guān)它等效成一個約70Ω的導(dǎo)通電阻串在信號路徑上而COM節(jié)點對地還存在寄生電容十幾pF量級。如果輸入信號源的輸出阻抗本身也比較高比如我早期測試時直接用一個10kΩ電阻分壓器提供電壓那么等效RC時間常數(shù)就有10kΩ乘以100pF左右大約是1μs。按RC充電的規(guī)律要穩(wěn)定到0.1%精度需要約7個時間常數(shù)也就是7μs穩(wěn)定到0.05%需要更久。但為什么我延時了50μs還不穩(wěn)因為這里還疊了一個因素STM32內(nèi)部ADC的采樣保持電容在啟動轉(zhuǎn)換瞬間會從COM線上吸取電荷如果COM線上的驅(qū)動源阻抗偏大采樣電容充電也會不充分導(dǎo)致每次讀取的值偏低且逐漸漂移。兩個RC串聯(lián)在一起等效充放電時間就更長了。數(shù)據(jù)手冊上STM32的ADC采樣周期最長可以配置到239.5個周期約20μs就是為高阻抗源準(zhǔn)備的。搞清楚原因解決思路就清晰了第一在硬件上盡量降低源阻抗運放跟隨器是最有效的辦法。第二軟件上把切換后的等待延時加大并且一定要丟棄切換后的第一次轉(zhuǎn)換值。實際修正后的采樣函數(shù)如下uint16_t Mux_ReadChannelStable(uint8_t ch) { uint16_t dummy; Mux_SelectChannel(ch); delay_us(50); /* 等待外部RC徹底穩(wěn)定 */ HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); dummy HAL_ADC_GetValue(); /* 丟棄僅用于讓信號鏈路完成充放電 */ HAL_ADC_Stop(hadc1); delay_us(5); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t value HAL_ADC_GetValue(); HAL_ADC_Stop(hadc1); return value; }這套修改之后用10kΩ源阻抗做測試讀數(shù)從“完全不能用”變成了“誤差穩(wěn)定在5mV以內(nèi)”。后來在運放跟隨器后端再測誤差更是小于1mV。這個坑的經(jīng)驗可以總結(jié)成一句大白話模擬開關(guān)切換后給信號鏈路一點“呼吸”時間再讀數(shù)不要催它。4.2 坑2未選中通道懸空導(dǎo)致串?dāng)_讀數(shù)亂跳還帶“鄰居效應(yīng)”第二個坑比第一個更隱蔽如果只靠軟件調(diào)試可能排查很久都定位不到根因。現(xiàn)象是這樣16個通道里只接了12路傳感器CH12到CH15四個通道因為暫時沒有傳感器就讓它們懸著沒有接任何線。程序里掃描完12個真實通道之后會順帶把這四個懸空通道的ADC值也打印出來做調(diào)試觀察。打印結(jié)果讓我非常困惑——懸空通道的數(shù)值在0到4095之間瘋狂跳動完全沒有任何規(guī)律。更離譜的是其中某一個懸空通道的讀數(shù)會跟著相鄰?fù)ǖ赖碾妷鹤兓兓?。比如我在CH12懸空、CH11上接了一個2.5V的電壓源當(dāng)我把CH11的電壓調(diào)高到3V時CH12讀到的值明顯也跟著跳高了仿佛CH12和CH11之間有某種看不見的導(dǎo)線。最初的排查方向完全是沖著PCB去的。我懷疑是板上焊盤殘留助焊劑導(dǎo)致漏電先用洗板水反復(fù)清洗又拿萬用表測CH11和CH12兩個焊盤之間的絕緣電阻測得兆歐級別的阻抗理論上不該造成那么明顯的串?dāng)_。我也懷疑過是不是通道選擇地址線毛刺導(dǎo)致開關(guān)瞬時誤選通但邏輯分析儀上抓到的地址信號非常干凈。后來查了CD74HC4067的數(shù)據(jù)手冊在開關(guān)特性那一欄里有一項參數(shù)叫OFF-state isolation中文叫關(guān)斷隔離度。這個參數(shù)描述的是開關(guān)斷開狀態(tài)下從輸入到輸出之間的信號泄漏程度。數(shù)據(jù)手冊給出的典型值是1MHz條件下約-50dB看起來抑制比不算差但在懸空的高阻抗節(jié)點上這個泄漏信號已經(jīng)足夠產(chǎn)生肉眼可見的電壓波動了。繼續(xù)順著這個思路分析當(dāng)CH11被選通時CH11引腳上的信號通過芯片內(nèi)部的寄生電容耦合到COM節(jié)點這部分信號是正常的。問題出在CH12這個“斷開但懸空”的引腳上。CH12引腳本身與COM之間也有斷開的MOS管和寄生電容當(dāng)COM上的電壓因為CH11信號而變化時CH12引腳會被反向感應(yīng)出電壓。因為CH12引腳懸空、沒有外部負(fù)載把它拉到一個確定電位這個感應(yīng)電壓就自由地暴露在電場和噪聲中讀數(shù)自然亂跳。同時CH11和CH12兩個引腳在芯片內(nèi)部物理位置相鄰它們之間也有微小的耦合電容這就解釋了為什么CH12會跟隨CH11的電壓變化。找到根因后解決方案有兩個層面。硬件層面最簡單粗暴把所有暫時不用的通道引腳全部直接接地。如果這些通道未來還可能復(fù)用再在這根線上加一個100kΩ下拉電阻接地既能把懸空節(jié)點固定在已知電位又不會因為下拉電阻太小而影響將來接傳感器時的信號精度。我最終在板上給每個未使用通道都預(yù)留了0歐姆電阻位需要啟用時把電阻換成傳感器線不用時讓0歐姆電阻接地靈活處理。軟件層面也做了一道保險輪詢掃描時跳過所有未啟用通道不讓懸空通道的異常數(shù)據(jù)進(jìn)入后續(xù)的電壓計算和控制邏輯。因為即使硬件上全部接地了在量產(chǎn)板或調(diào)試過程中難免出現(xiàn)漏接、接觸不良的情況軟件屏蔽這道防線能兜底。這個坑給我最深的教訓(xùn)是模擬開關(guān)的“斷開”和“隔離”是相對的永遠(yuǎn)不要把MOSFET斷開狀態(tài)想象成物理上的絕對開路更不要讓高阻抗節(jié)點懸空著暴露在電磁環(huán)境中。5. 進(jìn)階優(yōu)化與個人體會5.1 ADC偏移校正與三點校準(zhǔn)CD74HC4067本身是模擬開關(guān)不引入增益誤差但它前面信號鏈路上的分壓電阻精度、運放失調(diào)電壓、參考電壓偏差都會疊加到最終結(jié)果上。單純依賴ADC的12位分辨率很容易出現(xiàn)“采集值很穩(wěn)定但絕對不準(zhǔn)”的情況比如實際2.500V讀回來只有2.470V。對于電壓采集類應(yīng)用推薦做一次“外部ADC三點校準(zhǔn)”。具體做法是用精密電壓源或者高精度萬用表給信號鏈路輸入三個已知電壓點比如0.000V、1.650V和3.300V分別記錄下對應(yīng)的ADC原始值n0、n1、n2。如果ADC在三個點之間基本呈線性就做一個線性映射#define CAL_N1 2048 #define CAL_N2 4095 #define CAL_V1 1.650f #define CAL_V2 3.300f float AdcToVoltageLinear(uint16_t adc_raw) { float k (CAL_V2 - CAL_V1) / (float)(CAL_N2 - CAL_N1); float b CAL_V1 - k * CAL_N1; return k * (float)adc_raw b; }如果三個點的實測數(shù)據(jù)表明ADC本身存在明顯非線性那就用兩段線性擬合或者二次多項式擬合。分段線性擬合就是把0至n1、n1至n2兩段分別求系數(shù)雖然處理邏輯多幾行但12位ADC在這種場景下已經(jīng)足夠。校準(zhǔn)之后的誤差一般能控制在0.1%以內(nèi)對于電池電壓監(jiān)測綽綽有余。5.2 對CD74HC4067擴(kuò)展方案的幾個擴(kuò)展建議如果只是擴(kuò)展低速模擬量通道CD74HC4067這套方案已經(jīng)足夠完善。但結(jié)合我這次的經(jīng)驗還有幾點想補充一是關(guān)于掃描觸發(fā)方式。如果你的主循環(huán)比較忙建議用定時器每20ms觸發(fā)一次“全通道掃描”函數(shù)而不是在while循環(huán)里無腦輪詢。這樣可以保證每個通道的采樣間隔相對均勻后續(xù)做濾波和變化率檢測時數(shù)據(jù)質(zhì)量會好很多。二是關(guān)于隔離和濾波。如果應(yīng)用環(huán)境電磁干擾較強(qiáng)可以在CD74HC4067的COM輸出端加一個小電容到地比如1nF構(gòu)成簡單的低通濾波對抑制毛刺有明顯效果。注意電容不能太大否則又會顯著增加建立時間回到坑1的坑里。三是對單片機(jī)選型的提示。如果MCU是GD32、AT32這類國產(chǎn)ARM核ADC外設(shè)結(jié)構(gòu)和STM32基本類似上面的代碼可以直接移植。唯一要注意的是個別型號需要在ADC轉(zhuǎn)換前先執(zhí)行校準(zhǔn)否則零點偏移會比較明顯這一點務(wù)必以具體型號的數(shù)據(jù)手冊為準(zhǔn)。5.3 最后再分享一點實操體會兩個坑踩下來我最強(qiáng)烈的感受是模擬前端調(diào)試不能只靠軟件邏輯推演示波器永遠(yuǎn)是第一排查工具。第一次遇到坑1的時候如果我沒有直接看COM引腳的波形很可能會在ADC配置和DMA緩存上耗掉一下午。而坑2如果不去查數(shù)據(jù)手冊里的關(guān)斷隔離參數(shù)光靠軟件屏蔽懸空通道也是治標(biāo)不治本換了新板卡又會復(fù)現(xiàn)。另外一點是模擬多路開關(guān)的文檔里那些看起來毫不起眼的參數(shù)比如Ron、關(guān)斷隔離度、輸入電壓范圍在實際電路里每一個都會變成真實存在的問題。選型的時候多花十分鐘把數(shù)據(jù)手冊的“開關(guān)特性”這一節(jié)啃完比上了板再折騰要劃算得多。這套CD74HC4067擴(kuò)展16路ADC的方案現(xiàn)在已經(jīng)在我的板子上穩(wěn)定跑了兩個多月每天采集所有電池電壓和溫度數(shù)據(jù)穩(wěn)定在預(yù)期精度范圍內(nèi)。后續(xù)如果需求變化比如某一路要改采樣更高頻的信號我會給那一串通道單獨加一個ADC或者改用雙多路開關(guān)并聯(lián)的做法到那時候再寫一篇新的調(diào)試筆記來記錄。希望這篇分享能幫到正在調(diào)CD74HC4067或者其他模擬多路開關(guān)的朋友少走一點彎路。