Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Усреднение вычислений АЦП
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > MCS51, AVR, PIC, STM8, 8bit
Wantcan
Подскажите хороший алгоритм для усреднения вычислений АЦП,я сделал так-
Код
// Read the 8 most significant bits
// of the AD conversion result
unsigned char read_adc(unsigned char adc_input){
ADMUX=adc_input|ADC_VREF_TYPE;
// Start the AD conversion
ADCSRA|=0x40;
// Wait for the AD conversion to complete
while ((ADCSRA & 0x10)==0);
ADCSRA|=0x10;
return ADCH;}

while (1){....

   ta++;if(ta==21)ta=0;
   tempADC=(read_adc(0)/5);
   tempADC1=tempADC+(read_adc(0)/5);                  
   if(ta==20)temp=tempADC1/2;tempADC=0;tempADC1=0;
т.е. показывать на индикаторе среднее число из 20 вычислений,но все равно не так сглаженно получается.
rvk
Конечно не сглаженно, потому что Вы сняв двадцать значений стираете и начинаете набирать по новой.
И кстати складываете Вы двадцать значений, а делите только на два. Если это такая фишка, чтобы результат в итоге умножить на два,
тогда понятно.
И деление на 5 я провожу в самом конце, всего один раз над переменной temp. Это сделает более точными вычисления.

Поэтому у Вас есть усреднение не скользящее, а среднее на каждые отдельно взятые двадцать значений. Простой способ сделать скользящее, это вычитать самое раннее и прибавлять последнее. Но при этом обязательно накопится ошибка вычислений. Продвинутый способ, как писал в другой ветке resident, это хранить все двадцать последних значений в массиве, и делать среднее по нему, примерно вот так:
unsigned char tempData[20];
unsigned char ta=0;
unsigned char i;
unsigend short temp;

while (1){....

ta++;if(ta==20) ta=0;
tempData[ta]=read_adc(0);// деление на 5 перенесено в расчет temp

temp = 0;
for(i=0;i<20;i++){
temp += tempData[i];
}
temp =temp/100; // 20*5

}

В этом случае, переменная temp будет содержать усреднение последних снятых двадцати значений, и обновляться будет
при каждом чтении АЦП.
Wantcan
Цитата
И кстати складываете Вы двадцать значений, а делите только на два
-по другому почему-то не получилось-показания выходят из диапазона.Ваш пример попробую,но for не желательно(может даже не допустимо),т.к. этот алгоритм находится внутри программного генератора,for даст задержку и повлияет на частоту генератора,но попробую и отпишусь,что получилось.
rvk
Ну используйте вместо for вложенный цикл while:

temp = 0;
i=20;
while(i--){
temp += tempData[i];
}

И кстати, поскольку temp имеет тип unsogned short, никаких переполнений быть не должно.
Потому что даже максимальное значение АЦП 255*100= 25500 все равно меньше
65536, поэтому можно смело все складывать, а затем найти среднее.
Alex11
Есть еще один способ получить скользящее среднее. Инициализация:
unsigned int sum, Result;
#define shift 3 // от 1 до 7 при Ваших условиях
sum = 0;
На каждое измерение:
Result = sum >> shift;
sum = sum - Result + read_adc(0);
В зависимости от shift получается различная степень усреднения и разная скорость выхода на устоявшееся значение.
Wantcan
С последним способом у меня не получилось-цифры скачут, а вот так-тоже интересно-
Код
ta++;if(ta==100) ta=0;
   tempData[ta]=read_adc(0);
   tempADC=0;  
   for(a=0;a<100;a++){
   tempADC+=tempData[a];}
   temp=tempADC/500;
-как нагрузку подключаю-цифры на индикаторе пробегают от нуля до нормального значения,после отключения-обратно до нуля.
Herz
Цитата(Alex11 @ Jan 10 2009, 11:27) *
Есть еще один способ получить скользящее среднее. Инициализация:
unsigned int sum, Result;
#define shift 3 // от 1 до 7 при Ваших условиях
sum = 0;
На каждое измерение:
Result = sum >> shift;
sum = sum - Result + read_adc(0);
В зависимости от shift получается различная степень усреднения и разная скорость выхода на устоявшееся значение.

Здесь, похоже, кое-что упущено. Деление, например, или сдвиг в последней операции. Иначе алгоритм предполагает нарастание результата с различной скоростью, зависящей от shift.
rvk
Не забывайте только про переполнение temp. Если значение АЦП будет больше 65535/500 = 131, то произойдет переполнение
переменной temp и значения будут неверные. Проще говоря такая формула работает если значения АЦП были меньше 0x80.
А то, что от нуля до нормального значения, так и должно быть, ведь для первое значение делится на 100, остальные значения
ведь пустые. Второе значение складывается с первым и оба делятся на 100 и т.д. пока не будет заполнен весь массив.
Чтобы с первого значения было все верно, нужно отследить весь массив до заполнения и делить пропорционально
заполнению. Но это уже мелочи.
rezident
Цитата(rvk @ Jan 10 2009, 16:23) *
Чтобы с первого значения было все верно, нужно отследить весь массив до заполнения и делить пропорционально заполнению.
Совсем не обязательно. Достаточно при первом обращении к фильтру проинициализировать весь массив не нулями, а этим самым первым значением. Это для случая фильтрации типа "скользящее среднее" конечно же.
Demeny
Цитата(rvk @ Jan 10 2009, 08:57) *
Продвинутый способ, как писал в другой ветке resident, это хранить все двадцать последних значений в массиве, и делать среднее по нему, примерно вот так:
...........

Для усреднения по последним 20 значениям совсем необязательно хранить последние 20 значений в массиве, зачем расходовать память и ресурсы процессора, чтобы их каждый раз суммировать. А если нужно будет усреднять по 10000 значений ?
Вот простой способ получать усреднённое значение каждый раз при получении текущего измеренного значения:
Код
T_average = T_average + (T_current - T_average) / 20.0

где T_average - среднее значение на данный момент времени, T_current - мгновенное измеренное значение, снятое, например, с АЦП в текущий момент времени.
Этот пример НЧ-фильтра измерений практически эквивалентен усреднению последних 20 значений. Однако здесь число усреднений (20) необязательно может быть целым значением. Вообще говоря, в этой формуле число 20.0 является постоянной времени фильтра, которая вкупе с периодом измерений определяет скорость реакции (инертность) этого фильтра на резкие изменения входных измеряемых величин.
rezident
Цитата(Demeny @ Jan 10 2009, 23:33) *
Этот пример НЧ-фильтра измерений практически эквивалентен усреднению последних 20 значений.
Вообще-то это пример фильтра EMA (экспоненциальное скользящее среднее), а не SMA (простое скользящее среднее). В общем виде EMA выглядит как рекурсия Y(i+1)=Y(i)+(X(i)–Y(i))*K, где K=2/(N+1), Y(i+1) выходное значение фильтра, Y(i) - предыдущее выходное значение фильтра, X(i) - значение текущего отсчета. N имеет тот же смысл, что и "тау" RC-цепочки. Отличие EMA от SMA еще в том, что EMA это фильтр с БИХ, а SMA - с КИХ.
rvk
Цитата(Demeny @ Jan 10 2009, 21:33) *
Вот простой способ получать усреднённое значение каждый раз при получении текущего измеренного значения:
Код
T_average = T_average + (T_current - T_average) / 20.0

где T_average - среднее значение на данный момент времени, T_current - мгновенное измеренное значение, снятое, например, с АЦП в текущий момент времени.

Вот пример в числах есть 10 чисел. Берем скользящее среднее 5 значений:
11, 9, 11, 9, 10, 12, 11, 7, 8, 5,
среднее____ 10, 10.4, 10.52, 9.816, 9.4528, 8.56224 Это по Вашей формуле

А это по массиву из последних набранных пяти значений:
11, 9, 11, 9, 10, 12, 11, 7, 8, 5
среднее____ 10, 10.2, 10.6, 9.8, 9.6, 8.6

Поэтому эти формулы какие угодно, только не взаимозаменяемые. И Ваша формула не проще,
она просто другая.
Demeny
Цитата(rezident @ Jan 10 2009, 21:55) *
Вообще-то это пример фильтра EMA (экспоненциальное скользящее среднее), а не SMA (простое скользящее среднее). В общем виде EMA выглядит как рекурсия Y(i+1)=Y(i)+(X(i)–Y(i))*K, где K=2/(N+1), Y(i+1) выходное значение фильтра, Y(i) - предыдущее выходное значение фильтра, X(i) - значение текущего отсчета. N имеет тот же смысл, что и "тау" RC-цепочки. Отличие EMA от SMA еще в том, что EMA это фильтр с БИХ, а SMA - с КИХ.

Всё это верно, я и не сомневался в Вашей компетентности в этом вопросе. Автор топика просил алгоритм усреднения значений АЦП, поэтому я предложил практическую формулу, не вдаваясь в теорию фильтров, импульсные характеристики, оператор Лапласа и т. п. Эта формула неоднократно мною проверена и прекрасно работает, например, в системах управления газотурбинными двигателями.
Конечно, численно она отличается от простого усреднения, но практически ведёт себя точно также и позволяет избежать хранения и суммирования последних N измерений, что особенно актуально для систем на маломощных вычислителях с ограниченными ресурсами (AVR, PIC).
rezident
Цитата(Demeny @ Jan 11 2009, 00:42) *
Конечно, численно она отличается от простого усреднения, но практически ведёт себя точно также
Да как же они могут вести себя одинаково, если у них и ФЧХ и ПХ разные!?
777777
Цитата(Wantcan @ Jan 10 2009, 08:35) *
Подскажите хороший алгоритм для усреднения вычислений АЦП

Если показания АЦП дрожат, то нужно думать не о том, как их отфильтровать, а о том, как устранить источник помех. А для этого нужно совсем немного, как-то:

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

Я бы еще понял, если бы АЦП было 14-16 разрядным, но я не представляю, как надо изуродовать схему, чтобы дрожало 10-разрядное АЦП - чтобы там получить стабильные значения не требуется особо напрягаться.
Demeny
Цитата(rezident @ Jan 10 2009, 23:26) *
Да как же они могут вести себя одинаково, если у них и ФЧХ и ПХ разные!?

В первом посте автор топика обозначил задачу - "показывать на индикаторе среднее значение", для этой задачи различия КИХ и БИХ фильтров несущественны - оба являются инерционным звеном и справляются с поставленной задачей. Приведённая мной формула экспоненциального скользящего среднего позволяет обойтись без хранения массива измеренных значений и их пересуммирования на каждом цикле измерения.
Может быть, автору окажется полезной статья "Выбор типа скользящих средних" , которую я прикрепил к сообщению. Кстати, в ней на 52 странице приведены импульсные характеристики различных типов усреднения. Сравнив графики б) и г) легко убедиться, что ИХ простого взвешенного усреднения и экспоненциального весьма схожи.
777777
Цитата(rezident @ Jan 10 2009, 21:55) *
Вообще-то это пример фильтра EMA (экспоненциальное скользящее среднее), а не SMA (простое скользящее среднее). В общем виде EMA выглядит как рекурсия Y(i+1)=Y(i)+(X(i)–Y(i))*K, где K=2/(N+1), Y(i+1) выходное значение фильтра, Y(i) - предыдущее выходное значение фильтра, X(i) - значение текущего отсчета. N имеет тот же смысл, что и "тау" RC-цепочки.

