Работа с АЦП. Программные средства повышения точности
Автор DI HALT
Опубликовано 29 Янв 2009
Рубрики: AVR. Учебный курс
Метки: Assembler, AVR, Алгоритм, Аналог, АЦП
Вообще, сграбив сигнал в цифровую форму мы мы можем извращаться с ним как угодно. Методик цифровых фильтраций существует масса и все они основаны на сборе избыточной информации с последующим выделением сигнала. Я приведу для примера лишь один простейший способ - усреднение.
Суть усреднения в том, что у нас есть статичный (считаем, что за время измерения сигнал не меняется) сигнал к которому подмешан шум. Шум возникает изза работы транзисторов, из-за колебания опорного напряжения, помех, наведенных на сигнальные линии. Да от чего только он не возникает. Особенность шума в том что он, как правило, хаотичен. Так что во время нашего замера может меняться как в меньшую так и в большую сторону. Тут то мы его и прижучим.
Берем и снимаем не одно измерение, а сразу кучу. А потом берем по ним среднее арифметическое. Так как полезный сигнал у нас константен, то его составляющая такой и останется, как ее не усредняй, а вот шум изрядно приглушит. И чем больше выборок мы сделаем, тем сильней задавит шумовую составляющую. Западло этого метода очевидно - резко снижается скорость обработки. Так как вместо одной выборки нам приходится делать серию и объявлять ее как одну, но это неизбежное зло.
В качестве демонстрации метода я приведу пример усреднения. Программка простая, хватает 64 выборки, усредняет их и отправляет по UART. Сразу отмечу тот факт, что для эффективного подавления шума нужно чтобы частота выборок была ниже частоты всяких паразитных колебаний (вроде 50Гц наводок от сети) раза в два три, иначе у нас эти колебания благополучно пролезут как полезный сигнал. А еще число выборок нужно брать кратным двойке, чтобы можно было делить простым сдвигом. Впрочем, смотрите на код, там будет более понятно. Весь код я выкладывать не буду, только главный файл. Все инициализации АЦП и UART, а также ряд служебных процедурок я оставлю за кадром. Если интересно, то вы всегда можете скачать проект и посмотреть сами. Сбор числа у меня идет в прерывании от АЦП, а деление в прерывании по передаче. Так минимизируется число действий выполняемых процом. Хотя растягивание прерываний это не есть гуд. Но городить флаговую операционную систему мне тут впадлу, впрочем, дойдет и до нее время. А пока смотрите в код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | .include "m8def.inc" ; Используем ATMega8 .include "define.asm" ; Наши все определения переменных тут .include "macro.asm" ; Все макросы у нас тут .include "vectors.asm" ; Все вектора прерываний спрятаны в этом файле ;============================================================================= .ORG INT_VECTORS_SIZE Reset: OUTI SPL,low(RAMEND) OUTI SPH,High(RAMEND) ;============================================================================= .include "init.asm" ; Все инициализации тут. ;============================================================================= Main: SEI ; Разрешаем прерывания. ;Главный цикл пуст, весь экшн в прерываниях. RJMP Main ;============================================================================= ; Interrupts ;============================================================================= ; Прерывание от UART на передачу UART_TX: PUSH R16 ; Нычим регистры в стеке IN R16,SREG PUSH R16 ; Сумму мы нажрали в прерывании по АЦП, а делить ее для усреднения будем уже тут CLC ; Сбрасываем флаг переноса ROR SUMH ; И сдвигаем значение вправо сначала старший ROR SUML ; потом младший. Через флаг переноса. CLC ; Всего нам надо 6 сдвигов 2^6=64 ROR SUMH ; Что будет эквивалентно делению на 64 ROR SUML CLC ROR SUMH ROR SUML CLC ROR SUMH ROR SUML CLC ROR SUMH ROR SUML CLC ROR SUMH ; Теперь уменя готовое 10 байтное усредненное число ROR SUML ; Но 10 байт мне нафиг не уперлись. Куда мне их совать? ; Поэтому я отбрасываю еще два младших бита, превращая число ; в восьмибитное. Делаю я эту нехитрую операцию тем же самым сдвигом через перенос CLC ROR SUMH ROR SUML CLC ROR SUMH ROR SUML ; Все, теперь можно забыть про старший байт, он равен нулю ; А весь жыр теперь находится в младшем байте SUML OUT UDR,SUML ; Его то мы и отрыгиваем в UART. О переполнении буффера можно ; Не париться, так как отправка идет исключительно по ; прерыванию, а значит пока байт не ушел никаких левых засылок не будет. POP R16 ; Достаем из стека сныканое барахло. OUT SREG,R16 POP R16 RETI ; Выход из прерывания ;============================================================= ; Прерывание от UART на прием UART_RX: PUSH R16 ; Прячем в стек регистры IN R16,SREG PUSH R16 IN R16,UDR ; Берем пойманый байт CPI R16,'s' ; И анализируем. Если S - значит стоп BREQ StopADC CPI R16,'r' ; Если r значит - запуск. BREQ RunADC ; Запускаем АЦП, на непрерывное преобразование и включаем UART на передачу RunADC: OUTI UCSRB,(1<<RXEN)|(1<<TXEN)|(1<<RXCIE)|(1<<TXCIE) OUTI ADCSRA,(1<<ADEN)|(1<<ADIE)|(1<<ADSC)|(1<<ADFR)|(3<<ADPS0) OUTI UDR,'R' RJMP ExitRX ; А тут останавливаем и АЦП и выключаем UART иначе он болезный будет гадить ; по прерыванию до тех пор пока питалово не кончится. StopADC: OUTI ADCSRA,(0<<ADEN)|(0<<ADIE)|(0<<ADSC)|(0<<ADFR)|(3<<ADPS0) OUTI UCSRB,(1<<RXEN)|(0<<TXEN)|(1<<RXCIE)|(0<<TXCIE) ExitRX: POP R16 ; Достаем из стека данные наши OUT SREG,R16 POP R16 RETI ; Выходим в главный цикл ;============================================================================= ; Прерывание от АЦП - обработка завершена ADC_OK: PUSH R16 ; Сохранить регистры в стеке PUSH R17 IN R16,SREG ; Сохранить SREG PUSH R16 ; Определяем ряд макросов для удобства использования .def ADSH = R15 ; Старший байт суммы .def ADSL = R14 ; Младший байт суммы .def ACT = R13 ; Счетчик усредняемых значений .def SUMH = R12 ; Выходной регистр старший .def SUML = R11 ; Выходной регистр младший IN R16,ADCL ; Взять значение из АЦП IN R17,ADCH ; Теперь пару R17:R16 надо просуммировать с их n-1 значением. При первой итерации ; тут ноль. При второй уже будет сумма с первой выборкой и так далее. ADD ADSL,R16 ; Младший байт суммируем без переноса ADC ADSH,R17 ; Старший байт суммируем с учетом переноса ; В результате, в ADSH:ASSL будет накопленна сумма ADSH:ASSL(n) + ADSH:ASSL(n+1) ; Максимальное значение для 10битного АЦП - 1024, в то время как в 16разрядах ; Помещается 65536 значений, 65536/1024 = 64. Поэтому переменная АСТ должна быть ; Заранее инициализирована числом 64 DEC ACT ; Считаем сколько уже выборок мы сделали BRNE Exit ; Если это не последнее слагаемое, то выход MOV SUML,ADSL ; Сливаем выходное значение в регистр MOV SUMH,ADSH ; младший и старший байты CLR ADSL ; Потом мы очищаем регистры суммирования CLR ADSH ; Чтобы новый результат был с нуля LDI R16,64 ; Заполняем счетчик новым числом MOV ACT,R16 Exit: POP R16 ; Достаем из стека все как было. OUT SREG,R16 POP R17 POP R16 RETI ; Возвращаемся. |
Комментарии
23 комментариев на «Работа с АЦП. Программные средства повышения точности»
Оставьте свой отзыв
Вы должны войти, чтобы оставлять комментарии.





