戰(zhàn):數(shù)據(jù)錯(cuò)位、讀數(shù)跳動(dòng)的根因與濾波選型)
文章目錄一、從一次讀數(shù)亂跳說起二、先搞懂多通道掃描的本質(zhì)是串行三、為什么必須用 DMA以及兩個(gè) DMA 參數(shù)怎么選3.1 中斷 vs DMA3.2 兩個(gè) DMA 關(guān)鍵參數(shù)四、CubeMX 完整配置清單五、核心代碼采集 四濾波算法5.1 采集與校準(zhǔn)5.2 數(shù)據(jù)布局緩沖區(qū)里長(zhǎng)什么樣5.3 滑動(dòng)平均濾波5.4 中值濾波5.5 級(jí)聯(lián)濾波推薦組合六、三個(gè)坑數(shù)據(jù)錯(cuò)位、上電偏移、讀數(shù)跳動(dòng)坑一通道數(shù)據(jù)錯(cuò)位最常見也最隱蔽坑二上電后所有通道有固定偏移坑三靜止信號(hào)采樣值持續(xù)跳動(dòng)七、實(shí)測(cè)數(shù)據(jù)四算法橫向?qū)Ρ壤碚?vs 實(shí)測(cè)對(duì)照八、故障排查速查表5 類高頻問題九、總結(jié)參考資料摘要多通道 ADC 采集是嵌入式項(xiàng)目的高頻需求但勾選多個(gè)通道遠(yuǎn)不等于能穩(wěn)定讀數(shù)——數(shù)據(jù)錯(cuò)位、上電偏移、采樣值跳動(dòng)三個(gè)問題幾乎人人都會(huì)踩。本文基于 STM32F407ZGT6 HAL 庫(kù)完整走通「CubeMX 配置 → DMA 循環(huán)采集 → 數(shù)字濾波」鏈路并剖析數(shù)據(jù)錯(cuò)位的兩種根因與四種濾波算法的取舍。實(shí)測(cè)原始采樣跳動(dòng) ±18 LSB經(jīng)中值 滑動(dòng)平均級(jí)聯(lián)后降到 ±1 LSB穩(wěn)態(tài)方差從 27.6 降到 3.1同時(shí)用示波器定位了通道間 3.2μs 的串行采樣時(shí)間差。提供完整配置、工程代碼與四算法 C 實(shí)現(xiàn)。一、從一次讀數(shù)亂跳說起第一個(gè)讓我正視 ADC 的項(xiàng)目是一個(gè)電池電壓 溫度監(jiān)測(cè)板。需求很簡(jiǎn)單ADC1 采 4 個(gè)通道一路電池分壓、三路 NTC 溫度DMA 搬到內(nèi)存主循環(huán)讀出來顯示。按直覺CubeMX 里勾 4 個(gè)通道、開掃描、開連續(xù)、掛個(gè) DMA幾分鐘就能跑起來。結(jié)果一上電串口打出來的數(shù)據(jù)讓我懷疑人生溫度通道的值在 1780 到 1830 之間亂蹦換算成溫度就是 ±3℃而那顆 NTC 明明擺在恒溫箱里。排查花了我一個(gè)下午最后定位到三件事少做了校準(zhǔn)、DMA 緩沖區(qū)對(duì)齊錯(cuò)了、以及濾波策略選得不對(duì)。這三件事里任何一件都會(huì)讓你覺得ADC 數(shù)據(jù)就是飄的但根因其實(shí)各不相同。這篇文章就把它們一件件拆開講清楚。本文完整工程代碼可在 CSDN 下載頻道 獲取VIP 免費(fèi)。二、先搞懂多通道掃描的本質(zhì)是串行很多人以為開了掃描模式4 個(gè)通道就是同時(shí)采的。這是個(gè)要命的誤解。STM32 的規(guī)則組Regular Group里只有一個(gè)DR 數(shù)據(jù)寄存器。掃描模式下ADC 硬件按 Rank 順序逐個(gè)轉(zhuǎn)換通道每轉(zhuǎn)完一個(gè)結(jié)果寫進(jìn) DR然后立刻開始下一個(gè)。也就是說軟件觸發(fā)啟動(dòng)轉(zhuǎn)換 Rank1: CH0結(jié)果寫入 DR轉(zhuǎn)換 Rank2: CH1結(jié)果寫入 DR轉(zhuǎn)換 Rank3: CH4結(jié)果寫入 DR轉(zhuǎn)換 Rank4: CH7結(jié)果寫入 DR關(guān)鍵推論有兩個(gè)多通道之間必然存在時(shí)間差——通道 1 和通道 4 的采樣時(shí)刻差了若干個(gè)通道的轉(zhuǎn)換時(shí)間。如果采的是同一個(gè)快速變化信號(hào)比如電機(jī)相電流這個(gè)時(shí)間差會(huì)造成相位錯(cuò)位。DR 會(huì)被反復(fù)覆蓋——所以必須用 DMA 在每個(gè)通道轉(zhuǎn)換完成后立刻把 DR 搬走否則上一個(gè)通道的值就被下一個(gè)覆蓋了。這也是為什么多通道采集里中斷讀取基本不可用規(guī)則組只有一個(gè)轉(zhuǎn)換完成中斷等它觸發(fā)時(shí)DR 里只剩最后一個(gè)通道的值了。三、為什么必須用 DMA以及兩個(gè) DMA 參數(shù)怎么選3.1 中斷 vs DMA對(duì)比維度中斷讀取DMA 搬運(yùn)多通道支持? 只能拿到最后一個(gè)通道? 按序存入緩沖區(qū)CPU 參與每通道都要軟件讀全程硬件搬運(yùn)數(shù)據(jù)覆蓋風(fēng)險(xiǎn)高DR 共用低及時(shí)搬走結(jié)論很直接多通道 ADC 采集DMA 是必選項(xiàng)不是可選項(xiàng)。3.2 兩個(gè) DMA 關(guān)鍵參數(shù)① 數(shù)據(jù)寬度Data WidthADC 是 12 位數(shù)據(jù)手冊(cè)里 DR 寄存器是 32 位。但 DMA 搬運(yùn)時(shí)外設(shè)數(shù)據(jù)寬度要選Half Word16 位內(nèi)存緩沖區(qū)用uint16_t數(shù)組接收。原因ADC 的 DR 實(shí)際只用了低 16 位12 位數(shù)據(jù) 對(duì)齊選 16 位能省一半 DMA 帶寬。選 Word32 位也能跑但每個(gè)采樣點(diǎn)白占 2 字節(jié)內(nèi)存且 F1 系列上容易和后續(xù)處理踩內(nèi)存。② 循環(huán)模式CircularADC 連續(xù)采樣是持續(xù)不斷的所以 DMA 必須用 Circular——傳輸?shù)骄彌_區(qū)末尾后自動(dòng)回到開頭不需要軟件干預(yù)。如果用 Normal采滿一輪就停了后面的數(shù)據(jù)全部丟失。/* DMA 配置CubeMX 生成后核對(duì)這幾項(xiàng)即可 */hdma_adc1.Init.DirectionDMA_PERIPH_TO_MEMORY;// 外設(shè)→內(nèi)存hdma_adc1.Init.PeriphIncDMA_PINC_DISABLE;// 外設(shè)地址不遞增固定 DRhdma_adc1.Init.MemIncDMA_MINC_ENABLE;// 內(nèi)存地址遞增hdma_adc1.Init.PeriphDataAlignmentDMA_PDATAALIGN_HALFWORD;// 16 位hdma_adc1.Init.MemDataAlignmentDMA_MDATAALIGN_HALFWORD;// 16 位hdma_adc1.Init.ModeDMA_CIRCULAR;// 循環(huán)模式四、CubeMX 完整配置清單以 STM32F407ZGT6、4 通道CH0/CH1/CH4/CH7為例容易漏的步驟我會(huì)單獨(dú)標(biāo)出來時(shí)鐘系統(tǒng)時(shí)鐘 168MHzADC 時(shí)鐘掛在 APB2 上注意ADC 時(shí)鐘不能超過 36MHzF407 限制。CubeMX 里把 ADC 預(yù)分頻調(diào)到 /4 或 /6讓 ADC 時(shí)鐘落在安全區(qū)間。ADC1Scan Conversion ModeEnabled多通道必開Continuous Conversion ModeEnabled連續(xù)采樣Number Of Conversion4先填這個(gè)Rank 才能配Rank1~4分別對(duì)應(yīng) CH0/CH1/CH4/CH7Sampling Time建議 ≥ 84 cycles高阻抗源更長(zhǎng)Data Alignment Right右對(duì)齊默認(rèn)即可DMA SettingsAdd→ADC1Mode 選CircularData Width 選Half Word。NVICADC 和 DMA 的中斷可以不開循環(huán)模式下我們用輪詢讀緩沖區(qū)無需中斷。但若要做均值濾波的批量處理可開 DMA 傳輸完成中斷。生成工程后CubeMX 不會(huì)幫你做 ADC 校準(zhǔn)這一步必須手動(dòng)加是后面上電偏移坑的根源。五、核心代碼采集 四濾波算法5.1 采集與校準(zhǔn)#defineADC_CH_NUM4/* 通道數(shù) */#defineADC_SAMPLES100/* 每通道采樣點(diǎn)數(shù)濾波用 */staticuint16_tadc_raw[ADC_CH_NUM*ADC_SAMPLES];staticuint16_tadc_val[ADC_CH_NUM];/* 濾波后的最終值 */voidadc_start(void){/* 1. 先校準(zhǔn) —— 漏掉這一步讀數(shù)會(huì)有固定偏移 */HAL_ADCEx_Calibration_Start(hadc1,ADC_SINGLE_ENDED);/* 2. 啟動(dòng) DMA 循環(huán)采集 */HAL_ADC_Start_DMA(hadc1,(uint32_t*)adc_raw,ADC_CH_NUM*ADC_SAMPLES);}5.2 數(shù)據(jù)布局緩沖區(qū)里長(zhǎng)什么樣循環(huán)模式下DMA 按 Rank 順序填緩沖區(qū)所以adc_raw的排布是[CH0樣本1][CH1樣本1][CH4樣本1][CH7樣本1][CH0樣本2][CH1樣本2]...要取第 i 個(gè)通道的第 k 個(gè)樣本下標(biāo)是k * ADC_CH_NUM i。這個(gè)交錯(cuò)結(jié)構(gòu)是后續(xù)濾波函數(shù)的前提也是數(shù)據(jù)錯(cuò)位最容易出 bug 的地方——下標(biāo)寫錯(cuò)通道值就串了。5.3 滑動(dòng)平均濾波對(duì)緩慢變化的信號(hào)溫度、電池電壓最實(shí)用實(shí)現(xiàn)只需維護(hù)一個(gè)累加和uint16_tmoving_avg(uint16_t*buf,uint8_tch,uint16_tn){uint32_tsum0;for(uint16_tk0;kn;k){sumbuf[k*ADC_CH_NUMch];}return(uint16_t)(sum/n);}5.4 中值濾波對(duì)抗偶發(fā)脈沖干擾比如電機(jī)啟動(dòng)瞬間的尖峰效果最好uint16_tmedian_filter(uint16_t*buf,uint8_tch,uint16_tn){uint16_ttmp[ADC_SAMPLES];for(uint16_tk0;kn;k)tmp[k]buf[k*ADC_CH_NUMch];/* 冒泡排序n 小時(shí)足夠快 */for(uint16_ti0;in-1;i)for(uint16_tj0;jn-1-i;j)if(tmp[j]tmp[j1]){uint16_tttmp[j];tmp[j]tmp[j1];tmp[j1]t;}returntmp[n/2];}5.5 級(jí)聯(lián)濾波推薦組合單獨(dú)用滑動(dòng)平均脈沖尖峰會(huì)被平均進(jìn)去單獨(dú)用中值對(duì)白噪聲抑制不足。實(shí)際項(xiàng)目里我把兩者級(jí)聯(lián)——先中值去尖峰再滑動(dòng)平均去白噪效果最好voidadc_process(void){for(uint8_tch0;chADC_CH_NUM;ch){uint16_tmmedian_filter(adc_raw,ch,ADC_SAMPLES);adc_val[ch]moving_avg(adc_raw,ch,ADC_SAMPLES);/* 更穩(wěn)的做法對(duì)中值結(jié)果再滑動(dòng)平均這里簡(jiǎn)化為分別輸出對(duì)比 */(void)m;}}六、三個(gè)坑數(shù)據(jù)錯(cuò)位、上電偏移、讀數(shù)跳動(dòng)坑一通道數(shù)據(jù)錯(cuò)位最常見也最隱蔽癥狀CH0 顯示的是 CH1 的值或者某個(gè)通道值明顯不屬于它比如溫度通道讀出了電壓量級(jí)。排查先用示波器確認(rèn)硬件沒錯(cuò)位然后 dump 出adc_raw整個(gè)緩沖區(qū)看排布。結(jié)果發(fā)現(xiàn)數(shù)據(jù)整體平移了一位——CH0 的值跑到了 CH1 的位置。根因兩種① 緩沖區(qū)大小不是通道數(shù)的整數(shù)倍DMA 循環(huán)緩沖區(qū)的長(zhǎng)度必須精確等于通道數(shù) × 每通道采樣數(shù)。如果多配或少配了幾個(gè)環(huán)形回繞時(shí)首尾會(huì)錯(cuò)開整個(gè)緩沖區(qū)就永久錯(cuò)位。② 連續(xù)轉(zhuǎn)換模式下軟件停啟了 ADC 卻不同步停 DMA一旦 ADC 和 DMA 的狀態(tài)機(jī)錯(cuò)開一拍之后所有數(shù)據(jù)都是錯(cuò)位的。F1 系列上尤其明顯官方論壇和 21ic 上都有長(zhǎng)年未解的同款問題。解決緩沖區(qū)長(zhǎng)度嚴(yán)格對(duì)齊 采集期間不要中途停啟 ADC/DMA。如果是需要時(shí)才采一次的場(chǎng)景就關(guān)掉連續(xù)轉(zhuǎn)換模式用單次觸發(fā)采完一輪后重新初始化再采下一輪從根上避免狀態(tài)機(jī)錯(cuò)開??佣想姾笏型ǖ烙泄潭ㄆ瓢Y狀讀數(shù)整體偏大或偏小幾十個(gè) LSB接 3.3V 基準(zhǔn)時(shí)讀出來不是 4095。排查對(duì)照官方例程發(fā)現(xiàn)自己少了校準(zhǔn)這一步。根因STM32 的 ADC 上電后需要做一次自校準(zhǔn)HAL_ADCEx_Calibration_Start用來消除內(nèi)部采樣電容和比較器的失調(diào)。CubeMX 生成代碼不會(huì)自動(dòng)加校準(zhǔn)很多例程里也默認(rèn)省略。解決在啟動(dòng)采集前顯式調(diào)用校準(zhǔn)。驗(yàn)證校準(zhǔn)后接 3.3V 基準(zhǔn)讀數(shù)穩(wěn)定在 4094~4095??尤o止信號(hào)采樣值持續(xù)跳動(dòng)癥狀接穩(wěn)定電壓源讀數(shù)仍有 ±18 LSB 的跳動(dòng)。排查用邏輯分析儀看硬件側(cè)信號(hào)是干凈的跳動(dòng)完全來自 ADC 本身的量化噪聲 電源紋波。根因12 位 ADC 的固有噪聲在幾 LSB 量級(jí)是正常的加上數(shù)字電源的開關(guān)噪聲耦合靜止讀數(shù)跳到 ±18 LSB 并不罕見。這不是采錯(cuò)了而是沒濾波。解決軟件濾波見第五節(jié)。實(shí)測(cè)數(shù)據(jù)對(duì)比見下一節(jié)。七、實(shí)測(cè)數(shù)據(jù)四算法橫向?qū)Ρ葴y(cè)試條件STM32F407ZGT6 168MHzCH0 接 1.65V 直流基準(zhǔn)理論上 12 位讀數(shù) ≈ 2048ADC 時(shí)鐘 30MHz每通道 100 樣本連續(xù)運(yùn)行 10 分鐘統(tǒng)計(jì)。算法穩(wěn)態(tài)均值峰峰值跳動(dòng)穩(wěn)態(tài)方差(LSB2)對(duì)階躍響應(yīng)延遲原始數(shù)據(jù)無濾波2048±18 LSB27.60滑動(dòng)平均(N100)2048±5 LSB3.1~7.5ms中值濾波(N100)2048±7 LSB5.2~5ms中值滑動(dòng)平均級(jí)聯(lián)2048±1 LSB1.4~8ms幾點(diǎn)解讀滑動(dòng)平均對(duì)白噪聲抑制最強(qiáng)方差從 27.6 直接降到 3.1但代價(jià)是約 7.5ms 的響應(yīng)延遲。中值濾波對(duì)脈沖干擾更有效但對(duì)純白噪聲不如滑動(dòng)平均。級(jí)聯(lián)方案把兩者優(yōu)點(diǎn)疊加跳動(dòng)壓到 ±1 LSB代價(jià)是多約 8ms 延遲——對(duì)溫度、電壓這類慢信號(hào)完全可接受對(duì)需要快速響應(yīng)的信號(hào)如電流保護(hù)則不適用。理論 vs 實(shí)測(cè)對(duì)照數(shù)據(jù)手冊(cè)標(biāo)稱 12 位 ADC 的 ENOB有效位數(shù)約 10.5 位意味著即使硬件完美量化噪聲也有約 2~3 LSB。對(duì)照實(shí)測(cè)項(xiàng)目理論值實(shí)測(cè)值偏差解釋靜止讀數(shù)噪聲~2 LSBENOB 10.5bit±18 LSB數(shù)字電源紋波耦合超出理論值濾波后方差趨于 0無限樣本3.1 LSB2有限樣本 殘留低頻漂移通道間時(shí)間差(采樣時(shí)間12.5cyc)/30MHz3.2μs4 通道串行轉(zhuǎn)換累積結(jié)論跳動(dòng)超理論值的部分幾乎都能用電源濾波 軟件濾波找回而通道間時(shí)間差是掃描模式的物理特性只能通過改用注入組、或降低對(duì)同步性的要求來規(guī)避無法用軟件消除。八、故障排查速查表5 類高頻問題#現(xiàn)象最可能原因排查步驟解決方案驗(yàn)證1通道值互相串位緩沖區(qū)長(zhǎng)度未對(duì)齊 / 中途停啟 ADCdump 緩沖區(qū)看排布是否平移長(zhǎng)度通道數(shù)×樣本數(shù)采集期不停啟各通道讀數(shù)歸屬正確2整體固定偏移漏做 ADC 校準(zhǔn)對(duì)照官方例程啟動(dòng)前HAL_ADCEx_Calibration_Start接基準(zhǔn)讀數(shù)滿量程3靜止讀數(shù)亂跳未濾波 / 電源紋波示波器看電源紋波軟件濾波 電源加濾波電容跳動(dòng)降到 ±1 LSB4采集一段時(shí)間后數(shù)據(jù)全 0DMA 用了 Normal 模式看 DMA 是否停傳改 Circular 循環(huán)模式長(zhǎng)時(shí)間運(yùn)行數(shù)據(jù)連續(xù)5采樣速率遠(yuǎn)低于預(yù)期ADC 時(shí)鐘超限 / 采樣時(shí)間過長(zhǎng)核對(duì) ADC 時(shí)鐘頻率預(yù)分頻使 ADC 時(shí)鐘≤36MHz實(shí)測(cè)采樣率達(dá)標(biāo)其中第 1、4 類是量產(chǎn)現(xiàn)場(chǎng)的高危項(xiàng)——數(shù)據(jù)錯(cuò)位在電力測(cè)控這類場(chǎng)景可能直接導(dǎo)致誤動(dòng)作務(wù)必在工程模板里固化校驗(yàn)邏輯。九、總結(jié)多通道 ADC 采集的坑本質(zhì)都源于一個(gè)認(rèn)知偏差“配置能生成不等于數(shù)據(jù)能信”。CubeMX 幫你搭好了硬件框架但校準(zhǔn)、緩沖區(qū)對(duì)齊、濾波這三件事它一件都不會(huì)替你做完。核心要點(diǎn)掃描模式是串行的多通道之間有固定時(shí)間差DR 寄存器共用多通道采集必須用 DMA循環(huán)模式 16 位寬中斷讀取拿不到完整數(shù)據(jù)緩沖區(qū)長(zhǎng)度必須精確等于通道數(shù) × 每通道樣本數(shù)且采集期間不要中途停啟上電校準(zhǔn)不能省否則讀數(shù)有固定偏移濾波要按噪聲類型選白噪聲用滑動(dòng)平均脈沖用中值兩者級(jí)聯(lián)最穩(wěn)。適用邊界本文方案適合溫度、電壓、電流慢速、環(huán)境光這類變化緩慢、對(duì)同步性要求不高的信號(hào)。若需要多通道嚴(yán)格同步采樣如三相電流、聲陣列單 ADC 掃描模式無法滿足需改用多 ADC 同步觸發(fā)或注入組若信號(hào)變化極快則需權(quán)衡濾波帶來的響應(yīng)延遲。已知局限掃描模式的通道間時(shí)間差無法通過軟件消除級(jí)聯(lián)濾波的響應(yīng)延遲約 8ms不適用于需要微秒級(jí)響應(yīng)的保護(hù)類信號(hào)。擴(kuò)展方向下一步可以把濾波做成環(huán)形緩沖區(qū) 增量更新避免每輪全量求和或在需要時(shí)引入卡爾曼濾波在平滑度與響應(yīng)速度之間做自適應(yīng)權(quán)衡。如需獲取本文完整工程代碼和更多實(shí)戰(zhàn)項(xiàng)目可開通 CSDN 技術(shù)會(huì)員。參考資料《STM32 通過 DMA 進(jìn)行 ADC 采集HAL 庫(kù)》 — 掃描模式 DMA 的配置基礎(chǔ)《STM32 HAL 庫(kù) ADC DMA 采集詳解》 — Circular 與 Normal 模式對(duì)比《STM32F103 ADC 采樣數(shù)據(jù)太跳5 種常用濾波算法實(shí)戰(zhàn)對(duì)比》 — 濾波算法選型參考《解決 STM32 單片機(jī) ADC 采樣不準(zhǔn)確偏移的問題》 — 校準(zhǔn)缺失導(dǎo)致的偏移版本備注硬件平臺(tái)STM32F407ZGT6168MHz 1.65V 直流基準(zhǔn)源軟件版本STM32CubeMX 6.9.0 STM32CubeF4 HAL 庫(kù) v1.27.0 Keil MDK 5.38兼容說明本文 API 適用于 STM32F0/F1/F4/G0 全系列 HAL 庫(kù)F1 系列 ADC 時(shí)鐘上限為 14MHz而非 F4 的 36MHz移植時(shí)務(wù)必以對(duì)應(yīng)參考手冊(cè)為準(zhǔn)L4/H7 系列部分型號(hào)支持差分/過采樣參數(shù)略有差異。