Работа с АЦП в спящем режиме (ADC Noise Reduction)

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

Коллеги, никто не пробывал работать с ацп ардуино используя встроенную в контроллер систему подавления помех (ADC Noise Reduction) ? Может у кого-то есть наработки?  Об этой возможности часто упоминается, но нигде материал не доведён до конкретного рабочего скетча. Есть небольшой материал на эту тему у DI HALT , но там на асме, и вообще для другого контроллера. Есть топик на эту тему на arduino.cc , но тоже до рабочего скетча не доведён. Может у кого-то случайно уже есть готовый код на Си  для меги 8/168/328 ? Уж очень интересно, какой эффект даёт применение этой возможности.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

В принципе на arduino.cc  был скетч, который как-бы работает. Но я его тестировал, и никакого снижения девиации показаний по сравнении с обычным способом не увидел. Возможно и  в нём что-то  не так, т.к. никто восторжено не написал про полученный эффект. Но что именно не так -понять крайне затруднительно, вроде всё правильно. Сам вышеупомянутый скетч:

/* from http://forum.arduino.cc/index.php/topic,38119.0.html#14
sleep while doing adc
*/
#include <avr/sleep.h>

ISR(ADC_vect){ // else application is reset
}

void setup(){
  const byte channel = 5; // вход подключен к A5
  Serial.begin(115200);
  analogReference( DEFAULT);
  Serial.println(F("Read analog pin after sleeping adc"));
  
  // ref is DEFAULT
ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR) | (0x07 & channel); 

}

void loop(){
  int sensorvalue = rawAnalogReadWithSleep();
  
  Serial.println(sensorvalue,DEC);
   delay(100); // 

} 



int rawAnalogReadWithSleep( void )
{
  // Generate an interrupt when the conversion is finished
  ADCSRA |= _BV( ADIE ); // разрешили прерывания АЦП

  // Enable Noise Reduction Sleep Mode
  set_sleep_mode( SLEEP_MODE_ADC );

  sleep_enable();
  // Any interrupt will wake the processor including the millis interrupt so we have to...
  // Loop until the conversion is finished
  do
  {
    // The following line of code is only important on the second pass.  For the first pass it has no effect.
    // Ensure interrupts are enabled before sleeping
    sei();
    // Sleep (MUST be called immediately after sei)
    sleep_cpu();
    // Checking the conversion status has to be done with interrupts disabled to avoid a race condition
    // Disable interrupts so the while below is performed without interruption
    cli();
  }
  // Conversion finished?  If not, loop.
  while( ( (ADCSRA & (1<<ADSC)) != 0 ) );

  // No more sleeping
  sleep_disable();
  // Enable interrupts
  sei();

  // The Arduino core does not expect an interrupt when a conversion completes so turn interrupts off
  ADCSRA &= ~ _BV( ADIE );

  // Return the conversion result
  return( ADC );
}

 

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013

Данный код очень сильно шумит:

Если сравнить с тем же оверсемплингом:

void setup(){
  adc_init();
  Serial.begin(115200);
}

void loop(){
  int sensorvalue = analogReadOversampled(4);

  Serial.println(sensorvalue);
  //delay(100); // 

} 


void adc_init(void){
  ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0));    //16Mhz/128 = 125Khz the ADC reference clock
  ADMUX |= (1<<REFS0);                //Voltage reference from Avcc (5v)
  ADCSRA |= (1<<ADEN);                //Turn on ADC
  ADCSRA |= (1<<ADSC);                //Do an initial conversion because this one is the slowest and to ensure that everything is up and running
}

uint16_t read_adc(uint8_t channel){
  ADMUX &= 0xF0;                    //Clear the older channel that was read
  ADMUX |= channel;                //Defines the new ADC channel to be read
  ADCSRA |= (1<<ADSC);                //Starts a new conversion
  while(ADCSRA & (1<<ADSC));            //Wait until the conversion is done
  return ADCW;                    //Returns the ADC value of the chosen channel
}

unsigned int analogReadOversampled(uint8_t analogChannel)  {
  unsigned long aSum = 0;   // the sum of all analog readings
  for(int i = 0; i < 16; i++) 
    aSum += read_adc(analogChannel); // read and sum 16 ADС probes
  return aSum >> 4;   // ..
}

