Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: mega2561, проблема в АЦП с ADMUX
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
AngelChik
Всем доброго времени!!! Помогите пожалуйста разобраться в проблеме... Устал, уже весь мозг сломал, всё перепробовал. Устройство: АЦП контроллера измеряет сигнал через делитель напряжения (47/10 кОм), уровень сигнала после делителя - 2 вольта (к примеру). Таких три входа - ADC0-2. Проблема заключается в том, что если считывать только один канал, то всё измеряется на ура, всё (почти) точно по вольтметру. НО! Если в процессе измерения добавить второй канал (или любой другой), то отконвертированное напряжение становится "не правильным", например вместо 12,6 вольт начинает показывать 9.5. Тем не менее, считанное значение остаётся пропорциональным сигналу. Вот код:
CODE
void ADC_task()
{
if(!bMeasure)
return;
__disable_interrupt(); - это я добавил только для пробы, влияет ли таймер динамической индикации на работу АЦП...

samples1 = 0;

ADCSRA &= ~(1<<ADSC);
ADMUX = (1<<REFS1) | (1<<REFS0) + 0;

for(U8 i=0;i<5;i++)
{
ADCSRA |= (1<<ADSC);
while(ADCSRA & (1<<ADIF));
U8 low = ADCL;
U16 ADCResult = low + (ADCH<<8);
samples1 += (ADCResult*2.56)/1024;
}
samples1 = samples1/5;
fU = samples1*5.7; - компенсация делителя

ADCSRA &= ~(1<<ADSC); - это тоже уже от нечего делать...
ADMUX++; если закомментировать эту строчку, то напряжение fU - в норме,
совпадает с реальным, а если не комм. -
то меньше реального на некоторое значение


samples2 = 0;
for(U8 i=0;i<5;i++)
{
ADCSRA |= (1<<ADSC);
while(ADCSRA & (1<<ADIF));
U8 low = ADCL;
U16 ADCResult = low + (ADCH<<8);
samples2 += (ADCResult*2.56)/1024;
}
samples2 = samples2/5;

bMeasure = false;
__enable_interrupt(); - аналогично disble'у...
}


После смены каналов (ADMUX) вставлял задержки __delay_cycles(), самые разные, даже до 1 секунды... Пробовал клокить АЦП по таймеру... От чипа к чипу не меняется ничего (таких 10 плат). Ничего не помогает... У меня осталось только одно подозрение. Как Вы считаете, может делитель 47/10 кОм имеет слишком большое сопротивление для нормальной работы АЦП? Дело в том что сейчас по факту, на втором канале (ADC1) 0 вольт (делитель пустой), ни к чему не подсоединён ("пирожок ни с чем" sm.gif))... Вот я и подумал, переключение коммутатора с 2 вольт на 0 и обратно, поэтому надо вставить задержки после модификации ADMUX... Хрена. Не помогло никак.

Есть интересное наблюдение: если просмотреть самое первое считанное значение fU, то оно будет нормальным в любом случае, так как сразу после запуска проги на контроллере, первым измеряется именно ADC0, но после него идёт переключение канала ADMUX++ (раньше задавал жёстко, без считывания). Отсюда вывод: как только происходит хотя бы одно переключение, то следующие считывания канала ADC0 становятся испорченными... ПОЧЕМУ???

