АЦП у Мега8

Обсуждаем контроллеры компании Atmel.
Ответить
Грызет канифоль
Сообщения: 261
Зарегистрирован: Ср авг 01, 2012 10:45:42

Сообщение kk042fjv »

Все понятно теперь я разобрался
Спасибо за помощь
Реклама
Грызет канифоль
Сообщения: 261
Зарегистрирован: Ср авг 01, 2012 10:45:42

Сообщение kk042fjv »

Добрый день!
Подскажите, как высчитать средние показания т.е. (замерить 32 отсчетов, сложить, разделить на 32, среднее вывести на экран). Как правильно это написать?

while (1)
{
static unsigned char port=0;
ADMUX=0;
ADCSRA |= 0x40;
while((ADCSRA&0x10) == 0);
ADCSRA |= 0x10;
data = ADCW*50/1023;
sprintf(string, "%d.%d", data/10, data%10);
lcd_gotoxy(5,port);
lcd_puts(string);

}

}
Реклама
Друг Кота
Аватара пользователя
Сообщения: 4905
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

Самый простой вариант - как-то так:

Код: Выделить всё

void adcInit()
{
    ADMUX = 0;
    ADCSRA |= (1<<ADPS2) | (1<<ADPS0);  /* Делитель ADC = 32 */
    ADCSRA |= (1<<ADEN);                /* Включаем ADC */
    return;
}

uint16_t getAverage32()
{
    uint8_t i;
    int16_t adc_result, result;
    uint32_t total = 0;
    for (i = 0; i < 32; i++) {
        ADCSRA |= (1<<ADSC);            /* Запускаем преобразование */
        while (ADCSRA & (1<<ADSC));     /* Ждём конца измерений */
        adc_result = ADCL;
        adc_result |= (ADCH << 8);
        total += adc_result;
    }
    result = total >> 5;                /* Делим результат на 32 */
    return;
}
В коде выше делается просто: 32 измерения, сложили, поделили, дальше - снова 32 измерения, сложили, поделили...

А можно сделать и интереснее. Чтобы всегда иметь реально усреднение последних 32 измерений.
1. Создать массив из 32 элементов для хранения последних измерений.
2. Запускать преобразование по таймеру (период таймера должен быть больше, чем скорость ADC-преобразования).
3. Когда преобразование заканчивается, ADC вызывает прерывание, в обработчике которого считывается значение из ADC и сохраняется в ячейку массива. Следующее преобразование - в другую ячейку, и так далее по циклу.
4. Функция getAverage32 будет просто считать среднее значение массива.

Достоинство - преобразования делаются независимо, сами по себе, и в массиве всегда хранятся последние несколько значений, среднее от которых доступно в любой момент времени, без ожидания измерений.
Контактная информация:
Грызет канифоль
Сообщения: 261
Зарегистрирован: Ср авг 01, 2012 10:45:42

Сообщение kk042fjv »

WiseLord писал(а):Самый простой вариант - как-то так:

Код: Выделить всё

void adcInit()
{
    ADMUX = 0;
    ADCSRA |= (1<<ADPS2) | (1<<ADPS0);  /* Делитель ADC = 32 */
    ADCSRA |= (1<<ADEN);                /* Включаем ADC */
    return;
}

uint16_t getAverage32()
{
    uint8_t i;
    int16_t adc_result, result;
    uint32_t total = 0;
    for (i = 0; i < 32; i++) {
        ADCSRA |= (1<<ADSC);            /* Запускаем преобразование */
        while (ADCSRA & (1<<ADSC));     /* Ждём конца измерений */
        adc_result = ADCL;
        adc_result |= (ADCH << 8);
        total += adc_result;
    }
    result = total >> 5;                /* Делим результат на 32 */
    return;
}
В коде выше делается просто: 32 измерения, сложили, поделили, дальше - снова 32 измерения, сложили, поделили...

А можно сделать и интереснее. Чтобы всегда иметь реально усреднение последних 32 измерений.
1. Создать массив из 32 элементов для хранения последних измерений.
2. Запускать преобразование по таймеру (период таймера должен быть больше, чем скорость ADC-преобразования).
3. Когда преобразование заканчивается, ADC вызывает прерывание, в обработчике которого считывается значение из ADC и сохраняется в ячейку массива. Следующее преобразование - в другую ячейку, и так далее по циклу.
4. Функция getAverage32 будет просто считать среднее значение массива.