Смысл может и тот же, а насчет численного значения я сильно сомневаюсь. Чтобы получить фильтр с постоянной времени tau, коэффициент вычисляется по формуле K = 1 - exp(-T/tau), где T - период следования входных отсчетов.
Microwatt
Цитата(777777 @ Jan 11 2009, 11:53) *
Смысл может и тот же, а насчет численного значения я сильно сомневаюсь. Чтобы получить фильтр с постоянной времени tau, коэффициент вычисляется по формуле K = 1 - exp(-T/tau), где T - период следования входных отсчетов.

Вот то-то и оно. Знания и вера входят в конфликт.
Инженер, чтобы устранить мигание цифр на индикаторе, возьмет среднее арифметическое от 2-4 измерений. Или даже в АЦП что-нибудь из двух компонентов придумает.
Программист, ничтоже сумняшеся, будет брать экспоненту. При сем потребуется мощный процессор, плавающая запятая, дополнительные килобайты памяти, цикл решения задачи вырастет... Но он же за это не платит.
777777
Цитата(Microwatt @ Jan 11 2009, 12:03) *
Инженер, чтобы устранить мигание цифр на индикаторе, возьмет среднее арифметическое от 2-4 измерений. Или даже в АЦП что-нибудь из двух компонентов придумает.
Программист, ничтоже сумняшеся, будет брать экспоненту. При сем потребуется мощный процессор, плавающая запятая, дополнительные килобайты памяти, цикл решения задачи вырастет... Но он же за это не платит.

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

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

