ADC
計算機的世界是0和1的。單片機可以通過讀取0和1來確定按鍵狀態,也可以輸出0和1來控制LED。即使是看起來不太0和1的PWM,好像可以輸出0到5V之間的電壓一樣,達到0和1之間的效果,但本質上還是高低電平。
但是,世界上終究還是有0和1無法表示的。如果引腳上被施加0到5V之間的電壓,寄存器PINx無法告訴我們具體情況,只能指示這個電壓是1.5V以下還是3V以上(參考數據手冊“Electrical characteristics”)。這種可以連續變化的信號稱為模擬信號,與離散的、只能取0或1(0或5V)的數字信號對立。
這并不代表數字世界無法處理模擬信號,相反,一種相當常用的處理模擬信號的方法,就是把模擬信號轉換成數字信號,用處理器來運算,然后再轉換成模擬信號。這個過程中涉及到模擬-數字轉換和數字-模擬轉換,分別需要ADC和DAC來實現。大多數單片機,作為現實世界中的工具,需要接觸模擬信號,尤其是模擬信號的輸入,會集成ADC。
ADC的一個參數是分辨率,指它的位數,反映了可以產生的不同輸出的數量(8位ADC可以產生0~255的值)與量化最小物理量(通常是電壓)的能力(比如當參考電壓為2.56V時,理想情況下,8位ADC可以分辨兩個相差0.01V的電壓的不同)。AVR單片機帶有的ADC是10位的。
另一個參數是轉換速率,每秒進行A/D轉換的次數。AVR單片機的ADC為了達到10位分辨率的精度,最大轉換速率為15kSPS(千次采樣每秒)。如果可以接受較低的精度,也可以以200kSPS采樣,獲得8位數據。
分辨率與精度是不同的概念。在這篇入門級教程中,我們只需要知道,A/D轉換是會有誤差的(數據手冊23.7.4一節介紹了可能的誤差來源)。即使是相同的電壓,兩次測量的結果也可能是不同的。
要進行A/D轉換,需要提供參考電壓和待測電壓,轉換的結果為待測電壓參考電壓×2分辨率。寄存器ADMUX中的ADLAR位控制轉換結果的對齊方式。當右對齊時,公式中分辨率取10,轉換結果在16位寄存器ADC中(實際上是兩個8位寄存器ADCH與ADCL,但程序可以直接使用ADC,編譯器會處理好一些注意事項);當左對齊時,分辨率取8,轉換結果在ADCH中。可以直接把ADC當做16位寄存器,編譯器會處理好一些注意事項。
ADC有4種參考電壓可供選擇,分別是AREF、AVCC(5V)、1.1V和2.56V,由REFS1:0選擇。8個單端端口(開發板上引出了4個,端口0到3),以及一些差分端口(1x、10x、200x增益)和兩個參考電壓,共32個通道,可以通過多路復用器連接到ADC上進行轉換,由MUX4:0選擇。注意,ADC只有一個,在同一時刻只能轉換一個通道的電壓。
ADCSRA和ADCSRB用于控制A/D轉換。ADCSRA中ADEN啟用ADC組件,ADSC位啟動一次轉換,到ADIF位為1時轉換結束,需要寫1才能清零。ADPS2:0選擇ADC時鐘分頻系數,這關系到轉換速率:首次采樣(啟用ADC后第一次或同時)需要25個ADC時鐘周期,隨后每次采樣需要13個。ADCSRB可以選擇A/D轉換觸發源。
開發板提供了3.3V電源,可用于給只支持3.3V的設備供電。我們用ADC來測量這個電壓,然后在串口上輸出。
#include #include int main() { uart_init(UART_TX); ADMUX = 0b01 << REFS0 // AVCC as reference | 0b0 << ADLAR // right adjust | 0b00000 << MUX0; // ADC0 single ended ADCSRA = 1 << ADEN // enable ADC | 1 << ADSC // start conversion | 1 << ADIF // clear flag | 0b111 << ADPS0; // divide by 128 while (!(ADCSRA & 1 << ADIF)) // wait until flag is set ; uint16_t voltage = (uint32_t)ADC * 500 >> 10; // ADC / 1024 * 500 (* 10mV) uint8_t integer = 0; // integer part of voltage while (integer * 100 <= voltage) // calculate integer part ++integer; --integer; uint8_t decimal = voltage - integer * 100; // calculate decimal part uart_print_int(integer); // print the voltage uart_print_char('.'); uart_set_align(UART_ALIGN_RIGHT, 2, '0'); uart_print_int(decimal); uart_print_string("Vn"); while (1) ; } 數據手冊28.8節指明,當ADC時鐘為200kHz時,ADC絕對精度可以達到1.9LSB(1LSB就是1024中的1)。經計算得,為了使ADC時鐘不超過這個速率,分頻系數應該取128。 所測電壓為voltage=ADC1024×5V,但直接這樣計算會涉及到浮點運算,而AVR硬件不支持浮點,所有浮點運算都是軟件實現的,速度相當慢,兩個float相乘需要1000多個指令周期,除法需要更多,都是應該竭力避免的。盡管最后的電壓是一個小數,但可以通過移動小數點把它變成整數。5V參考電壓下,精度1.9LSB約為9.28mV,因此右移兩位,以10mV為單位計算。先算乘法以避免浮點除法,算式變為voltage=ADC×5001024。 ADC的值直接與500相乘會溢出,因此需要先提升為uint32_t。當然,你可以把算式約分一下,但不改變會溢出的事實。盡管32位整數不太好處理,但相比浮點數還是容易得多。然后是一個除法。16位整數除法需要173個CPU指令周期(參考:Multiply and Divide Routines),是比較耗時的。盡管這個程序中只計算一次,但還是應該盡量想辦法避免耗時的操作。注意到除數1024是一個特殊的數,是2的10次方,可以通過移位運算來做除法,而移位運算相比除法快得多(也許編譯器會把/ 1024優化成>> 10)。 然后我們需要把這個數的百位部分拿出來作電壓的整數部分,十位和個位作小數部分,可以通過除以100和模100來實現。由于這里的100是一個編譯期常數,編譯期很可能把這個除法和取模優化掉,不調用100多周期的過程。這里我們感受一下手動優化。由于變量voltage一定小于500,可以用乘法和比較的循環來試出這個商,其中乘法的執行次數不超過6次——AVR單片機有雙周期乘法指令。然后,用乘法與減法求出余數。 ADC是單片機編程中相對容易用到浮點與乘除法的場合,設計算法時應盡量注意避免耗時的運算,或手動編寫優化的算法來代替。 電位器 電位器,開發板右側兩個旋鈕中左邊一個,可以連續轉動300°。電氣屬性相當于物理實驗中的滑動變阻器,如果把兩個定片接在VCC和GND上,動片電壓就可以指示旋鈕旋轉的角度,并且通常與角度是成正比的。 之前提到過,A/D轉換是有誤差的,即使輸入電壓保持不變,轉換結果也可能上下浮動。如果再加上一些電磁干擾,比如附近有電機,這種噪音會更加明顯。如果一個程序需要檢測電位器旋轉的位置在中點的哪邊,并僅僅是簡單地比較轉換結果與128的大小關系,這種噪聲會導致嚴重后果,如紅色波形所示: 在閾值128附近,噪聲使轉換結果上下浮動,導致判斷出的狀態迅速跳變。用戶只是慢慢地把旋鈕轉過中間的位置,這顯然不是我們想要的結果。 這時候就需要滯回比較器出場了。滯回比較器的核心特性是,使輸出在0和1之間改變的輸入閾值在兩個方向上是不同的:當信號從低到高越過高閾值時,輸出變為1;當信號從高到低越過低閾值時,輸出變為0;如綠色波形所示(圖中是反相的)。于是,當輸入達到高閾值時,輸出變為1,此時只要噪音沒有大到使輸入回到低閾值,輸出將一直保持為1,濾除了噪聲。 我們寫一個程序,用LED來指示電位器旋鈕位置在中點的哪一側,并在串口上輸出每一次狀態改變,方便我們觀察。 #include #include #include #include void init(); void normal(); void hysteresis(); int main() { init(); while (1) { normal(); // hysteresis(); delay(1); } } static bool status; void change(bool _value) { status = _value; uart_print_string(_value ? "onn" : "offn"); led_set(LED_BLUE, _value); } void init() { pot_init(ADC_0); led_init(); uart_init(UART_TX); status = pot_read() >= 128; } void normal() { bool now = pot_read() >= 128; if (status != now) change(now); } void hysteresis() { uint8_t pot = pot_read(); if (status && pot < 124) change(0); else if (!status && pot >= 132) change(1); } normal和hysteresis函數二選一,其中后者使用了滯回比較的算法。 在normal模式下,把電位器調整到中點附近的一個位置,你會發現黃色的TX指示燈發了瘋一樣地閃,串口軟件顯示一長串的“on”和“off”(仔細調,一定會有)——你根本不需要制造任何干擾,僅憑ADC的誤差就可以讓程序運行地非常糟糕。如果用滿10位的分辨率,這樣的現象會更加明顯。 而在hysteresis模式下,這樣的狀況不會出現。 光敏電阻 光敏電阻是一種特殊的電阻器,在光強的時候電阻小,在光弱的時候電阻大。將一個光敏電阻與一個普通電阻串聯,接在VCC和GND之間,測量中間點的電壓,就能知道光的強弱。 當然,已知開發板上與光敏電阻串聯的電阻是10kΩ,根據某一時刻的ADC轉換結果,也可以計算出此時光敏電阻的阻值。不過不要誤會,是通過電壓而不是阻值來獲得光強。 與電位器一樣,如果要檢測光的強與弱兩種狀態,也要用到滯回比較。取兩個閾值為100和150,兩者相差較大,這是因為我們要在光較弱時開燈,這又會增強亮度(有點負反饋的意味),如果相差不夠大,就會陷入循環當中。 這兩個閾值是隨便取的,實際應用應根據具體環境取值。于是容易想到要把這個功能從應用程序中抽離出來成為一個庫。但是,不同于之前常用的、返回外設狀態讓客戶來決定操作的函數(盡管還是可以這么寫),這個庫是事件驅動的:客戶注冊事件發生時要執行的動作,把程序流程交給框架來控制。 程序分為三個文件:event.h、event.c和main.c,前兩個可以獨立成庫,供以后使用,為了方便,和可執行程序放在一起了。 event.h: #ifndef EVENT_H #define EVENT_H #include #include void ldr_event_init(uint8_t _thl, uint8_t _thh, void (*_func)(bool)); void ldr_event_cycle(); #endif event.c: #include "event.h" #include static void (*handler)(bool); static uint8_t low, high; static bool status; void ldr_event_init(uint8_t _thl, uint8_t _thh, void (*_func)(bool)) { ldr_init(ADC_1); low = _thl; high = _thh; handler = _func; uint8_t ldr = ldr_read(); if (ldr <= low) handler(status = 0); else handler(status = 1); } void ldr_event_cycle() { uint8_t ldr = ldr_read(); if (status && ldr <= low) handler(status = 0); else if (!status && ldr >= high) handler(status = 1); } main.c: #include #include #include "event.h" void handler(bool e) { if (e) led_off(); else led_on(); } int main() { led_init(); ldr_event_init(100, 150, handler); while (1) { ldr_event_cycle(); delay(1000); } } 客戶先編寫事件處理函數handler,參數為一個bool,返回void,這是ldr_event_init所規定的。handler根據參數執行相應動作:當e為true時,光由弱變強,關燈;反之開燈。在調用ldr_event_init時,把這個函數的指針作為參數傳入。隨后,每隔1秒調用一次ldr_event_cycle。 請先花一點時間,把庫的每一行理解清楚。然后,我們站在客戶的角度來看,使用這個庫是相對方便的——只需考慮事件,即光的變化,而無需考慮過程,即如何檢測這一變化——事實上客戶根本沒有去檢測,更別說如何了。不過,main函數必須每隔一段時間調用一次ldr_event_cycle。在學了定時器中斷以后,main函數就可以完全還給客戶了。
上一篇:BMP180測量海拔高度傳感器單片機程序
下一篇:ATmega328P定時器詳解
推薦閱讀
史海拾趣
隨著全球市場的不斷融合,EREM公司開始尋求國際合作機會。通過與國外知名企業的合作,EREM不僅將產品銷售到了全球各地,還學到了許多先進的管理經驗和技術。同時,EREM也積極參與國際展覽和交流活動,提升了品牌知名度和影響力。這些努力使得EREM在國際市場上逐漸嶄露頭角。
EREM公司起源于1960年代的瑞士日內瓦,由一群熱衷于精密工具制造的工程師創立。在當時,電子行業正逐漸興起,對高精度工具的需求日益增長。EREM的創始人看到了這一市場機遇,決定專注于生產高精度鑷子和鉗子,以滿足電子行業的需求。他們憑借精湛的工藝和不懈的努力,逐漸在行業內建立了良好的聲譽。
隨著全球市場的不斷融合,EREM公司開始尋求國際合作機會。通過與國外知名企業的合作,EREM不僅將產品銷售到了全球各地,還學到了許多先進的管理經驗和技術。同時,EREM也積極參與國際展覽和交流活動,提升了品牌知名度和影響力。這些努力使得EREM在國際市場上逐漸嶄露頭角。
為了進一步擴大業務范圍和提升技術實力,Keil公司決定與美國德克薩斯州理查森的Keil Software Inc進行聯合運營。這一舉措不僅使公司得以接觸更廣闊的市場,還促進了雙方在技術、產品和市場等方面的深度融合。聯合運營后,Keil公司的發展速度明顯加快,逐漸在嵌入式系統開發工具市場上占據了重要地位。
1980年,Dytran由傳感器行業的資深人士尼古拉斯·D.改變(Nicholas D. Change)創立。在此之前,尼古拉斯在紐約地區的一家開創性傳感器制造商擔任產品線經理,積累了豐富的行業經驗。他看到了傳感器市場的巨大潛力,于是決定成立自己的公司,將自己的設計和創新帶入這個不斷增長的領域。Dytran的名字來源于“動態”和“換能器”的結合,寓意著公司專注于動態測量和轉換技術的研發。
在創業初期,Dytran就展現出了其技術實力。公司專注于壓電加速度計的研發和生產,憑借其卓越的性能和穩定性,迅速在市場上嶄露頭角。此后,Dytran不斷推出新的傳感器產品,如稱重傳感器、壓力傳感器和脈沖錘等,逐漸在電子行業樹立了其技術領先的形象。
[code language="J#"] [/code] 企業新聞 => 數字技術中的模擬電路技術 【 字體:大 中 小 】【打印此頁】 發布日期:[2003-01-09] 共閱[83]次 ...… 查看全部問答∨ |
先設定應用背景: A測控16個溫度點,B控制4臺步進電機,C控制8個繼電器,D至少預留12個以上的測控端接受多種傳感器的電信號 還有一個小前提,芯片容易買到。因為我研究測控模塊只是為了提高產品的技術含量,不可能大批制造板子,更不會轉行 1、F ...… 查看全部問答∨ |
本帖最后由 jameswangsynnex 于 2015-3-3 20:02 編輯 CMOS即互補性金屬氧化物半導體。其在微處理器、閃存和特定用途集成電路(ASIC)的半導體技術上占有絕對重要的地位。CMOS和CCD都是可用來感受光線變化的半導體。CMOS主要是利用“硅”和“鍺”這兩 ...… 查看全部問答∨ |
|
pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE); 編譯時報告PTHREAD_MUTEX_RECURSIVE沒有聲明, 如何改正?? … 查看全部問答∨ |
|
我的要求很簡單,就是需要10HZ信號,5V左右的穩定幅度就可以了。供電電源為正負5V。 查過很多(如XR2206,ICL8038,MAX038,XR8038)都停產了,有沒有還沒有停產的推薦一下呀,最好成本不是太高的那種。 先謝謝了。… 查看全部問答∨ |
編譯2.6.14.1的內核出錯了,有會的看看,已經一個星期了,急,在線.......... 我用的是arm-linux-gcc3.3.2,在編譯kernel2.6.14.1時,報錯 /usr/local/arm/3.3.2/bin/arm-linux-ld:arch/arm/kernel/vmlinux.lds:711:parse error 這是什么錯誤啊,大概是什么原因引起的,如何修改呢? 大家幫幫忙,都弄了一個星期了,一點結果 ...… 查看全部問答∨ |