Снимал кондёр с риференс (был 1 нФ), пробовал увеличивать до 1 мкФ, ноль эмоций sad.gif(

К меге подсоединено восемь семисегментных индикаторов, реализована динам. индикация через таймер с прерыванием.

Что скажете? Что непонятно - спрашивайте. Могу выложить фотки устройства...
Alex_1811
После смены канала перед запуском следующего преобразования нужно подождать пока АЦП сделает 1 клок.
AngelChik
Цитата(Alex_1811 @ May 11 2011, 13:46) *
После смены канала перед запуском следующего преобразования нужно подождать пока АЦП сделает 1 клок.

В курсе, читал. Доки уже наизусть... Я же написал, что делал задержки после модификации ADMUX, никакого толку нет, всё равно бред считывает.
oran-be
Явно имеет место недозаряд емкости внутреннего УВХ АЦП. Задержка после перключения каналов должна была бы помочь.
Точнее, можно:
1 запустить преобразование и сразу переключить канал.
2. По завершении преобразования вынуть данные - они будут с предыдущего канала, с которого переключили.
3. подождать некоторое время до окончания переходного процесса
и - п. 1
AngelChik
Эгеее... Спасибо, ща попробую.

Ну вот, "перевернул"...
Код
void ADC_task_init()
{
  ADCSRA |= (1<<ADEN);
  DIDR0 = 0xFF;
  ADMUX = (1<<REFS1) | (1<<REFS0) + 1;
  ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
  ADCSRB = 0x00;
}

void ADC_task()
{
  if(!bMeasure)
    return;
  
  samples2 = 0;
  
  for(U8 i=0;i<5;i++)
  {
    ADCSRA |= (1<<ADSC);
    ADMUX = (1<<REFS1) | (1<<REFS0) + 0;
    while(ADCSRA & (1<<ADIF));
    U8 low = ADCL;
    U16 ADCResult = low  + (ADCH<<8);
    samples2 += (ADCResult*2.56)/1024;
  }
  samples2 = samples2/5;

  __delay_cycles(16000); // - задержка в 1 мС... Хренли толку?!?

  samples1 = 0;
  for(U8 i=0;i<5;i++)
  {
    ADCSRA |= (1<<ADSC);
    ADMUX = (1<<REFS1) | (1<<REFS0) + 1;
    while(ADCSRA & (1<<ADIF));
    U8 low = ADCL;
    U16 ADCResult = low  + (ADCH<<8);
    samples1 += (ADCResult*2.56)/1024;
  }
  samples1 = samples1/5;
  
  fU = samples1*5.7;
  
  bMeasure = false;
}


Получилась вот такая дрянь. Протестил. 12 вс 9,5 вольт... Бошкой об стенку бы побился, да не поможет, ибо стены из гипсокартона... А что на счёт входного сопротивления? Не многовато-ли?... Предделитель - 256, частота - 16 МГц, задержка - 1 мс....
oran-be
В принципе п. 2 можно опустить - не дожидаться конца преобразования, а вынимать данные непосредственно перед запуском следующего преобразования, которое можно запуускать по таймеру.
По опыту, на Меге8, для правильного конвертирования напряжения внутреннего ИОН, внутреннее сопротивление которого порядка 30 кОм, если переключаться с 0, необходимо было подождать не менее 10000 тактов (для 10-битной точности).

Без задержек на выходном сопротивлении источника ( порядка 10 кОм) будет безбожно врать.
Либо сопроивление источника понизить до менее 1 кОм, либо задержки.

З.Ы. А почему делается именно 5 выборок, а не круглое число? Сразу бы объем программы уменьшился)

И - во втором цикле первый результат будет из канала (1<<REFS1) | (1<<REFS0), который внесет свою коррективу в размере 20%)

Попробуйте сначала получить требуемый результат без фильтрации.
AngelChik
Спасибо ещё раз за мнение и совет. Снижу сопротивление в 10 раз, отпишуся...

Факир был пьян, фокус не удался sad.gif(( Заменил на делитель 4,7/1,0 кОм, не помогло нисколечко...Куда копать дальше?... Забыл признаться в гадости - у меня питание AVCC через фильтр 10 Ом 10 мкФ. Это ведь никак не могло повлиять?

Есть одна подвижка, подтверждающая теорию о недозаряде. Ускорил клоки АЦП со 128 до 16... Теперь напряжение показывает не 9,5 против реальных 12, а 4.7 sm.gif))

Питание - 5.03, на ИОНе - 2.563... Вроде всё как и должно быть.

5 выборок - это пока произвольно, здесь дело в принципе запустить для начала.

Цитата
И - во втором цикле первый результат будет из канала (1<<REFS1) | (1<<REFS0), который внесет свою коррективу в размере 20%)

Не совсем понял, а что за корректива??? Ну насчёт переворота понятно, поэтому я и поменял местами переменные samples1 и samples2.

АААААА... До меня дошло, о чём Вы sm.gif))) Первое считывание второго цикла - это результат из последнего считывания первого цикла, а вношу в усреднение другой переменной sm.gif)) Ща исправим.