Что же касается фильтра T_ave += (T_in - T_ave) / K то для него не требуется никакой плавающей точки - он работает c целыми переменными практически в таком виде как я здесь написал. Если же вы имеете в виду экспоненту для для вычисления K, то она вычисляется один раз на калькуляторе, а если она является степенью двойки, то не требуется даже деления, можно обойтись сдвигами. А дополнительные килобайты нужны как раз при вычислении среднего арифметического - в данном случае кроме текущего и предыдущего отсчетов хранить ничего не требуется.
Wantcan
Спасибо за все ответы,на этой неделе не будет времени этим заниматься,но проблему обязательно надо будет решить.Измерить и показать нужно примерный ток устройства,точность совсем не нужна,стоит в устройстве как допольнительная функция,да-помехи по питанию есть,надо будет и с ними побороться тоже,но усреднение тоже нужно.
rezident
Цитата(Demeny @ Jan 11 2009, 12:46) *
Кстати, в ней на 52 странице приведены импульсные характеристики различных типов усреднения. Сравнив графики б) и г) легко убедиться, что ИХ простого взвешенного усреднения и экспоненциального весьма схожи.
Действительно схожи. Видимо я что-то перепутал или подзабыл sad.gif
Цитата(777777 @ Jan 11 2009, 12:53) *
Смысл может и тот же, а насчет численного значения я сильно сомневаюсь. Чтобы получить фильтр с постоянной времени tau, коэффициент вычисляется по формуле K = 1 - exp(-T/tau), где T - период следования входных отсчетов.
А где это вы увидели, что я утверждал про идентичность численных значений? cranky.gif Для EMA через количество отсчетов равное N величина выходного сигнала сравняется со величиной входного единичного импульсного сигнала. Для настоящей RC-цепочки, если на вход подан единичный импульсный сигнал, то через промежуток времени равный τ=RC величина сигнала на выходе фильтра увеличиться в e раз.
Да, расчет для RC не очень простой, но существует радиолюбительские правила "пяти тау" и "двадцати тау". Первое гласит, что "за время равное τ=5*RC напряжение на конденсаторе RC-цепочки достигнет (с приемлимой точностью) 99% от входного". И соответственно для правила "двадцати тау" то же самое, но для τ=20*RC и отношение - 99,9%.
Tanya
Цитата(rezident @ Jan 11 2009, 20:04) *
Для настоящей RC-цепочки, если на вход подан единичный импульсный сигнал, то через промежуток времени равный τ=RC величина сигнала на выходе фильтра увеличиться в e раз.

