|
ADC в ATtiny461, калибровка |
|
|
|
Jul 2 2010, 19:24
|

Профессионал
    
Группа: Свой
Сообщений: 1 175
Регистрация: 5-01-05
Пользователь №: 1 807

|
Только что закончил небольшшой заказ в 10 устройств на ATtiny461. В каждом устройстве необходимо было мерить как униполярные, так и дифф сигналы. Так как прослеживалась тенденция к повторным заказам, решил приложить определенные усилия к написанию программы калибровки внутреннего ADC. Попытки сделать это чуть не сорвали сроки проекта - оказалось, что ADC ATtiny461 имеют существенные разбросы по параметрам от экз к экз и др. тонкости: 1. Обычное униполярное измерение в 4 экземплярах из 10 показало ошибку в 20 LSB при напряжениях на входе около 2.2 вольт при опоре 2.56 вольт. Вроде требования даташита выполнены - для униполярных сигналов все напряжения в доп пределах. Измерения низких напряжений (в районе 100 mV) были практически идеальны. Насколько я понимаю, ADC в этих 4 экз. имеют слишком большой INL. Как с этим бороться просто не представляю. А если бы устройств было не 10, а 100? Что, делать стенд для отбраковки кристаллов? 2. ADC в AVR по скорости работы и так аутсайдер, но определенные тонкости выявились и при переключениях каналов. Особенно когда есть и униполярные и дифф. (у меня было по 2 сигнала каждого типа). Засада в том, что униполярные мерялись стабильно, но дифф колбасило конкретно, сэмплы отличались почти в два раза. Сначала подумал, что PCB неправильно разведена, но вроде не первый раз развожу, решил искать причину дальше. По-моему, из AVR аппликух была в свое время выдрана некая функция AdcStable(): Код void AdcStable(void){ signed int Vmax,Vmin,V[4]; unsigned char i; //Loop until the ADC value is stable. (Vmax <= (Vmin+1)) for (Vmax=10,Vmin=0;Vmax > (Vmin+1);){ V[3] = V[2];V[2] = V[1];V[1] = V[0];// сдвиг в буфере SETBIT(ADCSRA,ADSC);while(CHKBIT(ADCSRA,ADSC));// получить результат V[0] = ADC; Vmin = V[0]; // Vmin is the lower VOLTAGE Vmax = V[0]; // Vmax is the higher VOLTAGE for (i=0;i<=3;i++){//Save the max and min voltage if (V[i] > Vmax) Vmax=V[i]; if (V[i] < Vmin) Vmin=V[i]; } } } C ней дифф сигналы стали измеряться нормально, но косяк проявился в том, что компилятор IAR v5.50 при оптимизации по скорости сделал так, что цикл в этой функции становился вечным и я не смог разобраться в чем дело. При оптимизации по коду все работало нормально. Однако это наблюдение покоя так и не дало, поэтому данную функцию пришлось выкинуть. В даташите на ATmega324 написано, что при измер дифф сигналов схеме нужно время чтобы прошли переходные процессы переключения и это время указано - 125us. Как только поставил паузу 200us после смены мультиплексора (измерения шли группой, но single conversions) - сразу все начало измеряться нормально. Но горький осадок остался: ADC и так не быстр, скорость выборок падает с увеличением кол-ва сигналов, дак еще и переходные задерживают... Что же делать? Кто как выкручивается? Мне особенно интересно, как все это хозяйство калибруется в серию? Это же все серьезный риск получить приключений на собственное то самое место...
|
|
|
|
|
Jul 3 2010, 07:57
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
А на каком именно цикле он зацикливается? Может сделать Код volatile signed int Vmax, Vmin, V[4]; volatile unsigned char i; ? А если на этом виснет Код while(CHKBIT(ADCSRA,ADSC));// получить результат то это немного странно, но тоже проверять объявления CHKBIT ADCSRA ADSC добавить volatile куда надо(хотя какраз таки тут он должен быть уже по дефолту)... А ещё может быть при оптимизации по скорости у вас вообще ADC не инициализируется, а цикл ожидания не может дождаться результатов.
--------------------
The truth is out there...
|
|
|
|
|
Jul 3 2010, 18:23
|

Профессионал
    
Группа: Свой
Сообщений: 1 175
Регистрация: 5-01-05
Пользователь №: 1 807

|
Цитата А если на этом виснет Код while(CHKBIT(ADCSRA,ADSC));// получить результат то это немного странно, но тоже проверять объявления CHKBIT ADCSRA ADSC добавить volatile куда надо(хотя какраз таки тут он должен быть уже по дефолту)... А ещё может быть при оптимизации по скорости у вас вообще ADC не инициализируется, а цикл ожидания не может дождаться результатов. нет, тут точно не виснет. Инициализация ADC происходит в начале программы один раз, кроме того с оптимизацией по коду все работает нормально. Цитата А на каком именно цикле он зацикливается? Может сделать Код volatile signed int Vmax, Vmin, V[4]; volatile unsigned char i; ? да, наверно стоит попробовать... хотя в аппнотах вроде не было объявлений volatile. Хорошо, что проблема решилась простой задержкой 200us после переключения мультиплексора. Хотя сейчас я задумался, а не лучше ли вместо задержки просто запускать холостое преобразование? В том же даташите на atmega324 проскочила фраза, что при переключении дифф каналов это время необходимо в частности для инициализации схемы компенсации смещения на входах... Но все же ясно, что время холостого преобразования зависит от частоты тактирования ADC, и на высоких частотах одного холостого будет снова не хватать... Потому считаю, что метод введения задержки все же лучше. Разобрался частично с калибровкой. Большая часть ошибки при N=900...1023 состояла в том, что я опрометчиво посчитал опору как 2560mV (типовое значение), а по факту оказалось 2500mV. Измеренное смещение ADC составило +1LSB, и после всего этого стало ясно что и gain error укладывается в +2LSB. В общем, тяжело, но разобрался. Если бы тока в AVR делали опору поточнее, было бы совсем здорово, а то не от чего оттолкнуться...
|
|
|
|
|
Jul 9 2010, 22:27
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата По-моему, из AVR аппликух была в свое время выдрана некая функция AdcStable() такого говнокода не могло быть в AVR аппликухах. Программистов такого уровня в Atmel не держат. Цитата Как только поставил паузу 200us после смены мультиплексора Многократно обсуждалось... При смене канала просто делайте два измерения. Первое выбрасывайте, второе оставляйте.
|
|
|
|
|
Jul 10 2010, 00:44
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата(AHTOXA @ Jul 10 2010, 02:47)  Высокого же вы мнения о программистах атмеля  . Да, высокого, вот оригинал по ссылке: Код void Stable_ADC(void) // loop until you have a stable value { int V[4]; char i; int Vmax, Vmin; //Loop until the ADC value is stable. (Vmax <= (Vmin+1)) for (Vmax=10,Vmin= 0;Vmax > (Vmin+1);) { V[3] = V[2]; V[2] = V[1]; V[1] = V[0]; ADCSR |= 0x40; // Start a new A/D conversion while (!(ADCSR & (1<<ADIF))) // wait until ADC is ready ; V[0] = ADC; Vmin = V[0]; // Vmin is the lower VOLTAGE Vmax = V[0]; // Vmax is the higher VOLTAGE /*Save the max and min voltage*/ for (i=0;i<=3;i++) { if (V[i] > Vmax) Vmax=V[i]; if (V[i] < Vmin) Vmin=V[i]; } } } функция написана и отформатирована читаемо по крайней мере. Цитата(singlskv @ Jul 10 2010, 02:44)  Это Вы ерунду "сморозили"... при смене на дифф. каналах нужна просто пауза на 125мкс: Alternatively, conversion results obtained within this period should be discarded. Отнюдь не ерунду, а то самое "alternatively" из ДШ. Только не уточнил что АЦП не разгонять выше 8KSPS (125 мкс --> 8Khz). Вот например как оно делается у меня для дифф каналов: Код #pragma vector=TIMER1_COMPA_vect __interrupt void T1OCAISRHandler(void) { // 1ms timer event (Fd = 1kHz) if (!adcContext.lock) { adcContext.CurrentChan = 0; adcContext.DummyCycle = TRUE; // notify to discard this result as mux channel changes adcContext.SampleSequence += 1; // inc sample id adcStartConversion( adc_GetMuxIndex( adcContext.CurrentChan ) ); adcContext.lock = 1; } else { // overload.... skip current sample on all channels. adcContext.nSampleOffsetErrors += 1; } }
#pragma vector=ADC_vect __interrupt void AdcISRHandler(void) { U16 Val = ADC; if (adcContext.DummyCycle) { // restart current channel adcStartConversion( adc_GetMuxIndex( adcContext.CurrentChan ) ); adcContext.DummyCycle = FALSE; } else {
// handle current conversion result adcUpdateChannel( &adcContext.Channels[ adcContext.CurrentChan ], Val );
// switch to the next channel and launch it adcContext.CurrentChan++; if (adcContext.CurrentChan > MUX_LAST_INDEX) { adcContext.lock = 0; adcContext.CurrentChan = 0; } else { adcStartConversion( adc_GetMuxIndex( adcContext.CurrentChan ) ); adcContext.DummyCycle = TRUE; // mux changes - need to discard the next result }
} } Куда приятнее в прерывании от АЦП просто забирать 100% верный результат, чем натыкивать delay_us() в основном цикле программы убивая при этом ее портируемость и способность к расширению.
|
|
|
|
|
Jul 10 2010, 09:57
|
    