Жаль что ресурсы жрёт как паровоз, но если учесть что мега 8-я на частоте 16 мГц может выполнять до 16 миллионов инструкций в секунду, то не всё так плохо, хотя лучше чтобы мега тратила те MIPSы на что-то другое, если у Вас что-то удастся найти давайте, с удовольствием поиграюсь.

Если я правильно понял, в режиме  noise reduction МК ещё и кушает меньше?

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

HWman, похоже тот скетч не даёт процессору уснуть, на досуге поразбираюсь более основательно.

Если интересно, то проблему с шумами для себя я решил пока альтернативным способом, не прибегая кстати к оверсемплингу. Алгоритм в кратце такой: делаю 25 циклов чтения analogRead, заполняю двухмерный массив из 5 строк. В строке 2 ячейки, в одну ячейку вносится значение analogRead, во вторую ячейку вноситься количество повторов, пришедшееся на это значение. Новое значение отсчёта вносится в пустую строчку, итд. В конце цикла по максимальному количеству повторов вычисляется самый частый отсчёт, это промежуточный результат. Он довольно стабилен, но всё равно результаты циклов при статическом состоянии напряжения на входе АЦП могут биться между 2х значений, что бы это ликвидировать сделал 2-ю фазу, в ней запоминается текущий отсчёт, и если отсчёт из следущего цикла не равен предыдущему, то  включается счёт времени, и если спустя 50 миллисекунд очередной отсчёт не сравнялся, то результат меняется на последний. Таким образом скорострельность вывода данных составляет в среднем 20..50 миллисекунд. Что например для вольтметров-амперметров, и много чего *метров более чем хорошо. Стабильность отображения цифр получилась практически железобетонная, лучше чем у среднеклассового мультиметра. Перевёл уже одно из своих изделий на этот алгоритм, очень доволен.

Да, вот  моё творение :)

// тонкая настройка алгоритма  //
#define ts 5 // *table size* количество строк массива для хранения данных , для девиации +/- 2 отсчёта оптимально 4 строки и одна в запас.
#define ns 25 //  *number samples*, от 10..до 50 максимальное количество выборок для анализа 1й части алгоритма
#define ain A5 // какой аналоговый вход читать
#define mw 50 // *max wait* от 15..до 200 ms ожидать повтора отсчёта для 2 части алгоритма

unsigned int myArray[ts][2], aread, firstsample, oldfirstsample, numbersamples, rezult; 
unsigned long prevmillis = 0;
boolean waitbegin=false; //флаг включённого счётчика ожидания повтора отсчёта
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup() {
lcd.begin(16, 2);
Serial.begin(115200);
}
 
 
void loop() {  
  
  shumodav();  // уходим в основную функцию

// тестовые графики для программы serial oscill
Serial.print(aread); // исходное значение analogread
Serial.print(",");
Serial.print(rezult); // результат работы алгоритма
Serial.println();


// рассчёт напряжения
float voltage=rezult*5.04/1023;

//voltage= floor(voltage*100+0.5)/100; //если нужно округлить до2х знаков
lcd.setCursor(0,0);
lcd.print(voltage,3);

}   




   void shumodav(){ // главная функция
   //заполнить таблицу нолями в начале цикла
   for (int s=0; s < ts; s++ ) {
   for (int e=0; e<2; e++){
   myArray[s][e] = 0;  }    }
   // основной цикл накопления данных
   for (numbersamples=0; numbersamples <ns; numbersamples++) { 
   aread=analogRead(ain);
 
  // уходим работать с таблицей////
   tablework();  }
    // заполнен массив, вычисляем максимально повторяющееся значение
  int max1=0; // временная переменная для хранения максимумов
  for (byte n = 0; n <ts ; n++) {
  if (myArray[n][1] > max1) { //перебор 2-х элементов строк
  max1=myArray[n][1];  // запомним куда больше всего попало 
  firstsample = myArray[n][0]; // его 1 элемент = промежуточный результат.
  }     }    

  
   //*****вторая фаза алгоритма *********/////
   // если старый отсчёт не равен новому, 
   //и флага включения счёта времени небыло, то
   if (oldfirstsample != firstsample  && waitbegin==false) {
    prevmillis = millis(); // скидываем счётчик времени на начало
    waitbegin=true; } // активируем флаг ожидания
   // если до истечения лимита времени отсчёт сравнялся 
   //со старым, то снимаем флаг
   if (waitbegin==true && oldfirstsample == firstsample) {
   waitbegin=false;
   rezult=firstsample; }
   // если всё таки отсчёт не сравнялся, а время ожидания вышло
   if (waitbegin==true && millis()-prevmillis >= mw) {
   oldfirstsample = firstsample;
   waitbegin=false;
   rezult=firstsample;  } //то признаём новый отсчёт конечным результатом функции.
   } // конец главной функции




        void tablework(){// функция внесения данных в таблицу
        // если в таблице совпадает отсчёт, то инкрименировать 
        //его счётчик во втором элементе
        for (byte n=0; n < ts; n++) {
        if (myArray[n][0] == aread) { 
        myArray[n][1] ++;       return; }  }  
        // перебираем ячейки что б записать значение aread в таблицу
        for (byte n=0; n<ts; n++) { 
        if (myArray[n][0] == 0)  { //если есть пустая строка
        myArray[n][0]=aread;         return;  }  }          
        // если вдруг вся таблица заполнена раньше чем кончился цикл,     
        numbersamples = ns;     } //  то счётчик циклов на максимум
 

 

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013