Удивительные вещи Вы пишите... Праздники не проходят бесследно?
777777
Цитата(rezident @ Jan 11 2009, 20:04) *
Действительно схожи. Видимо я что-то перепутал или подзабыл sad.gif

Экспонента и прямая?.. Да, схожи, но... при наличии достаточного воображения. Или после 300...500 граммов.

Цитата(Tanya @ Jan 12 2009, 11:02) *
Цитата
Для настоящей RC-цепочки, если на вход подан единичный импульсный сигнал, то через промежуток времени равный τ=RC величина сигнала на выходе фильтра увеличиться в e раз.

Удивительные вещи Вы пишите... Праздники не проходят бесследно?

Да ладно вам придираться, все поняли о чем речь. Это если на входе единичный сигнал упал в 0, то на выходе он уменьшится в e раз через время tau=RC. На выходе этого фильтра, тоже.
demiurg_spb
Цитата(rezident @ Jan 11 2009, 20:04) *
Для настоящей RC-цепочки, если на вход подан единичный импульсный сигнал, то через промежуток времени равный τ=RC величина сигнала на выходе фильтра увеличиться в e раз.
Я предпочитаю несколько другое "качественное" понимание T=RC: касательная проведённая в момент времени 0 к экспоненте пересечёт линию установившегося значения этой экспоненты в момент времени Т=RC - это я так, к словуsmile.gif
Папа Карло
Возник вопрос по формуле T_average = T_average + (T_current - T_average) / 20.0.