Группа: Свой
Сообщений: 1 928
Регистрация: 11-07-06
Пользователь №: 18 731

|
Цитата(ivainc1789 @ Jul 2 2010, 23:24)  ... По-моему, из AVR аппликух была в свое время выдрана некая функция AdcStable(): ... C ней дифф сигналы стали измеряться нормально ... поэтому данную функцию пришлось выкинуть. По поводу осмысленности кода - целиком согласен с defunct. Даже индийские программеры уже вроде бы запомнили, что нельзя читать неинициализированные автоматические переменные. Измеряться правильно стало потому, что в функции делается минимум два измерения. Максимальное число измерений в функции зависит от начального содержимого signed int V[4]. Ещё правильнее то, что Вы эту функцию выкинули. Цитата В даташите на ATmega324 написано, что при измер дифф сигналов схеме нужно время чтобы прошли переходные процессы переключения и это время указано - 125us. Как только поставил паузу 200us после смены мультиплексора (измерения шли группой, но single conversions) - сразу все начало измеряться нормально. Но горький осадок остался: ADC и так не быстр, скорость выборок падает с увеличением кол-ва сигналов, дак еще и переходные задерживают... Что же делать? Кто как выкручивается? Дык других вариантов и нет - после переключения канала/режима измерения нужно немного подождать. Как ждать - через delay_us(), по таймеру или скипать одно измерение после переключения - решать Вам. Цитата Мне особенно интересно, как все это хозяйство калибруется в серию? У АЦП АВР очень больное место одно - точность встроенной опоры. имхо, если исключить прочие неразумности, ничего кроме опоры калибровать не требуется. Правда, это тянет за собой деление (зачастую ненужное где-либо ещё в проге). Еще одно "но" - входные сигналы не должны превышать _минимальное_ даташитное Vref. Естественный вариант - использовать внешнюю опору или внешний(-ие) АЦП.
|
|
|
|
|
Jul 10 2010, 21:26
|

