|
нестабильность показаний АЦП, нестабильность показаний АЦП |
|
|
|
Sep 24 2012, 21:56
|
Участник

Группа: Участник
Сообщений: 40
Регистрация: 9-07-12
Пользователь №: 72 673

|
Вообщем нужно изменять некое напряжение (для примера я взял 5в). АЦП меряет, значение преобразуется для отображения на четырехразрядном семисегментрике и выводится на него. Но два последних разряда АЦП постоянно дают разные значения (крайние правые разряды индикатора постоянно показывают разные значения, при этом частота АЦП = 15 кГц). После этого, я добавил в схему повторитель на ОУ (т.к. думал что его малое выходное значение и большое входное положительно отобразится на стабильности измерений, но не помогло - значения по прежнему скачают). Подскажите что может исправить положения дел, что бы различные помехи наводки почти не влияли на результат измерений. Видео, упрощенная схема и код прилагаются. И, кстати, до нуля вход на АЦП тоже не доходит - там где минимум 1.2в, я думаю это из-за особенностей ОУ - так как он не rail-to-rail. Демонстрация работы CODE #include <avr/io.h> #include <avr/interrupt.h>
#define F_CPU 1000000UL #include <util/delay.h>
int ADC_data; unsigned char i; unsigned char d0,d1,d2,d3;
unsigned char kod_simvola[10]= //коды цифр для семисегментного индикатора { 0b00111111, //0 0b00000110, //1 0b01011011, //2 0b01001111, //3 0b01100110, //4 0b01101101, //5 0b01111101, //6 0b00000111, //7 0b01111111, //8 0b01101111 //9 };
//=====================ВЫВОД 4-ех ЦИФР НА СЕМИСЕГМЕНТНЫЙ ИНДИКАТОР===================== void vivod_led(unsigned char digit0,unsigned char digit1,unsigned char digit2,unsigned char digit3) { cli(); unsigned char razr, digit; DDRC=0b11111111; //PORTC0:7 подключаем к сегментам индикатора DDRB=0b00001111; //PORTB0:3 подключаем к разрядам индикатора for(razr=0;razr<4;razr++) { switch (razr) { case 0: digit=digit0; break; case 1: digit=digit1; break; case 2: digit=digit2; break; case 3: digit=digit3; break; } switch (digit) { case 0: PORTC=kod_simvola[0]; //выводим в порт нужную цифру break; case 1: PORTC=kod_simvola[1]; break; case 2: PORTC=kod_simvola[2]; break; case 3: PORTC=kod_simvola[3]; break; case 4: PORTC=kod_simvola[4]; break; case 5: PORTC=kod_simvola[5]; break; case 6: PORTC=kod_simvola[6]; break; case 7: PORTC=kod_simvola[7]; break; case 8: PORTC=kod_simvola[8]; break; case 9: PORTC=kod_simvola[9]; break; default: //если задан некорректный символ - выводится "_" PORTC=0b0001000; break; } if (razr==0) PORTC |=0b10000000; //зажигаем точку после первого рязряда PORTB=1<<razr; //зажигаем нужный разряд индикатора _delay_us(500); //менее 200 мкс - падает яркость, более 5 мс - заметно мерцание PORTB=0<<razr; //гасим разряд индикатора } sei(); }
int main(void) { DDRA=0b00000000; int temp; //для хранения результата преобразования АЦП //====================ИНИЦИАЛИЗАЦИЯ ТАЙМЕРА T0=================================== TCCR0=(1<<CS00) | (1<<CS01); /*Timer/Counter Control Register устанавливаем коэффициент делителя таймера 64*/ OCR0=156; /*Output Compare Register число тактов котое будет сравниватся с числом тактов таймера*/ /*TIMSK=(1<<OCIE0); / *Timer/Counter Interrupt Mask Register разрешаем прерывания по совпадению значения таймера T0 и OCR0=156* /*/ TIMSK=(1<<TOIE0); /*Timer/Counter Interrupt Mask Register разрешаем прерывания по переполнению таймера T0*/ //======================ИНИЦИАЛИЗАЦИЯ АЦП======================================= ADMUX=(1<<REFS0) | (1<<ADLAR); /*ADC Multiplexer Selection Register REFS1:0=01 - за ИОН берем Vcc контроллера (AVcc) MUX4:0=0000 - выбераем канал для АЦП - PORTA.0 ADLAR=1 - выравнивание результата преобразования АЦП по левому краю байтов результата*/ ADCSRA=(1<<ADEN) | (1<<ADSC) | (1<<ADPS1) | (1<<ADPS2) | (1<<ADATE);// | (1<<ADIE); /*ADC Control and Status Register A ADEN=1 - включаем АЦП ADSC=1 - запускаем первое преобразование АЦП, дальше само идет автоматически ADPS2:0=110 - делитель тактовой частоты для АЦП 1 Мгц/64 = 15.625 кГц ADATE=1 - запускаем АЦП в режиме непрерывных последовательных преобразовний одно за другим ADIE=0 - запрещаем прерывания по окончанию преобразования АЦП*/ SFIOR &=~(1<<ADTS0) | (1<<ADTS1) | (1<<ADTS2); /*Special FunctionIO Register ADTS2:0=000 - преобразование идет в непрерывном режиме с момента запуска*/ sei(); //разрешаем глобальные прерывания while(1) { //ADC_data=(ADCL>>6) | (ADCH<<2); //байт ADCH всегда должен читатся последним temp=ADC_data*0.004858*1000; //0.0049 - значение напряжение на одну ступень 4.97/1023 d3=temp%10; temp /=10; d2=temp%10; temp /=10; d1=temp%10; temp /=10; d0=temp%10; vivod_led(d0,d1,d2,d3); } }
ISR(TIMER0_OVF_vect) //обработчик прерывания по переполнению T0 { if(i==5) { ADC_data=(ADCL>>6) | (ADCH<<2); //байт ADCH всегда должен читатся последним i=0; } else i++; }
Сообщение отредактировал IgorKossak - Sep 25 2012, 06:45
Причина редактирования: [codebox] для длинного кода!!!
|
|
|
|
|
Sep 24 2012, 22:30
|

Mute Beholder
  
Группа: Свой
Сообщений: 260
Регистрация: 4-04-07
Из: Третья планета от Солнца
Пользователь №: 26 754

|
Цитата(endasm @ Sep 25 2012, 07:56)  АЦП меряет, значение преобразуется для отображения на четырехразрядном семисегментрике и выводится на него. Но два последних разряда АЦП постоянно дают разные значения (крайние правые разряды индикатора постоянно показывают разные значения, при этом частота АЦП = 15 кГц). Установившего числа у вас никогда и не будет (разве что для самых крайних значений). Улучшением аппаратной (в основном) и программной части вы можете только приближаться к заявленным параметрам и получать все большую точность. У вас на видео видно что провода от ОУ до АЦП где-то с полметра в воздухе болтаются, чего ж вы хотели? Вот ваши несколько младших разрядов и прыгают из-за наловленного шума. Плюс контакты, разъемы, панельки... И, судя по плате, разводка аналоговых и цифровых земель скорее всего неоптимальная - плата, похоже, скорее для обучения, чем для реальной работы. Ну и выполнять такие операции Код temp=ADC_data*0.004858*1000; //0.0049 - значение напряжение на одну ступень 4.97/1023 где Код int temp; как бы не совсем корректно :> PS Кстати, угадайте сколько прилетит на вход АЦП у вас оторвется проводок идущий от потенциометра к входу "+" ОУ.
--------------------
Common sense is not so common.
|
|
|
|
|
Sep 24 2012, 22:49
|
Гуру
     
Группа: Свой
Сообщений: 2 128
Регистрация: 21-05-06
Пользователь №: 17 322

|
Цитата(endasm @ Sep 25 2012, 00:56)  Вообщем нужно изменять некое напряжение (для примера я взял 5в). АЦП меряет, значение преобразуется для отображения на четырехразрядном семисегментрике и выводится на него. Но два последних разряда АЦП постоянно дают разные значения (крайние правые разряды индикатора постоянно показывают разные значения, при этом частота АЦП = 15 кГц). Ну чо, усредняйте... Иногда ещё так делают: первое преобразование после переключения MUX-а отбрасывают (может это и не помогает). Или возьмите 12-бит АЦП и чтоб килогерц на 100-1000 мерял. Ну и так к слову... Цитата(endasm @ Sep 25 2012, 00:56)  ADC_data не volatile?...странно. Цитата(endasm @ Sep 25 2012, 00:56)  Код switch (digit) { case 0: PORTC=kod_simvola[0]; //выводим в порт нужную цифру break; case 1: Не лень такую портянку писать? Читать точно тяжело... Код PORTC = (digit<10) ? (kod_simvola[digit]) : (0b0001000); Цитата(endasm @ Sep 25 2012, 00:56)  Код ADC_data=(ADCL>>6) | (ADCH<<2); //байт ADCH всегда должен читатся последним Код ADC_data=ADC;// компилятор сам разберётся в каком порядке ему читать (или GCC не разберётся?) И зачем вообще эти сдвиги? Поставьте ADLAR=0.
|
|
|
|
|
Sep 25 2012, 04:31
|
Участник

Группа: Участник
Сообщений: 40
Регистрация: 9-07-12
Пользователь №: 72 673

|
Пробовал в другом порядке считывать - после первого раза встает намертво и больше ацп не работает.
Ну а апаратно кроме как уменьшения длины проводов как-то же можно отфильтровать? И нужен ли там в действительности ОУ?
|
|
|
|
Guest_TSerg_*
|
Sep 25 2012, 17:08
|
Guests

|
>Вот тут описан простой фильтр... Откровения отца Иоанна простому народу
|
|
|
|
|
Sep 26 2012, 10:47
|
Частый гость
 
Группа: Участник
Сообщений: 149
Регистрация: 9-08-08
Пользователь №: 39 519

|
Стабильность показаний АЦП зависит в первую очередь от стабильности источника опорного напряжения
Младшие разряды прыгают не просто так, прыгает напряжение на ноге АЦП, можете взять осциллограф и померять, вот как на осцилограме будет прыгать так и показания АЦП будут прыгать.
В основном все прыгает благодаря наводкам сетевой проводки 50Гц Недавно значения показателя АЦП вывел по ЮАРТУ в терминал, сбросил все в ексель и нарисовал график, в результате получилась синусоида 50Гц
Как уменьшить влияние помех на АЦП? Разделить аналоговую и сигнальную землю, поставить нормальный источник опорного напряжения, поставить малошумящий ОУ, экранировать провода
Иначе поднося руку к проводам будут уже другие показания
Обновляйте информацию о напряжении реже (раз в пол секунды) В течении 0.5с делаейте 10 выборок с периодом 50мс, додавайте и делите на 10 (среднее значения напряжения за период 0.5с)
|
|
|
|
|
Sep 27 2012, 03:27
|
Участник

Группа: Участник
Сообщений: 40
Регистрация: 9-07-12
Пользователь №: 72 673

|
А как добится полного нуля на выходе ОУ, ведь у меня там 1,3 в когда на ОУ ноль подан. Это можно сделать без применения rail to rail ОУ?
|
|
|
|
|
Sep 27 2012, 05:44
|

Местный
  
Группа: Свой
Сообщений: 397
Регистрация: 3-12-09
Из: Россия, Москва
Пользователь №: 54 040

|
Ну в качестве придирки цитата из даташита: "By default, the successive approximation circuitry requires an input clock frequency between 50kHz and 200kHz to get maximum resolution. If a lower resolution than 10 bits is needed, the input clock frequency to the ADC can be higher than 200kHz to get a higher sample rate." Я бы поставил рекомендуемые 62,5 или 125кГц
|
|
|
|
|
Sep 27 2012, 07:53
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(uriy @ Sep 25 2012, 19:45)  «Ну кто так строит!»™ Цитата Y(n) = (Na*Y(n-1) + Nb*X(n)) >> 8 Нельзя в целых числах просто так «взять и поделить» Из-за отбрасывания при делении будет предельный цикл, после какого-то шага данный фильтр остановится не дойдя до нужного значения. В данном случае при приближении снизу (при положительной ступеньке на входе фильтра). Вот выдача простой тестовой программки (исходник ниже) для Na из указанного в статье диапазона при начальном значении фильтра 0 и значении на входе 100. Код Na = 239: Y stabilised at 85 after step 36 Na = 240: Y stabilised at 85 after step 38 Na = 241: Y stabilised at 83 after step 39 ... Na = 247: Y stabilised at 72 after step 48 Na = 248: Y stabilised at 69 after step 50 Т.е. при Na = 248 после 50-го шага сколько не фильтруй, а на выходе фильтра будет 69 при входе 100. При движении сверху (отрицательная ступенька) на 60-том шаге доходит до цели 100, тут всё нормально. Причём симметричное округление, а не отбрасывание остатка, даст только симметризацию установившегося значения, при движении снизу при Na=248 остановится на 85, при движении сверху на 116. Можно к фиксированной точке перейти, запас разрядности соответственно уменьшит отклонение предельного значения от правильного. В простом варианте спасти может округление в сторону цели, т.е. если Y >= X то отбрасывать остаток, а если Y < X то перед делением добавлять (делитель-1). Вот исходник тестовой программки для PC. Поскольку в тесте ступенькой вход - константа, для входа взята не какая-то переменная X, а константа YTARGET CODE #include <stdio.h> #include <stdint.h>
//Y(n) = (Na*Y(n-1) + Nb*X(n)) >> 8 // 239 <= Na <= 248, Nb = 256 - Na
enum { NA_FROM = 239, NA_TO = 248, YTARGET = 100, };
typedef int (*calcfunc_t)(int,int);
int calc_original(int y, int na) { return (na * y + (256-na) * YTARGET) >> 8; }
int calc_symm(int y, int na) { int temp = na * y + (256-na) * YTARGET; temp += 128; return temp / 256; }
int calc_to_target(int y, int na) { int temp = na * y + (256-na) * YTARGET; if (y < YTARGET) temp += 255; return temp / 256; }
void tester(const char *header, int initial, calcfunc_t func) { int y;
printf("\n%s\nY initial value %d, target %d\n", header, initial, YTARGET);
for (int na = NA_FROM; na <= NA_TO; ++na) { printf("Na = %3d: ", na); int y = initial; int yprev; int step = 0; do { yprev = y; y = func(y, na); ++step; } while( y != yprev); printf("Y stabilised at %3d after step %3d\n", y, step); } }
int main() { tester("Original calculation code", 0, calc_original); tester("Original calculation code", 2*YTARGET, calc_original);
tester("Symmetric rounding", 0, calc_symm); tester("Symmetric rounding", 2*YTARGET, calc_symm);
tester("Rounding to target", 0, calc_to_target); tester("Rounding to target", 2*YTARGET, calc_to_target); return 0; } И её выдача: CODE Original calculation code Y initial value 0, target 100 Na = 239: Y stabilised at 85 after step 36 Na = 240: Y stabilised at 85 after step 38 Na = 241: Y stabilised at 83 after step 39 Na = 242: Y stabilised at 82 after step 40 Na = 243: Y stabilised at 81 after step 42 Na = 244: Y stabilised at 79 after step 43 Na = 245: Y stabilised at 77 after step 45 Na = 246: Y stabilised at 75 after step 47 Na = 247: Y stabilised at 72 after step 48 Na = 248: Y stabilised at 69 after step 50
Original calculation code Y initial value 200, target 100 Na = 239: Y stabilised at 100 after step 38 Na = 240: Y stabilised at 100 after step 40 Na = 241: Y stabilised at 100 after step 42 Na = 242: Y stabilised at 100 after step 43 Na = 243: Y stabilised at 100 after step 45 Na = 244: Y stabilised at 100 after step 47 Na = 245: Y stabilised at 100 after step 50 Na = 246: Y stabilised at 100 after step 53 Na = 247: Y stabilised at 100 after step 56 Na = 248: Y stabilised at 100 after step 60
Symmetric rounding Y initial value 0, target 100 Na = 239: Y stabilised at 93 after step 37 Na = 240: Y stabilised at 93 after step 39 Na = 241: Y stabilised at 92 after step 40 Na = 242: Y stabilised at 91 after step 42 Na = 243: Y stabilised at 91 after step 44 Na = 244: Y stabilised at 90 after step 46 Na = 245: Y stabilised at 89 after step 48 Na = 246: Y stabilised at 88 after step 50 Na = 247: Y stabilised at 86 after step 52 Na = 248: Y stabilised at 85 after step 56
Symmetric rounding Y initial value 200, target 100 Na = 239: Y stabilised at 107 after step 37 Na = 240: Y stabilised at 108 after step 39 Na = 241: Y stabilised at 108 after step 40 Na = 242: Y stabilised at 109 after step 42 Na = 243: Y stabilised at 109 after step 44 Na = 244: Y stabilised at 110 after step 46 Na = 245: Y stabilised at 111 after step 48 Na = 246: Y stabilised at 112 after step 51 Na = 247: Y stabilised at 114 after step 52 Na = 248: Y stabilised at 116 after step 55
Rounding to target Y initial value 0, target 100 Na = 239: Y stabilised at 100 after step 38 Na = 240: Y stabilised at 100 after step 40 Na = 241: Y stabilised at 100 after step 42 Na = 242: Y stabilised at 100 after step 43 Na = 243: Y stabilised at 100 after step 45 Na = 244: Y stabilised at 100 after step 47 Na = 245: Y stabilised at 100 after step 50 Na = 246: Y stabilised at 100 after step 53 Na = 247: Y stabilised at 100 after step 56 Na = 248: Y stabilised at 100 after step 60
Rounding to target Y initial value 200, target 100 Na = 239: Y stabilised at 100 after step 38 Na = 240: Y stabilised at 100 after step 40 Na = 241: Y stabilised at 100 after step 42 Na = 242: Y stabilised at 100 after step 43 Na = 243: Y stabilised at 100 after step 45 Na = 244: Y stabilised at 100 after step 47 Na = 245: Y stabilised at 100 after step 50 Na = 246: Y stabilised at 100 after step 53 Na = 247: Y stabilised at 100 after step 56 Na = 248: Y stabilised at 100 after step 60 Конечно, если считать во всяких электронных таблицах или в самописных программах в плавучке, то этого не видно.
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Sep 27 2012, 08:28
|

Универсальный солдатик
     
Группа: Модераторы
Сообщений: 8 634
Регистрация: 1-11-05
Из: Минск
Пользователь №: 10 362

|
Цитата(ReAl @ Sep 27 2012, 10:53)  «Ну кто так строит!»™ Можно к фиксированной точке перейти, запас разрядности соответственно уменьшит отклонение предельного значения от правильного. Можно просто хранить Y, сдвинутое <<8, т.е. в виде 0xmmff (целая часть, дробная часть). И входное значение X сдвинуть <<8. А потом уже оба складывать в своей пропорции. А на вывод выдавать только целую часть Y, т.е. сдвинуть >>8.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|