Теперь код у нас вот такой:
Код
|= (1<<ADSC);
  ADMUX = (1<<REFS1) | (1<<REFS0) + 0;
  while(ADCSRA & (1<<ADIF));
  U8 low = ADCL;
  U16 ADCResult = low  + (ADCH<<8);
  samples2 = (ADCResult*2.56)/1024;
  
  __delay_cycles(32000);    // - 2 мс
  
  ADCSRA |= (1<<ADSC);
  ADMUX = (1<<REFS1) | (1<<REFS0) + 1;
  while(ADCSRA & (1<<ADIF));
  U8 low = ADCL;
  U16 ADCResult = low  + (ADCH<<8);
  samples1 = (ADCResult*2.56)/1024;
  fU = samples1*5.7;


УРААААА!!!!!!!! Я пошёл искать бетонную стену... Вот это чё такое?!?!!?

Код
while(ADCSRA & (1<<ADIF));


А должно быть
Код
while(!(ADCSRA & (1<<ADIF)));


Ждать-то надо, пока нет флага!!!

ИТОГ:

Код
void ADC_task()
{
  if(!bMeasure)
    return;
  
  samples1 = 0;
  ADMUX = (1<<REFS1) | (1<<REFS0) + 0;
  __delay_cycles(10);
  
  for(U8 i=0;i<5;i++)
  {
    ADCSRA |= (1<<ADSC);
    while(!(ADCSRA & (1<<ADIF)));
    U8 low = ADCL;
    U16 ADCResult = low  + (ADCH<<8);
    samples1 += (ADCResult*2.56)/1024;
  }
  samples1 = samples1/5;
  fU = samples1*5.7;
  
  samples2 = 0;
  ADMUX = (1<<REFS1) | (1<<REFS0) + 1;
  __delay_cycles(10);
  
  for(U8 i=0;i<5;i++)
  {
    ADCSRA |= (1<<ADSC);
    while(!(ADCSRA & (1<<ADIF)));
    U8 low = ADCL;
    U16 ADCResult = low  + (ADCH<<8);
    samples2 += (ADCResult*2.56)/1024;
  }
  samples2 = samples2/5;
  
  bMeasure = false;
}
=GM=
Неплохо бы и флаг ADIF сбрасывать после чтения регистров АЦП, а то будете долго читать одно и тоже.
AngelChik
Точняк! Забыл, спасибо. Сброс производится записью 1 в него? правильно?

Типа
Код
ADCSRA |= (1<<ADIF);
? Так?
oran-be
Операцию
ADMUX = (1<<REFS1) | (1<<REFS0) + 1;
можно поставить пораньше - например, сразу после выхода из первого цикла. Операции деления с float сработают как задержка куда большая, чем на 10 тактов). А можно и вообще обналеть и поставить в первом цикле
if(i == 4) ADMUX = (1<<REFS1) | (1<<REFS0) + 1;
тогда время заряда второго канала увеличится на время преобразования, т.е. на 13*К деления тактов.
=GM=
Цитата(AngelChik @ May 11 2011, 10:19) *
ADCSRA |= (1<<ADIF)? Так?

Так.

Ещё одно мелкое замечание. В цикле вы вычисляете samples2 += (ADCResult*2.56)/1024;
Оставьте только samples2 +=ADCResult, а после цикла samples2 = samples2*2.56/5120. Быстрее будет.
AngelChik
Цитата(=GM= @ May 11 2011, 15:49) *
Так.

Ещё одно мелкое замечание. В цикле вы вычисляете samples2 += (ADCResult*2.56)/1024;
Оставьте только samples2 +=ADCResult, а после цикла samples2 = samples2*2.56/5120. Быстрее будет.

Логично, спасибо.
нечитатель
Там эта. На всякий случай.
Когда АЦП, то надо не только сделать вход входом (DDR), но и подтяжку на нём отключить (PORT).
... а если про подтяжку забыть, то вероятно: никто никогда и не заметит такую багу. Ну будет ноль не совсем нулём, ну будет чучуть ошибка ваще мелкая, нудаисним.

(не читал)
GYUR22
была подобная фигня на mega128 - ref был с AVCC
все решилось когда было замерено падение напряжение на неизвестной smd 1210 индуктивности фильтра -20 -30мв
ее сопротивление было десятки ом
заменил на 1210 47uH или даже 100uH - и все заработало нормально
короче проверьте ваш фильтр avcc
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.