кекс
     
Группа: Свой
Сообщений: 3 825
Регистрация: 17-12-05
Из: Киев
Пользователь №: 12 326

|
Цитата(AHTOXA @ Jul 10 2010, 23:27)  Вы не ответили на мой вопрос. Вы считаете, что если говнокод отформатировать, то он перестанет быть говнокодом? Я считаю, что любой нормальный код можно превратить в говнокод одним лишь форматированием. Например просто - разместить всё в одной строке. Цитата Или даже спрошу ещё более конкретно: считаете ли вы приведённый вами кусок кода от атмеля - говнокодом? Не считаю, потому что код оформлен читаемо - алгоритм читается, править/отладить легко, потенциальный баг видно. Другой вопрос - дубовая (не расширяемая) реализация алгоритма - это сделано специально, т.к. основная задача любого наглядного пособия - донести суть/принцип работы, а не чтобы его можно было взять и бездумно вставить в свой проект. В первом же варианте код не читается, править/отлаживать нереально, и баги не видно - это и делает его говнокодом.
|
|
|
|
|
Jul 10 2010, 21:42
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(defunct @ Jul 10 2010, 04:44)  Отнюдь не ерунду, а то самое "alternatively" из ДШ. Только не уточнил что АЦП не разгонять выше 8KSPS (125 мкс --> 8Khz). Вот например как оно делается у меня для дифф каналов: Такой способ конечно возможен, но у него есть и недостатки. - Вы почти в 2 раза уменьшаете итоговый sample rate АЦП - окончание Sаmple & Hold(для "правильного" преобразования) заметно сдвигается во времени от начала этого пребразования(это надо учитывать при необходимости) Цитата Куда приятнее в прерывании от АЦП просто забирать 100% верный результат, чем натыкивать delay_us() в основном цикле программы убивая при этом ее портируемость и способность к расширению. delay_us() на 125мкс в основном цикле это уже какой-то ненормальный вариант, таймеры еще никто не отменял...
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|