計(jì))
簡(jiǎn)介面向嵌入式開發(fā)者的AD7656與AD5676 STM32接口程序包聚焦高精度模擬信號(hào)采集與輸出控制。AD7656是16位ADCAD5676是16位DAC源碼通過SPI或I2C與STM32銜接涵蓋初始化配置、數(shù)據(jù)讀取、轉(zhuǎn)換控制等核心函數(shù)適合工業(yè)控制、醫(yī)療設(shè)備、儀器儀表等需要精確模擬量處理的場(chǎng)景。壓縮包內(nèi)僅含1個(gè)C語(yǔ)言源文件整體大小僅2KB代碼精簡(jiǎn)無(wú)冗余便于直接閱讀也可快速移植到HAL或標(biāo)準(zhǔn)庫(kù)工程。該資源已有1183人學(xué)習(xí)說明其在同類參考代碼中具備一定熱度。程序經(jīng)多項(xiàng)目調(diào)試優(yōu)化穩(wěn)定性可靠能幫助開發(fā)者縮短ADC/DAC驅(qū)動(dòng)開發(fā)周期降低底層排錯(cuò)成本源碼內(nèi)部函數(shù)劃分清晰還可作為SPI外設(shè)驅(qū)動(dòng)編寫的范例特別適合有一定STM32基礎(chǔ)、希望復(fù)用成熟代碼的嵌入式工程師。1. AD7656 與 AD5676 同時(shí)掛在 STM32 上的典型場(chǎng)景六通道同步采樣 ADC 加八通道 16 位 DAC同時(shí)掛在同一顆 STM32 上這種組合常見于伺服驅(qū)動(dòng)器電流環(huán)回采、測(cè)試機(jī)箱的信號(hào)源與回波驗(yàn)證、老化臺(tái)架的時(shí)序控制。AD7656 負(fù)責(zé)把多路模擬量在同一時(shí)刻鎖存轉(zhuǎn)換AD5676 負(fù)責(zé)輸出另一組精確模擬量?jī)深w芯片拼在一起才能組成一個(gè)完整的模擬前端。真正把它們放到一條 SPI 總線上時(shí)最先踩的坑往往不是采樣率而是片選設(shè)計(jì)、24 位 D/A 幀與 16 位 A/D 幀的共存以及 DAC 更新和 ADC 轉(zhuǎn)換之間的同步一致性。這篇按硬件連接、SPI 讀寫、定時(shí)器硬同步、雙緩沖數(shù)據(jù)流和標(biāo)定驗(yàn)證的順序往下排新手可以按步驟跑通老手重點(diǎn)看同步與地回路部分。2. AD7656 與 AD5676 的硬件連接與 SPI 總線規(guī)劃很多工程一開始是兩塊板各自獨(dú)立ADC 板用并行總線接 STM32 的 FSMCDAC 板用另一路 SPI等到整機(jī)聯(lián)調(diào)時(shí)引腳全部擠滿。如果是我來(lái)排會(huì)從第一步就把兩顆芯片規(guī)劃到同一條 SPI2 總線上AD7656 走串行模式AD5676 只發(fā)不收兩個(gè)片選分開占用的引腳量最小。2.1 AD7656 串行模式下的關(guān)鍵引腳固定AD7656 上電后默認(rèn)是并行模式必須把 SER/PAR 拉低才能進(jìn)入串行。W/B 決定串行輸出字寬16 位時(shí)拉高RANGE 決定模擬輸入是 /-5V 還是 /-10V。SEL A/B/C 決定哪一組合通道被納入轉(zhuǎn)換例如全接低時(shí)只轉(zhuǎn)換 V1/V2DOUTA 引腳串行輸出這兩個(gè)通道的結(jié)果。下面這張表是固定接法引腳連接說明SER/PARGND強(qiáng)制串行輸出W/BVDRIVE16 位字寬RANGE3.3V 或 GND3.3V 為 /-10VGND 為 /-5VSEL A/B/CGND本方案只回采 V1/V2VDRIVE3.3V數(shù)字電平與 STM32 一致REFCAP100nF 10uF參考電壓去耦電容盡量靠近引腳這里最容易被忽略的是 W/B 要接 VDRIVE 而不是 VDD。VDD 是 5V 模擬供電如果按 5V 接DOUT 高電平會(huì)被拉到 VDRIVE 電壓超過 STM32 IO 的容忍范圍。VDRIVE 單獨(dú)接 3.3V數(shù)字信號(hào)才能和 MCU 電平對(duì)齊。2.2 雙芯片共用一條 SPI 的引腳分配SPI2 在這塊板上的分配關(guān)系如下MISO 只接 AD7656 的一根 DOUTAMOSI 只接 AD5676 的 DINSCLK 和兩個(gè)片選分別獨(dú)立控制。信號(hào)STM32 引腳方向說明SPI2_SCKPB13輸出兩芯片共用SPI2_MISOPB14輸入AD7656 DOUTASPI2_MOSIPB15輸出AD5676 DINAD7656_CSPE8輸出低有效AD7656_BUSYPB7輸入轉(zhuǎn)換期間為高AD7656_CONVSTPA8輸出TIM1_CH1定時(shí)器觸發(fā)AD5676_CSPB9輸出低有效AD5676_LDACPA9輸出TIM1_CH2低脈沖刷新CONVST 放到定時(shí)器通道上不在軟件里手動(dòng)拉這樣采樣間隔是硬件周期決定的不受主循環(huán)抖動(dòng)影響。LDAC 也放到定時(shí)器通道和 CONVST 同源D/A 更新時(shí)刻與 A/D 采樣時(shí)刻才能對(duì)齊。BUSY 必須用普通 IO 讀不能復(fù)用成其他功能否則轉(zhuǎn)換完成信號(hào)會(huì)被中斷處理?yè)屨?。如果六通道全要采集SPI 只有一根 MISO 就不夠用了。AD7656 串行輸出有三根 DOUTA/DOUTB/DOUTC每根輸出兩組通道CS 拉低后需要連續(xù)讀 48 位。真想六通道全采要么換帶多路 MISO 的 MCU要么把 AD7656 切到并行模式并用 FSMC 接口。只回采前兩個(gè)通道時(shí)一根 DOUTA 正好夠用成本最低。2.3 電源、參考電壓與地回路設(shè)計(jì)AD7656 需要 5V 模擬供電AD5676 建議也由 5V 供電數(shù)字接口部分再統(tǒng)一由 3.3V 驅(qū)動(dòng)。參考電壓方面AD7656 內(nèi)部自帶 2.5V 參考REFCAP 引腳按手冊(cè)要求接 100nF 與 10uF 電容去耦。這顆電容不能放遠(yuǎn)它直接決定轉(zhuǎn)換結(jié)果的穩(wěn)定度。地回路采用單點(diǎn)接地模擬地平面和數(shù)字地平面在電源入口處匯合DAC 輸出走線盡量遠(yuǎn)離 ADC 模擬輸入線否則 DAC 每次跳變都會(huì)通過寄生電容耦合到 ADC 輸入端表現(xiàn)為輸出碼字上的周期性毛刺。我一般會(huì)把引腳宏定義全部集中在 ad76xx_io.h 里不依賴 CubeMX 生成的 main.h。這樣換單片機(jī)型號(hào)時(shí)只需要改這個(gè)頭文件的映射關(guān)系和 SPI 時(shí)鐘分頻不用在重新生成的代碼里到處找引腳CubeMX 重新生成工程也不會(huì)覆蓋這塊配置。3. STM32 SPI 初始化與 AD7656/AD5676 讀寫時(shí)序接線完成后進(jìn)入驅(qū)動(dòng)層。AD7656 串行讀是 16 位一幀AD5676 寫入是 24 位一幀兩種幀長(zhǎng)不同處理方式也不同。先說 SPI2 的初始化參數(shù)再分別給讀和寫的實(shí)現(xiàn)。3.1 SPI2 時(shí)鐘極性、相位與分頻參數(shù)參數(shù)取值說明CPOL0空閑時(shí) SCLK 為低CPHA1第二個(gè)邊沿采樣數(shù)據(jù)幀長(zhǎng)16 位AD7656 讀幀波特率PCLK1/8以 PCLK1 為準(zhǔn)片選軟件控制不使用硬件 NSSCPOL0、CPHA1 的意思是SCLK 空閑為低主機(jī)在下降沿把數(shù)據(jù)放到線上從機(jī)在上升沿采樣。AD7656 的 DOUT 在 SCLK 下降沿移出數(shù)據(jù)正好匹配AD5676 的 DIN 在 SCLK 上升沿被采入同樣匹配。所以兩顆芯片可以用同一組時(shí)鐘參數(shù)。波特率計(jì)算要結(jié)合 PCLK1例如 PCLK145MHz 時(shí)BR 字段選 8 分頻得到 5.625MHz這個(gè)速度對(duì) AD7656 的串行時(shí)鐘上限來(lái)說余量充足。void spi2_init_raw(void) { RCC-APB1ENR | RCC_APB1ENR_SPI2EN; SPI2-CR1 SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_CPHA | SPI_CR1_BR_2; /* BR010, 按 PCLK1/8 */ SPI2-CR2 SPI_CR2_DS_1 | SPI_CR2_DS_0; /* 16 位數(shù)據(jù)幀 */ SPI2-CR1 | SPI_CR1_SPE; }默認(rèn)的查詢方式里主機(jī)要產(chǎn)生時(shí)鐘就必須先寫 SPI2-DR然后等 RXNE 置位。CPHA 位如果不顯式加上默認(rèn)是 CPHA0在上升沿放數(shù)據(jù)下降沿采樣讀回來(lái)經(jīng)常是錯(cuò)位的數(shù)據(jù)。寄存器方式初始化還有一個(gè)好處驅(qū)動(dòng)代碼不依賴 HAL 的鎖和超時(shí)邏輯可以在中斷上下文直接調(diào)用不會(huì)因?yàn)?HAL 內(nèi)部的狀態(tài)機(jī)把讀寫時(shí)機(jī)拖偏。3.2 等 BUSY 下降沿與 AD7656 16 位讀取啟動(dòng)轉(zhuǎn)換后不能立刻讀 DOUT。AD7656 的 BUSY 引腳在轉(zhuǎn)換期間保持高電平轉(zhuǎn)換完成才會(huì)拉低。讀取代碼必須把等待 BUSY 放在片選拉低之前否則會(huì)讀到轉(zhuǎn)換中的中間數(shù)據(jù)。static uint32_t bus_timeout; int ad7656_convert_once(void) { /* 啟動(dòng)轉(zhuǎn)換: 上升沿鎖存當(dāng)前模擬輸入 */ HAL_GPIO_WritePin(CONVST_GPIO_Port, CONVST_Pin, GPIO_PIN_SET); __NOP(); HAL_GPIO_WritePin(CONVST_GPIO_Port, CONVST_Pin, GPIO_PIN_RESET); /* 等待轉(zhuǎn)換完成, 必須帶超時(shí) */ bus_timeout 100000; while (HAL_GPIO_ReadPin(BUSY_GPIO_Port, BUSY_Pin) GPIO_PIN_SET) { if (--bus_timeout 0) return -1; } /* 片選拉低后移出 16 位數(shù)據(jù) */ AD7656_CS_LOW(); uint16_t raw spix_read16(); AD7656_CS_HIGH(); return (int)raw; } static inline uint16_t spix_read16(void) { while (!(SPI2-SR SPI_SR_TXE)) { } /* 等待發(fā)送寄存器空 */ SPI2-DR 0x0000; /* 寫 0, 產(chǎn)生 16 個(gè)時(shí)鐘 */ while (!(SPI2-SR SPI_SR_RXNE)) { } /* 等待接收完成 */ return (uint16_t)SPI2-DR; }代碼里的SPI2-DR 0x0000是必須的。主機(jī)模式下 SPI 不會(huì)自己產(chǎn)生時(shí)鐘必須有數(shù)據(jù)寫入發(fā)送寄存器SCLK 才會(huì)翻轉(zhuǎn)。讀到的 16 位數(shù)據(jù)中最高位對(duì)應(yīng) DOUT 移出的第一個(gè)位直接作為原始碼字使用。超時(shí)值bus_timeout根據(jù)實(shí)際轉(zhuǎn)換時(shí)間來(lái)定AD7656 在最快模式下的轉(zhuǎn)換時(shí)間在微秒量級(jí)10 萬(wàn)次遞減足夠余量寧可多一點(diǎn)也不能用裸 while 死等否則一旦 BUSY 信號(hào)異常整個(gè)控制循環(huán)都會(huì)卡死。3.3 AD5676 寫入為什么用 GPIO 模擬AD5676 的 24 位幀格式是高 4 位命令中間 4 位地址低 16 位數(shù)據(jù)。寫命令、寫 DAC 寄存器、更新輸出都通過這 24 位完成。很多人直接把 STM32 的 SPI 配成 16 位幀分兩次發(fā)送第二幀多出來(lái)的時(shí)鐘會(huì)被 AD5676 當(dāng)成新的寫命令結(jié)果寄存器被反復(fù)覆蓋。void ad5676_write(uint8_t cmd, uint8_t addr, uint16_t data) { uint32_t frame ((uint32_t)(cmd 0x0F) 20) | ((uint32_t)(addr 0x0F) 16) | data; AD5676_CS_LOW(); for (int i 23; i 0; i--) { SCLK_LOW(); if (frame (1u i)) { DIN_HIGH(); } else { DIN_LOW(); } SCLK_HIGH(); } AD5676_CS_HIGH(); /* LDAC 低脈沖刷新輸出, 需要同步時(shí)放到定時(shí)器中斷里做 */ LDAC_LOW(); __NOP(); __NOP(); __NOP(); LDAC_HIGH(); }用 GPIO 模擬是為了避開幀長(zhǎng)不匹配的問題24 個(gè)時(shí)鐘一個(gè)不差。這個(gè)函數(shù)在控制周期 10us 的場(chǎng)景里性能足夠因?yàn)槊總€(gè)周期最多更新幾個(gè)通道幾位 GPIO 翻轉(zhuǎn)的開銷遠(yuǎn)小于一次 SPI 傳輸加狀態(tài)切換。如果輸出通道數(shù)多、更新頻率高可以改用硬件 SPI 配成 8 位幀CS 拉低后連續(xù)發(fā) 3 個(gè)字節(jié)同樣滿足 24 位格式要求。4. 同步采樣與同步輸出定時(shí)器、DMA 與雙緩沖單獨(dú)調(diào)通 ADC 和 DAC 之后最核心的問題變成同步ADC 什么時(shí)候采樣DAC 什么時(shí)候更新輸出。很多毛刺問題的根源不是數(shù)據(jù)算錯(cuò)而是兩個(gè)動(dòng)作發(fā)生在不同的時(shí)間基準(zhǔn)上。4.1 用定時(shí)器輸出比較通道直接驅(qū)動(dòng) CONVST 與 LDAC一種常見做法是在主循環(huán)里先調(diào)用ad7656_convert_once()再調(diào)用ad5676_write()間隔取決于主循環(huán)執(zhí)行時(shí)間抖動(dòng)不可控。同步采樣場(chǎng)景下ADC 的轉(zhuǎn)換起點(diǎn)和 DAC 的刷新時(shí)刻必須由同一個(gè)時(shí)鐘源觸發(fā)。用 STM32 的定時(shí)器輸出比較功能可以做到TIM1 的兩個(gè)通道分別輸出 CONVST 和 LDAC 波形更新事件由計(jì)數(shù)器周期自動(dòng)觸發(fā)和軟件中斷延遲無(wú)關(guān)。TIM_HandleTypeDef htim1 {0}; TIM_OC_InitTypeDef sConfigOC {0}; __HAL_RCC_TIM1_CLK_ENABLE(); htim1.Instance TIM1; htim1.Init.Period 900 - 1; /* 90MHz/900 100kHz */ htim1.Init.Prescaler 0; htim1.Init.CounterMode TIM_COUNTERMODE_UP; HAL_TIM_OC_Init(htim1); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 2; sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; HAL_TIM_OC_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_1); /* PA8, CONVST */ sConfigOC.Pulse 898; sConfigOC.OCPolarity TIM_OCPOLARITY_LOW; HAL_TIM_OC_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_2); /* PA9, LDAC */ HAL_TIM_OC_Start(htim1, TIM_CHANNEL_1); HAL_TIM_OC_Start(htim1, TIM_CHANNEL_2);PA8 輸出周期 10us 的窄脈沖上升沿觸發(fā)一次 AD7656 轉(zhuǎn)換。PA9 平時(shí)保持高電平只在計(jì)數(shù)器接近末尾時(shí)拉低一個(gè)窄脈沖這個(gè)下降沿把 AD5676 之前寫入的數(shù)據(jù)同時(shí)刷新到模擬輸出端。CCR1 和 CCR2 的差值決定了 DAC 更新相對(duì) ADC 采樣點(diǎn)的相位90MHz 下調(diào)整粒度約 11ns。這比在中斷里翻轉(zhuǎn) GPIO 的抖動(dòng)小了三個(gè)數(shù)量級(jí)以上。4.2 DMA 讀取與雙緩沖防止丟采轉(zhuǎn)換由定時(shí)器觸發(fā)后每次 BUSY 下降沿都代表一次采樣完成。如果在 BUSY 中斷里用 CPU 查詢 SPI 接收控制算法或 DAC 寫入隨時(shí)可能搶占總線讀取延時(shí)的微小變化會(huì)引入采樣抖動(dòng)。繞開辦法是讓 BUSY 下降沿啟動(dòng)一次 DMA 讀取ADC 數(shù)據(jù)由 DMA 自動(dòng)搬進(jìn)內(nèi)存。uint16_t dma_tx[2] {0x0000, 0x0000}; uint16_t dma_rx[2] {0, 0}; /* BUSY 下降沿中斷中只做一件事 */ void EXTI9_5_IRQHandler(void) { if (EXTI-PR (1 7)) { EXTI-PR (1 7); AD7656_CS_LOW(); HAL_SPI_TransmitReceive_DMA(hspi2, (uint8_t*)dma_tx, (uint8_t*)dma_rx, 1); } } void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { AD7656_CS_HIGH(); process_sample(dma_rx[0]); }DMA 傳輸完成中斷里只做兩件事拉高片選、把數(shù)據(jù)交給處理函數(shù)。處理函數(shù)里不要做浮點(diǎn)換算或控制算法只把結(jié)果放入環(huán)形緩沖或者直接切換雙緩沖指針。static uint16_t adc_buf[2][64]; static uint8_t active_buf 0; static volatile uint8_t ready 0; void process_sample(uint16_t raw) { adc_buf[active_buf][buf_index] raw; } void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { AD7656_CS_HIGH(); active_buf ^ 1; ready 1; }主循環(huán)或者控制任務(wù)只在ready置位后處理adc_buf[active_buf ^ 1]。這個(gè)結(jié)構(gòu)的價(jià)值在于采樣動(dòng)作由硬件觸發(fā)數(shù)據(jù)搬運(yùn)由 DMA 完成CPU 只在數(shù)據(jù)準(zhǔn)備就緒后才介入計(jì)算不會(huì)出現(xiàn)“等 SPI 接收”造成的采樣空洞。4.3 中斷優(yōu)先級(jí)與資源沖突雙緩沖跑起來(lái)之后中斷優(yōu)先級(jí)分配決定系統(tǒng)是否會(huì)在高負(fù)載下丟數(shù)據(jù)。中斷源搶占優(yōu)先級(jí)說明BUSY 下降沿2啟動(dòng) DMA 讀取越早越好SPI2 DMA 完成3切換雙緩沖標(biāo)記數(shù)據(jù)就緒TIM1 更新4周期任務(wù)算控制量、準(zhǔn)備 DAC 數(shù)據(jù)SysTick1系統(tǒng)節(jié)拍但中斷里不能做重活BUSY 優(yōu)先級(jí)如果太低轉(zhuǎn)換結(jié)束到發(fā)起 DMA 之間的窗口會(huì)被其他中斷擠掉表現(xiàn)為采樣值偶發(fā)回跳。TIM1 更新中斷用來(lái)準(zhǔn)備下一拍的控制量不能在里面調(diào)用 SPI 讀寫函數(shù)否則會(huì)阻塞其他低優(yōu)先級(jí)任務(wù)。這里要特別提醒TIM1 中斷和 DMA 中斷里都不能調(diào)用 HAL_Delay它依賴 SysTick在中斷里如果 SysTick 被關(guān)掉就會(huì)直接卡死這是很多工程在聯(lián)調(diào)階段才暴露出來(lái)的坑。5. AD7656 與 AD5676 的增益偏移標(biāo)定與軟件修正芯片手冊(cè)給的線性度指標(biāo)是理論值實(shí)際板上還有參考電壓誤差、輸入分壓電阻誤差、DAC 輸出緩沖失調(diào)。做伺服或測(cè)試設(shè)備時(shí)這些誤差靠軟件標(biāo)定吃掉比換更高精度芯片便宜得多。5.1 雙極性碼值換算AD7656 工作在雙極性 /-10V 范圍時(shí)按偏移二進(jìn)制格式0x8000 對(duì)應(yīng) 0V0x0000 對(duì)應(yīng) -10V0xFFFF 對(duì)應(yīng)接近 10V。換算公式float adc_code_to_volt(uint16_t code, float range_v) { return ((int32_t)code - 32768) * range_v / 32768.0f; }range_v傳 10.0 或 5.0對(duì)應(yīng) RANGE 引腳設(shè)置的滿量程。這里有個(gè)常見誤用直接用code / 65535 * 20算出來(lái)零點(diǎn)會(huì)偏到 32767/65535 的位置偏移量不大但會(huì)在零點(diǎn)附近造成持續(xù)的系統(tǒng)偏差標(biāo)定時(shí)很難查。5.2 端點(diǎn)標(biāo)定與定點(diǎn)修正標(biāo)定流程用兩個(gè)或多個(gè)已知電壓點(diǎn)記錄 ADC 原始碼字然后計(jì)算增益與偏移修正量。步驟輸入電壓讀取碼字10Vcode_mid210Vcode_high3-10Vcode_low理想情況下 code_mid 應(yīng)是 32768code_high 減 code_mid 應(yīng)是 32768。實(shí)際板子上會(huì)有偏差把這兩個(gè)偏差量固化成修正系數(shù)typedef struct { int32_t gain_q15; /* Q15 定點(diǎn)增益, 1.0 表示為 32768 */ int32_t offset; /* 零輸入時(shí)的碼字偏移 */ } adc_cal_t; void adc_cal_init(adc_cal_t *cal, int32_t code_mid, int32_t code_high) { cal-offset code_mid - 32768; int32_t delta code_high - code_mid; if (delta 0) delta 1; cal-gain_q15 (32768 15) / delta; } int32_t adc_cal_convert(const adc_cal_t *cal, int32_t raw) { return (int32_t)(((int64_t)(raw - cal-offset) * cal-gain_q15) 15); }增益用 Q15 定點(diǎn)表示而不是浮點(diǎn)因?yàn)?F103 這類沒有 FPU 的 MCU 上浮點(diǎn)運(yùn)算會(huì)占用大量 CPU 時(shí)間而在 100kHz 控制周期里每個(gè)樣本都要做一次修正定點(diǎn)乘法的開銷比浮點(diǎn)小一個(gè)數(shù)量級(jí)。raw - cal-offset可能出現(xiàn)負(fù)數(shù)所以中間變量用 int64_t避免符號(hào)位在移位時(shí)被丟棄。標(biāo)定時(shí)多采幾個(gè)點(diǎn)做最小二乘法擬合比只看兩個(gè)端點(diǎn)更穩(wěn)兩點(diǎn)法只適合初步驗(yàn)證。5.3 DAC 輸出修正的逆映射AD5676 輸出 0 到 5V 時(shí)理想情況下 code 與電壓是線性關(guān)系0x0000 對(duì)應(yīng) 0V0x8000 對(duì)應(yīng) 2.5V0xFFFF 對(duì)應(yīng)接近 5V。實(shí)際輸出的問題是零碼偏移和增益誤差。標(biāo)定時(shí)寫入兩個(gè) DAC 碼用萬(wàn)用表測(cè)實(shí)際電壓步驟寫入碼字實(shí)測(cè)電壓10x4000V_low20x8000V_midfloat dac_slope; /* V/code */ float dac_offset; /* 零碼輸出電壓 */ void dac_cal_init(float v_low, float v_mid) { dac_slope (v_mid - v_low) / (0x8000 - 0x4000); dac_offset v_mid - dac_slope * 0x8000; } uint16_t dac_code_for_voltage(float vtarget) { float code (vtarget - dac_offset) / dac_slope; if (code 0.0f) code 0.0f; if (code 65535.0f) code 65535.0f; return (uint16_t)(code 0.5f); }dac_slope的單位是 V/code如果在 0x4000 到 0x8000 之間測(cè)得 2.5V 跨度斜率約為 2.5V / 16384。逆映射階段不要用單純的比例系數(shù)因?yàn)?AD5676 輸出緩沖在接近軌電壓附近線性度會(huì)下降標(biāo)定時(shí)取中段兩點(diǎn)軟件里限制輸出范圍避免進(jìn)入非線性區(qū)。只標(biāo)一個(gè)點(diǎn)不標(biāo)斜率低端輸出會(huì)明顯偏大。6. 上電后的驗(yàn)證順序與異常排除驅(qū)動(dòng)和同步結(jié)構(gòu)寫完上電調(diào)試別急著跑控制算法按順序驗(yàn)證幾個(gè)關(guān)鍵點(diǎn)能省下半天定位時(shí)間。先把 ADC 輸入短接到地不加任何信號(hào)讀到的碼字應(yīng)該穩(wěn)定落在 0x7F80 到 0x8080 之間波動(dòng)范圍取決于 PCB 噪聲和參考電壓去耦。如果偏出這個(gè)范圍優(yōu)先查 RANGE 引腳電平與代碼里range_v是否一致。再用信號(hào)源輸入 1V理論上讀數(shù)約 36045允許 /- 幾十個(gè) LSB。這一步過了再接入 DAC 輸出做閉環(huán)。等待 BUSY 的邏輯建議用 DWT 周期計(jì)數(shù)器代替 HAL_Delay這樣即使 SysTick 被關(guān)掉也不會(huì)卡死。void dwt_init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } int busy_wait_us(uint32_t us) { uint32_t start DWT-CYCCNT; while (HAL_GPIO_ReadPin(BUSY_GPIO_Port, BUSY_Pin) GPIO_PIN_SET) { uint32_t elapsed DWT-CYCCNT - start; /* 無(wú)符號(hào)回卷安全 */ if (elapsed (SystemCoreClock / 1000000u) * us) return -1; } return 0; }DWT-CYCCNT 是 CPU 周期計(jì)數(shù)器讀取本身是單條指令不依賴任何中斷。之前遇到過delay卡死的工程多數(shù)是在中斷里調(diào) HAL_DelaySysTick 被更高優(yōu)先級(jí)中斷搶占后永遠(yuǎn)等不到節(jié)拍換成 DWT 后這個(gè)問題直接消失?,F(xiàn)象常見原因讀出全 0xFF片選沒拉低 / VDRIVE 電壓不足 / CPHA 配錯(cuò)讀出全 0x00DOUT 懸空 / SPI2 MISO 配置錯(cuò)誤數(shù)值比實(shí)際大 2 倍RANGE 引腳與換算范圍不匹配零點(diǎn)在 0x8000 附近跳動(dòng)等待 BUSY 超時(shí)太短讀到轉(zhuǎn)換中間值偶發(fā)大毛刺DAC 輸出耦合到 ADC 輸入 / LDAC 與 CONVST 未對(duì)齊手摸輸入信號(hào)碼值不變前端 RC 濾波時(shí)間常數(shù)太大示波器同時(shí)看 CONVST、BUSY、CS 三個(gè)信號(hào)每次 CONVST 上升沿后 BUSY 拉高BUSY 下降沿后 CS 才拉低時(shí)序就正確了。最后把 DAC 輸出接到采樣前端觀察 100kHz 控制周期內(nèi)是否出現(xiàn)周期毛刺沒有毛刺說明地回路和同步配置都過關(guān)。本文還有配套的精品資源點(diǎn)擊獲取