Если я правильно понял, то первое значение лучше сразу занести в переменную T_average.
А если не записать, то показания будут нарастать в зависимости от кол-ва отсчётов ?
777777
Цитата(Папа Карло @ Jan 21 2009, 18:16) *
Возник вопрос по формуле T_average = T_average + (T_current - T_average) / 20.0.

Если я правильно понял, то первое значение лучше сразу занести в переменную T_average.
А если не записать, то показания будут нарастать в зависимости от кол-ва отсчётов ?

Да, лучше инициализировать не нулем, а первым считанным значением, тогда фильтру не придется долго выходить на установившееся значение. А формулу лучше представить в сишном виде, даже если будешь реализовывать ее на ассемблере, легче для понимания:

T_average += (T_current - T_average) / 20.0.

То есть из считанного значения вычитаем текущее, делим на коэффициент и прибавляем к текущему. Плавающая точка здесь ни к чему, в целых числах тоже можно считать, правда там есть свои тонкости.
haker_fox
Хочу сказать, что для фильтрации данных с АЦП применил программный ФНЧ, любезно выложенный defunc в одной из веток AVR. Результатом остался доволен!!!
Wantcan
haker_fox, прошу тнуть пальцем в то место форума,о котором вы упомянули,т.к. для меня все еще актуальна эта тема..
777777
Цитата(Wantcan @ Feb 12 2009, 20:53) *
haker_fox, прошу тнуть пальцем в то место форума,о котором вы упомянули,т.к. для меня все еще актуальна эта тема..

То есть вы упорно пытаетесь усреднить помехи, вместо того, чтобы устранить их?
blackfin
Цитата(777777 @ Feb 13 2009, 09:15) *
То есть вы упорно пытаетесь усреднить помехи, вместо того, чтобы устранить их?

Так ведь, "усреднить помехи" по сути и означает отфильтровать их, т.е. - устранить.. Или нет?
Tanya
Цитата(blackfin @ Feb 13 2009, 09:26) *
Так ведь, "усреднить помехи" по сути и означает отфильтровать их, т.е. - устранить.. Или нет?

В данном случае, наверное, - нет. См 19 и 20 посты.
Палыч
Цитата(777777 @ Jan 21 2009, 19:05) *
T_average += (T_current - T_average) / 20.0.

То есть из считанного значения вычитаем текущее, делим на коэффициент и прибавляем к текущему. Плавающая точка здесь ни к чему, в целых числах тоже можно считать, правда там есть свои тонкости.
Вот, именно - тонкости! Представьте, что разность между считанным и текущим значениями - меньше двадцати. Что получим? По этой же причине у автора вопроса и прыгают значения: в первом топике он привёл код, якобы усредняющий из двадцати значений... Имхо, сумма подсчитывается из значений измерений делённых на 5 - так усреднять, мягко говоря, не корректно.
777777
Цитата(blackfin @ Feb 13 2009, 09:26) *
Так ведь, "усреднить помехи" по сути и означает отфильтровать их, т.е. - устранить.. Или нет?

Да, но только устраняют их обычно на входе АЦП, в аналоговом виде, так чтобы АЦП выдавал устойчивый код, который не пришлось бы фильтровать. И не путем подключения RC-цепочки, а путем выяснения их природы и устранения того, что их вызывает. Например, распространенная ошибка - обратный ток от светодиодных индикаторов идет по той же земле, на которой висит АЦП. Падение тока на нем вызывает скачки на земле. Эти помехи вы не устраните усреднением, потому что все они "в одну сторону", так что даже усреднив их вы получите неправильный результат.
blackfin
Цитата(777777 @ Feb 13 2009, 14:40) *
Да, но только устраняют их обычно на входе АЦП, в аналоговом виде,...