Кстати, для более точных замеров можно сделать измерение питания контроллера, и учесть напряжение питания при расчёте напряжения.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

По умолчанию, в качестве опорного у ардуино используется внутреннее опорное напряжение, которое стабилизированно диодом и не зависит от напряжения питания. Так что для более точного измерения этот совет не подходит, разве что только если у вас датчик подключен к тому же питанию, что и процессор, при этом он аналоговый. Но тут самым правильным решением будет подать на Vref опорное напряжение с резистивного делителя подключенного к питанию. Таким методом вы аппаратно уничтожите ошибку возникающую из-за питания. И танцы с бубнами будут не нужны ;)

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

Повозился ещё раз со спящим ацп - толку ноль. И со всеми регистрами поигрался, и с усыпляющими командами, как хошь крути-верти, но на результат не влияет. Уже думаю, может оно так и есть, то есть эффект от использования этого режима не такой уж и очевидный? Ещё раз поспрашивал гугля на эту тему, но никто нигде особо не сравнивал и не хвалил спящий способ чтения. Видимо придётся оставить эту задумку.

HWman, я не сразу обратил внимание, но на том графике в 3 сообщении совсем чудовищная дерготня. У меня, напомню, в среднем +/- 1 единица, хотя если постараться, то можно найти участки, где звенит +/- 2 отсчёта.  Но +/- 10 - это явные косяки в аппаратной части. Вот мой график для сравнения  красная -сырое чтение analogRead, жёлтая -обработанное алгоритмом.

brokly
brokly аватар
Offline
Зарегистрирован: 08.02.2014

А помоему на графике очень да же не плохо. Можно предположить, что значение 588.5 .  В даташите же написано • ± 2 LSB Absolute Accuracy • . То есть "шум" в пределах 4 единицы считается нормой. 

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013

Подпаял керамику на ножку AREF и на землю и получилось как у Вас, а я так долго бился головой об стену почему у меня шумы около  +/- 10, а если юзать источник опорного напряжения так там вообще ужас был.

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

Но всё равно буду юзать оверсемплинг, при помощи него можно спокойно выжать 12 битное разрешение от меги 8, а если юзать только 1 канал АЦП тогда думаю будет и больше.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

HWman, абсолютная погрешность, заявленная производителем соответствует цене двух 10-битных отсчётов в плюс и минус, на 5 вольтовой шкале это +/- 10мв, то есть фактически чистой  8-битной разрядности. От того, что появится 11 бит или даже 12 он точнее не станет, просто врать начнёт более изящно.  Оверсемплинг это прекрасный инструмент, но он предназначен для других целей, когда нужна относительная точность, например в сканирующей матрице можно схватить больше полутонов, в электронном графопостроителях рисовать более плавную линию, в звуке уменьшить искажения наконец. Но вольтметру нужна абсолютная точность. Вы не задумывались, какая погрешность у обычного среднеценового мультиметра? Примерно 0,5%, а то и больше. На 5-вольтовой шкале это будет +/- 12 мв, что даже хуже, чем максимальная обещанная погрешность меги +/- 10мв на 10 битах.  Нужно точнее? Тогда это задача для более серьёзного ацп. Хотите обманываться, и смотреть на скачущие единицы милливольт и три цифры после запятой? Да пожалуйста )

ites
Offline
Зарегистрирован: 26.12.2013

dimax пишет:

