Как рассчитать среднее значение показаний AD C? - PullRequest
1 голос
/ 27 марта 2020

Цель состоит в том, чтобы сохранить в массиве самые новые 10 AD C показаний, а затем вычислить их среднее значение для использования в других местах. Удаление самого старого при каждом обновлении.

Что касается синхронизации светодиодов, она должна переключать синхронизацию с 1 с на 0,25 с, если показания AD C находятся в пределах границ, как написано ниже, как это можно правильно реализовать? Я знаю, что мой метод работает, но может быть лучше. Что касается светодиодов, они должны менять схемы, если вы нажимаете на переключатель, как вы можете видеть, что они делают, но еще раз я уверен, что это можно сделать другим, более простым способом!

Ниже приведен мой код, также я Я уверен, что есть много ошибок и много места для оптимизации, я с удовольствием приму все это!

#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;
    }


}




Ответы [ 2 ]

3 голосов
/ 27 марта 2020

В зависимости от ваших процессоров, вы можете сохранить скорость ISR() и избежать дорогостоящих /,%.

Светодиод, который я бы обработал при прерывании по таймеру.

#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
}

Я бы порекомендовал целочисленную версию фильтра нижних частот , чем среднее значение за последний N.

2 голосов
/ 27 марта 2020

Что касается скользящего усреднения w / N = 10, chux - Reinstate Monica предоставила решение. Chux - Reinstate Monica также рекомендует взглянуть на целочисленную версию фильтра нижних частот. Мне лично нравится экспоненциально взвешенное скользящее среднее (EWMA), потому что его довольно просто кодировать, и для усреднения требуется всего несколько значений. Это по сравнению с необходимостью держать 10 в массиве в вашем случае. Для этого я бы порекомендовал главу 12 «Программирование AVR Эллиота Уильямса». Если у вас нет легкого доступа к этому, EWMA, как описано в Make AVR, начинается с

y_current = (1/16) * x_current + (15/16) * y_previous

, где в нашем случае y_current - это обновленное значение EWMA, x_current - новейшая выборка из вашей AD C, а y_previous - последнее значение EWMA. Выбор 16 также может быть изменен вместе с весами, 1 и 15. Однако, как вы увидите, важно сохранять степень 2. Как показано в книге Эллиота Уильямса, вы умножаете на 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, который вы можете использовать в своем коде, и пример его отправки, который является напоминанием о том, что значение масштабируется. Помните, что это только усредненное значение AD C. Вероятно, вы захотите использовать значение AD C в качестве входа для функции, чтобы получить значение некоторой измеренной величины. Вместо того, чтобы фактически использовать функцию и вычислять значения, вы можете создать справочную таблицу, где индекс - это значение AD C, а запись массива с этим индексом - предварительно рассчитанное значение.

С точки зрения вашего другой код, вещи, которые можно исправить / упростить, находятся в ваших 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) вы используете побитовое, &, а не логическое и, &&. Вы получаете тот же результат, но это все равно, что использовать гаечный ключ для забивания гвоздя. Я знаю, что это много, но я надеюсь, что это поможет, и дайте мне знать, если вам нужны разъяснения.

...