Меня смутила формулировка.. ИМХО, есть разница между "устранение помех" и "устранение причин из-за которых эти помехи возникают на входе АЦП"..
777777
Цитата(Палыч @ Feb 13 2009, 10:09) *
Вот, именно - тонкости! Представьте, что разность между считанным и текущим значениями - меньше двадцати. Что получим?

Получим, что результат никогда не достигнет входного значения. Но ведь и реальная, математическая экспонента никогда его не достигает! Вопрос лишь в необходимой точности. Если нужна большая - результат храним в long, а используем в качестве результата старшую половину - то есть как бы число с фиксированной точкой, в котором младшие 16 разрядов дробные.
Цитата(Палыч @ Feb 13 2009, 10:09) *
По этой же причине у автора вопроса и прыгают значения: в первом топике он привёл код, якобы усредняющий из двадцати значений... Имхо, сумма подсчитывается из значений измерений делённых на 5 - так усреднять, мягко говоря, не корректно.

Почему? Просто результат будет в 4 раза больше.

Цитата(Палыч @ Feb 13 2009, 10:09) *
По этой же причине у автора вопроса и прыгают значения: в первом топике он привёл код, якобы усредняющий из двадцати значений... Имхо, сумма подсчитывается из значений измерений делённых на 5 - так усреднять, мягко говоря, не корректно.

Пардон, не доглядел - конечно же надо сначала суммировать 20 значений, а результат делить на 5. А еще лучше - использовать степень двойки.
Палыч
Цитата(777777 @ Feb 13 2009, 14:55) *
Получим, что результат никогда не достигнет входного значения. Но ведь и реальная, математическая экспонента никогда его не достигает! Вопрос лишь в необходимой точности.
Математическая экспанента хотя бы стремиться к нему. В Вашем же случае мы будем иметь постоянную ошибку. Результат этого - всё тот же - отбрасывание остатка при делении, который тем больше, чем больше делитель (это - ведь, наверное, и есть те тонкости, о которых Вы говорили выше). Тогда, уж лучше что-нибудь такое:

Инициализация переменной
T_average_tmp = T_current * N
...................
T_average_tmp += (T_current - T_average)
T_average= T_average_tmp / N

Последнюю строчку, может быть, записать даже так:
T_average= (T_average_tmp + N/2) / N
xemul
Цитата(Палыч @ Feb 13 2009, 15:59) *
...
Последнюю строчку, может быть, записать даже так:
T_average= (T_average_tmp + N/2) / N

Последнюю строчку только так и нужно писать, чтобы погрешность от целочисленного деления осталась +-0.5LSB.
777777
Цитата(Палыч @ Feb 13 2009, 15:59) *
Математическая экспанента хотя бы стремиться к нему. В Вашем же случае мы будем иметь постоянную ошибку.

Что значит "постоянную ошибку"? Разумеется, экспонента будет отличаться от реальной из-за округлений, это неизбежно при цифровых вычислениях, вопрос лишь в необходимой точности. Даже если вы примените float, все равно ошибка будет, так как float тоже дискретно. Но задача ведь не в том, чтобы имитировать экспоненту, а в том, чтобы сгладить сигнал. Для этого вычисления в целых вполне достаточно.
Цитата(Палыч @ Feb 13 2009, 15:59) *
Результат этого - всё тот же - отбрасывание остатка при делении, который тем больше, чем больше делитель (это - ведь, наверное, и есть те тонкости, о которых Вы говорили выше). Тогда, уж лучше что-нибудь такое:
Инициализация переменной
T_average_tmp = T_current * N
...................
T_average_tmp += (T_current - T_average)
T_average= T_average_tmp / N

Тогда уж лучше что-нибудь такое:
Код
    static long Adc0, AdcF;

    Adc0 = (long)ADC << 16;
    AdcF += (Adc0 - AdcF)>>3; // или разделить на нужное число, если оно не степень двойки
    int Result = AdcF >> 16;