Оверсемплинг это прекрасный инструмент, но он предназначен для других целей, когда нужна относительная точность, например в сканирующей матрице можно схватить больше полутонов, в электронном графопостроителях рисовать более плавную линию, в звуке уменьшить искажения наконец.

Если продолжать аналогию, то и больше разрядов, так что заявление о 12 битах вполне верное (8+4, где 4 бита дают 16 сэмплов). А вопрос абсолютной точности при этом решается калибровкой.

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013

Для моей задачи оверсемплинг именно то что мне нужно(будет чем заполнить теоретическую часть ;) ).

Вы я вижу разбираетесь в МК, у меня до Вас такой вопрос, есть ли смысл юзать ИОН в измерениях? Где-то читал что не желательно его использовать из за нестабильности, я так понимаю внутри МК есть стабилитрон который и обеспечивает ИОН,  из этого можно предположить что есть подавать на МК относительно стабилизированное напряжение то и на ИОНе будет такое же стабилизированное напряжение.

Для моей задачи использовать ИОН для измерений куда лучше чем измерять просто по напряжению питания + можно же переключаться между питаловом и ИОНом, что значительно расширит диапазон напряжений и точностей.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

HWman, кто ж такое писал, ИОН как раз и нужен для стабильности. И полезен во всех случаях кроме случаев, когда измеряемая схема питается от того-же, от чего и сам контроллер, и когда точность не нужна.  Для измерения напряжения ион просто обязателен, как минимум внутренний ион можно задействовать. Но лучше внешний, я пользуюсь обычно REF 192..195.

ites, не совсем понятно что в хотели сказать в первой строке, а по поводу абсолютной точности, то увы, калибровка дело хорошее, но её возможности очень ограничены. Есть некоторые факторы, как например нелинейность усиления, дифференциальная нелинейность, которые не скомпенсируешь просто так.  

ites
Offline
Зарегистрирован: 26.12.2013

dimax пишет:

ites, не совсем понятно что в хотели сказать в первой строке, а по поводу абсолютной точности, то увы, калибровка дело хорошее, но её возможности очень ограничены. Есть некоторые факторы, как например нелинейность усиления, дифференциальная нелинейность, которые не скомпенсируешь просто так.  

Что ж такого непонятного? По даташитам и опыту людей получается, что ADC имеет точность порядка 8 разрядов, выдавая при этом 10 формальных. В последних двух как бы шум. Если считать измеряемый сигнал константным за время измерения, а шум равномерно распределённым, то 16 семплов дают дополнительные 4 бита точности.

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

ites пишет:
Если считать измеряемый сигнал константным за время измерения, а шум равномерно распределённым, то 16 семплов дают дополнительные 4 бита точности.

Ну дают, а толку-то. Это разрешающая способность, и на абсолютную точность она влияет к сожалению довольно слабо.  Применительно к вольтметру не так важно разрешение в 5 милливольт или 1 милливольт, когда при этом он может законно врать на 20 милливольт.

ites
Offline
Зарегистрирован: 26.12.2013

dimax пишет:

ites пишет:
Если считать измеряемый сигнал константным за время измерения, а шум равномерно распределённым, то 16 семплов дают дополнительные 4 бита точности.

Ну дают, а толку-то. Это разрешающая способность, и на абсолютную точность она влияет к сожалению довольно слабо.  Применительно к вольтметру не так важно разрешение в 5 милливольт или 1 милливольт, когда при этом он может законно врать на 20 милливольт.

Так о том и речь, что тут важно понимать "функцию вранья". Если погрешность распределена равномерно в диапазоне  допустимой ошибки, то повышение точности работает.

ites
Offline
Зарегистрирован: 26.12.2013

ites пишет:

Так о том и речь, что тут важно понимать "функцию вранья". Если погрешность распределена равномерно в диапазоне  допустимой ошибки, то повышение точности работает.

Подумал ещё раз и думаю, что я прав тут только частично. Времени нет обдумать более полно, так что, на всякий случай признаю правоту dimax

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013
HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013

 

Добавил около аналоговой части МК(АVcc) кондёрчик на 100 мкФ, почти около самых ног МК. Теперь почти ровная линия и без всяких функций на подобии лоу ноис дрюкшн или оверсемплинг.
Вот только когда переключа.сь на ИОН тогда почему-то шум опять возвращаеться:

 

Immortal
Offline
Зарегистрирован: 28.12.2013

HWman, а что это за софт на скриншотах?

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013