動(dòng)移植:從時(shí)序到濾波的完整指南)
簡(jiǎn)介面向CS5513與CS5532芯片的嵌入式驅(qū)動(dòng)/引導(dǎo)程序源碼包適合單片機(jī)開(kāi)發(fā)、工業(yè)控制及通信接口調(diào)試人員參考使用。壓縮包共4個(gè)文件以C源文件和頭文件為主整體僅5KB包含CS5513引導(dǎo)加載程序及CS5532相關(guān)驅(qū)動(dòng)代碼。C源文件承載初始化與通信邏輯頭文件定義寄存器映射和接口便于快速接入現(xiàn)有工程。源碼展示了芯片初始化、寄存器配置、基礎(chǔ)通信等核心寫(xiě)法可幫助開(kāi)發(fā)者理解外設(shè)控制流程并在其基礎(chǔ)上定制驅(qū)動(dòng)。引導(dǎo)加載部分可作為設(shè)備啟動(dòng)時(shí)加載固件的參考CS5532相關(guān)實(shí)現(xiàn)則提供了接口控制的思路。已有560人學(xué)習(xí)下載對(duì)于需要接入傳感器、顯示器或通信模塊的嵌入式項(xiàng)目這是一份小巧而實(shí)用的參考樣例。1. 從 CS55XX.rar 說(shuō)起這套 24 位 ADC 驅(qū)動(dòng)在解決什么問(wèn)題做稱(chēng)重儀表或者工業(yè)變送器繞不開(kāi) 24 位 Δ-Σ ADC 的驅(qū)動(dòng)移植。CS55XX.rar 里這兩顆芯片CS5513 管低功耗單通道CS5532 管雙通道加可編程增益和片內(nèi)校準(zhǔn)屬于電子秤、應(yīng)變橋測(cè)量、電池供電采集里很常見(jiàn)的一檔。資源里cs5513bs.h/c、cs5532.h/c是一整套可編譯的 C 源碼覆蓋初始化、三線/SPI 數(shù)據(jù)讀取、寄存器配置和校準(zhǔn)狀態(tài)處理。這篇文章按源碼的實(shí)際組織方式把芯片差異、驅(qū)動(dòng)分層、時(shí)序和濾波一次講透。適合要把這套驅(qū)動(dòng)移植到 STM32、51 或國(guó)產(chǎn) ARM 單片機(jī)上的開(kāi)發(fā)者也適合手里有評(píng)估板但讀不懂采樣值為什么亂跳的工程師。2. CS5513 與 CS5532 的差異為什么同一套源碼要分兩個(gè)文件2.1 一個(gè)容易先入為主的誤讀cs5513bs 不是 bootloader資源列表里的cs5513-bs和cs5513bs.h/c很容易被想成 Bootloader。從工程命名習(xí)慣看bs更可能是 Board Support 的縮寫(xiě)也就是把芯片驅(qū)動(dòng)里的時(shí)序操作綁定到具體最小系統(tǒng)板引腳的適配層。Bootloader 在嵌入式工程里一般叫boot.c、bootloader.c不會(huì)和cs5532.c混在同一層目錄更不會(huì)出現(xiàn)在驅(qū)動(dòng)頭文件里。把bs和算法代碼分開(kāi)是這類(lèi) ADC 評(píng)估板源碼的常見(jiàn)組織方式底層 SPI/三線時(shí)序、片選引腳、延時(shí)函數(shù)放在bs文件里寄存器命令、校準(zhǔn)順序、轉(zhuǎn)換結(jié)果讀取放在主驅(qū)動(dòng)文件里。這樣替換 MCU 時(shí)只需要重寫(xiě)bs文件算法層一行不動(dòng)。讀這套源碼時(shí)先認(rèn)清這個(gè)邊界后面找 bug 會(huì)省很多時(shí)間。2.2 寄存器接口與數(shù)據(jù)格式CS5513 是單通道差分輸入的 Δ-Σ ADC串行接口是典型三線制CS 片選、SCLK 時(shí)鐘、SDO 數(shù)據(jù)輸出。主機(jī)只是被動(dòng)提供時(shí)鐘芯片在時(shí)鐘邊沿把轉(zhuǎn)換結(jié)果一位一位推出來(lái)。它沒(méi)有復(fù)雜的寄存器組適合做低成本電池供電稱(chēng)重缺點(diǎn)是零點(diǎn)和增益誤差要靠外部校準(zhǔn)在應(yīng)用層扣除。CS5532 就復(fù)雜不少雙通道差分輸入、SPI 兼容四線接口、可編程增益1 到 64 倍、片內(nèi)偏移校準(zhǔn)和增益校準(zhǔn)。正是因?yàn)槎嗔伺渲眉拇嫫骱托?zhǔn)引擎才必須在cs5532.c里單獨(dú)實(shí)現(xiàn)一套命令讀寫(xiě)。CS5532 的串行命令格式比較固定驅(qū)動(dòng)里一般會(huì)先把讀寫(xiě)命令收斂成宏#define CS5532_CMD_WRITE(reg) (0x40u | (reg)) /* bit7 1 表示寫(xiě)寄存器 */ #define CS5532_CMD_READ(reg) (0xC0u | (reg)) /* bit7 1, bit6 1 表示讀寄存器 */ enum { CS5532_REG_CONFIG, /* 轉(zhuǎn)換速率、連續(xù)/單次模式、校準(zhǔn)使能 */ CS5532_REG_CHSETUP, /* 通道選擇、PGA 增益、極性設(shè)置 */ CS5532_REG_OFSCAL, /* 偏移校準(zhǔn)系數(shù) */ CS5532_REG_GNCAL /* 增益校準(zhǔn)系數(shù) */ };命令字節(jié)的低 5 位是寄存器索引高位區(qū)分讀寫(xiě)方向。這里最容易踩坑的是枚舉順序必須和芯片手冊(cè)里的寄存器映射一一對(duì)應(yīng)不能靠感覺(jué)調(diào)整索引寫(xiě)錯(cuò)的表現(xiàn)通常不是報(bào)錯(cuò)而是讀回來(lái)的配置值張冠李戴校準(zhǔn)狀態(tài)永遠(yuǎn)等不到。2.3 兩顆芯片的關(guān)鍵差異對(duì)比項(xiàng)CS5513CS5532輸入通道1 路差分2 路差分可編程增益無(wú)固定增益有典型 1/2/4/8/16/32/64串行接口三線 CS/SCLK/SDOSPI 兼容四線片內(nèi)校準(zhǔn)無(wú)應(yīng)用層做零點(diǎn)扣除有偏移和增益校準(zhǔn)典型用途電池供電秤、低功耗采集工業(yè)變送器、多通道稱(chēng)重CS5513 的 SDO 上承擔(dān)數(shù)據(jù)輸出和準(zhǔn)備信號(hào)CS5532 的SDO/DRDY引腳在轉(zhuǎn)換完成后拉低。很多移植失敗的根因就是把 CS5532 的 DRDY 當(dāng)成普通 SPI 的 MISO 去讀導(dǎo)致讀到的始終是上次轉(zhuǎn)換的殘留值。驅(qū)動(dòng)分層再完整引腳語(yǔ)義搞錯(cuò)一樣白搭。3. 驅(qū)動(dòng)分層與核心函數(shù)從 cs55xx.h 到實(shí)際讀取轉(zhuǎn)換結(jié)果3.1 把總線操作收斂成函數(shù)指針一套驅(qū)動(dòng)想在 STM32、51、或者 Linux 用戶態(tài)模擬器上都能跑最直接的做法是把硬件操作收斂成一組函數(shù)指針?lè)旁诳偩€結(jié)構(gòu)體里。CS55XX 源碼里能看到的典型抽象如下typedef struct { void (*cs)(uint8_t level); /* 片選控制低有效 */ void (*sclk)(uint8_t level); /* 串行時(shí)鐘電平 */ uint8_t (*sdo_read)(void); /* 讀數(shù)據(jù)位返回 0/1 */ void (*delay_us)(uint32_t us); /* 微秒級(jí)延時(shí) */ } cs55xx_bus_t; typedef struct { cs55xx_bus_t bus; int32_t last_code; /* 最近一次轉(zhuǎn)換結(jié)果 */ uint8_t channel; /* 當(dāng)前通道 0/1 */ uint8_t pga; /* CS5532 增益倍數(shù)編碼 */ uint8_t inited; /* 初始化標(biāo)志 */ } cs55xx_dev_t;使用函數(shù)指針而不是直接調(diào)HAL_GPIO_WritePin核心原因是 CS5513 和 CS5532 的時(shí)序要求不同。CS5513 只需要三個(gè)引腳CS5532 需要四個(gè)如果驅(qū)動(dòng)代碼里到處寫(xiě)死 HAL 函數(shù)換 MCU 時(shí)要把所有 SCLK 操作替換一遍。收斂成bus結(jié)構(gòu)體后cs5513bs.c只需要做一件事把這三個(gè)或四個(gè)函數(shù)指針對(duì)應(yīng)到目標(biāo)板的具體 GPIO。cs55xx_dev_t里的channel和pga是給上層保存當(dāng)前工作狀態(tài)的讀取函數(shù)不需要每次重新解析配置。3.2 初始化序列先停轉(zhuǎn)換再寫(xiě)配置CS5532 的初始化順序比較講究。先寫(xiě)配置寄存器把轉(zhuǎn)換停掉再寫(xiě)通道和增益設(shè)置最后啟動(dòng)轉(zhuǎn)換反過(guò)來(lái)操作時(shí)配置寄存器可能在轉(zhuǎn)換進(jìn)行中被覆蓋。int cs55xx_init(cs55xx_dev_t *dev) { dev-bus.cs(1); dev-bus.delay_us(10); dev-bus.cs(0); /* 拉低片選進(jìn)入串行就緒態(tài) */ dev-bus.delay_us(20); if (dev-inited ! 0x5A) { cs5532_write_reg(dev, CS5532_REG_CONFIG, 0x0000); /* 停止當(dāng)前轉(zhuǎn)換 */ cs5532_write_reg(dev, CS5532_REG_CHSETUP, (dev-channel 5) | pga_to_bits(dev-pga)); dev-inited 0x5A; } return 0; }代碼里先把配置寄存器寫(xiě)成 0是為了防止芯片上電后處于連續(xù)轉(zhuǎn)換狀態(tài)時(shí)配置寫(xiě)入被狀態(tài)機(jī)忽略。channel 左移 5 位是 CS5532 通道選擇位的常見(jiàn)排布方式具體位偏移在移植時(shí)要以數(shù)據(jù)手冊(cè)的 CHSETUP 寄存器定義為準(zhǔn)。inited標(biāo)志位防止重復(fù)初始化時(shí)把校準(zhǔn)系數(shù)沖掉很多人忽略這一點(diǎn)導(dǎo)致每次調(diào)用初始化函數(shù)后測(cè)量值都回到默認(rèn)增益誤差。3.3 讀取 24 位轉(zhuǎn)換碼讀取轉(zhuǎn)換結(jié)果是這套驅(qū)動(dòng)里最關(guān)鍵的函數(shù)。CS5532 要先等 DRDY 拉低CS5513 則要求 CS 拉低后給出連續(xù)時(shí)鐘。兩者拼裝 24 位數(shù)據(jù)的邏輯是一致的。int32_t cs55xx_read_code(cs55xx_dev_t *dev) { int32_t code 0; int i, n; for (i 0; i 10000; i) { /* 超時(shí)保護(hù)防止 SDO 卡死 */ if (dev-bus.sdo_read() 0) break; dev-bus.delay_us(1); } if (i 10000) return -1; n 23; while (n 0) { code 1; /* 先移位騰出最低位 */ if (dev-bus.sdo_read()) code | 1; dev-bus.sclk(1); dev-bus.delay_us(1); dev-bus.sclk(0); dev-bus.delay_us(1); n--; } if (code 0x800000) /* 24 位補(bǔ)碼擴(kuò)展到 int32 */ code | ~0xFFFFFF; dev-last_code code; return code; }注意循環(huán)里是先讀 SDO再拉高 SCLK。這是為了保證數(shù)據(jù)在 SCLK 變化前被采樣到順序?qū)懛磿?huì)整體右移一位導(dǎo)致讀出的數(shù)字翻倍或者出現(xiàn)明顯的 2 倍關(guān)系。如果把 SCLK 拉高后再去讀 SDO就要等芯片把數(shù)據(jù)更新到位時(shí)序余量會(huì)變得很緊張。timeout對(duì) CS5532 尤其重要如果通道懸空或者 PGA 設(shè)置超出輸入范圍DRDY 可能永遠(yuǎn)不拉低沒(méi)有超時(shí)的驅(qū)動(dòng)在產(chǎn)線上就是死等。提示讀取期間不要被中斷打斷。24 個(gè)時(shí)鐘必須連續(xù)中間插入一個(gè) RTOS 調(diào)度或者串口中斷SCLK 上的毛刺會(huì)讓位移錯(cuò)位讀回來(lái)的值差得很離譜。確實(shí)有中斷需求時(shí)臨時(shí)關(guān)中斷或者用 DMA 加片選同步。4. 片內(nèi)校準(zhǔn)與 SPI 時(shí)序CS5532 采樣值不穩(wěn)時(shí)先查這里4.1 片內(nèi)校準(zhǔn)的完整順序CS5532 的片內(nèi)校準(zhǔn)不是上電自動(dòng)完成的必須由驅(qū)動(dòng)主動(dòng)觸發(fā)。完整順序是先選通道和增益再把輸入短接或接地做偏移校準(zhǔn)接著輸入已知參考電壓做增益校準(zhǔn)校準(zhǔn)完成后各寄存器里的系數(shù)直接參與后續(xù)轉(zhuǎn)換。static int cs5532_calibrate(cs55xx_dev_t *dev, uint8_t ch, uint8_t pga) { cs5532_set_channel(dev, ch, pga); /* 先鎖定工作條件 */ cs5532_set_input(dev, CS5532_CH_GND); /* 偏移校準(zhǔn)要求輸入接近 0 */ cs5532_start_calib(dev, CS5532_CAL_OFFSET); /* 觸發(fā)偏移校準(zhǔn) */ while (dev-bus.sdo_read()) { /* 等待 SDO 拉低 校準(zhǔn)完成 */ if (timeout_expired()) return -1; } cs5532_set_input(dev, CS5532_CH_VREF); /* 再接已知參考電平 */ cs5532_start_calib(dev, CS5532_CAL_GAIN); /* 觸發(fā)增益校準(zhǔn) */ while (dev-bus.sdo_read()) { if (timeout_expired()) return -1; } return 0; }cs5532_start_calib通常就是往配置寄存器寫(xiě)校準(zhǔn)使能位但不同版本的數(shù)據(jù)手冊(cè)對(duì)校準(zhǔn)完成標(biāo)志的定義有差異有的看命令寄存器回讀位有的直接看 DRDY 電平。這里用sdo_read()輪詢(xún)是兼容性最好的做法代價(jià)是校準(zhǔn)期間 CPU 被占用CS5532 校準(zhǔn)最長(zhǎng)可能需要幾百毫秒對(duì)低功耗產(chǎn)品來(lái)說(shuō)這個(gè)時(shí)間要算進(jìn)啟動(dòng)流程里。還有一個(gè)容易被忽視的細(xì)節(jié)偏移校準(zhǔn)和增益校準(zhǔn)必須使用同一組 PGA 設(shè)置。如果你用 64 倍增益做偏移校準(zhǔn)卻用 1 倍增益做增益校準(zhǔn)校準(zhǔn)系數(shù)之間沒(méi)有對(duì)應(yīng)關(guān)系最終測(cè)量值會(huì)比不做校準(zhǔn)還難處理。驅(qū)動(dòng)里要把通道和增益作為校準(zhǔn)函數(shù)的入?yún)⒉灰鳛槿肿兞侩[式使用。4.2 用邏輯分析儀核對(duì)時(shí)序拿到板子先別急著看上位機(jī)數(shù)據(jù)把 CS、SCLK、SDO 三根線掛到邏輯分析儀上抓一次完整的讀取過(guò)程。重點(diǎn)關(guān)注下面的參數(shù)檢查點(diǎn)推薦做法常見(jiàn)問(wèn)題CS 拉低到第一個(gè) SCLK預(yù)留 1us 以上上電立即讀數(shù)得到全 FFSCLK 高低電平寬度按手冊(cè)最小脈寬加 100ns 余量SPI 分頻太快SDO 還沒(méi)穩(wěn)定SDO 更新邊沿確認(rèn)是上升沿還是下降沿鎖存邊沿選反讀回全 0 或全 1CS5532 DRDY 信號(hào)轉(zhuǎn)換完成應(yīng)該是低電平脈沖一直為高說(shuō)明沒(méi)啟動(dòng)轉(zhuǎn)換CS5513 的三線時(shí)序里沒(méi)有 DRDY 這個(gè)角色芯片在 CS 拉低后由 SCLK 推動(dòng)數(shù)據(jù)輸出主機(jī)必須假設(shè)只要 CS 拉低數(shù)據(jù)就是本次轉(zhuǎn)換的結(jié)果。這意味著外部一定要保證 CS 拉低時(shí)轉(zhuǎn)換已經(jīng)完成或者像驅(qū)動(dòng)代碼里那樣在 CS 拉低前輪詢(xún) SDO 作為準(zhǔn)備信號(hào)。SPI 主頻的選擇不是越快越好。CS5532 雖然標(biāo)稱(chēng)支持幾 MHz 的時(shí)鐘但實(shí)際布線和電源噪聲會(huì)把高速時(shí)鐘的采樣窗口壓得很小。工程上我一般先把 SCLK 放到 100kHz 驗(yàn)證數(shù)據(jù)通路確認(rèn)讀碼穩(wěn)定后再逐步提高分頻系數(shù)。數(shù)據(jù)通路驗(yàn)證通過(guò)后如果發(fā)現(xiàn)偶發(fā)跳變優(yōu)先檢查的是電源去耦和地線而不是繼續(xù)降頻。4.3 典型怪異現(xiàn)象的排查表采樣值不穩(wěn)時(shí)大部分問(wèn)題集中在下面幾類(lèi)讀回全是 0xFFCS5532 的 DRDY 還沒(méi)變低就開(kāi)始讀數(shù)或者 CS 拉低后空跑了幾個(gè)時(shí)鐘真正的 24 位數(shù)據(jù)被擠出去。讀回全是 0x00SCLK 采樣邊沿選反或者把 SDI 和 SDO 接反了。CS5532 的 SDI 只負(fù)責(zé)接收命令SDO 只負(fù)責(zé)輸出數(shù)據(jù)兩者在板上不能互換。讀值翻倍或者成 2 倍關(guān)系移位順序錯(cuò)誤大概率是先拉時(shí)鐘再讀數(shù)據(jù)導(dǎo)致的整體錯(cuò)位。低位持續(xù)亂跳基準(zhǔn)源噪聲、模擬電源紋波、長(zhǎng)飛線串?dāng)_。和 Modbus 那種按幀接收、等空閑超時(shí)的串口程序不一樣SPI 讀 ADC 是逐 bit 同步的波形上毛刺會(huì)直接進(jìn)入結(jié)果協(xié)議棧層面的重試機(jī)制救不回來(lái)。校準(zhǔn)狀態(tài)一直不完成通道輸入懸空。CS5532 校準(zhǔn)引擎需要輸入引腳有一個(gè)確定電平懸空時(shí)前端比較器處于不確定狀態(tài)校準(zhǔn)過(guò)程可能一直等不到標(biāo)志位。排查順序建議是先看 DRDY 有沒(méi)有再看 24 個(gè)時(shí)鐘是否完整最后才去懷疑校準(zhǔn)系數(shù)和濾波。因?yàn)榍叭呤悄懿荒茏x到正確位流的問(wèn)題后者是位流準(zhǔn)不準(zhǔn)的問(wèn)題兩者混在一起查會(huì)非常浪費(fèi)時(shí)間。5. 雙通道輪詢(xún)與濾波把 24 位編碼變成可用工程量5.1 通道切換后丟掉第一幀CS5532 的雙通道切換本身不復(fù)雜寫(xiě) CHSETUP 寄存器之后等 DRDY 即可但通道切換后的第一個(gè)轉(zhuǎn)換結(jié)果必須丟棄。原因是輸入多路開(kāi)關(guān)和 Δ-Σ 調(diào)制器內(nèi)部的濾波器需要幾個(gè)轉(zhuǎn)換周期才能穩(wěn)定直接取第一幀數(shù)據(jù)相當(dāng)于拿濾波器瞬態(tài)響應(yīng)當(dāng)有效信號(hào)。for (;;) { cs5532_set_channel(dev, 0, PGA_64); cs55xx_read_code(dev); /* 丟棄幀 */ ch0 cs55xx_read_code(dev); /* 有效幀 */ cs5532_set_channel(dev, 1, PGA_1); cs55xx_read_code(dev); /* 丟棄幀 */ ch1 cs55xx_read_code(dev); /* 有效幀 */ process(ch0, ch1); }這個(gè)切換后先丟一幀的經(jīng)驗(yàn)對(duì) CS5532 和 CS5513 都適用。對(duì)更快的動(dòng)態(tài)信號(hào)可能需要丟兩到三幀具體看輸出速率和模擬前端 RC 的建立時(shí)間。輪詢(xún)周期里最容易被忽略的是 PGA 切換本身帶來(lái)的建立時(shí)間高阻信號(hào)源配大增益時(shí)尤其明顯。5.2 滑動(dòng)平均與防脈沖濾波24 位 ADC 的原始碼值直接顯示成重量肉眼會(huì)受不了那個(gè)跳動(dòng)。常用做法是滑動(dòng)平均窗口長(zhǎng)度選 8 到 16。注意滑動(dòng)平均對(duì) 50Hz 工頻干擾有抑制效果但窗口長(zhǎng)度和輸出速率要匹配輸出速率是 60SPS 時(shí)16 點(diǎn)平均對(duì)應(yīng) 4Hz 帶寬對(duì)呼吸秤這種慢信號(hào)夠用對(duì)動(dòng)態(tài)稱(chēng)重就會(huì)拖后腿。#define FLT_LEN 8 static int32_t filter_feed(int32_t v) { static int32_t buf[FLT_LEN]; static uint8_t idx; int32_t sum 0; buf[idx (FLT_LEN - 1)] v; /* 環(huán)形緩沖下標(biāo)按 8 取模 */ idx; for (uint8_t i 0; i FLT_LEN; i) sum buf[i]; return sum / FLT_LEN; }環(huán)形緩沖配合取模避免了每次插入都平移整個(gè)數(shù)組FLT_LEN取 2 的冪是為了讓取模運(yùn)算編譯成位與指令。對(duì)于偶發(fā)的脈沖干擾可以在滑動(dòng)平均前加一個(gè) 3 到 5 點(diǎn)的中值濾波。中值濾波剔除的是異常碼值滑動(dòng)平均平滑的是隨機(jī)噪聲兩者組合比單一算法更能兼顧抗干擾和響應(yīng)速度。5.3 從碼值換算成工程量CS5513 和 CS5532 輸出的都是 24 位二進(jìn)制補(bǔ)碼正滿量程接近 0x7FFFFF負(fù)滿量程接近 0x800000。把碼值換算成物理量關(guān)鍵是先算每 LSB 對(duì)應(yīng)的電壓#define VREF 2.5f /* 參考電壓?jiǎn)挝?V */ #define PGA 64 /* 當(dāng)前增益 */ #define LSB_V (VREF / (float)(PGA * (1L 23))) /* 每碼電壓 */ float weight; int32_t code; code cs55xx_read_code(dev); weight (float)code * LSB_V * SENSOR_SCALE; /* SENSOR_SCALE: mV/V 換算到質(zhì)量 */1L 23對(duì)應(yīng) 24 位雙極性碼的半程VREF / PGA是滿量程輸入電壓。CS5532 在高增益下滿量程只有幾十毫伏模擬前端任何一點(diǎn)偏移都會(huì)被放大成大數(shù)這也是前幾章反復(fù)強(qiáng)調(diào)校準(zhǔn)和時(shí)序的原因。如果確認(rèn)每 LSB 電壓后讀出的碼值和萬(wàn)用表對(duì)不上先把 PGA 配置打印出來(lái)核一遍——這個(gè)參數(shù)寫(xiě)在 CHSETUP 寄存器的位域里不同資料給的位寬極容易搞錯(cuò)校準(zhǔn)函數(shù)和讀取函數(shù)若各用各的 PGA 值換算結(jié)果會(huì)系統(tǒng)性偏移且濾波無(wú)法修正。本文還有配套的精品資源點(diǎn)擊獲取