如何计算 ADC 读数的平均值?
How can the average of the ADC Readings be calculated?
目的是将最新的 10 个 ADC 读数存储在一个数组中,然后计算它们的平均值以用于其他地方。每次更新时删除最旧的。
关于 LED 时序,如果 ADC 读数在如下所写的边界内,则必须将时序从 1 秒切换到 0.25 秒,如何正确实现?我知道我的方法有效,但可以做得更好。
至于 LED,如果按下开关,它们必须改变模式,正如您所见,它们会这样做,但我再次确定可以通过另一种更简单的方式完成!
下面是我的代码,而且肯定有很多错误和优化空间,我会欣然接受!
#include <avr/io.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
unsigned int timecount0;
unsigned int adc_reading;
volatile uint32_t timing = 1;
volatile uint32_t accumulator = 0;
volatile uint16_t average = 0;
volatile uint16_t samples = 0;
#define LED_RED PORTB = ((PORTB & ~0b00001110)|(0b00000010 & 0b00001110))
#define LED_GREEN PORTB = ((PORTB & ~0b00001110)|(0b00001000 & 0b00001110))
#define LED_BLUE PORTB = ((PORTB & ~0b00001110)|(0b00000100 & 0b00001110))
#define LED_RGB PORTB = ((PORTB & ~0b00001110)|(0b00001000 & 0b00001110))
#define DELAY_COUNT 6
volatile uint8_t portdhistory = 0xFF;
void Timer0_init(void)
{
timecount0 = 0; // Initialize the overflow count. Note its scope
TCCR0B = (5<<CS00); // Set T0 Source = Clock (16MHz)/1024 and put Timer in Normal mode
TCCR0A = 0; // Not strictly necessary as these are the reset states but it's good
// practice to show what you're doing
TCNT0 = 61; // Recall: 256-61 = 195 & 195*64us = 12.48ms, approx 12.5ms
TIMSK0 = (1<<TOIE0); // Enable Timer 0 interrupt
PCICR |= (1<<PCIE0);
PCMSK0 |= (1<<PCINT0);
sei(); // Global interrupt enable (I=1)
}
void ADC_init(void)
{
ADMUX = ((1<<REFS0) | (0<<ADLAR) | (0<<MUX0)); /* AVCC selected for VREF,ADLAR set to 0, ADC0 as ADC input (A0) */
ADCSRA = ((1<<ADEN)|(1<<ADSC)|(1<<ADATE)|(1<<ADIE)|(7<<ADPS0));
/* Enable ADC, Start Conversion, Auto Trigger enabled,
Interrupt enabled, Prescale = 32 */
ADCSRB = (0<<ADTS0); /* Select AutoTrigger Source to Free Running Mode
Strictly speaking - this is already 0, so we could omit the write to
ADCSRB, but included here so the intent is clear */
sei(); //global interrupt enable
}
int main(void)
{
ADC_init();
Timer0_init();
DDRD = 0b00100000; /* set PORTD bit 5 to output */
DDRB = 0b00111110; /* set PORTB bit 1,2,3,4,5 to output */
sei(); // Global interrupt enable (I=1)
while(1)
{
if(!(PIND & (1<<PIND2)))
{
PORTD = PORTD |= (1<<PORTD5);
PORTB = PORTB |= (1<<PORTB4);
if(average>512)
{
PORTB = PORTB |= (1<<PORTB5);
}
}
else
{
PORTD = PORTD &= ~(1<<PORTD5);
PORTB = PORTB &= ~(1<<PORTB4);
}
}
}
ISR(TIMER0_OVF_vect)
{
TCNT0 = 61; //TCNT0 needs to be set to the start point each time
++timecount0; // count the number of times the interrupt has been reached
if(!(PIND & (1<<PIND3)))
{
if (timecount0 >= 0) // 40 * 12.5ms = 500ms
{
PORTB = ((PORTB & ~0b00001110)|(0b00000000 & 0b00001110));
}
if (timecount0 >= 8*timing)
{
LED_RED;
}
if (timecount0 >= 16*timing)
{
LED_GREEN;
}
if (timecount0 >= 24*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00000110 & 0b00001110));
}
if (timecount0 >= 32*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00001000 & 0b00001110));
}
if (timecount0 >= 40*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00001010 & 0b00001110));
}
if (timecount0 >= 48*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00001100 & 0b00001110));
}
if (timecount0 >= 56*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00001110 & 0b00001110));
}
if (timecount0 >= 64*timing)
{
timecount0 = 0;
}
}
else
{
if (timecount0 >= 0)
{
PORTB = ((PORTB & ~0b00001110)|(0b00000000 & 0b00001110)); //ALL OFF
}
if (timecount0 >= 8*timing)
{
LED_RED;
//PORTB = ((PORTB & ~0b00001110)|(0b00000010 & 0b00001110)); //RED
}
if (timecount0 >= 16*timing)
{
LED_GREEN;
}
if (timecount0 >= 24*timing)
{
LED_BLUE;
}
if (timecount0 >= 32*timing)
{
timecount0 = 0;
}
}
}
ISR (ADC_vect) //handles ADC interrupts
{
adc_reading = ADC; //ADC is in Free Running Mode
accumulator+= adc_reading;
if ((adc_reading > 768) & (adc_reading <= 1024))
{
timing = 10;
}
if ((adc_reading >= 0) & (adc_reading<= 768) )
{
timing = 2.5;
}
samples++;
if(samples == 10)
{
average = accumulator/10;
accumulator = 0;
samples = 0;
}
}
根据您的处理器,您可能会保持 ISR()
快速并避免昂贵 /,%
。
LED 的东西,我会在定时器中断中处理。
#define N 10
volatile unsigned sample[N];
volatile unsigned count = 0;
volatile unsigned index = 0;
volatile unsigned sum = 0;
ISR (ADC_vect) {
if (count >= N) {
sum -= sample[index];
} else {
count++;
}
sample[index] = ADC;
sum += sample[index];
index++;
if (index >= N) {
index = 0;
}
}
unsigned ADC_GetAvg(void) {
block_interrupts();
unsigned s = sum;
unsigned n = count;
restore_interrupts();
if (n == 0) {
return 0; //ADC ISR never called
}
return (s + n/2)/n; // return rounded average
}
我推荐 low pass filter 的整数版本,而不是最后一个 N
的平均值。
关于移动平均 w/ N = 10,chux - Reinstate Monica 提供了解决方案。 Chux - Reinstate Monica 还建议查看低通滤波器的整数版本。我个人喜欢指数加权移动平均线 (EWMA),因为它的编码相当简单,并且只需要几个值即可进行平均。这与在您的情况下必须在数组中保留 10 相比。为此,我会推荐 Elliot Williams 的 Make:AVR Programming Chapter 12。如果您无法轻松访问它,EWMA,如 Make AVR 中所述,以
开头
y_current = (1/16)*x_current + (15/16)*y_previous
在我们的例子中,y_current 是更新后的 EWMA 值,x_current 是 ADC 的最新样本,y_previous 是最后一个 EWMA 值。 16 的选择也可以随权重 1 和 15 一起更改。不过,保持它的 2 的幂很重要,正如您将看到的那样。如 Elliot Williams 书中所示,您乘以 16 并补偿舍入问题并得到以下结果,
16*y_current = x_current + 16*y_previous - (16*y_previous - 8)/16.
现在,我知道这看起来很难看,但我们得到的是 16 个整数的平均值,并且仅依赖于整数加法(16*y_previous 存储为一个值,因此您不需要做乘法)和位移;这就是为什么在 EWMA 中选择 2 的幂,除以 16 与右移 4 相同。好的,那么这个平均值在代码中是什么样子的:
// Snippet from Make: AVR Programming
uint16_t x_current; // ADC value.
uint16_t y_current; // Average ADC value.
// Get the EWMA.
y_current = x_current + y_current - ((y_current - 8) >> 4);
// Send the value over USART (assuming it's wired up). Remember that
// y_current is scaled by 16.
printf("%d\n",(y_current>>4));
以上只是您可以在您的代码中使用的 EWMA 以及发送它的示例,这只是提醒您该值是否已缩放。请记住,这只是平均 ADC 值。您可能希望将 ADC 值用作函数的输入以获取某些测量量的值。您可以创建一个查找表 table,而不是实际使用函数和计算值,其中索引是 ADC 值,该索引处的数组条目是预先计算的值。
就您的其他代码而言,可能 corrected/streamlined 的内容在您的 ISR 中。在 ISR(TIMER0_OVF_vect) 中,你有一些常量的位操作,可以预先计算,这样你就不会在每次 ISR(TIMER0_OVF_vect) 触发时都这样做。
PORTB = ((PORTB & ~0b00001110)|(0b00000000 & 0b00001110));
变成
PORTB = ((PORTB & 0b11110001)|(0b00000000)); // Saves a bit inversion and '&'
这表明您的 ORing | 不会影响结果,因为您是针对全零进行 ORing。
最后,在您的 ISR (ADC_vect) 中,您使用的是按位 &,而不是逻辑与 &&。您会得到相同的结果,但这就像用扳手敲钉子一样。我知道这很多,但我希望它能有所帮助,如果您需要澄清,请告诉我。
目的是将最新的 10 个 ADC 读数存储在一个数组中,然后计算它们的平均值以用于其他地方。每次更新时删除最旧的。
关于 LED 时序,如果 ADC 读数在如下所写的边界内,则必须将时序从 1 秒切换到 0.25 秒,如何正确实现?我知道我的方法有效,但可以做得更好。 至于 LED,如果按下开关,它们必须改变模式,正如您所见,它们会这样做,但我再次确定可以通过另一种更简单的方式完成!
下面是我的代码,而且肯定有很多错误和优化空间,我会欣然接受!
#include <avr/io.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>
unsigned int timecount0;
unsigned int adc_reading;
volatile uint32_t timing = 1;
volatile uint32_t accumulator = 0;
volatile uint16_t average = 0;
volatile uint16_t samples = 0;
#define LED_RED PORTB = ((PORTB & ~0b00001110)|(0b00000010 & 0b00001110))
#define LED_GREEN PORTB = ((PORTB & ~0b00001110)|(0b00001000 & 0b00001110))
#define LED_BLUE PORTB = ((PORTB & ~0b00001110)|(0b00000100 & 0b00001110))
#define LED_RGB PORTB = ((PORTB & ~0b00001110)|(0b00001000 & 0b00001110))
#define DELAY_COUNT 6
volatile uint8_t portdhistory = 0xFF;
void Timer0_init(void)
{
timecount0 = 0; // Initialize the overflow count. Note its scope
TCCR0B = (5<<CS00); // Set T0 Source = Clock (16MHz)/1024 and put Timer in Normal mode
TCCR0A = 0; // Not strictly necessary as these are the reset states but it's good
// practice to show what you're doing
TCNT0 = 61; // Recall: 256-61 = 195 & 195*64us = 12.48ms, approx 12.5ms
TIMSK0 = (1<<TOIE0); // Enable Timer 0 interrupt
PCICR |= (1<<PCIE0);
PCMSK0 |= (1<<PCINT0);
sei(); // Global interrupt enable (I=1)
}
void ADC_init(void)
{
ADMUX = ((1<<REFS0) | (0<<ADLAR) | (0<<MUX0)); /* AVCC selected for VREF,ADLAR set to 0, ADC0 as ADC input (A0) */
ADCSRA = ((1<<ADEN)|(1<<ADSC)|(1<<ADATE)|(1<<ADIE)|(7<<ADPS0));
/* Enable ADC, Start Conversion, Auto Trigger enabled,
Interrupt enabled, Prescale = 32 */
ADCSRB = (0<<ADTS0); /* Select AutoTrigger Source to Free Running Mode
Strictly speaking - this is already 0, so we could omit the write to
ADCSRB, but included here so the intent is clear */
sei(); //global interrupt enable
}
int main(void)
{
ADC_init();
Timer0_init();
DDRD = 0b00100000; /* set PORTD bit 5 to output */
DDRB = 0b00111110; /* set PORTB bit 1,2,3,4,5 to output */
sei(); // Global interrupt enable (I=1)
while(1)
{
if(!(PIND & (1<<PIND2)))
{
PORTD = PORTD |= (1<<PORTD5);
PORTB = PORTB |= (1<<PORTB4);
if(average>512)
{
PORTB = PORTB |= (1<<PORTB5);
}
}
else
{
PORTD = PORTD &= ~(1<<PORTD5);
PORTB = PORTB &= ~(1<<PORTB4);
}
}
}
ISR(TIMER0_OVF_vect)
{
TCNT0 = 61; //TCNT0 needs to be set to the start point each time
++timecount0; // count the number of times the interrupt has been reached
if(!(PIND & (1<<PIND3)))
{
if (timecount0 >= 0) // 40 * 12.5ms = 500ms
{
PORTB = ((PORTB & ~0b00001110)|(0b00000000 & 0b00001110));
}
if (timecount0 >= 8*timing)
{
LED_RED;
}
if (timecount0 >= 16*timing)
{
LED_GREEN;
}
if (timecount0 >= 24*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00000110 & 0b00001110));
}
if (timecount0 >= 32*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00001000 & 0b00001110));
}
if (timecount0 >= 40*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00001010 & 0b00001110));
}
if (timecount0 >= 48*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00001100 & 0b00001110));
}
if (timecount0 >= 56*timing)
{
PORTB = ((PORTB & ~0b00001110)|(0b00001110 & 0b00001110));
}
if (timecount0 >= 64*timing)
{
timecount0 = 0;
}
}
else
{
if (timecount0 >= 0)
{
PORTB = ((PORTB & ~0b00001110)|(0b00000000 & 0b00001110)); //ALL OFF
}
if (timecount0 >= 8*timing)
{
LED_RED;
//PORTB = ((PORTB & ~0b00001110)|(0b00000010 & 0b00001110)); //RED
}
if (timecount0 >= 16*timing)
{
LED_GREEN;
}
if (timecount0 >= 24*timing)
{
LED_BLUE;
}
if (timecount0 >= 32*timing)
{
timecount0 = 0;
}
}
}
ISR (ADC_vect) //handles ADC interrupts
{
adc_reading = ADC; //ADC is in Free Running Mode
accumulator+= adc_reading;
if ((adc_reading > 768) & (adc_reading <= 1024))
{
timing = 10;
}
if ((adc_reading >= 0) & (adc_reading<= 768) )
{
timing = 2.5;
}
samples++;
if(samples == 10)
{
average = accumulator/10;
accumulator = 0;
samples = 0;
}
}
根据您的处理器,您可能会保持 ISR()
快速并避免昂贵 /,%
。
LED 的东西,我会在定时器中断中处理。
#define N 10
volatile unsigned sample[N];
volatile unsigned count = 0;
volatile unsigned index = 0;
volatile unsigned sum = 0;
ISR (ADC_vect) {
if (count >= N) {
sum -= sample[index];
} else {
count++;
}
sample[index] = ADC;
sum += sample[index];
index++;
if (index >= N) {
index = 0;
}
}
unsigned ADC_GetAvg(void) {
block_interrupts();
unsigned s = sum;
unsigned n = count;
restore_interrupts();
if (n == 0) {
return 0; //ADC ISR never called
}
return (s + n/2)/n; // return rounded average
}
我推荐 low pass filter 的整数版本,而不是最后一个 N
的平均值。
关于移动平均 w/ N = 10,chux - Reinstate Monica 提供了解决方案。 Chux - Reinstate Monica 还建议查看低通滤波器的整数版本。我个人喜欢指数加权移动平均线 (EWMA),因为它的编码相当简单,并且只需要几个值即可进行平均。这与在您的情况下必须在数组中保留 10 相比。为此,我会推荐 Elliot Williams 的 Make:AVR Programming Chapter 12。如果您无法轻松访问它,EWMA,如 Make AVR 中所述,以
开头y_current = (1/16)*x_current + (15/16)*y_previous
在我们的例子中,y_current 是更新后的 EWMA 值,x_current 是 ADC 的最新样本,y_previous 是最后一个 EWMA 值。 16 的选择也可以随权重 1 和 15 一起更改。不过,保持它的 2 的幂很重要,正如您将看到的那样。如 Elliot Williams 书中所示,您乘以 16 并补偿舍入问题并得到以下结果,
16*y_current = x_current + 16*y_previous - (16*y_previous - 8)/16.
现在,我知道这看起来很难看,但我们得到的是 16 个整数的平均值,并且仅依赖于整数加法(16*y_previous 存储为一个值,因此您不需要做乘法)和位移;这就是为什么在 EWMA 中选择 2 的幂,除以 16 与右移 4 相同。好的,那么这个平均值在代码中是什么样子的:
// Snippet from Make: AVR Programming
uint16_t x_current; // ADC value.
uint16_t y_current; // Average ADC value.
// Get the EWMA.
y_current = x_current + y_current - ((y_current - 8) >> 4);
// Send the value over USART (assuming it's wired up). Remember that
// y_current is scaled by 16.
printf("%d\n",(y_current>>4));
以上只是您可以在您的代码中使用的 EWMA 以及发送它的示例,这只是提醒您该值是否已缩放。请记住,这只是平均 ADC 值。您可能希望将 ADC 值用作函数的输入以获取某些测量量的值。您可以创建一个查找表 table,而不是实际使用函数和计算值,其中索引是 ADC 值,该索引处的数组条目是预先计算的值。
就您的其他代码而言,可能 corrected/streamlined 的内容在您的 ISR 中。在 ISR(TIMER0_OVF_vect) 中,你有一些常量的位操作,可以预先计算,这样你就不会在每次 ISR(TIMER0_OVF_vect) 触发时都这样做。
PORTB = ((PORTB & ~0b00001110)|(0b00000000 & 0b00001110));
变成
PORTB = ((PORTB & 0b11110001)|(0b00000000)); // Saves a bit inversion and '&'
这表明您的 ORing | 不会影响结果,因为您是针对全零进行 ORing。
最后,在您的 ISR (ADC_vect) 中,您使用的是按位 &,而不是逻辑与 &&。您会得到相同的结果,但这就像用扳手敲钉子一样。我知道这很多,但我希望它能有所帮助,如果您需要澄清,请告诉我。