AVR ATMega328P ADC通道选择问题
AVR ATMega328P ADC channel selection issue
我现在正在摆弄 ATMega328P,想通过 ADC 从引脚读取模拟值,然后简单地将值输出到 4 个 LED。真的很简单
#define F_CPU 20000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define BRIGHTNESS_PIN 2
#define ADC_SAMPLES 5
void init_adc()
{
//set ADC VRef to AVCC
ADMUX |= (1 << REFS0);
//enable ADC and set pre-scaler to 128
ADCSRA = (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2) | (1 << ADEN);
}
uint16_t read_adc(unsigned char channel)
{
//clear lower 4 bits of ADMUX and select ADC channel according to argument
ADMUX &= (0xF0);
ADMUX |= (channel & 0x0F); //set channel, limit channel selection to lower 4 bits
//start ADC conversion
ADCSRA |= (1 << ADSC);
//wait for conversion to finish
while(!(ADCSRA & (1 << ADIF)));
ADCSRA |= (1 << ADIF); //reset as required
return ADC;
}
int main(void)
{
uint32_t brightness_total;
uint16_t brightness = 0;
uint32_t i = 0;
init_adc();
sei();
while (1)
{
//reset LED pins
PORTB &= ~(1 << PINB0);
PORTD &= ~(1 << PIND7);
PORTD &= ~(1 << PIND6);
PORTD &= ~(1 << PIND5);
PORTB |= (1 << PINB1); //just blink
read_adc(BRIGHTNESS_PIN); //first throw-away read
//read n sample values from the ADC and average them out
brightness_total = 0;
for(i = 0; i < ADC_SAMPLES; ++i)
{
brightness_total += read_adc(BRIGHTNESS_PIN);
}
brightness = brightness_total / ADC_SAMPLES;
//set pins for LEDs depending on read value.
if(brightness > 768)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
PORTD |= (1 << PIND6);
PORTD |= (1 << PIND5);
}
else if (brightness <= 768 && brightness > 512)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
PORTD |= (1 << PIND6);
}
else if (brightness <= 512 && brightness > 256)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
}
else if (brightness <= 256 && brightness >=64)
{
PORTB |= (1 << PINB0);
}
_delay_ms(500);
PORTB &= ~(1 << PINB1); //just blink
_delay_ms(500);
}
}
除了通道 selection 外,这工作得很好。当我 select 一个通道时它工作正常,但独立于 selected 通道,通道 0 也总是读取和转换。我的意思是,如果我将电缆插入 selected 通道引脚,它会正确读取值。当我将它插入任何其他通道引脚时,它显然没有,除了 ADC0。无论我设置什么通道,不仅读取那个通道,还读取ADC0。
为什么会这样,我该如何解决?
我已经检查了我的 PCB 是否存在焊桥,但确实存在 none,而且我还预计会有一些稍微不同的行为。
ADC4 和 ADC5 似乎也没有正确转换。知道为什么吗?我在数据表中找到的唯一线索是,这两个使用数字电源,而所有其他 ADC 使用模拟电源。有什么区别,为什么重要,为什么它不能正确转换我的模拟信号?
ARef 和 AVCC 都按照数据表连接,除了 ARef 缺少电感器。
我认为发生的事情是
ADMUX &= (0xF0);
正在将频道设置为 0,并且
ADMUX |= (channel & 0x0F);
正在将频道设置为您想要的频道。然后您读取读数并将结果丢弃,这应该意味着初始通道设置为 0 无关紧要。
然而,当您随后尝试获取实际读数时,您将再次设置通道,使用read_adc 获取读数。所以,你永远不要丢掉一个读数。
我要做的是将您的 ADMUX 设置命令替换为:
ADMUX = (0xF0) | (channel & 0x0F)
然后将其移动到一个名为 set_adc_channel(int channel)
的单独函数中。在该函数中包含一次丢弃读取,然后从 read_adc
函数中删除 ADMUX
设置。只需开始转换并获得结果。
另请注意,由于您只使用一个频道,因此您可以将频道设置部分移至 init_adc()
。我假设它在一个单独的函数中,这样您以后可以阅读多个频道。
我希望这很清楚。如果没有请告诉我。
编辑:正如您所说,ADIF
实际上是通过写入逻辑 1 来重置的。
我刚刚测试了你的 adc_read
功能,它对我有用(如果你不介意 Arduino 混合物)
uint16_t read_adc(unsigned char channel)
{
//clear lower 4 bits of ADMUX and select ADC channel according to argument
ADMUX &= (0xF0);
ADMUX |= (channel & 0x0F); //set channel, limit channel selection to lower 4 bits
//start ADC conversion
ADCSRA |= (1 << ADSC);
//wait for conversion to finish
while(!(ADCSRA & (1 << ADIF)));
ADCSRA |= (1 << ADIF); //reset as required
return ADC;
}
void setup() {
Serial.begin(57600);
//set ADC VRef to AVCC
ADMUX |= (1 << REFS0);
//enable ADC and set pre-scaler to 128
ADCSRA = (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2) | (1 << ADEN);
pinMode(A0, INPUT_PULLUP);
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
pinMode(A3, INPUT_PULLUP);
}
void loop() {
Serial.println(read_adc(0));
Serial.println(read_adc(1));
Serial.println(read_adc(2));
Serial.println(read_adc(3));
delay(1000);
}
我只是将其中一个通道连接到 3.3V 引脚,它会显示 713。其他通道拉升到1017左右的水平。
我现在正在摆弄 ATMega328P,想通过 ADC 从引脚读取模拟值,然后简单地将值输出到 4 个 LED。真的很简单
#define F_CPU 20000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define BRIGHTNESS_PIN 2
#define ADC_SAMPLES 5
void init_adc()
{
//set ADC VRef to AVCC
ADMUX |= (1 << REFS0);
//enable ADC and set pre-scaler to 128
ADCSRA = (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2) | (1 << ADEN);
}
uint16_t read_adc(unsigned char channel)
{
//clear lower 4 bits of ADMUX and select ADC channel according to argument
ADMUX &= (0xF0);
ADMUX |= (channel & 0x0F); //set channel, limit channel selection to lower 4 bits
//start ADC conversion
ADCSRA |= (1 << ADSC);
//wait for conversion to finish
while(!(ADCSRA & (1 << ADIF)));
ADCSRA |= (1 << ADIF); //reset as required
return ADC;
}
int main(void)
{
uint32_t brightness_total;
uint16_t brightness = 0;
uint32_t i = 0;
init_adc();
sei();
while (1)
{
//reset LED pins
PORTB &= ~(1 << PINB0);
PORTD &= ~(1 << PIND7);
PORTD &= ~(1 << PIND6);
PORTD &= ~(1 << PIND5);
PORTB |= (1 << PINB1); //just blink
read_adc(BRIGHTNESS_PIN); //first throw-away read
//read n sample values from the ADC and average them out
brightness_total = 0;
for(i = 0; i < ADC_SAMPLES; ++i)
{
brightness_total += read_adc(BRIGHTNESS_PIN);
}
brightness = brightness_total / ADC_SAMPLES;
//set pins for LEDs depending on read value.
if(brightness > 768)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
PORTD |= (1 << PIND6);
PORTD |= (1 << PIND5);
}
else if (brightness <= 768 && brightness > 512)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
PORTD |= (1 << PIND6);
}
else if (brightness <= 512 && brightness > 256)
{
PORTB |= (1 << PINB0);
PORTD |= (1 << PIND7);
}
else if (brightness <= 256 && brightness >=64)
{
PORTB |= (1 << PINB0);
}
_delay_ms(500);
PORTB &= ~(1 << PINB1); //just blink
_delay_ms(500);
}
}
除了通道 selection 外,这工作得很好。当我 select 一个通道时它工作正常,但独立于 selected 通道,通道 0 也总是读取和转换。我的意思是,如果我将电缆插入 selected 通道引脚,它会正确读取值。当我将它插入任何其他通道引脚时,它显然没有,除了 ADC0。无论我设置什么通道,不仅读取那个通道,还读取ADC0。
为什么会这样,我该如何解决?
我已经检查了我的 PCB 是否存在焊桥,但确实存在 none,而且我还预计会有一些稍微不同的行为。
ADC4 和 ADC5 似乎也没有正确转换。知道为什么吗?我在数据表中找到的唯一线索是,这两个使用数字电源,而所有其他 ADC 使用模拟电源。有什么区别,为什么重要,为什么它不能正确转换我的模拟信号?
ARef 和 AVCC 都按照数据表连接,除了 ARef 缺少电感器。
我认为发生的事情是
ADMUX &= (0xF0);
正在将频道设置为 0,并且
ADMUX |= (channel & 0x0F);
正在将频道设置为您想要的频道。然后您读取读数并将结果丢弃,这应该意味着初始通道设置为 0 无关紧要。
然而,当您随后尝试获取实际读数时,您将再次设置通道,使用read_adc 获取读数。所以,你永远不要丢掉一个读数。
我要做的是将您的 ADMUX 设置命令替换为:
ADMUX = (0xF0) | (channel & 0x0F)
然后将其移动到一个名为 set_adc_channel(int channel)
的单独函数中。在该函数中包含一次丢弃读取,然后从 read_adc
函数中删除 ADMUX
设置。只需开始转换并获得结果。
另请注意,由于您只使用一个频道,因此您可以将频道设置部分移至 init_adc()
。我假设它在一个单独的函数中,这样您以后可以阅读多个频道。
我希望这很清楚。如果没有请告诉我。
编辑:正如您所说,ADIF
实际上是通过写入逻辑 1 来重置的。
我刚刚测试了你的 adc_read
功能,它对我有用(如果你不介意 Arduino 混合物)
uint16_t read_adc(unsigned char channel)
{
//clear lower 4 bits of ADMUX and select ADC channel according to argument
ADMUX &= (0xF0);
ADMUX |= (channel & 0x0F); //set channel, limit channel selection to lower 4 bits
//start ADC conversion
ADCSRA |= (1 << ADSC);
//wait for conversion to finish
while(!(ADCSRA & (1 << ADIF)));
ADCSRA |= (1 << ADIF); //reset as required
return ADC;
}
void setup() {
Serial.begin(57600);
//set ADC VRef to AVCC
ADMUX |= (1 << REFS0);
//enable ADC and set pre-scaler to 128
ADCSRA = (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2) | (1 << ADEN);
pinMode(A0, INPUT_PULLUP);
pinMode(A1, INPUT_PULLUP);
pinMode(A2, INPUT_PULLUP);
pinMode(A3, INPUT_PULLUP);
}
void loop() {
Serial.println(read_adc(0));
Serial.println(read_adc(1));
Serial.println(read_adc(2));
Serial.println(read_adc(3));
delay(1000);
}
我只是将其中一个通道连接到 3.3V 引脚,它会显示 713。其他通道拉升到1017左右的水平。