Достоинство - преобразования делаются независимо, сами по себе, и в массиве всегда хранятся последние несколько значений, среднее от которых доступно в любой момент времени, без ожидания измерений.
Спасибо что откликнулись.
Я первый понимаю смысл, но у меня CodeVisionAVR ругается на ADPS2 и ADPS0, ADEN. В чем может быть причина?
Реклама
Эиком - электронные компоненты и радиодетали
Друг Кота
Аватара пользователя
Сообщения: 4905
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

Не знаю. Это стандартные названия битов регистра ADC для ATMega16. Может, у вас другой контроллер, у которого они иначе называются. Может, Codevision их как-то иначе называет. Может, надо подключать какой-то заголовочный файл с определениями. А вообще, можно вместо них просто номера записать (2, 0 и 7 соответственно)

Да, и не нужно цитировать чужое последнее сообщение, тем более целиком. Оно и так видно. А то настучат по голове за 2.7
Контактная информация:
Реклама
Грызет канифоль
Сообщения: 261
Зарегистрирован: Ср авг 01, 2012 10:45:42

Сообщение kk042fjv »

да обычный ATMega8
Реклама
Друг Кота
Аватара пользователя
Сообщения: 25560
Зарегистрирован: Чт янв 10, 2008 22:01:02
Откуда: Московская область, Фрязино

Сообщение КРАМ »

WiseLord писал(а): А можно сделать и интереснее. Чтобы всегда иметь реально усреднение последних 32 измерений.
1. Создать массив из 32 элементов для хранения последних измерений.
2. Запускать преобразование по таймеру (период таймера должен быть больше, чем скорость ADC-преобразования).
3. Когда преобразование заканчивается, ADC вызывает прерывание, в обработчике которого считывается значение из ADC и сохраняется в ячейку массива. Следующее преобразование - в другую ячейку, и так далее по циклу.
4. Функция getAverage32 будет просто считать среднее значение массива.
В прерывании, помимо прочего, нужно взводить флаг готовности данных, который гасится в теле getAverage32, а вызов ее осуществлять по взведенному флагу.
Иначе при невысокой (относительно исполнения цикла main) частоте семплирования АЦП МК будет впустую исполнять getAverage32, загружая вычислительные ресурсы.
Друг Кота
Аватара пользователя
Сообщения: 4905
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

Тоже верно. Если getAverage из основной программы вызывается чаще, чем запускается АЦП, нет смысла обсчитывать результат, если он не отличается от старого, поскольку новых данных в массив не поступило.
Контактная информация:
Это не хвост, это антенна
Аватара пользователя
Сообщения: 1315
Зарегистрирован: Пт ноя 27, 2009 19:47:13
Откуда: Казань

Сообщение НАПАЛМ »

Здравствуйте.
Вопрос не по программе, а по железу, связанный с АЦП mega8.
Какие лучше индуктивности применять для отфильровки питания (которое к AVCC идет)?
Картинки приветствуются!
Вот то, что у меня есть на данный момент:
СпойлерИзображение
Изображение
Изображение
Изображение
Изображение
Изображение
Также есть вариант с намоткой провода на резистор.
Грызет канифоль
Сообщения: 261
Зарегистрирован: Ср авг 01, 2012 10:45:42

Сообщение kk042fjv »

Добрый день!
Так и не могу я добиться чтоб на АЦП у меня не прыгали показания.
Можете посмотреть, подсказать может я зря стараюсь.
Сигнал с датчика подается на АЦП с частотой 1Гц.
Вложения
Проект.rar
(18.02 КБ) 156 скачиваний
Друг Кота
Аватара пользователя
Сообщения: 25560
Зарегистрирован: Чт янв 10, 2008 22:01:02
Откуда: Московская область, Фрязино

Сообщение КРАМ »

