2011年7月27日 星期三

7) ADC

ADC 暫存器

ADC (Analog to Digital Convertor) 是類比轉數位的模組。ADC 受底下這幾個暫存器控制:

  1. ADMUX: 參考電壓的選擇 (透過 REFS0 和 REFS1 位元),轉換結果靠右或靠左對齊的選擇 (透過 ADLAR 位元)以及 Channel 的選擇 (透過 MUX3:MUX0 位元)
  2. ADCSRA 暫存器: 啟用 ADC(ADEN), 開始 ADC 轉換 (ADSC), 啟用中斷 (ADIE) 以及 Prescale 的設定 (透過ADPS2:ADPS0 位元)
  3. ADCL 及 ADCH 暫存器: 存放 ADC 轉換結果

ADC 可以用中斷的方式運作,不過不在本文的討論範圍。

底下簡介各個暫存器:

首先是 ADMUX – ADC Multiplexer Selection Register:

image

image

值得注意是:

  1. REFS1 和 REFS0 位元是設定參考電壓,一般來說,使用 AVCC 就可以了 (即 01)
  2. ADLAR (ADC Left Adjust Result) 用來選擇轉換結果要靠右或靠左對齊,0 代表靠右對齊,1 代表靠左對齊

接著是 ADCSRA – ADC Control and Status Register A:

image

ADCSRA 是 ADC 模組的主要控制暫存器,用來啟用 (ADEN)、啟動轉換 (ADSC)、設定中斷 (ADIE) 以及選擇 Prescale (ADPS2:ADPS0)。ADPS2:ADPS0 可設定的值為:

image

值得注意的是,要使用 ADC 轉換:

  1. 必須把 ADC clock 設在 50 KHz ~ 200 KHz 之間,也就是說,如果是 CPU clock 是 16 MHz,那麼 Prescale 應設為 128,因為 16 MHz / 128 = 125 KHz
  2. 把 ADEN 設成 1 以啟用 ADC
  3. 把 ADSC 設定為 1 開始轉換,當轉換完畢時 ADSC 會變成 0

最後是 ADCL 和 ADCH 暫存器:

image

這兩個暫存器比較簡單,只是用來存放轉換結果。要注意的是,轉換結果會受 ADLAR 位元 (屬於 ADMUX 暫存器)的影響。

要特別注意的是,一定要先讀取 ADCL 再讀 ADCH,因為一讀 ADCL 後,ADCH 就不會再更新,直到 ADCH 被讀取為止。

實驗目的

使用可變電阻 (potentiometer) 控制 LED 的燈光亮度,達到調光的目的。

接線

image

程式

先來看用 Arduino 寫的程式:

當你旋轉可變電阻,Serial Port 上顯示的數值也會跟著改變,而且 LED 燈號的亮度也會跟著改變。

接著我們自己來寫個 analogRead() 函式:

這個 my_analogRead() 函式跟 Arduino 的 analogRead() 功能是一樣的。

8 意見:

Rick Chung 提到...

您好, 我在此範例中發現一個可修正之處,

關於ADMUX暫存器選擇ADC Channel(Line34),

此處若使用OR運算更新ADMUX,在切換channel時,

是否可能會造成讀取錯誤:

ex: 若欲讀取channel 0與channel 1,

可能因為OR運算導致MUX0被開啟後便無法清除.

若採用:

ADCMUX = (pin & 0x07) | (1 << REFS0)

似乎可以修正此問題.

煩請版主釋疑解惑:)

非常謝謝您的熱心分享關於Arduino -> AVR的實驗!

祝 順心平安

Rick

Cooper Maa 提到...

ADCMUX = (pin & 0x07) | (1 << REFS0);

你這行跟底下這兩行相比,效果是一樣的喔:

ADMUX |= _BV(REFS0);
ADMUX |= (pin & 0x07);

"若欲讀取channel 0與channel 1, 可能因為OR運算導致MUX0被開啟後便無法清除" ??

應該是不會,因為:

當 pin = 0
pin & 0x07 會等於 0

當 pin = 1
pin & 0x07 會等於 1

