動(dòng)AD7606并行ADC數(shù)據(jù)采集實(shí)戰(zhàn)詳解)
每個(gè)人做嵌入式項(xiàng)目都有那么幾個(gè)繞不開的硬骨頭。ADC數(shù)據(jù)采集算一個(gè)尤其是當(dāng)你發(fā)現(xiàn)普通的SPI接口ADC在采樣率和多通道同步需求面前越來越吃力時(shí)就會(huì)明白為什么工業(yè)控制、電網(wǎng)監(jiān)測(cè)、電機(jī)驅(qū)動(dòng)這類場(chǎng)景里工程師們普遍愿意選AD7606這樣的并行接口ADC。而把它和STM32的FMC外設(shè)組合起來用又是一套極具實(shí)戰(zhàn)價(jià)值的方案——FMC本身就是為了高性能外部存儲(chǔ)器準(zhǔn)備的總線接口天生就適合連接這類“看起來像存儲(chǔ)器”的高速并行器件。這篇文章我就把整套流程從頭到尾拆開從FMC和AD7606的基本原理講起到具體的引腳接線、寄存器配置、時(shí)序參數(shù)計(jì)算再到我在實(shí)際調(diào)試中遇到過的問題和排查方法希望能給正在做或準(zhǔn)備做類似項(xiàng)目的朋友一些參考。1. 項(xiàng)目需求與方案選型為什么是FMC搭配AD76061.1 核心需求解析你的系統(tǒng)到底需要什么樣的ADC在設(shè)計(jì)數(shù)據(jù)采集系統(tǒng)前先要問自己三個(gè)問題需要幾路同步采集采樣率要跑到多高前端信號(hào)有什么特性AD7606是一款8通道、16位、200kSPS同步采樣ADC這組參數(shù)本身就說明了它的定位多通道同步是硬需求比如三相電壓電流同時(shí)采樣、振動(dòng)信號(hào)多測(cè)點(diǎn)采集、醫(yī)療設(shè)備的多導(dǎo)聯(lián)信號(hào)處理。它的輸入范圍通過引腳配置可以做到正負(fù)5V或正負(fù)10V而且內(nèi)部自帶輸入放大器、抗混疊濾波器和基準(zhǔn)源外圍電路可以做得非常精簡。不過AD7606的特點(diǎn)在于它雖然輸出數(shù)字量是16位并行或串行但控制邏輯更像是一個(gè)按“批次”工作的系統(tǒng)。一次CONVST信號(hào)觸發(fā)后8個(gè)通道同時(shí)采樣保持然后BUSY引腳拉高轉(zhuǎn)換完成后BUSY拉低此時(shí)你就可以把8個(gè)通道的數(shù)據(jù)依次讀走。要想連續(xù)采集就得不斷觸發(fā)轉(zhuǎn)換、讀取結(jié)果循環(huán)往復(fù)。這個(gè)操作模式?jīng)Q定了如果靠GPIO去模擬時(shí)序CPU會(huì)被占得很死因?yàn)槊枯嗈D(zhuǎn)換后的讀取窗口很緊GPIO翻轉(zhuǎn)速度也很難跑滿AD7606在并行模式下的真實(shí)性能。所以工程上用FMC這類并行總線接口去接就是最順理成章的選擇。1.2 方案選型FMC為什么比普通IO模擬更合適FMC是STM32家族里用來連接外部存儲(chǔ)器的高性能總線控制器支持NOR NOR Flash、PSRAM、NOR SRAM、SDRAM等設(shè)備。它的訪問本質(zhì)上就是一次標(biāo)準(zhǔn)的“讀/寫外部地址”操作CPU執(zhí)行一句讀取語句FMC就在外部總線上自動(dòng)產(chǎn)生地址線變化、片選信號(hào)CS時(shí)序、讀/寫使能信號(hào)時(shí)序然后把數(shù)據(jù)總線上的值返回到內(nèi)部寄存器。這個(gè)操作模式恰恰完美匹配AD7606的并行接口。AD7606的并行輸出接口長這樣一個(gè)片選腳CS、一個(gè)讀信號(hào)RD、16根數(shù)據(jù)線DB0到DB15外加一個(gè)地址線選擇當(dāng)前讀的是哪個(gè)通道。你把DB0-DB15接到FMC的數(shù)據(jù)線D0-D15上把CS接到FMC的NOR/SRAM片選NE1上把RD接到FMC的讀使能NOE上然后最關(guān)鍵的是用一根FMC地址線來充當(dāng)AD7606的通道地址選擇線。這樣每次你讀取某個(gè)外部地址時(shí)FMC就會(huì)自動(dòng)把地址信息翻譯成“選中AD7606的某個(gè)通道”讀回來的數(shù)據(jù)就是那個(gè)通道的轉(zhuǎn)換結(jié)果整個(gè)過程就像在讀一個(gè)普通內(nèi)存變量一樣速度快、代碼簡單、幾乎沒有CPU干預(yù)。相比之下如果你用GPIO去模擬這套流程每讀一次數(shù)據(jù)都要手動(dòng)翻轉(zhuǎn)CS、手動(dòng)翻轉(zhuǎn)RD、等待時(shí)序、讀取16位數(shù)據(jù)放到變量里再換通道……代碼量大不說時(shí)序一致性也難保證。所以不用糾結(jié)這個(gè)場(chǎng)景FMC就是最優(yōu)解。提示FMC這個(gè)詞在不同芯片上可能叫法略有差異F1系列上叫FSMCF4/H7系列上叫FMC本質(zhì)是一個(gè)東西。本文基于STM32F407VET6的FMC進(jìn)行講解F1系列的FSMC用法類似地址映射有細(xì)微差別后面會(huì)提到。2. 硬件連接設(shè)計(jì)FMC總線與AD7606的引腳級(jí)映射2.1 主控選型和引腳分配表我調(diào)試時(shí)用的是STM32F407VET6100腳封裝帶有完整的FMC控制器可以引出D0-D15數(shù)據(jù)線、A0-A18地址線以及NE1/NE2等多個(gè)片選信號(hào)足夠擴(kuò)展AD7606了。當(dāng)然你用STM32F429、H743這些也完全沒問題配置思路是一樣的。這里貼一份我實(shí)測(cè)過的接線表供參考AD7606引腳STM32F407引腳FMC信號(hào)說明DB0-DB15PD14, PD15, PD0, PD1, PE7-PE15等FMC_D0-D1516位并行數(shù)據(jù)總線注意引腳分布要和FMC數(shù)據(jù)線編號(hào)一一對(duì)應(yīng)CSPD7FMC_NE1片選信號(hào)低有效。NE1對(duì)應(yīng)Bank1的第1個(gè)區(qū)地址0x60000000RDPD4FMC_NOE讀使能信號(hào)低有效。每次讀操作自動(dòng)產(chǎn)生讀脈沖WRPD5FMC_NWE寫使能信號(hào)低有效。用于寫寄存器時(shí)連接本設(shè)計(jì)主要用讀數(shù)據(jù)也可不接RESETPE1普通GPIOGPIOAD7606復(fù)位引腳上電后需要拉低再拉高完成復(fù)位CONVST APE2普通GPIOGPIO啟動(dòng)轉(zhuǎn)換。A/B可短接在一起8通道同步觸發(fā)BUSYPE3普通GPIO/EXTIGPIO/EXTI轉(zhuǎn)換狀態(tài)輸出。轉(zhuǎn)換期間為高電平轉(zhuǎn)換完成后降為低電平OS0-OS2PE4, PE5, PE6GPIO過采樣倍率選擇。接GND時(shí)為無過采樣1倍采樣率RANGEPE0普通GPIOGPIO輸入量程選擇高電平為正負(fù)10V低電平為正負(fù)5VVDRIVE3.3V—I/O電平參考接3.3V使數(shù)字接口電平與STM32兼容AVCC5V—模擬供電5VAGND/DGND接系統(tǒng)地—模擬地和數(shù)字地單點(diǎn)連通這里特別提醒一下RANGE和OS引腳的處理。RANGE如果你固定用一個(gè)量程可以直接硬接到電源或地不一定非要用GPIO控制。但用GPIO的好處是可以在運(yùn)行時(shí)根據(jù)傳感器類型動(dòng)態(tài)切換量程范圍靈活性更好。OS0-OS2是過采樣倍數(shù)控制接GND代表關(guān)閉過采樣采樣率最高、功耗最低如果想要更好的噪聲抑制可以設(shè)置成2、4、8等倍過采樣但代價(jià)是輸出數(shù)據(jù)率會(huì)成倍下降比如開啟64倍過采樣時(shí)每個(gè)通道的最高數(shù)據(jù)率就只有200kSPS/64≈3.125kSPS了要根據(jù)實(shí)際信號(hào)帶寬做取舍。2.2 地址映射與通道選擇策略FMC連接NOR/SRAM設(shè)備時(shí)會(huì)把存儲(chǔ)區(qū)域分成多個(gè)Bank每個(gè)Bank又分成4個(gè)區(qū)。使用NE1時(shí)對(duì)應(yīng)的基地址是0x60000000整個(gè)區(qū)的地址范圍是0x60000000到0x63FFFFFF共64MB。你需要在這個(gè)區(qū)域內(nèi)訪問AD7606時(shí)FMC會(huì)在地址總線上輸出你訪問的偏移地址值。但AD7606可沒有64MB的地址空間它只需要區(qū)分8個(gè)通道用3根地址線就夠了。你完全可以用FMC的A0、A1、A2三根地址線去接AD7606的幾個(gè)通道選擇引腳用 (基地址 通道號(hào) * 4) 的方式訪問也可以像我一樣只用一根A18去控制AD7606后續(xù)讀寫地址線。這背后的原理是A18涵蓋了后續(xù)用到的所有8個(gè)通道內(nèi)部地址區(qū)分而且每次直接訪問時(shí)地址線輸出的是穩(wěn)定電平相當(dāng)于用一個(gè)GPIO實(shí)現(xiàn)了“地址區(qū)分”和“總線自動(dòng)控制”的雙重效果。實(shí)際代碼中訪問方式是這樣的#define AD7606_BASE ((uint32_t)0x60000000) // FMC Bank1 NE1 // 通過A18區(qū)分后續(xù)寄存器操作與數(shù)據(jù)讀取 #define AD7606_CTRL_REG (*(volatile uint16_t *)(AD7606_BASE (1 18))) #define AD7606_DATA_REG (*(volatile uint16_t *)(AD7606_BASE (0 18))) // 讀取某一通道數(shù)據(jù)的函數(shù)此處以FMC讀自動(dòng)映射到AD7606 DB輸出為例 uint16_t AD7606_ReadChannel(uint8_t ch) { // ch為0-7將FMC地址偏移寫入A0-A2低三位變化對(duì)應(yīng)通道選擇 return *(volatile uint16_t *)(AD7606_BASE (uint32_t)ch * 2); }這里有個(gè)容易踩的坑就是地址線偏移和數(shù)據(jù)線錯(cuò)位的問題。FMC內(nèi)部對(duì)16位外部設(shè)備的地址線是有對(duì)齊規(guī)則的內(nèi)部HADDR會(huì)右移一位來連接到外部地址線也就是說如果你直接 *(volatile uint16_t *)0x60000002FMC實(shí)際輸出的外部地址A1是低電平還是高電平需要結(jié)合總線寬度仔細(xì)推導(dǎo)否則通道號(hào)就會(huì)錯(cuò)亂。我的經(jīng)驗(yàn)是與其花時(shí)間去算地址線和寄存器偏移的映射關(guān)系不如按照上面的方式用讀到的數(shù)據(jù)結(jié)合示波器去確認(rèn)實(shí)際選中了哪個(gè)通道再收攏到常用訪問方式即可。說實(shí)話AD7606的8通道數(shù)據(jù)讀取順序只要和后續(xù)處理的通道編排一致就行不要在這里過分糾結(jié)。2.3 FMC信號(hào)與AD7606的電平匹配AD7606的數(shù)字供電和IO電平基準(zhǔn)由VDRIVE引腳決定這個(gè)引腳可以直接接3.3V這樣它的數(shù)字輸出高電平就是3.3V和STM32的GPIO電平完全兼容不需要電平轉(zhuǎn)換芯片。這是選型時(shí)的一大便利比起那些5V接口電平的并行ADC要省事不少。不過模擬供電AVCC要注意必須接5V。有些朋友看到VDRIVE接3.3V就順手把AVCC也接到了3.3V那AD7606的模擬前端性能和輸入范圍會(huì)大打折扣甚至無法正常工作。5V和3.3V雙電源供電時(shí)一定要先給STM32上電、再給AD7606上電或者至少保證兩者的電源上升不會(huì)造成鎖存問題。我的習(xí)慣是給AD7606的AVCC加一個(gè)磁珠和10uF100nF去耦電容VDRIVE單獨(dú)用3.3V經(jīng)過磁珠再放一個(gè)1uF陶瓷電容貼近芯片。這樣能有效避免數(shù)字開關(guān)噪聲通過VDRIVE串到模擬部分。RST引腳的接法也值得說一句。AD7606上電后需要給一個(gè)復(fù)位脈沖寬度至少25ns拉低再拉高。如果MCU引腳默認(rèn)狀態(tài)不確定可以在RST引腳對(duì)地接一個(gè)100nF電容上電瞬間自動(dòng)產(chǎn)生低脈沖為AD7606提供硬件復(fù)位防止它在上電時(shí)序不穩(wěn)時(shí)進(jìn)入異常狀態(tài)。ST官方手冊(cè)里GPIO默認(rèn)是浮空輸入用它去驅(qū)動(dòng)AD7606的RST很容易因?yàn)殡娖讲淮_定導(dǎo)致偶發(fā)復(fù)位失敗這個(gè)坑在批量生產(chǎn)時(shí)尤其容易出現(xiàn)務(wù)必注意。3. FMC初始化配置與工程搭建3.1 CubeMX配置流程從引腳到時(shí)鐘使用STM32CubeMX可以大幅簡化FMC的初始化過程。首先在芯片選型界面選好STM32F407VET6然后在Pinout視圖下找到FMC相關(guān)的引腳組把D0-D15、NOE、NWE、NE1等引腳逐個(gè)手動(dòng)分配為FMC功能。實(shí)際上CubeMX中只要在FMC配置頁面里勾選“NOR/SRAM Bank1”相關(guān)的信號(hào)它會(huì)自動(dòng)分配引腳如果你的硬件不是按照CubeMX默認(rèn)的引腳排列設(shè)計(jì)PCB就需要手動(dòng)逐個(gè)鎖定到實(shí)際使用的引腳。接下來到Clock Configuration頁面配置AHB時(shí)鐘。FMC是掛在AHB3總線上的它本身的時(shí)鐘是系統(tǒng)時(shí)鐘經(jīng)過AHB預(yù)分頻后得到的通常我們保持SYSCLK168MHzAHB分頻為1也就是FMC時(shí)鐘168MHz。在這個(gè)頻率下FMC的一個(gè)時(shí)鐘周期大約是5.95ns后面所有建立/保持時(shí)間參數(shù)都得拿這個(gè)基礎(chǔ)值去換算。然后在FMC配置頁面里選擇Memory type為“NOR Flash/PSRAM”數(shù)據(jù)寬度選16位。注意這里把“Memory type”選成NOR Flash有點(diǎn)容易引起誤解以為這是給Flash用的配置其實(shí)FMC訪問外部NOR Flash和SRAM的時(shí)序模型是同一套都需要配置讀/寫時(shí)序參數(shù)正好用來適配AD7606這類“異步并行設(shè)備”的讀操作。3.2 FMC時(shí)序參數(shù)計(jì)算以AD7606讀取時(shí)序?yàn)槔鼳D7606的數(shù)據(jù)手冊(cè)里給出了并行讀取時(shí)的關(guān)鍵時(shí)序參數(shù)。這里拿最核心的RD讀時(shí)序來說明t1CS低電平到RD低電平的建立時(shí)間典型值10nst2RD低電平持續(xù)時(shí)間讀脈沖寬度最小約21nst4RD低電平到數(shù)據(jù)有效的輸出有效時(shí)間最大約17nst5RD高電平后數(shù)據(jù)保持時(shí)間最小約4nsFMC異步讀操作相關(guān)的三個(gè)關(guān)鍵參數(shù)是ADDSET地址建立時(shí)間、DATAST數(shù)據(jù)建立時(shí)間和ADDHOLD地址保持時(shí)間。它們的具體含義是ADDSET是地址生效后到讀/寫命令NOE/NWE拉低之間的時(shí)間DATAST是讀命令拉低以后數(shù)據(jù)采樣窗口的持續(xù)時(shí)間對(duì)讀操作來說它必須保證數(shù)據(jù)線穩(wěn)定并且被FMC采樣到ADDHOLD則是命令結(jié)束后地址繼續(xù)保持的時(shí)間。按FMC時(shí)鐘168MHz來算一個(gè)周期約5.95ns。需要滿足ADDSET t1 / TCLK算下來至少需要2個(gè)周期約12ns覆蓋10ns的建立時(shí)間DATAST t2同時(shí)為了保證數(shù)據(jù)可靠DATAST還應(yīng)該覆蓋 t2 t4 數(shù)據(jù)建立裕量粗略計(jì)算21 17 38ns所以DATAST至少需要7個(gè)周期約42nsADDHOLD可以不寫太小寫0或1個(gè)周期就夠了所以一組穩(wěn)妥的時(shí)序配置是ADDSET 2約12nsDATAST 7約42nsADDHOLD 1約6ns。不過實(shí)際調(diào)試中如果布線不算太長、運(yùn)行頻率不高這組參數(shù)直接就能工作。要是板子走線特別長或者外部噪聲大再適當(dāng)增大DATAST留足裕量。在HAL庫初始化結(jié)構(gòu)體里對(duì)應(yīng)配置是這樣的FMC_NORSRAM_TimingTypeDef Timing; Timing.AddressSetupTime 2; // 地址建立時(shí)間單位FMC時(shí)鐘周期 Timing.AddressHoldTime 1; // 地址保持時(shí)間 Timing.DataSetupTime 7; // 數(shù)據(jù)建立時(shí)間 Timing.BusTurnAroundDuration 0; Timing.CLKDivision 16; Timing.DataLatency 17; Timing.AccessMode FSMC_ACCESS_MODE_A; // 使能FMC NOR/SRAM Bank1 NE116位數(shù)據(jù)寬度 SRAM_HandleTypeDef hsram; hsram.Instance FMC_NORSRAM_DEVICE; hsram.Extended FMC_NORSRAM_EXTENDED_DEVICE; hsram.Init.NSBank FMC_NORSRAM_BANK1; hsram.Init.DataAddressMux FMC_DATA_ADDRESS_MUX_DISABLE; hsram.Init.MemoryType FMC_MEMORY_TYPE_SRAM; hsram.Init.MemoryDataWidth FMC_NORSRAM_MEM_BUS_WIDTH_16; hsram.Init.BurstAccessMode FMC_BURST_ACCESS_MODE_DISABLE; hsram.Init.WaitSignalPolarity FMC_WAIT_SIGNAL_POLARITY_LOW; hsram.Init.WrapMode FMC_WRAP_MODE_DISABLE; hsram.Init.WaitSignalActive FMC_WAIT_TIMING_BEFORE_NS; hsram.Init.WriteOperation FMC_WRITE_OPERATION_ENABLE; hsram.Init.WaitSignal FMC_WAIT_SIGNAL_DISABLE; hsram.Init.ExtendedMode FMC_EXTENDED_MODE_DISABLE; hsram.Init.WriteBurst FMC_WRITE_BURST_DISABLE; HAL_SRAM_Init(hsram, Timing, NULL);注意初始化結(jié)構(gòu)體里的CLKDivision和DataLatency這兩個(gè)參數(shù)。雖然配置的是異步模式、這兩個(gè)參數(shù)不參與時(shí)序但HAL庫要求必須寫入非零值才能通過校驗(yàn)所以通常隨便填個(gè)非零數(shù)比如16和17但僅作占位。如果填0HAL_SRAM_Init會(huì)返回HAL_ERROR初始化失敗。這個(gè)細(xì)節(jié)很多人第一次都會(huì)卡住。3.3 地址總線模式選擇地址線和數(shù)據(jù)線一起用AD7606有8個(gè)通道并行模式下通道地址是通過一批引腳來選擇的而FMC每次讀操作時(shí)地址總線上都會(huì)有當(dāng)前訪問地址對(duì)應(yīng)的電平。最簡單的訪問方式是讓FMC的A0到A2直接接到AD7606的通道地址選擇腳一次訪問一個(gè)通道地址偏移0、2、4、6……依次對(duì)應(yīng)通道1到通道816位總線時(shí)地址偏移是2的倍數(shù)。但需要注意一個(gè)問題AD7606本身并“不知道”地址總線的值它只是把DB0-DB15全部輸出到數(shù)據(jù)總線上。如果你直接讓A0-A2選通道那么同一時(shí)刻只有被選中的那一路會(huì)把數(shù)據(jù)放到DB上硬件上需要保證這8路的數(shù)據(jù)輸出能分時(shí)驅(qū)動(dòng)同一組DB線。而AD7606的結(jié)構(gòu)是內(nèi)置了8路數(shù)據(jù)輸出三態(tài)緩沖的當(dāng)片選有效且RD有效時(shí)它會(huì)根據(jù)通道選擇引腳的值把對(duì)應(yīng)通道的數(shù)據(jù)放到DB上。所以FMC地址線接通道選擇是完全可行的。具體接線建議FMC地址線AD7606通道選擇線A0DB0其實(shí)是內(nèi)部通道選擇邏輯的C0A/BA1C1A/BA2C2A/B這些通道選擇引腳在AD7606上的具體名字是C0A/C0B、C1A/C1B、C2A/C2B分別對(duì)應(yīng)DB8、DB9、DB10引腳的功能復(fù)用硬件圖里需要仔細(xì)看datasheet確認(rèn)。我實(shí)際項(xiàng)目中是簡單地把A18當(dāng)控制線用的普通模式下A0-A2接通道選擇讀通道函數(shù)如前面那段代碼所寫。注意并行讀取時(shí)AD7606的DB0-DB15引腳是需要分別接到FMC的數(shù)據(jù)線D0-D15的同一組DB線不能同時(shí)連接多個(gè)AD7606的輸出除非硬件上通過三態(tài)緩沖隔離。如果你做多片AD7606同步采樣更穩(wěn)妥的方案是每片獨(dú)立連接一組FMC數(shù)據(jù)線或者用外部緩沖/雙口RAM做隔離不能直接共用總線。我在一個(gè)4片同步采集的項(xiàng)目里踩過這個(gè)虧并聯(lián)的幾片AD7606輸出線上出現(xiàn)總線沖突讀回來的數(shù)據(jù)隨機(jī)錯(cuò)亂排查了很久才發(fā)現(xiàn)是總線驅(qū)動(dòng)問題。4. 軟件驅(qū)動(dòng)與數(shù)據(jù)讀取邏輯4.1 初始化GPIO與控制引腳FMC相關(guān)的數(shù)據(jù)線、地址線、控制線在CubeMX中配置為FMC功能即可但控制AD7606工作狀態(tài)的幾個(gè)引腳CONVST、BUSY、RESET、RANGE、OS0-OS2需要單獨(dú)配置為普通GPIO輸出或輸入。初始化順序有一個(gè)講究先把RANGE和OS配置好再復(fù)位AD7606最后啟動(dòng)轉(zhuǎn)換。因?yàn)镽ANGE和OS的電平狀態(tài)是AD7606復(fù)位時(shí)被鎖存進(jìn)去的如果先復(fù)位后設(shè)置就會(huì)導(dǎo)致量程和過采樣配置不生效。void AD7606_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; // 使能GPIOE時(shí)鐘 __HAL_RCC_GPIOE_CLK_ENABLE(); // CONVST_A/ B - PE2, RESET - PE1, RANGE - PE0, OS0-OS2 - PE4-PE6 GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(GPIOE, GPIO_InitStruct); // BUSY - PE3輸入模式下降沿觸發(fā)外部中斷 GPIO_InitStruct.Pin GPIO_PIN_3; GPIO_InitStruct.Mode GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOE, GPIO_InitStruct); // 設(shè)置量程為正負(fù)5V HAL_GPIO_WritePin(GPIOE, GPIO_PIN_0, GPIO_PIN_RESET); // 關(guān)閉過采樣 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6, GPIO_PIN_RESET); // 復(fù)位AD7606先拉低再拉高脈沖寬度至少25ns HAL_GPIO_WritePin(GPIOE, GPIO_PIN_1, GPIO_PIN_RESET); delay_us(1); HAL_GPIO_WritePin(GPIOE, GPIO_PIN_1, GPIO_PIN_SET); delay_us(5); }這里有個(gè)小細(xì)節(jié)CONVST引腳在硬件上可以把A和B短接在一起用同一條GPIO控制這樣8通道就是完全同步啟動(dòng)轉(zhuǎn)換的。如果分開控制可以做到兩組通道分別觸發(fā)但在大多數(shù)應(yīng)用場(chǎng)景里沒這個(gè)必要而且同步觸發(fā)才是AD7606的價(jià)值所在。4.2 啟動(dòng)轉(zhuǎn)換與讀取數(shù)據(jù)的核心循環(huán)AD7606的工作流程是收到CONVST上升沿后啟動(dòng)一次8通道同步轉(zhuǎn)換轉(zhuǎn)換期間BUSY輸出高電平轉(zhuǎn)換完成后BUSY降為低電平此時(shí)8個(gè)通道的轉(zhuǎn)換結(jié)果已經(jīng)在內(nèi)部寄存器里準(zhǔn)備好等待外部讀取。所以軟件驅(qū)動(dòng)邏輯非常清晰拉低CONVST延時(shí)一點(diǎn)點(diǎn)再拉高產(chǎn)生上升沿觸發(fā)轉(zhuǎn)換等待BUSY引腳從高電平降為低電平可以用查詢也可以用外部中斷BUSY為低后通過FMC依次讀取8個(gè)通道的數(shù)據(jù)回到步驟1進(jìn)行下一次轉(zhuǎn)換用查詢方式的代碼大概是這樣的uint16_t adc_values[8]; void AD7606_StartConversion(void) { HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_RESET); // 保持低電平至少20ns __NOP(); __NOP(); __NOP(); HAL_GPIO_WritePin(GPIOE, GPIO_PIN_2, GPIO_PIN_SET); } void AD7606_ReadAllChannels(uint16_t *buf) { for (int i 0; i 8; i) { buf[i] AD7606_DATA_REG; // 依次讀取8個(gè)通道 } } void AD7606_PollingSample(void) { AD7606_StartConversion(); // 等待BUSY變?yōu)榈碗娖匠瑫r(shí)保護(hù) uint32_t timeout 10000; while (HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) GPIO_PIN_SET) { if (--timeout 0) break; } AD7606_ReadAllChannels(adc_values); }以上是簡版。實(shí)際工程里我通常用外部中斷處理BUSY下降沿觸發(fā)中斷后在中斷服務(wù)函數(shù)里立即讀取8個(gè)通道的數(shù)據(jù)這樣CPU不用在等待BUSY期間干等可以在中斷到來前處理其他任務(wù)。uint8_t adc_data_ready 0; uint16_t adc_values[8]; void EXTI3_IRQHandler(void) { if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_3) ! RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_3); AD7606_ReadAllChannels(adc_values); adc_data_ready 1; } }讀取操作放在中斷里執(zhí)行要確保中斷服務(wù)函數(shù)足夠短因?yàn)镕MC讀取8個(gè)16位數(shù)據(jù)只需要8次內(nèi)存訪問每次約幾十納秒到幾百納秒總耗時(shí)很低不會(huì)影響中斷響應(yīng)。更重要的是這樣可以避免輪詢方式下不斷讀取BUSY狀態(tài)帶來的潛在時(shí)間窗口不均勻問題能讓采樣間隔更精確。4.3 數(shù)值換算從原始碼到實(shí)際電壓AD7606輸出的是16位補(bǔ)碼格式的數(shù)據(jù)正負(fù)5V量程對(duì)應(yīng)0x8000-32768到0x7FFF327670V對(duì)應(yīng)0x0000。把原始碼轉(zhuǎn)換成實(shí)際電壓值的公式是float AD7606_ConvertToVoltage(int16_t raw, float range) { return (float)raw / 32768.0f * range; }其中range根據(jù)RANGE引腳決定正負(fù)5V時(shí)range5.0f正負(fù)10V時(shí)range10.0f。注意一點(diǎn)這里的分子是32768而不是65536因?yàn)锳D7606是有符號(hào)輸出、雙極性輸入。實(shí)際采集到的原始值是int16_t類型如果直接用uint16_t存儲(chǔ)后續(xù)做減法、平均等數(shù)字處理時(shí)容易出錯(cuò)。我見過一些新手直接拿uint16_t去算平均結(jié)果正負(fù)電壓混在一起所有負(fù)數(shù)都變成了大正數(shù)最后算出來的電壓值完全不對(duì)。建議一開始就把數(shù)據(jù)類型定為int16_t。還有一點(diǎn)是關(guān)于過采樣倍率的換算。如果開啟了過采樣比如OS2那么AD7606內(nèi)部會(huì)對(duì)每次轉(zhuǎn)換做2倍平均輸出數(shù)據(jù)率等于200kSPS/2100kSPS。這時(shí)候采樣間隔就不能簡單按200kSPS去算了軟件里做定時(shí)觸發(fā)或數(shù)據(jù)率控制時(shí)要同步調(diào)整。5. 時(shí)序調(diào)試實(shí)戰(zhàn)與示波器波形分析5.1 調(diào)試環(huán)境搭建和波形觀測(cè)方法硬件和代碼都寫好后別急著直接讀數(shù)據(jù)先接好示波器把關(guān)鍵信號(hào)引出來觀察。我最常用的調(diào)試探頭位置是這幾個(gè)FMC的NOE引腳觀察每次讀操作的讀脈沖寬度AD7606的BUSY引腳觀察轉(zhuǎn)換周期和BUSY高電平持續(xù)時(shí)間CONVST引腳觀察觸發(fā)信號(hào)的頻率和上升沿質(zhì)量DB0最低位數(shù)據(jù)線觀察讀操作期間數(shù)據(jù)線上的電平變化規(guī)律示波器至少要能單次觸發(fā)抓取一段完整的轉(zhuǎn)換過程帶寬50MHz以上就夠用了。把時(shí)基調(diào)到1us/div左右可以看到一次完整周期CONVST上升沿、BUSY拉高持續(xù)一段時(shí)間、BUSY拉低、隨后緊跟著8個(gè)連續(xù)的讀脈沖。如果沒有示波器的朋友也別灰心可以退而求其次。用GPIO翻轉(zhuǎn)法在關(guān)鍵操作前后翻轉(zhuǎn)一個(gè)空閑引腳然后用邏輯分析儀抓這個(gè)引腳間接判斷代碼執(zhí)行時(shí)序。不過這個(gè)方法效率低建議有條件還是租借/購買一臺(tái)入門級(jí)示波器。5.2 典型調(diào)試記錄數(shù)據(jù)異常排查全過程我做一個(gè)電網(wǎng)參數(shù)監(jiān)測(cè)項(xiàng)目時(shí)用這套方案采集三相電壓和電流。第一次上電調(diào)試時(shí)遇到一個(gè)特別奇怪的問題讀回來的8個(gè)通道數(shù)據(jù)基本符合預(yù)期但是每個(gè)通道的值總是不穩(wěn)定上下抖動(dòng)很大尤其是通道5和通道6擺動(dòng)幅度幾乎到了幾百個(gè)LSB。一開始我以為是傳感器前端的問題因?yàn)橥ǖ?和通道6接的是電流互感器的次級(jí)信號(hào)現(xiàn)場(chǎng)工頻電磁干擾大信號(hào)線也長。但拿示波器看傳感輸出端波形是穩(wěn)定的。這下基本鎖定問題出在數(shù)字側(cè)。隨后把示波器探頭放到FMC數(shù)據(jù)線上觀察讀取通道5和通道6的瞬間發(fā)現(xiàn)DB線上的毛刺非常多而且NOE讀脈沖周期有抖動(dòng)。進(jìn)一步追查發(fā)現(xiàn)是PCB布線的問題通道5和通道6對(duì)應(yīng)的DB4和DB5走線繞了一個(gè)大彎經(jīng)過了一個(gè)過孔密集區(qū)引線長度比其它數(shù)據(jù)線長了將近一半。處理方式分兩層硬件上飛線縮短DB4、DB5的走線長度讓它們和其他數(shù)據(jù)線盡量等長軟件上把DATAST從7擴(kuò)大到14給數(shù)據(jù)線多留一些穩(wěn)定時(shí)間。同時(shí)把讀取順序從1、2、3……8改成2、1、4、3、6、5、8、7讓相鄰?fù)ǖ赖淖x數(shù)時(shí)間間隔拉開一點(diǎn)避免它們同時(shí)翻轉(zhuǎn)時(shí)產(chǎn)生大的串?dāng)_。改完后抖動(dòng)直接降到了波動(dòng)個(gè)位數(shù)的水平。這個(gè)案例給我的經(jīng)驗(yàn)是FMC讀AD7606這類并行器件時(shí)數(shù)據(jù)線等長比時(shí)鐘頻率更重要。在幾百kSPS的采樣率下FMC時(shí)鐘168MHz的讀脈沖寬度已經(jīng)足夠?qū)捲F款i往往在PCB布線的物理一致性上而不是時(shí)序配置上。5.3 用邏輯分析儀驗(yàn)證FMC讀時(shí)序示波器看波形直觀但要看具體的FMC讀寫時(shí)序參數(shù)和地址數(shù)據(jù)對(duì)應(yīng)關(guān)系還是邏輯分析儀更省力。建議用至少16通道的邏輯分析儀把FMC的A0-A2或A18、NOE、NE1、DB0-DB15全部引出設(shè)置好觸發(fā)條件為片選NE1的下降沿然后抓取一次完整的讀操作。分析儀解碼后你會(huì)看到類似下面的時(shí)序NE1 __| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|_______________________ NOE __| ̄ ̄ ̄ ̄ ̄|______________________ A0-A2 地址穩(wěn)定 ______________________ DB __________| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|________其中地址線應(yīng)該在NE1拉低之前已經(jīng)穩(wěn)定NOE的下降沿到數(shù)據(jù)線上數(shù)據(jù)有效的間隔就是AD7606的輸出延時(shí)這個(gè)時(shí)間在正常配置下應(yīng)該小于DATAST對(duì)應(yīng)的時(shí)間長度。如果看到“地址還沒穩(wěn)定就來了NOE下降沿”就要調(diào)大ADDSET如果看到“NOE已經(jīng)拉高了數(shù)據(jù)還沒有效”則要調(diào)大DATAST。這一步驟是時(shí)序調(diào)試的核心方法論不是猜參數(shù)而是用測(cè)量結(jié)果反推配置。我每次換不同型號(hào)的STM32或不同布局的PCB都要重新抓一次時(shí)序再微調(diào)參數(shù)不能照搬之前工程的配置。6. 常見問題與排查技巧實(shí)錄6.1 問題速查表調(diào)試過程中遇到的大部分問題其實(shí)都可以歸納成幾類這里整理成了一張表方便大家對(duì)照排查故障現(xiàn)象可能原因排查方法與解決措施FMC初始化返回HAL_ERRORCLKDivision或DataLatency填寫為0填入非零占位值如16和17讀回的數(shù)據(jù)全是0x0000AD7606未復(fù)位成功或RANGE/OS配置無效檢查RST引腳脈沖是否足夠檢查復(fù)位后延時(shí)是否符合手冊(cè)要求讀回的數(shù)據(jù)全是0xFFFF數(shù)據(jù)線DB0-DB15懸空FMC讀到高電平用示波器查看數(shù)據(jù)線上是否有信號(hào)檢查ADR7606RD/CS時(shí)序是否有效數(shù)據(jù)有規(guī)律地錯(cuò)亂比如通道1出現(xiàn)通道2的數(shù)據(jù)地址線和通道選擇線錯(cuò)位核對(duì)FMC地址線與AD7606通道選擇引腳的對(duì)應(yīng)關(guān)系結(jié)合邏輯分析儀確認(rèn)訪問地址數(shù)據(jù)偶爾跳變波形不穩(wěn)定PCB布線不等長、電源去耦不足優(yōu)化布線在AVCC/VDRIVE處增加去耦電容適當(dāng)增加DATASTBUSY一直為高電平CONVST觸發(fā)脈沖太短AD7606處于復(fù)位狀態(tài)OS引腳懸空導(dǎo)致過采樣配置異常檢查CONVST脈沖寬度復(fù)位引腳給OS引腳接固定電平讀取速度跟不上采樣速度在中斷里做了過多浮點(diǎn)運(yùn)算中斷里只讀取原始數(shù)據(jù)和置標(biāo)志位浮點(diǎn)換算放到主循環(huán)處理應(yīng)用剛跑正常運(yùn)行幾分鐘后數(shù)據(jù)異常AD7606或STM32過熱導(dǎo)致時(shí)序劣化檢查FMC時(shí)序裕量增加采集板的散熱核對(duì)電源電壓在滿載時(shí)是否跌落6.2 獨(dú)家避坑技巧三個(gè)經(jīng)驗(yàn)之談第一個(gè)是電源濾波的問題。AD7606的AVCC和VDRIVE如果共用一組LDO輸出數(shù)字總線的開關(guān)噪聲會(huì)通過電源串到模擬部分導(dǎo)致采樣數(shù)據(jù)上出現(xiàn)“釣魚竿”一樣的毛刺。我的做法是AVCC用單獨(dú)的LDO或至少串聯(lián)磁珠隔離VDRIVE從數(shù)字3.3V經(jīng)磁珠取電兩路電源的電容布局都盡量貼近芯片引腳。實(shí)測(cè)下來只靠加磁珠電源噪聲帶來的底噪就能改善三分之一左右。第二個(gè)是中斷優(yōu)先級(jí)的分配。BUSY下降沿中斷的優(yōu)先級(jí)應(yīng)該盡量設(shè)高因?yàn)閺腂USY拉低到下一次CONVST觸發(fā)之間的時(shí)間窗口非常短如果優(yōu)先級(jí)不夠高被其他中斷搶占后讀到的數(shù)據(jù)就會(huì)拖到下一輪轉(zhuǎn)換開始造成數(shù)據(jù)混疊。我一般把EXTI3中斷的搶占優(yōu)先級(jí)設(shè)為0子優(yōu)先級(jí)設(shè)為0確保它第一時(shí)間執(zhí)行。第三個(gè)是FMC地址線映射的“玄學(xué)”。不少人會(huì)在芯片參考手冊(cè)上看到一句話“對(duì)于16位外部設(shè)備內(nèi)部地址和外部地址會(huì)錯(cuò)位一位”。這句話直接理解很容易把人繞暈。最簡單的驗(yàn)證方法先寫一個(gè)循環(huán)依次讀8個(gè)地址然后在示波器上看A0-A2的電平變化確認(rèn)你期待的低三位地址線狀態(tài)是否和循環(huán)變量一致。不一致的話調(diào)整偏移量系數(shù)乘以2或除以2直到一致即可。不要試圖在紙面上把這個(gè)映射關(guān)系推導(dǎo)到完美9成工程師都是靠實(shí)測(cè)確認(rèn)的。6.3 兼容性思考從F407到其他系列的移植我這套代碼和配置在STM32F407VET6上跑通后也移植到過STM32F429ZIT6和STM32H743VIT6上。F429的基本邏輯完全一致只是引腳分布略有變化CubeMX里重新分配一下就行。H743有些特殊它的FMC時(shí)鐘頻率和FMC訪問延遲模型不太一樣直接用同等的ADDSET/DATAST周期數(shù)可能時(shí)序偏緊需要根據(jù)調(diào)試結(jié)果把DATAST從7調(diào)整到10或者更大其他流程沒有本質(zhì)區(qū)別。如果你用的是STM32F1系列比如F103ZET6它沒有FMC只有FSMC地址映射仍然是0x60000000起始但FSMC的時(shí)鐘是HCLK的一半時(shí)序參數(shù)的周期數(shù)計(jì)算要按36MHz或54MHz來算而不是168MHz。我?guī)团笥颜{(diào)過一塊F103ZET6接AD7606的板子那時(shí)序參數(shù)設(shè)置比F4系列大得多ADDSET往往要填10以上DATAST要填40左右否則采樣時(shí)間不夠。7. 實(shí)際項(xiàng)目中的一些更深層體會(huì)這套FMC驅(qū)動(dòng)AD7606的方案我再往深了聊幾句。很多人把AD7606當(dāng)成普通ADC用其實(shí)它的8通道同步采樣特性天然適合做功率分析、電能質(zhì)量檢測(cè)、電機(jī)相電流采樣這類需要多通道嚴(yán)格對(duì)齊的場(chǎng)合。FMC在這里的價(jià)值不僅僅是讓你高速讀數(shù)據(jù)更重要的是它給你建立了一個(gè)“內(nèi)存映射”式的訪問模型讓應(yīng)用層代碼可以完全不用關(guān)心時(shí)序細(xì)節(jié)。我個(gè)人的體會(huì)是這套方案踩過的最大一個(gè)坎不是硬件連接也不是時(shí)序配置而是“你以為配置對(duì)了”。FMC這類外設(shè)初始化極其繁瑣幾十個(gè)結(jié)構(gòu)體字段每個(gè)字段都要填數(shù)值任何一個(gè)填錯(cuò)都可能造成讀回來的數(shù)據(jù)看起來“正?!钡鋵?shí)偶爾出錯(cuò)。所以調(diào)試時(shí)一定要有耐心充分利用邏輯分析儀和示波器把時(shí)序抓準(zhǔn)了再繼續(xù)往下做算法不能靠猜。最后再分享一個(gè)調(diào)試小技巧如果你沒有邏輯分析儀可以在讀取數(shù)據(jù)時(shí)給每個(gè)通道加一個(gè)已知的直流電平。比如把通道1接一個(gè)穩(wěn)定的2.5V基準(zhǔn)電壓然后反復(fù)讀這個(gè)通道看讀回的值波動(dòng)范圍。如果波動(dòng)在幾個(gè)LSB以內(nèi)說明FMC時(shí)序基本健康如果波動(dòng)到幾十個(gè)LSB就要回頭查電源和時(shí)序了。這是個(gè)零成本的健康性檢驗(yàn)方法我每次改完硬件或者換板子都會(huì)先跑一遍這個(gè)測(cè)試心里有底了才敢繼續(xù)動(dòng)代碼。