kk042fjv писал(а):Добрый день!
Так и не могу я добиться...
Вы ничего не сказали, а ЧТО ВЫ СДЕЛАЛИ для этого.
Кроме того, приводить проект в Протеусе с хексом как то бессмысленно...
Нужен исходник и схема в обычном виде. Далеко не все держат протеус на своем компьютере...
Грызет канифоль
Сообщения: 261
Зарегистрирован: Ср авг 01, 2012 10:45:42

Сообщение kk042fjv »

Код: Выделить всё

#include <mega8.h>
#asm
   .equ __lcd_port=0x18 ;PORTB
#endasm
#include <lcd.h>
#include <delay.h>
#include <stdio.h>

char lcd_buffer[30];

void main(void)
{
int data;
float V;

ACSR=0x80;
SFIOR=0x00;
 
ADMUX=0;
ADCSRA=0x85;

lcd_init(16);
lcd_clear();

while (1)
      {                   
      delay_ms(20);
      ADCSRA |= 0x40;
      while((ADCSRA&0x10) == 0);
      ADCSRA |= 0x10;     
      data = ADCW;      
      V = (float) data*0.0048828125;
      sprintf(lcd_buffer, " %1.2f ",V);
      lcd_gotoxy(5,0);
      lcd_puts(lcd_buffer);
              
};
}
Начну лучше сначала, вот обычный исходник обычного АЦП с делителем 32.
С датчика поступает сигнал, допустим 5В с частотой 1Гц.
естественно на экране будет показывать

Код: Выделить всё

50505050505050
хочется что бы на экране цифры не прыгали, а показывали или всегда 5 или среднее, я бы его отрегулировал как мне надо.
Друг Кота
Аватара пользователя
Сообщения: 4905
Зарегистрирован: Чт апр 11, 2013 11:19:59
Откуда: Минск

Сообщение WiseLord »

Я не совсем понимаю, в чём проблема.
АЦП снимает некоторое "мгновенное" значение, оно же на дисплее и отображается. Приблизительно раз в (20мс + время замера)
Чтобы видеть какое-то среднее значение, можно, например, завести массив из нескольких элементов. Сколько нужно элементов - зависит от минимальной частоты сигнала и количестве выборок. Хорошо бы перекрыть хотя бы пару периодов сигнала.
В массив поочерёдно заносите показания ADC, после заполнения последнего элемента возвращаетесь в первый. Потом уже усредняете и переводите в double с отображением на экран.
Заполнение массива хорошо бы делать по прерыванию от таймера, а не в основном цикле программы, который пусть только берёт готовые результаты.
Контактная информация:
Вымогатель припоя
Сообщения: 513
Зарегистрирован: Сб фев 19, 2011 18:04:08
Откуда: Татарстан, пос. Актюбинский

Сообщение phanis »

А как еще можно усреднять, если например используется 8 каналов?
Желательно с минимальными затратами памяти.В моем случае, чем больше усреднений тем больше озу используется.
Спойлер

Код: Выделить всё


#define ADC_start_ch 0 // канал с которого начать измерение 0..7
#define ADC_ch       8 // количество измеряемых каналов     1..8
#define ADC_aver     32 // усреднений 1..256

void ADC_init(void);
void ADC_read(void);


unsigned int ADC_raw[ADC_ch][ADC_aver];  // 2-х мерный массив [канал0..7][ значение АЦП 0..1023]  
unsigned int ADC_value[ADC_ch];          // усредненное значение  по каждому каналу
unsigned long ADC_temp_buff[ADC_ch];     // буфер хранения суммы накопленных значений замеров по каждому каналу
unsigned char ADC_cur_ch;                // текущий канал АЦП 0..7
unsigned char ADC_curr_aver;             // текущий замер 

#pragma vector =   TIMER2_COMP_vect // прерывание по совпадению TCNT2 = OCR2 100 Гц
__interrupt void TIMER2_COMP(void)
{
 BUT_Debrief(); // опрос кнопок
 ADC_read(); // опрос АЦП
};