Правда, gnu-тый компилятор не очень эффективно кодирует сдвиги (не знаю, может IAR поумнее), поэтому этот участок лучше написать на ассемблере.
Смысл в том, что в старших 16 битах AdcF хранится фильтрованное значение, а в младших - его дробная часть, т.е. оно представляет собой число с фиксированной точкой. При кодировании на ассемблере можно даже подойти к делению (сдвигу) более творчески: если в отбрасываемых битах старший 1, то к оставшимся прибавляем 1, а если 0 - то не прибавляем, т.е. выполняем деление с округлением до целого. Тогда не будет ошибки с отбрасыванием, которой ты так боишься.
ReAl
Цитата(777777 @ Feb 14 2009, 13:18) *
Тогда уж лучше что-нибудь такое:
Код
    static long Adc0, AdcF;

    Adc0 = (long)ADC << 16;
    AdcF += (Adc0 - AdcF)>>3; // или разделить на нужное число, если оно не степень двойки
    int Result = AdcF >> 16;

...
Смысл в том, что в старших 16 битах AdcF хранится фильтрованное значение, а в младших - его дробная часть, т.е. оно представляет собой число с фиксированной точкой.
+1
Давно делаю похожим образом, часто запас разрядов в аккумуляторе делаю равным числу сдвигов для деления на коффициент фильтра (он степень двойки). Т.е. для беззнаковых 10-битных отсчётов и "рециркуляции" 7/8, т.е. весе очередного отсчёта 1/8 будет так:
Код
    uint16_t acc;
void update(uint16_t val) {
   acc = acc - ((acc + 4) >> 3) + val;
}

uint16_t get_result() {
   return  (acc + 4) >> 3;
}

Собственно, с не-степенью двойки будет аналогично, просто
Код
(acc+4)>>3
езде заменится на
Код
(acc+DIVIDER/2)/DIVIDER
, но я не вижу в этом особого смысла. Если задача так чувствительна к тому, что новый отсчёт будет входить не с 1/8 или 1/16, а имено с 1/10 или 1/13, то скорее всего там и фильтр нужен более высокого порядка.
Wantcan
Пожалуйста,помогите переделать найденный на этом форуме программный ФНЧ под мои условия,сам не могу разобраться.
Код
int nStepFilter( int val)
{
#define  shift  (4)   // n = 2^shift
#define  lsb    ((1 << (shift)) >> 1)

    static int x = 0;
    int av = (x + lsb) >> shift;

    x += val - av;

    return av;
#undef lsb
#undef shift
}
Wantcan
Остановился на таком варианте
Код
ta++; if(ta==20){ta=0;temp=tempADC/100;}
   if(ta==0)tempADC=0;  
   tempADC+=read_adc(0);
Флюктуация ваккума
Нужно создать в оперативной памяти закольцованный FIFO-буфер с двумя указателями (PTR_first и PTR_last).

И регистр суммы. Разрядность его должна быть достаточной для хранения суммы 20-ти значний без округления.

Как только приходит очередное значение от АЦП Вы его записываете в ячейку, на которую указывает PTR_last, добавляете его к сумме и вычитаете из суммы значение, на которое указывает PTR_first.

После чего двигаете PTR_first и PTR_last на следующие ячейки в кольце.

Таким образом у Вас всегда в регистре cуммы будет акутульное значение среднего, умноженное на 20.

Чтобы получить среднее достаточно будет поделить значение, находящееся в данный момент в регистре суммы на 20.
Bronislav
Такой вариант не подойдет?
for(i=0;i<20;i++) // усредняем 20 выборки
{
ADC_cur = ADС;
if (i == 0)
{
ADC_old = ADC_cur; // первое значение отправляем в сумму
ADC_sum = ADC_old;
}
else
{
ADC_old += ADC_cur; // сложили текущее значение со старым
ADC_old /= 2; // поделили на 2
ADCsum += ADC_old; // просуммировали
}
}
ADC_cur = ADC_sum/20; // cумму поделили на 20 */
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.