所以跑到 pin = 1 的時候,channel 就會從 0 變成 1 了。

謝謝你的鼓勵。

"From Arduino to AVR" 這系列文章可能比較硬一點
沒有人帶的話,可能不容易進入,所以,這系列文章的回應比較少。
不過,只要對人有益,不管回應是多少,只要有回響我就覺得值得了。:)

Sven Wang 提到...

請問Cooper前輩

ADMUX |= (pin & 0x07);

這段的意思是什麼呢?
為什麼會有0x07這個數字出現
另外pin腳腳位放在這邊的用意是什麼?
為什麼需要和0x07做&?

謝謝~

Cooper Maa 提到...

ADMUX 暫存器的 MUX3:MUX0 是用來選擇 ADC channel 的,比如當 Arduino 下 analogRead(A5) 時,MUX3:MUX0 就要設為 0101,這樣 ADC 就會轉換 channel 5 (對應 Arduino 的 A5) 的類比訊號。

所以,pin 就是 analog pin number,以 UNO 而言是 A0 至 A5 (即 0 至 5)
至於為什麼 pin 要和 0x07 做 AND 動作呢
原因是 UNO 只有 0 至 5 共 6 個 channel
只需用到 MUX2:MUX0 三個位元,為了不影響 ADMUX 暫存器的其它設定,所以就把 pin 跟 0x07 做 AND 動作,然後再 OR 放到 ADMUX 裏。

Sven Wang 提到...

了解了~
但還是有以下問題

1. 假設我確定我的pin不會寫錯,0x07這樣的設定是否為必要?? 還是說ADMUX |= pin; 其實這樣也ok?

2. 想請教簡單的問題
假設MUX3:MUX0本身裡面的資料在設定前原來是1011
當我們使用ADMUX |= (pin & 0x07);的設定時(pin=5)
那就會變成ADMUX = 1011 & 0101
ADMUX=1111;
這樣的設定就不會是我們原本想要的值(PIN=5)了
請問這樣的問題會出現嗎?

因為先前在您的網誌看到的設定方法都是
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
像這樣一個bit一個bit很謹慎的設定0或是1
但是這邊就比較不一樣, 請問這邊為什麼不使用
ADMUX = _BV(MUX0);這樣的方式設定呢?

謝謝Cooper的解說~~~

Cooper Maa 提到...

假設你確定 pin 不會寫錯,是不需要用 0x07 做 bitmask,寫 ADMUX |= pin 這樣就好沒錯。可是,如果你是寫 library,就像 Arduino 開發者寫 Arduino API 一樣,雖然一般 Arduino 只有 6 個 analog input pins,可是難保使用者不會寫 analogRead(255) 這樣離譜的程式啊。

除非程式去動 MUX3 這個位元,不然它不會是 1 啊。也就是說,MUX3:MUX0 不會預設是 1011,除非程式有去動過 MUX3 的設定才有可能。了解嗎?

ADMUX 開機預設值一定八個位元都是 0,如果沒去動到 MUX3,不然 MUX3 不會是 1。所以那個假設是「在某個但書」下才會發生,比如說 programmer 不呼叫你寫的 API,而是自己去動 ADMUX 暫存器。

ADMUX = _BV(MUX0);這樣的方式設定呢?
哈~ 因為簡單啊!

analogRead(pinNumber), pinNumber = 0 - 5

你想一下,設若 pinNumber 為 5,如果要一個 bit 一個 bit 設定 ADMUX 暫存器,那 analogRead() 該怎麼寫好呢?

analogRead(int pin) {
...
switch (pin) {
case 5:
ADMUX |= _BV(MUX2)| _BV(MUX0);
ADMUX &= ~_BV(MUX1);
...
}
}

這樣的話,analogRead()程式碼會變得很長喔,如果是有 16 組 analog input pins 的 atmega2560,那開發者可能會寫到手痠。haha~

Sven Wang 提到...

恩恩~~所以較長暫存器的設定都直接使用
暫存器名稱=數字
例如: ADMUX |= 5
這樣就簡單多了~~~~~ :))

Cooper Maa 提到...

Yes, you're right! ^o^