戰(zhàn):從寄存器配置到濾波校準(zhǔn))
簡(jiǎn)介面向嵌入式初學(xué)者的MC9S12DG128 AD采集實(shí)驗(yàn)工程圍繞飛思卡爾S12系列單片機(jī)片上ATD模塊演示多通道8位高速采樣、數(shù)據(jù)轉(zhuǎn)換與12864液晶實(shí)時(shí)顯示適合學(xué)習(xí)CodeWarrior環(huán)境下外設(shè)驅(qū)動(dòng)開(kāi)發(fā)也適用于工業(yè)控制、汽車電子等需要模擬量采集的場(chǎng)景。壓縮包共79個(gè)文件以cmd調(diào)試命令、prm鏈接參數(shù)、map映射、s19燒錄文件和c源碼為主另有o目標(biāo)文件、ini工程配置、mcp工程文件等涵蓋PE、TBDML、SofTec等常見(jiàn)調(diào)試器配置整體僅432KB便于快速下載。已有131人學(xué)習(xí)瀏覽。工程內(nèi)含完整CodeWarrior項(xiàng)目可直接打開(kāi)運(yùn)行或?qū)φ臻喿x幫助理解AD轉(zhuǎn)換器初始化、采樣觸發(fā)、中斷讀取與LCD顯示驅(qū)動(dòng)的配合方式同時(shí)多套調(diào)試腳本和鏈接參數(shù)可作為模板遷移到類似HCS12項(xiàng)目中使用。1. MC9S12DG128的AD實(shí)驗(yàn)讀電位器誰(shuí)都會(huì)難在把它當(dāng)成可靠數(shù)據(jù)源MC9S12DG128的AD實(shí)驗(yàn)在很多開(kāi)發(fā)板上是第一道“外設(shè)入門”關(guān)擰一個(gè)電位器在數(shù)碼管或者串口上看到 01023 的變化就宣布自己學(xué)會(huì) AD 了??烧娴阶鲰?xiàng)目時(shí)這塊芯片的 ATD 模塊遠(yuǎn)比表面復(fù)雜——參考電壓怎么接、ATD 時(shí)鐘分到多少兆、通道之間會(huì)不會(huì)串?dāng)_、第一次采樣為什么永遠(yuǎn)不對(duì)這些才是真正決定你能不能把模擬量交給控制邏輯去用的關(guān)鍵。這篇文章我按做這顆芯片的常見(jiàn)路徑走一遍從寄存器原理講到最小驅(qū)動(dòng)代碼再講到多通道掃描、濾波和校準(zhǔn)最后把最容易翻車的幾個(gè)現(xiàn)象按“現(xiàn)象→原因→解決”拆開(kāi)。適合手里有這塊芯片的開(kāi)發(fā)板、想繞過(guò)一堆舊資料里的坑、把 AD 實(shí)驗(yàn)直接做實(shí)的人。2. 先認(rèn)識(shí) ATD 模塊參考電壓、ATD 時(shí)鐘與采樣時(shí)間三個(gè)參數(shù)決定成敗2.1 ATD0 和 ATD1、AN0 到 AN7引腳怎么找和哪些外設(shè)共用MC9S12DG128 內(nèi)部有兩個(gè)獨(dú)立的 ADC 模塊分別叫 ATD0 和 ATD1每個(gè)模塊最多支持 8 個(gè)模擬輸入通道對(duì)應(yīng)到芯片引腳上通常標(biāo)成 AN0AN7 和 AN8AN15也有板子直接復(fù)用 AD0AD7 的絲印。多數(shù)實(shí)驗(yàn)板只把 ATD0 的 8 個(gè)通道引出來(lái)因?yàn)?ATD0 的引腳排布更靠近電位器、傳感器插座而 ATD1 的引腳往往和普通 IO、串口、PWM 復(fù)用在同一個(gè)管腳上布局上不夠友好。所以新手做實(shí)驗(yàn)優(yōu)先找 ATD0 對(duì)應(yīng)的一組引腳別一上來(lái)就對(duì)著芯片管腳圖找“AD”字樣。找引腳有個(gè)非常實(shí)用的辦法先看板子原理圖找電位器或者傳感器輸出接到了哪個(gè)管腳那個(gè)管腳名字里帶 AN、AD、PAD 字樣基本就是模擬輸入。如果手頭沒(méi)有原理圖就看開(kāi)發(fā)板絲印很多板子會(huì)在電位器旁邊標(biāo)注 AD0 或 AN0。找不到絲印時(shí)再用萬(wàn)用表蜂鳴檔量電位器中間抽頭連到芯片的哪個(gè)管腳這個(gè)方法雖然土但比對(duì)著幾十頁(yè)管腳表猜要快得多。還有一個(gè)非常容易踩的共用問(wèn)題這些模擬輸入引腳在上電復(fù)位后是普通 IO要等到你配置完 ATD 寄存器才會(huì)切到模擬功能。如果同一個(gè)引腳上還接了按鍵、LED、數(shù)碼管段選之類的數(shù)字器件測(cè)量結(jié)果會(huì)變成“數(shù)字電平和模擬電壓疊加”的奇怪值而不是單純的傳感器電壓。我見(jiàn)過(guò)不止一個(gè)同學(xué)在 AN7 上接了一個(gè)開(kāi)關(guān)然后死活想不通為什么讀數(shù)只有 0 和 1023 兩個(gè)值。所以配置模擬通道之前先確認(rèn)這個(gè)引腳沒(méi)有被其他外設(shè)占用。2.2 VRH 與 VRL 基準(zhǔn)為什么 5V 供電不等于 5V 參考ATD 模塊并不是拿芯片的 VDD 當(dāng)作滿量程參考它使用的是獨(dú)立的參考電壓引腳 VRH 和 VRL。多數(shù)實(shí)驗(yàn)板上 VRH 直接接 5V、VRL 接 GND所以你在軟件里換算電壓時(shí)默認(rèn)滿量程是 5.0V這沒(méi)問(wèn)題。但要注意VRH 上的電壓紋波、毛刺會(huì)直接進(jìn)入轉(zhuǎn)換結(jié)果的最高位數(shù)字電源的開(kāi)關(guān)噪聲會(huì)通過(guò)這個(gè)引腳耦合進(jìn) ADC導(dǎo)致數(shù)據(jù)在某個(gè)固定值附近來(lái)回跳。如果你的應(yīng)用只有一顆芯片和一個(gè)電位器直接共用 5V 數(shù)字電源也可以但要在 VRH 引腳旁邊放一個(gè) 0.1μF 陶瓷電容走線盡量短這是成本最低的改善手段。更講究一點(diǎn)的做法是用一顆低噪聲穩(wěn)壓芯片單獨(dú)給 VRH 供電模擬地和數(shù)字地單點(diǎn)連接。做項(xiàng)目時(shí)模擬部分和數(shù)字部分的地如果混在一起采樣值的抖動(dòng)會(huì)明顯到用肉眼都能看出來(lái)。判斷是不是基準(zhǔn)問(wèn)題很簡(jiǎn)單把輸入通道短接到 GND讀到的應(yīng)該接近 0再把輸入短接到 VRH讀到的應(yīng)該接近滿量程。如果這兩個(gè)數(shù)都不穩(wěn)定說(shuō)明基準(zhǔn)或者地線有問(wèn)題不是軟件濾波能救回來(lái)的。2.3 ATD 時(shí)鐘分頻與采樣時(shí)間一張表算出一次轉(zhuǎn)換的真實(shí)耗時(shí)ATD 模塊有一個(gè)內(nèi)部轉(zhuǎn)換時(shí)鐘它是由總線時(shí)鐘分頻得到的公式是ATD 時(shí)鐘 總線時(shí)鐘 / (2 × (PRS 1))PRS 是 ATD0CTL4 寄存器低 4 位的值范圍 015。芯片手冊(cè)里通常要求 ATD 時(shí)鐘落在 0.5MHz2MHz 之間太高轉(zhuǎn)換結(jié)果不可靠太低轉(zhuǎn)換速度沒(méi)法看。實(shí)際調(diào)板子時(shí)我一般把 ATD 時(shí)鐘壓在 1MHz2MHz 之間這樣既穩(wěn)定速度也夠用。很多開(kāi)發(fā)板的總線時(shí)鐘是 24MHz在這個(gè)條件下不同 PRS 的典型結(jié)果如下PRS 值分頻系數(shù)ATD 時(shí)鐘結(jié)果0212MHz嚴(yán)重超規(guī)格不可用264MHz偏高容易跳動(dòng)5122MHz處于上限能用7161.5MHz推薦1532750kHz偏保守但很穩(wěn)采樣時(shí)間由 ATD0CTL4 里的采樣時(shí)間位控制常見(jiàn)的設(shè)置有 2 個(gè) ATD 時(shí)鐘周期和 4 個(gè) ATD 時(shí)鐘周期兩種。采樣時(shí)間短適合信號(hào)源阻抗低的場(chǎng)景信號(hào)源阻抗高比如直接用一個(gè)大電阻串到 ADC 引腳時(shí)就要用更長(zhǎng)的采樣時(shí)間否則內(nèi)部采樣電容還沒(méi)充滿就進(jìn)入逐次比較讀出來(lái)的值會(huì)系統(tǒng)性偏低。拿一個(gè) 10kΩ 電阻和 0.1μF 電容組成的傳感器電路來(lái)舉例2 個(gè)周期的采樣時(shí)間很可能不夠換成 4 個(gè)周期之后讀數(shù)立刻穩(wěn)定。這個(gè)參數(shù)不需要頻繁改但遇到“怎么采都是偏小”的怪異現(xiàn)象時(shí)優(yōu)先懷疑采樣時(shí)間。3. 最小驅(qū)動(dòng)代碼寄存器初始化、輪詢讀取與電壓換算3.1 初始化序列ATD0CTL2 到 ATD0CTL5 一次寫(xiě)對(duì)看別人工程的初始化代碼會(huì)發(fā)現(xiàn) ATD 這部分通常只有四五行寄存器賦值但順序和值都有講究。我的習(xí)慣是先上電、再配置模式、最后選通道中間留一個(gè)上電穩(wěn)定延時(shí)。void AD_Delay(unsigned long count) { while (count--) /* 簡(jiǎn)單的軟件延時(shí)用于ATD模塊上電穩(wěn)定 */ ; } void ATD_Init(void) { ATD0CTL2 0xC0; /* ADPU1 模塊上電AFFC1 讀結(jié)果寄存器后自動(dòng)清完成標(biāo)志 */ AD_Delay(100000); /* 給模塊上電和內(nèi)部基準(zhǔn)留出幾毫秒穩(wěn)定時(shí)間 */ ATD0CTL3 0x00; /* 轉(zhuǎn)換序列長(zhǎng)度取最短單次只出一個(gè)結(jié)果結(jié)果放在DR0 */ ATD0CTL4 0x83; /* PRS3ATD時(shí)鐘約1.5MHz采樣時(shí)間取較長(zhǎng)的一檔 */ ATD0CTL5 0x00; /* 單通道模式選中AN0寫(xiě)入同時(shí)觸發(fā)第一次轉(zhuǎn)換 */ }這段代碼里ATD0CTL2 的 bit6 也就是 AFFC 位很關(guān)鍵。不設(shè)置它讀完結(jié)果寄存器后必須手動(dòng)去清完成標(biāo)志否則下一次循環(huán)會(huì)拿到上一次的舊數(shù)據(jù)設(shè)置之后只要讀一次結(jié)果寄存器硬件自動(dòng)把標(biāo)志位清掉代碼會(huì)簡(jiǎn)潔很多。ATD0CTL3 控制轉(zhuǎn)換序列長(zhǎng)度0x00 是最短序列對(duì)只讀一個(gè)通道來(lái)說(shuō)已經(jīng)足夠。ATD0CTL4 的低 4 位是分頻值這里用 3配合 24MHz 總線時(shí)鐘算出來(lái)的 ATD 時(shí)鐘是 1.5MHz比較居中。ATD0CTL5 是啟動(dòng)和通道選擇的關(guān)鍵重新寫(xiě)這個(gè)寄存器會(huì)觸發(fā)一輪新的轉(zhuǎn)換這也是接下來(lái)的讀取函數(shù)能反復(fù)調(diào)用的原因。上電穩(wěn)定延時(shí)不能省。ATD 模塊剛上電時(shí)內(nèi)部采樣電容和參考電壓網(wǎng)絡(luò)還沒(méi)有建立好立即啟動(dòng)轉(zhuǎn)換容易把第一輪數(shù)據(jù)弄成 0 或滿量程。這個(gè)延時(shí)不需要精確到毫秒級(jí)一個(gè)足夠長(zhǎng)的空循環(huán)就行但不要把它寫(xiě)成“先啟動(dòng)轉(zhuǎn)換再延時(shí)”的順序。3.2 單通道輪詢讀取一個(gè) ATD_ReadChannel 函數(shù)解決 90% 的需求初始化完成之后讀取單個(gè)通道的邏輯非常簡(jiǎn)單選通道、啟動(dòng)轉(zhuǎn)換、等待完成標(biāo)志、讀結(jié)果。下面這個(gè)函數(shù)適配 AN0AN7 任意單通道。unsigned int ATD_ReadChannel(unsigned char channel) { unsigned int value; ATD0CTL5 channel 0x07; /* 低3位選擇AN0~AN7寫(xiě)入即觸發(fā)新轉(zhuǎn)換 */ while ((ATD0STAT0 0x80) 0) /* CCF0置1表示第一個(gè)結(jié)果已經(jīng)出來(lái) */ ; value ATD0DR0; /* AFFC模式下讀操作同時(shí)清掉CCF0標(biāo)志 */ return value; }調(diào)用方式很直接raw ATD_ReadChannel(0);就能拿到 AN0 的原始值。這里channel 0x07是為了防止傳入大于 7 的通道號(hào)雖然寫(xiě)越界值也能被寄存器吞掉但會(huì)選中一個(gè)你根本沒(méi)用到的內(nèi)部通道。while 循環(huán)等待的是 CCF0 標(biāo)志也就是結(jié)果寄存器 0 的轉(zhuǎn)換完成位單通道模式下結(jié)果一定放在 ATD0DR0 里。有一點(diǎn)要提醒這個(gè)函數(shù)是輪詢方式轉(zhuǎn)換期間 CPU 會(huì)卡在循環(huán)里。對(duì)大多數(shù)實(shí)驗(yàn)場(chǎng)景這不是問(wèn)題比如讀溫度、讀電壓、讀電位器速度要求都不高。但如果某個(gè)項(xiàng)目的主循環(huán)里還有實(shí)時(shí)性很強(qiáng)的任務(wù)就不要在循環(huán)里頻繁調(diào)用這個(gè)函數(shù)等待可以用中斷方式這部分我們放到多通道章節(jié)里講。3.3 原始值到真實(shí)電壓除法、偏移和取整的寫(xiě)法ATD 結(jié)果寄存器里的是一個(gè)無(wú)量綱的原始值。10 位分辨率下滿量程是 1024 個(gè)碼也就是 01023對(duì)應(yīng)的電壓范圍是 VRH 和 VRL 之間的電壓差。如果 VRH 接 5.0V、VRL 接地真實(shí)電壓就是原始值 × 5.0 / 1024。實(shí)際工程里我建議直接輸出毫伏而不是輸出“0.823456V”這種高精度假象。因?yàn)?10 位的分辨率換算成電壓一位碼大約對(duì)應(yīng) 4.88mV把結(jié)果寫(xiě)成七八位小數(shù)沒(méi)有任何工程意義。unsigned int ATD_ReadMillivolt(unsigned char channel) { unsigned int raw; unsigned long mv; raw ATD_ReadChannel(channel); mv (unsigned long)raw * 5000UL; /* 假設(shè)VRH5.0V先放大到毫伏 */ mv mv 10; /* 右移10位等價(jià)于除以1024 */ return (unsigned int)mv; }這里有個(gè)容易寫(xiě)錯(cuò)的小地方很多人會(huì)除以 1023其實(shí)對(duì) 10 位 ADC 來(lái)說(shuō)滿量程值 1023 對(duì)應(yīng)的是“無(wú)限接近參考電壓”而不是等于參考電壓。校準(zhǔn)和換算時(shí)用 1024 做分母數(shù)學(xué)上更干凈也避免出現(xiàn)除以 1023 后最大讀數(shù)算出 5000.xx mV 的尷尬。如果你用的參考電壓不是標(biāo)準(zhǔn) 5V把代碼里的 5000 換成實(shí)際量到的毫伏數(shù)即可。4. 多通道掃描與連續(xù)轉(zhuǎn)換把采樣變成一套穩(wěn)定機(jī)制4.1 多通道順序掃描的配置MULT 與序列長(zhǎng)度的組合一個(gè)項(xiàng)目里通常不會(huì)只采一路模擬量電機(jī)電流一路、電池電壓一路、電位器一路這時(shí)候就要用多通道掃描。ATD 模塊的 MULT 位用來(lái)開(kāi)啟多通道模式配合轉(zhuǎn)換序列長(zhǎng)度可以實(shí)現(xiàn)“一次啟動(dòng)自動(dòng)把 AN0 到 AN7 按順序采一遍”。void ATD_MultiScan_Init(void) { ATD0CTL2 0xC0; /* 上電 AFFC和單通道模式保持一致 */ AD_Delay(100000); ATD0CTL3 0x07; /* 序列長(zhǎng)度設(shè)為8對(duì)應(yīng)8個(gè)通道輪詢一次 */ ATD0CTL4 0x83; /* PRS3采樣時(shí)間與單通道時(shí)相同 */ ATD0CTL5 0x30; /* MULT1從AN0開(kāi)始單次掃描8個(gè)通道 */ }啟動(dòng)掃描之后轉(zhuǎn)換結(jié)果按順序自動(dòng)填入 ATD0DR0ATD0DR7。也就是說(shuō)AN0 的結(jié)果在 DR0AN1 的結(jié)果在 DR1依此類推。讀取時(shí)先判斷序列完成標(biāo)志再一口氣把 8 個(gè)結(jié)果全部搬走。注意ATD0CTL3 的低 4 位序列長(zhǎng)度字段我這里的 0x07 對(duì)應(yīng) 8 個(gè)轉(zhuǎn)換這是按“長(zhǎng)度 字段值 1”的規(guī)則來(lái)的。網(wǎng)上有些工程里寫(xiě) 0x08 甚至 0x10那是因?yàn)椴煌姹镜男酒Y料對(duì)這個(gè)字段的表述有差異你在自己板子上以官方寄存器表為準(zhǔn)不必照抄陌生代碼。多通道模式下AFFC 的優(yōu)勢(shì)更明顯如果不啟用 AFFC讀一個(gè)結(jié)果就要手動(dòng)清一次標(biāo)志8 個(gè)通道加序列標(biāo)志位代碼量翻倍還容易漏。開(kāi)了 AFFC讀 DR 寄存器就順帶清了對(duì)應(yīng)標(biāo)志最后檢查 SCF 序列完成標(biāo)志即可。4.2 結(jié)果寄存器的順序?qū)RD0 到 D7 與 AN0 到 AN7 的映射讀取掃描結(jié)果的代碼如下void ATD_ReadMulti(unsigned int buffer[8]) { unsigned char i; ATD0CTL5 0x30; /* 重新寫(xiě)入CTL5觸發(fā)一輪新的多通道掃描 */ while ((ATD0STAT1 0x80) 0) /* SCF置1表示整輪序列轉(zhuǎn)換完成 */ ; for (i 0; i 8; i) buffer[i] ATD0DR[i]; /* DR0對(duì)應(yīng)AN0DR1對(duì)應(yīng)AN1以此類推 */ }這里最容易翻車的地方是結(jié)果對(duì)應(yīng)關(guān)系。有些開(kāi)發(fā)板的絲印把通道標(biāo)成 AD0、AD1和芯片內(nèi)部的 DR0、DR1 在順序上一致但也有板子中間插了別的硬件跳線導(dǎo)致 AN2 和 AN3 在 PCB 上互換過(guò)位置。不要依賴絲印名字直接拿一根杜邦線把 AN0 接到一個(gè)固定電壓比如 3.3V然后看程序里哪一個(gè) buffer 元素讀出來(lái)是滿量程附近用這個(gè)實(shí)測(cè)結(jié)果做一張通道映射表再寫(xiě)進(jìn)代碼注釋里。4.3 中斷方式收結(jié)果比輪詢更快、比 DMA 更簡(jiǎn)單的中間選項(xiàng)輪詢方式在單通道實(shí)驗(yàn)里沒(méi)有問(wèn)題但多通道掃描時(shí) CPU 會(huì)空轉(zhuǎn)等待整輪轉(zhuǎn)換完成這段時(shí)間雖然不長(zhǎng)在控制類程序里卻很可能耽誤事。中斷方式是一個(gè)更好的中間選項(xiàng)ATD 轉(zhuǎn)換完成時(shí)觸發(fā)中斷在中斷服務(wù)程序里把結(jié)果讀走主循環(huán)完全不用等待。void ATD_Interrupt_Init(void) { ATD0CTL2 0xE0; /* ADPU1AFFC1ADIE1 使能轉(zhuǎn)換完成中斷 */ AD_Delay(100000); ATD0CTL3 0x07; ATD0CTL4 0x83; ATD0CTL5 0x30; /* 啟動(dòng)多通道掃描 */ ATD0STAT0 0x00; /* 清掉殘留的完成標(biāo)志 */ } #pragma CODE_SEG __NEAR_SEG NON_BANKED void ATD0_ISR(void) { unsigned char i; for (i 0; i 8; i) sample[i] ATD0DR[i]; /* 中斷里只搬數(shù)據(jù)不做浮點(diǎn)運(yùn)算 */ ATD0STAT1 0x80; /* 清序列完成標(biāo)志SCF */ } #pragma CODE_SEG DEFAULT中斷服務(wù)程序里要注意三個(gè)習(xí)慣。第一只做數(shù)據(jù)搬運(yùn)和標(biāo)志清理不要做電壓換算、濾波、顯示這類耗時(shí)操作否則中斷時(shí)間過(guò)長(zhǎng)會(huì)阻塞其他中斷。第二讀取 DR 寄存器之后要確認(rèn)標(biāo)志確實(shí)被清了如果用的是非 AFFC 模式必須手動(dòng)寫(xiě) ATD0STAT1 清 SCF否則會(huì)反復(fù)進(jìn)入中斷。第三中斷服務(wù)函數(shù)名和中斷向量表的對(duì)應(yīng)關(guān)系由開(kāi)發(fā)環(huán)境決定移植代碼時(shí)先查一下官方的中斷向量配置不要直接復(fù)制網(wǎng)上不同芯片工程的函數(shù)名。5. AD 實(shí)驗(yàn)常見(jiàn)問(wèn)題排查跳變、串?dāng)_、首測(cè)失效與毛刺5.1 現(xiàn)象采樣值整體偏高或偏低還是無(wú)規(guī)律跳動(dòng)現(xiàn)象一把輸入接到一個(gè)精確的 2.5V 電壓上讀出來(lái)一直在 2.47V 到 2.53V 之間晃現(xiàn)象二讀出來(lái)穩(wěn)定在 2.45V用萬(wàn)用表量卻是 2.50V。兩種現(xiàn)象的根因完全不同。先看整體偏移。最常見(jiàn)原因是 VRH 實(shí)際電壓不是 5V而是開(kāi)發(fā)板上某個(gè)穩(wěn)壓器輸出的 4.9V 或者 5.05V。你按 5.0V 換算電壓自然整體偏移。解決方法是把 VRH 引腳的電壓用萬(wàn)用表實(shí)測(cè)然后把換算代碼里的 5000 改成實(shí)際毫伏數(shù)。還有一種偏移是因?yàn)樾盘?hào)源內(nèi)阻太大采樣時(shí)間不足導(dǎo)致采樣電容沒(méi)充滿結(jié)果會(huì)比真實(shí)值偏低。再看無(wú)規(guī)律跳動(dòng)。跳動(dòng)通常和地線有關(guān)特別是電烙鐵焊接的杜邦線、面包板長(zhǎng)跳線都會(huì)引入幾十毫伏的噪聲。解決方法是縮短模擬信號(hào)線長(zhǎng)度、使用帶屏蔽的線、在 ADC 引腳和 GND 之間加 0.1μF 電容。不要把鍋都甩給“芯片精度不夠”這塊 10 位 ADC 的靜態(tài)精度沒(méi)這么差多數(shù)跳動(dòng)是外部布局造成的。5.2 現(xiàn)象改讀 AN1AN0 的讀數(shù)跟著被拉過(guò)去實(shí)驗(yàn)板上有兩個(gè)電位器分別接 AN0 和 AN1。單獨(dú)讀 AN0 時(shí)正常單獨(dú)讀 AN1 也正常但只要快速交替讀兩個(gè)通道兩個(gè)讀數(shù)都會(huì)變得不準(zhǔn)甚至 AN0 的值明顯偏向 AN1。這個(gè)現(xiàn)象叫通道串?dāng)_根因有兩個(gè)層面。第一芯片內(nèi)部采樣電容在轉(zhuǎn)換完 AN1 之后還殘留一部分 AN1 的電荷立刻去采 AN0 時(shí)殘留電荷還沒(méi)來(lái)得及釋放就被并入了 AN0 的采樣結(jié)果。第二外部電路上兩個(gè)通道的引腳距離很近PCB 或者說(shuō)杜邦線之間存在寄生電容高阻抗的傳感器信號(hào)很容易被相鄰?fù)ǖ礼詈?。解決通道串?dāng)_最有效的辦法是放慢切換節(jié)奏。多通道掃描時(shí)序列長(zhǎng)度已經(jīng)給足的話芯片自己就帶了“先復(fù)位采樣電容再采樣”的機(jī)制問(wèn)題往往出現(xiàn)在你自己寫(xiě)的輪流調(diào)用單通道讀取函數(shù)、切換速度非??斓那闆r下。另一種做法是硬件上在每個(gè)通道引腳串一個(gè)幾百歐姆的電阻限制寄生電容的沖擊電流效果立竿見(jiàn)影。如果最終讀數(shù)還是要用于精確控制還有一個(gè)軟件補(bǔ)救每個(gè)通道切換后丟第一次采樣結(jié)果用第二次以后的數(shù)據(jù)代價(jià)是多花一點(diǎn)轉(zhuǎn)換時(shí)間。5.3 現(xiàn)象板子上電后第一次轉(zhuǎn)換結(jié)果永遠(yuǎn)是 0x0000 或滿量程很多人在主循環(huán)里一開(kāi)始就調(diào)用 ATD_ReadChannel結(jié)果發(fā)現(xiàn)程序跑起來(lái)后第一次數(shù)據(jù)要么是 0 要么是 1023之后的數(shù)據(jù)完全正常。這個(gè)問(wèn)題出現(xiàn)得極其規(guī)律基本可以斷定是上電時(shí)序問(wèn)題。原因有兩類。第一類是 ATD 模塊上電后沒(méi)有留穩(wěn)定時(shí)間ADPU 位置 1 只是打開(kāi)電源模塊內(nèi)部的偏置電路和參考電壓建立需要一段時(shí)間在這個(gè)期間啟動(dòng)轉(zhuǎn)換得到的就是無(wú)效值。第二類是外部傳感器或電位器的供電還沒(méi)穩(wěn)定芯片已經(jīng)開(kāi)始采樣采到的是電源爬升過(guò)程的中間值。解決方法是約定俗成的初始化之后先做一次“空轉(zhuǎn)采樣”把結(jié)果直接丟棄再用后面輸出的數(shù)據(jù)。如果連丟一次都覺(jué)得不夠穩(wěn)就做兩次空轉(zhuǎn)。這不算浪費(fèi)因?yàn)?ADC 的建立時(shí)間本來(lái)就是硬件特性不是代碼能靠邏輯繞過(guò)去的。5.4 現(xiàn)象加了濾波還是抖問(wèn)題出在結(jié)果寄存器被重復(fù)讀有人寫(xiě)代碼時(shí)為了取平均值在同一個(gè)轉(zhuǎn)換序列結(jié)束前反復(fù)讀同一個(gè)結(jié)果寄存器以為能拿到多個(gè)不同時(shí)刻的采樣值。實(shí)際結(jié)果可能是濾波之后依舊抖動(dòng)甚至抖動(dòng)更厲害。原因在于沒(méi)有清完成標(biāo)志就重復(fù)讀寄存器。AFFC 模式下讀 DR0 的首次操作會(huì)清掉 CCF0第二次讀同一個(gè)寄存器時(shí)轉(zhuǎn)換序列并沒(méi)有真正產(chǎn)生新數(shù)據(jù)你讀到的只是 CPU 某個(gè)緩存或寄存器里殘留的舊值。這時(shí)候做的“平均”實(shí)際上是把同一個(gè)數(shù)加了 N 次再除以 N等于沒(méi)做。正確做法是把讀到的原始值存進(jìn)一個(gè)數(shù)組通過(guò)數(shù)組做滑動(dòng)平均或去極值平均而不是重復(fù)讀寄存器。6. 讓 AD 實(shí)驗(yàn)?zāi)苌享?xiàng)目滑動(dòng)平均濾波與兩點(diǎn)校準(zhǔn)的十行收尾最后這個(gè)階段把實(shí)驗(yàn)代碼往工程上收。濾波和校準(zhǔn)這兩個(gè)環(huán)節(jié)是實(shí)驗(yàn)代碼和量產(chǎn)代碼之間最常見(jiàn)的分水嶺。濾波我推薦用去極值滑動(dòng)平均原理簡(jiǎn)單、占用資源小每輪連續(xù)讀 5 個(gè)原始值去掉最大值和最小值剩下三個(gè)取平均。這種濾波方式對(duì)尖峰脈沖免疫又不像純滑動(dòng)平均那樣滯后嚴(yán)重。unsigned int ATD_FilteredRead(unsigned char channel) { unsigned int sample[5]; unsigned long sum 0; unsigned char i; unsigned int max, min; for (i 0; i 5; i) sample[i] ATD_ReadChannel(channel); /* 連續(xù)讀5次 */ max sample[0]; min sample[0]; for (i 0; i 5; i) { if (sample[i] max) max sample[i]; if (sample[i] min) min sample[i]; } for (i 0; i 5; i) sum sample[i]; sum - max min; /* 去掉一個(gè)最高和一個(gè)最低 */ return (unsigned int)(sum / 3); }校準(zhǔn)更值得做。直接用原始值 × 5000 / 1024換算電壓默認(rèn)了滿量程精確等于 5V、零點(diǎn)精確等于 0V實(shí)際芯片和外圍電路總有一點(diǎn)誤差。我的做法是在板子上做兩點(diǎn)校準(zhǔn)給某個(gè)通道輸入 0V記錄 raw0再輸入一個(gè)已知精確電壓 ref_mV記錄 raw1。之后所有這個(gè)通道的轉(zhuǎn)換都套用斜率和偏移修正。unsigned int ATD_CalibratedMillivolt(unsigned int raw, int raw0, int raw1, unsigned int ref_mv) { if (raw1 raw0) return 0; return ((unsigned long)(raw - raw0) * ref_mv) / (raw1 - raw0); }注意 raw 和 raw0 的差值要用 unsigned long 計(jì)算防止乘法規(guī)格溢出。兩點(diǎn)校準(zhǔn)只修正線性誤差大多數(shù)情況下能把 10 位 ADC 的整體誤差控制在幾個(gè)毫伏量級(jí)。校準(zhǔn)參數(shù)建議存到芯片內(nèi)部的 EEPROM 或者 Flash 參數(shù)區(qū)不要在每次啟動(dòng)時(shí)重新測(cè)量否則溫度漂移后你會(huì)分不清是硬件漂了還是校準(zhǔn)參數(shù)過(guò)期了。關(guān)鍵不是把代碼寫(xiě)得多花哨而是每次改動(dòng)后做同一個(gè)驗(yàn)證動(dòng)作拿萬(wàn)用表量參考電壓和程序輸出的毫伏值對(duì)比至少測(cè) 0V、中間電壓、接近滿量程三個(gè)點(diǎn)誤差在預(yù)期范圍內(nèi)再加濾波和校準(zhǔn)。我自己的習(xí)慣是永遠(yuǎn)保留一路“裸讀”調(diào)試入口校驗(yàn)濾波效果時(shí)切到原始值看變化這樣能很快判斷到底是傳感器信號(hào)本身在抖、還是電源在抖、還是算法把趨勢(shì)給削平了。這些留一手的方法都是被各種莫名其妙的模擬量問(wèn)題教育出來(lái)的希望你少走一遍彎路。本文還有配套的精品資源點(diǎn)擊獲取