void ADC_init(void)
{
 ADMUX  = (0<<REFS1)|(0<< REFS0)|(0<< ADLAR)|(0<<     0)|(0<<  MUX3)|(0<<  MUX2)|(0<<  MUX1)|(0<<  MUX0);
 ADCSR  = (1<< ADEN)|(0<<  ADSC)|(0<<  ADFR)|(0<<  ADIF)|(0<<  ADIE)|(1<< ADPS2)|(1<< ADPS1)|(0<< ADPS0);
 
 ADC_cur_ch = ADC_start_ch;  
 
 ADMUX = ADC_cur_ch|(ADMUX & 0xF0);
 
 ADC_curr_aver=0; // 
 
 ADCSR |= (1<<ADSC); //start convertion

};

void ADC_read(void)
{
unsigned int ADC_temp;

if((ADCSR & (1<<ADSC)) == 0) //Adc complete
{
 

 ADC_temp  = ADCL;
 ADC_temp  |=(int) ADCH<<8;
  
 ADC_temp_buff[ADC_cur_ch] += ADC_temp;
 ADC_temp_buff[ADC_cur_ch] -= ADC_raw[ADC_cur_ch][ADC_curr_aver];
 ADC_raw[ADC_cur_ch][ADC_curr_aver] = ADC_temp;
 ADC_value[ADC_cur_ch] = ADC_temp_buff[ADC_cur_ch] / ADC_aver;
 
 if(++ADC_cur_ch>=ADC_ch)
 {
 ADC_cur_ch = ADC_start_ch;

 if (++ADC_curr_aver >= ADC_aver) ADC_curr_aver=0;
 }
 
 ADMUX = ADC_cur_ch|(ADMUX & 0xF0);
 ADCSR |= (1<<ADSC); //start convertion

}

};

while (1)// основной цикл
    {
    //ADC_value[0..7]; усредненные значения по каждому каналу   
  
   }
    
}

Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 25560
Зарегистрирован: Чт янв 10, 2008 22:01:02
Откуда: Московская область, Фрязино

Сообщение КРАМ »

Усреднение в скользящем окне называется фильтром КИХ (с конечной импульсной характеристикой).
А можно усреднять (фильтровать) с помощью рекурсивного фильтра (БИХ - с бесконечной импульсной характеристикой). Этот вариант не требует массива, но требует расчета УСТОЙЧИВОГО фильтра).
Подробности, полагаю, Вы найдете самостоятельно...
Грызет канифоль
Сообщения: 261
Зарегистрирован: Ср авг 01, 2012 10:45:42

Сообщение kk042fjv »

Добрый день!
Возник вопрос, как сделать, чтобы на жк когда напряжение (для примера) меньше 1В загоралась надпись "ЗАРЯДКА", когда напряжение поднимается (больше 1В) загоралась надпись "ок"
Вот пример кода:

Код: Выделить всё

...
 delay_ms(20);
      ADCSRA |= 0x40;
      while((ADCSRA&0x10) == 0);
      ADCSRA |= 0x10;     
      data = ADCW;      
      V = (float) data*0.0048828125;
      sprintf(lcd_buffer, " %1.2f ",V);
      lcd_gotoxy(5,0);
      lcd_puts(lcd_buffer);
      
             if (V < 1) 
                   sprintf(lcd_buffer ,"ЗАРЯДКА", V);
             else
                   sprintf(lcd_buffer ,"ok", V); 
                   
                   lcd_gotoxy(4,1); 
                   lcd_puts(lcd_buffer);
у меня получается: горит "ок", как напряжение опускается меньше 1В загорается "ЗАРЯДКА", как увеличивается больше 1В загорается "окРЯДКА".
Как нужно правильно сделать?
Друг Кота
Аватара пользователя
Сообщения: 25560
Зарегистрирован: Чт янв 10, 2008 22:01:02
Откуда: Московская область, Фрязино

Сообщение КРАМ »

добавить пробелы к "ОК" так, чтобы суммарная длина ОК и ЗАРЯДКА совпадала.
Грызет канифоль
Сообщения: 261
Зарегистрирован: Ср авг 01, 2012 10:45:42

Сообщение kk042fjv »

КРАМ писал(а):добавить пробелы к "ОК" так, чтобы суммарная длина ОК и ЗАРЯДКА совпадала.
Так и сделал.
Ответить

Вернуться в «AVR»