Для медленно изменяющихся сигналов я шунтирую аналоговый вход керамическим конденсатором порядка 0,1 - 1,0 мкф. Поскольку измеряемое напряжение всегда подаю через ограничительный резистор или с делителя, шумы давятся достаточно хорошо. Кроме того, это уменьшает погрешность, возникающую при сопротивлении измерительной цепи более нескольких килоом (в момент измерения импеданс входа АЦП микроконтроллеров имеет емкостной характер).
Зато усреднение по множеству точек при очень хорошо сделанной электронике позволяет “как бы увеличить разрядность АЦП”. Например, если значение на входе между значениями A и A+1, то вероятности выпадения этих значений будут разные, и если сложить например N*4 значений а поделить на N, то младшие биты будут показывать значение входного сигнала более точно.
А еще усреднение можно делать “скользящим окном” - имеется буфер A (скажем на 64 числа), в него по кольцу записываются новые значения АЦП, имеется сумма буфера S и имеется указатель в буфере i. Изначально буфер обнулен, сумма равна нулю. Каждое значение АЦП обрабатывается следующим образом:
- из суммы S вычитаем элемент A[i]
- считываем новое значение АЦП в adc
- прибавляем его к сумме S
- записываем его в A[i]
- Прибавляем i и если оно превысило 63 - обнуляем i
За значение, полученное с АЦП, принимаем S/64.
Такой алгоритм во многом эквивалентен конденсатору - на каждое измерение АЦП мы получаем результат, но он усреднен с предыдущими. При этом не нужно каждый раз рассчитывать сумму и тратить на это время. Преимущества - можно усложнить алгоритм - если разница между полученным значением АЦП и полученным средним значением больше некоторого порога, можно заполнить весь массив новым значением, установить сумму как это значение*64 - и мы получим быструю реакцию на резкие изменения при стабильном показании, если на входе сигнал почти не меняется.
А в чем отличие команды MOV от LDI если по сути они оинаково пихают данные в регистр? Или это просто команда премещения байта из регистра в регистр (MOV R15,R14) ?
MOV копирует содержимое одного регистра в другой, LDI загружает в регистр константу (непосредственно заданное число).
Вообще, в AVR огромное количество команд отличается лишь типом операнда или методом адресации. Зато их много! (Атмел хвастается их количеством).
А например, у PIC контроллеров команды более универсальны, и их всего 31-35. (И Микрочип считает, что это хорошо, т.к. их легче запомнить). В общем, каждому - свое. А мне так пофигу, я использую и те, и другие. Процессорам тем более пофигу, они работают только с теми 0 и 1, которые в них зашьет программатор.
А вообще, да. Шум в сигнале давится старым добрым RC фильтром, а данный метод хорош только усреднять шумы самого ацп. Вот я помню, пытался кое-что сделать на DS2450, так там младшие три бита - настоящий генератор случайных чисел. Плюнул, и придушил точность, а что ещё делать?
Немножечко оффтопа.
Хм, а почему все таки ассемблер? Просто есть нормальные Компиляторы на си, паскале, бэйсике, разных самописных экзотических языках. И платные, и бесплатные. И вполне компактный код дают потом. И уже хренова туча всяких макросов и стандартных процедур сведены к простм функциям.
Чтобы было понятно как это работает на самом деле. А то я наблюдал такую картину:
Человек пишет прогу под МК на Си, тоже с ацп. Я ему сказал про усреднение. Он берет усреднение по 100 точкам, а потом выполняет деление (ладно хоть не с плавающей точкой), а потом долго не может понять почему у него программа не влазит в ПЗУ. Ну хуле, что ты хотел дорогой - деление.
А на асме когда пишешь сразу понятно откуда ноги торчат, как работают прерывания, как и куда девать переменные. И многие проблемы отпадают сами собой.
А если не брать готовые процедуры (которые чуть более чем полностью являются избыточными, сделанными на все случаи жизни, в том числе гипотетические), то все программирование на том же си сводится к пинанию байтов по служебным регистрам.
Мне, например, на ассемблере это делать в разы проще чем на сях. Да и ответственные проекты тоже предпочитаю писать на асме. чтобы проц шел четко по моим командам и я не получил потом задницу в самом неподходящем месте изза какого нибудь срыва стека.
А еще я жадный, если программу можно уменьшить на пару байт я это постараюсь сделать. Зато у меня никогда не возникает проблем с нехваткой места на кристалле.
Сэр DI HLT, это как понимать? В смысле, ваш крайний ответ. Вы что, ОТМАЗЫВАЕТЕСЬ ???? Это всё равно, если бы вы в совершенстве знали итальянский, по выходным бы летали, чтобы в Ла-Скала послушать Верди в оригинале, и вас бы упрекали в незнании сурджика такжикских гастарбайтеров!!! “Насяйника, три клёк смент ната, тааа.” Как вам не стыдно! Наоборот, гнать надо лентяев, которые не удосужились изучить принципы работы ввереной им мат.части.
Пыщь!,пыщь отсюда со своим птичьим языком. Чем это они смогут удивить мир - волшебным умением программировать, или знанием алгоритмов? Нет, они способны удивить мир лишь хорошо затверженными баснями про кросс-платформенную переносимость. Переносимость? Такого зверя нет в природе, а если и есть, то он подобен обезьяне-ленивцу, что день-деньской висит на ветке головой вниз (c)Р. Бредбери.
А вы им ешё потакаете….
Каюсь каюсь. Смалодушничал. В следующий раз буду отвечать коротко - Потому что на ассемблере свои примеры писал Дональд Кнут!
Ну вообще “наезд на наш подъезд”! Ассемблер это конечно хорошо, но более-менее серьезный проект на нем писать слишком тяжело. Там уже не до экономии памяти - пойди через год вспомни о чем тут речь шла и где нужно изменить чтобы “вот тут буковка изменилась и температура в цельсиях стала показываться, а не в кельвинах”. Так что основы работы контроллера лучше объяснять на asm, а вот принципы построения программ - на Си. Ибо тут важно _усреднение_, а не _как_его_написать_на_асме_.
А где ты тут видишь сложный проект? ;))))) Я щас, кстати, мозги робота развожу (точнее наверное это все равно будет мозжечок, сам мозг наверное будет все же на пропеллере или арме) на АТМЕГА128 вот там буду писать на сях.
А тут я сделал так, чтобы показать, как усреднение делается, да еще простым сдвигом. Практически нахаляву.
Господа, простите, что я встреваю, но не могу молчать
Уважаемый DI Halt, что такое “частота шума”? До сих пор думал, что частота бывает только у периодических сигналов… Если вы имели в виду верхнюю частоту спектра шума, то это плохо. Будет наложение (Котельников и все такое) и весь оверсэмплинг коту под хвост.
Оверсэмплинг действительно хорошо помогает увеличить точность, но при правильном применении и не совсем программно, например в сигма-дельта ацп/цап.
Вообще говоря, железо софт не заменит. В присутствии шума, если ширина его спектра больше или равна частоте дискретизации, весь спектр цифрового сигнала исказится, даже постоянная составляющая.
Да, я там неверно выразился, имя под шумом всякие левые наводки, которые обычно присутствуют, например сетевые 50Гц.
Железо софт не заменит, я это не отрицаю. Но зачастую, для простых случаев, элементарный софт, вроде усредненки, вполне себе спасает ситуацию.
хм, вообще-то существуют аналоговые защелки. Может они здесь будут полезны?
Нет, они сохраняют значение, существовавшее в момент выборки, а в условиях помех оно может оказаться каким угодно. Больше подходит интегратор, он сглаживает случайные броски.
Кроме того, в микроконтроллерах уже есть устройство выборки/хранения, - в начале цикла измерения заряжается конденсатор в АЦП от входа, затем отключается, и начинается его разряд калиброванными порциями.
Если команду ror при сдвиге старшего байта заменить на lsr, то можно выкинуть очистку флага переноса.
8 раз:
CLC
ROR SUMH
ROR SUML
—–
OUT UDR,SUML
не равносильно ли просто
OUT UDR,SUMH ?
и еще
CPI R16,’s’
BREQ StopADC
CPI R16,’r’
BREQ RunADC
RunADC: …….
Последний BREQ не делает ничего =)
Насчет первой части - похоже что результат тот же, но DI видимо просто хотел более наглядно показать, что именно делается и зачем (сдвиг сначала на 6 потом еще на 2).
По поводу последнего BREQ - да, формально он излишен. Однако я, например, так пишу потому, что так легче работать с процедуами в дальнейшем. Если понадобиться добавить потом перед RunADC:… какой-нить код, то вполне можно порушить логику. Лучше сразу писать с явным условным переходом там, где надо.
А для чего в каждом прерывании в самом начале стоят такие команды?
PUSH R16
PUSH R17
IN R16,SREG
PUSH R16
Матчасть
Спасибо,ссылочку открыл все прочитал. Все понял. Все описано ясно и толково с хорошими примерами. DIHALT ты просто гениус.
Ошибочку заметил: в 54 строчке кода написано “ROR SUMH ; Теперь уменя готовое 10 байтное усредненное число”. Не 10-байтное, а, все-таки 10-битное.