Работа с АЦП. Программные средства повышения точностиPrint This Post

Автор DI HALT
Опубликовано 29 Янв 2009 
Рубрики: 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 комментариев на «Работа с АЦП. Программные средства повышения точности»


  1. SWG 29 Янв 2009 1:40

    Для медленно изменяющихся сигналов я шунтирую аналоговый вход керамическим конденсатором порядка 0,1 - 1,0 мкф. Поскольку измеряемое напряжение всегда подаю через ограничительный резистор или с делителя, шумы давятся достаточно хорошо. Кроме того, это уменьшает погрешность, возникающую при сопротивлении измерительной цепи более нескольких килоом (в момент измерения импеданс входа АЦП микроконтроллеров имеет емкостной характер).

    Alb

    Зато усреднение по множеству точек при очень хорошо сделанной электронике позволяет “как бы увеличить разрядность АЦП”. Например, если значение на входе между значениями A и A+1, то вероятности выпадения этих значений будут разные, и если сложить например N*4 значений а поделить на N, то младшие биты будут показывать значение входного сигнала более точно.
    А еще усреднение можно делать “скользящим окном” - имеется буфер A (скажем на 64 числа), в него по кольцу записываются новые значения АЦП, имеется сумма буфера S и имеется указатель в буфере i. Изначально буфер обнулен, сумма равна нулю. Каждое значение АЦП обрабатывается следующим образом:
    - из суммы S вычитаем элемент A[i]
    - считываем новое значение АЦП в adc
    - прибавляем его к сумме S
    - записываем его в A[i]
    - Прибавляем i и если оно превысило 63 - обнуляем i
    За значение, полученное с АЦП, принимаем S/64.
    Такой алгоритм во многом эквивалентен конденсатору - на каждое измерение АЦП мы получаем результат, но он усреднен с предыдущими. При этом не нужно каждый раз рассчитывать сумму и тратить на это время. Преимущества - можно усложнить алгоритм - если разница между полученным значением АЦП и полученным средним значением больше некоторого порога, можно заполнить весь массив новым значением, установить сумму как это значение*64 - и мы получим быструю реакцию на резкие изменения при стабильном показании, если на входе сигнал почти не меняется.


  2. _riko_ 29 Янв 2009 10:00

    А в чем отличие команды MOV от LDI если по сути они оинаково пихают данные в регистр? Или это просто команда премещения байта из регистра в регистр (MOV R15,R14) ?

    SWG

    MOV копирует содержимое одного регистра в другой, LDI загружает в регистр константу (непосредственно заданное число).
    Вообще, в AVR огромное количество команд отличается лишь типом операнда или методом адресации. Зато их много! (Атмел хвастается их количеством).
    А например, у PIC контроллеров команды более универсальны, и их всего 31-35. (И Микрочип считает, что это хорошо, т.к. их легче запомнить). В общем, каждому - свое. А мне так пофигу, я использую и те, и другие. Процессорам тем более пофигу, они работают только с теми 0 и 1, которые в них зашьет программатор.


  3. ArgusB 29 Янв 2009 12:05

    А вообще, да. Шум в сигнале давится старым добрым RC фильтром, а данный метод хорош только усреднять шумы самого ацп. Вот я помню, пытался кое-что сделать на DS2450, так там младшие три бита - настоящий генератор случайных чисел. Плюнул, и придушил точность, а что ещё делать?


  4. cahbtexhuk 29 Янв 2009 14:08

    Немножечко оффтопа.
    Хм, а почему все таки ассемблер? Просто есть нормальные Компиляторы на си, паскале, бэйсике, разных самописных экзотических языках. И платные, и бесплатные. И вполне компактный код дают потом. И уже хренова туча всяких макросов и стандартных процедур сведены к простм функциям.

    DI HALT

    Чтобы было понятно как это работает на самом деле. А то я наблюдал такую картину:

    Человек пишет прогу под МК на Си, тоже с ацп. Я ему сказал про усреднение. Он берет усреднение по 100 точкам, а потом выполняет деление (ладно хоть не с плавающей точкой), а потом долго не может понять почему у него программа не влазит в ПЗУ. Ну хуле, что ты хотел дорогой - деление.

    А на асме когда пишешь сразу понятно откуда ноги торчат, как работают прерывания, как и куда девать переменные. И многие проблемы отпадают сами собой.

    А если не брать готовые процедуры (которые чуть более чем полностью являются избыточными, сделанными на все случаи жизни, в том числе гипотетические), то все программирование на том же си сводится к пинанию байтов по служебным регистрам.

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

    DI HALT

    А еще я жадный, если программу можно уменьшить на пару байт я это постараюсь сделать. Зато у меня никогда не возникает проблем с нехваткой места на кристалле.

    ArgusB

    Сэр DI HLT, это как понимать? В смысле, ваш крайний ответ. Вы что, ОТМАЗЫВАЕТЕСЬ ???? Это всё равно, если бы вы в совершенстве знали итальянский, по выходным бы летали, чтобы в Ла-Скала послушать Верди в оригинале, и вас бы упрекали в незнании сурджика такжикских гастарбайтеров!!! “Насяйника, три клёк смент ната, тааа.” Как вам не стыдно! Наоборот, гнать надо лентяев, которые не удосужились изучить принципы работы ввереной им мат.части.
    Пыщь!,пыщь отсюда со своим птичьим языком. Чем это они смогут удивить мир - волшебным умением программировать, или знанием алгоритмов? Нет, они способны удивить мир лишь хорошо затверженными баснями про кросс-платформенную переносимость. Переносимость? Такого зверя нет в природе, а если и есть, то он подобен обезьяне-ленивцу, что день-деньской висит на ветке головой вниз (c)Р. Бредбери.
    А вы им ешё потакаете….

    DI HALT

    Каюсь каюсь. Смалодушничал. В следующий раз буду отвечать коротко - Потому что на ассемблере свои примеры писал Дональд Кнут!


  5. Alb 30 Янв 2009 1:15

    Ну вообще “наезд на наш подъезд”! Ассемблер это конечно хорошо, но более-менее серьезный проект на нем писать слишком тяжело. Там уже не до экономии памяти - пойди через год вспомни о чем тут речь шла и где нужно изменить чтобы “вот тут буковка изменилась и температура в цельсиях стала показываться, а не в кельвинах”. Так что основы работы контроллера лучше объяснять на asm, а вот принципы построения программ - на Си. Ибо тут важно _усреднение_, а не _как_его_написать_на_асме_.

    DI HALT

    А где ты тут видишь сложный проект? ;))))) Я щас, кстати, мозги робота развожу (точнее наверное это все равно будет мозжечок, сам мозг наверное будет все же на пропеллере или арме) на АТМЕГА128 вот там буду писать на сях.

    А тут я сделал так, чтобы показать, как усреднение делается, да еще простым сдвигом. Практически нахаляву.


  6. mtr 30 Янв 2009 19:34

    Господа, простите, что я встреваю, но не могу молчать
    Уважаемый DI Halt, что такое “частота шума”? До сих пор думал, что частота бывает только у периодических сигналов… Если вы имели в виду верхнюю частоту спектра шума, то это плохо. Будет наложение (Котельников и все такое) и весь оверсэмплинг коту под хвост.
    Оверсэмплинг действительно хорошо помогает увеличить точность, но при правильном применении и не совсем программно, например в сигма-дельта ацп/цап.
    Вообще говоря, железо софт не заменит. В присутствии шума, если ширина его спектра больше или равна частоте дискретизации, весь спектр цифрового сигнала исказится, даже постоянная составляющая.

    DI HALT

    Да, я там неверно выразился, имя под шумом всякие левые наводки, которые обычно присутствуют, например сетевые 50Гц.

    Железо софт не заменит, я это не отрицаю. Но зачастую, для простых случаев, элементарный софт, вроде усредненки, вполне себе спасает ситуацию.


  7. nictrace.livejournal.com 31 Янв 2009 2:43

    хм, вообще-то существуют аналоговые защелки. Может они здесь будут полезны?

    SWG

    Нет, они сохраняют значение, существовавшее в момент выборки, а в условиях помех оно может оказаться каким угодно. Больше подходит интегратор, он сглаживает случайные броски.
    Кроме того, в микроконтроллерах уже есть устройство выборки/хранения, - в начале цикла измерения заряжается конденсатор в АЦП от входа, затем отключается, и начинается его разряд калиброванными порциями.


  8. KeFiR 19 Ноя 2009 3:32

    Если команду ror при сдвиге старшего байта заменить на lsr, то можно выкинуть очистку флага переноса.


  9. BuHTuk 01 Дек 2009 17:16

    8 раз:
    CLC
    ROR SUMH
    ROR SUML
    —–
    OUT UDR,SUML

    не равносильно ли просто

    OUT UDR,SUMH ?

    и еще
    CPI R16,’s’
    BREQ StopADC
    CPI R16,’r’
    BREQ RunADC

    RunADC: …….

    Последний BREQ не делает ничего =)

    ZeroQu

    Насчет первой части - похоже что результат тот же, но DI видимо просто хотел более наглядно показать, что именно делается и зачем (сдвиг сначала на 6 потом еще на 2).
    По поводу последнего BREQ - да, формально он излишен. Однако я, например, так пишу потому, что так легче работать с процедуами в дальнейшем. Если понадобиться добавить потом перед RunADC:… какой-нить код, то вполне можно порушить логику. Лучше сразу писать с явным условным переходом там, где надо.


  10. dima_m 12 Апр 2010 16:58

    А для чего в каждом прерывании в самом начале стоят такие команды?
    PUSH R16
    PUSH R17
    IN R16,SREG
    PUSH R16

    DI HALT

    Матчасть

    dima_m

    Спасибо,ссылочку открыл все прочитал. Все понял. Все описано ясно и толково с хорошими примерами. DIHALT ты просто гениус.


  11. shura.luberetsky.ru 19 Апр 2010 17:47

    Ошибочку заметил: в 54 строчке кода написано “ROR SUMH ; Теперь уменя готовое 10 байтное усредненное число”. Не 10-байтное, а, все-таки 10-битное.

Оставьте свой отзыв

Вы должны войти, чтобы оставлять комментарии.


Материалы сайта являются авторскими. Копирование и публикация материалов без активной ссылки на первоисточник запрещено.

Реклама: