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

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

Суть усреднения в том, что у нас есть статичный (считаем, что за время измерения сигнал не меняется) сигнал к которому подмешан шум. Шум возникает изза работы транзисторов, из-за колебания опорного напряжения, помех, наведенных на сигнальные линии. Да от чего только он не возникает. Особенность шума в том что он, как правило, хаотичен. Так что во время нашего замера может меняться как в меньшую так и в большую сторону. Тут то мы его и прижучим.

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

В качестве демонстрации метода я приведу пример усреднения. Программка простая, хватает 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 "m16def.inc"	; Используем ATMega16
		.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			; Возвращаемся.

54 thoughts on “Работа с АЦП. Программные средства повышения точности”

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      2. Раз речь пошла за минимизацию кода, не будет ли проще код

        CLC
        ROR SUMH
        ROR SUML

        заменить на

        LSR SUMH
        ROR SUML

        2 команды против 3 все таки=)))

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

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

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

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

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

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

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

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

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

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

    OUT UDR,SUMH ?

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

    RunADC: …….

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

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

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

  9. Наконец-то добрался до чтения учебного курса, есть много вкусного, но ИМХО я бы SEI все таки вынес за пределы бесконечного цикла 8-)

  10. А как вам такое решение
    star_adc_data-старое значение рез-тата АЦП
    adc_data-новое
    urov_pomex-уровень помех в мВ
    dU-разница показании
    if (star_adc_data!=adc_data)//Если рез-тат изменился то выполняем
    {
    if((adc_data>star_adc_data-urov_pomex)&&(adc_data<star_adc_data+urov_pomex))//Проверку
    // Если рез-тат adc_data лежит пределах star_adc_data+urov_pomexstar_adc_data-urov_pomex
    {adc_data=star_adc_data;dU=0;}//То мы записываем в него старое значение
    else
    { if(adc_data>star_adc_data){dU=adc_data-star_adc_data;};
    if(adc_data<star_adc_data) {dU=star_adc_data-adc_data;};
    star_adc_data=adc_data;
    };
    };
    Например star_adc_data=12
    новый рез-тат adc_data изменился за счет помехи на 3 мВ adc_data=15
    urov_pomex=4; 12-4<15<12+4
    поэтому adc_data примет значение star_adc_data и на выв.экране (lcd или usart comp) не так будут прыгать цифры.

      1. urov_pomex я изменял в ходе программы кнопками

        if(PIND.6==0){kn1=1;};
        if((PIND.6==1)&&(kn1==1))
        { delay_ms(20);
        if(PIND.6==1){urov_pomex++;kn1=0;};
        };
        if(PIND.7==0){kn2=1;};
        if((PIND.7==1)&&(kn2==1)&&(urov_pomex>0))
        { delay_ms(20);
        if(PIND.7==1){urov_pomex—;kn2=0;};
        };
        Увеличивая urov_pomex я смотрел на экран (lcd или comp usart) пока цифры не переставали прыгать

  11. А как можно на асме разделить два переменных числа??? я хотел бы разделить на результат преобразования другого канала АЦП….
    Здесь же сдвиг разрядов не поможет!!!

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

  12. Что в студии не могу найти, как создать файл с расширением .inc. Подскажите, пожалуйста! А еще есть ли какая-нибудь разница, в файле с каким расширеним будут храниться макросы, то ли в *.inc, то ли в *.asm ?

    1. Берешь и создаешь его вручную. Какие проблемы?

      Студии без разницы в каком разрешении хранятся макросы.

  13. В примере суммируются 64 значения, так как для двухбайтной суммы большее количество даст переполнение. Подскажите, как суммировать большее количество результатов АЦП, если результат суммы будет храниться уже в четырех байтах. А то у меня с математикой и работой с флагами при этом не очень :)
    Попробывал суммировать не 64 значения, а 32, а потом усреднять — результат стал гораздо лучше, чем при 64 выборках. Это даже при 10-ти битном конечном результате.

  14. В некоторых случаях удобно использовать рекурсивную фильтрацию. Позволяет строить как вч, так и нч фильтры с достаточно высоким быстродействием.
    Имхо, в некоторых случаях даже оптимальнее и проще, чем пакетная обработка.

    Есть группа коэффициентов фильтра (Cx, Cy), есть группа значений аккумулятора (Ay) (в простейшем случае это последний вычисленный отсчет или ноль в состоянии, когда данные еще не получены).
    Когда приходит значение с ацп (Ax), вычисляется новый отсчет по формуле
    Ay’ = Ax * Cx + Ay * Cy
    Меняя коэффициенты, можно отсеивать ненужные помехи в различных частях спектра сигнала.
    Для полосового фильтра, очевидно, самое простое — скаскадировать два таких фильтра с разными параметрами
    Расчет таких фильтров описан много где, сейчас примера книжки под рукой нет, вечером могу скинуть

  15. Добрый день, DI HALT. Старые коменты потёрли похоже… У меня к Вам есть несколько вопросов. Надюсь найдёте время на них ответить. Для начала кратко введу в курс дела. Программа на тини13 измеряет медленно меняющийся аналоговый сигнал и на выдает импульсы с F=200Гц . Длительность зависит от измеренного ацп. Чем ацп меньше , тем импульс короче. В принципе выжал из ацп 10 разрядов за счёт усреднения, но иногда последний разряд слегка гуляет… В моей системе этот дребезг нежелателен. Поэтому написал подпрограмму микрогистерезис… Название само за себя говорит. Код добавлю… http://easyelectronics.ru/repository.php?act=view&id=79 Я вообще не программер.. Поэтому хотел узнать, как подобные вещи делают профи? И как увеличить окно гистерезиса не раздувая сильно при этом код?
    Да и вообще правильно ли я сделал? …. :) Вроде всё работает как надо, но мало ли…

    1. Есть такая штука — скользящее среднее. Т.е. мы набираем массив из замеров. На выходе его усреднение. Каждый новый замер добавляется в массив, а самый старый выталкивается. В результате мы

      1) Имеем усреднение хоть по тысяче замеров.
      2) не теряем в скорости выдачи результатов — они равны скорости замеров. Разве что вначале будет некоторый лаг, пока не накопится первый блок замеров.

      Для счетика буфера оптимальней будет выделить переменную и ограничивать ее либо маской, либо вообще не ограничивать (тогда буфер будет 256байт) и тупо инкрементировать, прибавляя это значение к указателю указывающему на начало, Тогда не надо будет следить за кольцом и выходом его за границы — разрядность переменной не даст переполниться и уйти не туда. А после вноса — весь буфер в цикле складываем, делим — выдаем на выход. Получается очень быстро и очень компактно.

      1. Я в точности так и делаю, как вы описали выше… Это понятно… Просто иногда младший разряд подёргивается, как ты его не усредняй…. Даже если сигнал на входе практически не меняется… Поэтому, как дополнительную меру от дрючинга придумал гистерезис… Он проглатывает дрючинг в один отсчёт ацп, но при этом младший разряд остаётся значащим… Блин , мож я чёт не так объясняю….? Для меня очень важна стабильность импульса на выходе либо плавное его изменение, но не дёрганье… Если просто занулю младший бит, не хватит отсчётов для задачи… Пэтому гистерезис.
        Думал это стандартная задача….. :(

        1. Это не гистерезис. Это частотный фильтр уже получается — сглаживаем сигнал, не давая ему колебаться быстрей чем положено :) А крайний бит и будет дергаться, если значения находятся в пороговом диапазоне. Это не помеха и не шум. Это нормально. Если усредненный сигнал идет как раз на границе младшего разряда.

          1. Да ,так и есть… В динамике система ведёт себя отлично, но когда на входе сигнал почти постоянный, то начинается этот дребезг… Понимаю , что так и должно быть, но он влияет на мою систему… Поэтому решил избавиться от него с помощью гистерезиса… в один отсчёт… Наверно такой изврат никто не делает?….. А мне вот , блин, надо…. :(

  16. Да, и ещё… .Суть программы примерно такова — если текущее значение ацп меньше предыдущего , то пинаем в основную программу истинное значение ацп, а если текущее больше прошлого, то отнимаем 1. …при некоторых условиях….

  17. В ближайщее время, похоже, мне давольно крепко придётся изучать программирование и АВР в частности…. Хочу ПинБорду брать…. Ток не могу определиться какую… Те проэкты , которые под первую создавались они на второй пойдут? Хочется весь курс прогнать… :)

  18. Понял. Спасибо. А по гистерезису, что-нибудь скажешь? Может код хитровыдрюченый…? Я как на информатике в школе учили нарисовал усё, составил блок-схему, а потом тупо на асм перевёл. Мож по этому по коду не очень понятно как он работает? Как вообще подобное правильно делать?

    1. Код смотреть некогда. Понятие гистерезиса тут не особо применимо. Тут же не пороговое переключение. Скорей лучше выкидывать выскочек из расчета. Т.е. у тебя есть что то среднее. Если появляется резкая выскочка которая отличается от среднего (и может его поколебать) на некую пороговую величину, то мы ее игнорируем, не включая в пул усреднения. Считая за шум. Главное тут не прозевать резкое изменение сигнала, Когда оно реально ушло. Так что таких выскочек можно отсеивать, скажем, не более трех раз подряд. Если больше — уже система, включаем в пул.

  19. Усреднение по 64 замерам с использованием сдвига вправо на 6 бит довольно интересное решение, т.к. сильно экономит «трудозатраты» по делению, в сравнении с делением с применением отдельной процедуры. Но возникает одна неприятная особенность — при таком сдвиге мы напрочь теряем знаки после запятой, т.е. целочисленное деление.
    Например в 63 замерах с ADC мы получим 100mV, а в одном замере 98mV.
    Имеем среднее значение (100*63+98)/64 = 99,97mV, и при сдвиге вправо на 6 бит числа (100*63+98) = 6398 = bx1100011111110 >>6 = bx1100011 = 99mV, что довольно прилично отличается от 99,97mV. Возможно такая точность для большинства случаев и не нужна, но можно попробовать забрать немного байтов памяти и тактов у процессора и повысить результат усреднения.

    Готовых решений не нашел и решил проделать следующее (возможно идея неверна, на бумаге долго ее не проверял):
    После каждого сдвига вправо на 1 бит (кроме предпоследнего) проверяем результат, если результат четный, то идем на следующий сдвиг, если нечетный, то добавляем единицу, и только потом делаем следующий сдвиг.

    Выглядит это так:
    // (первый сдвиг)
    LSR SUMH ; сдвигаем сумму вправо
    ROR SUML ; на 1 бит
    SBRS SUML,0 ; если результирующее число нечетное, т.е. младший бит равен 1, то
    RJMP NxtShift ; пропускаем следующую команду, иначе прыгаем на NxtShift
    LDI R16,1 ; если мы тут, то число после сдвига оказалось нечетным,
    ADD SUML,R16 ; значит прибавляем к нему 1, превращая его в четное число
    CLR R16
    ADC SUMH,R16

    // (второй сдвиг)
    NxtShift:
    LSR SUMH ; сдвигаем сумму вправо
    ROR SUML ; на 1 бит
    SBRS SUML,0 ; проделываем все тоже самое, как и после первого сдвига
    RJMP NxtShift2
    ........

    Можно проследить деление числа (100*63+98) = 6398 = bx1100011111110 на 64:
    bx1100011111110>>1 = bx110001111111 = 3199
    bx110001111111 + 1 = bx110010000000 = 3200
    bx110010000000>>1 = bx11001000000 = 1600
    bx11001000000>>1 = bx1100100000 = 800
    bx1100100000>>1 = bx110010000 = 400
    bx110010000>>1 = bx11001000 = 200
    bx11001000>>1 = bx1100100 = 100mV — вот, в итоге мы получили 100mV, вместо 99mV, при этом реальное среднее равно (100*63+98)/64 = 99,97mV.

    Можно взять пограничное значение, допустим 17 отсчетов по 100mV и 47 отсчетов по 98mV, при этом среднее равно (100*17+98*47)/64 = 98,53mV.
    При сдвиге вправо на 6 бит имеем :
    (100*14+98*50) = 6306 = bx1100010100010>>6 = bx1100010 = 98mV
    Теперь сдвигаем по алгоритму:
    bx1100010100010>>1 = bx110001010001 = 3153
    bx110001010001 +1 = bx110001010010 = 3154
    bx110001010010>>1 = bx11000101001 = 1577
    bx11000101001 +1 = bx11000101010 = 1578
    bx11000101010>>1 = bx1100010101 = 789
    bx1100010101 + 1 = bx1100010110 = 790
    bx1100010110>>1 = bx110001011 = 395
    bx110001011 +1 = bx110001100 = 396
    bx110001100>>1 = bx11000110 = 198
    bx11000110>>1 = bx1100011 = 99mV — вот, получили 99mV, что ближе к реальному среднему 98,53mV, чем 98mV, которые получаются при простом сдвиге вправо на 6 бит.

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

      Вышла «очепятка»: прибавлять единицу надо до последнего сдвига, а после последнего сдвига не прибавлять, иначе всегда будем получать четный, завышенный результат.

      1. «Например в 63 замерах с ADC мы получим 100mV, а в одном замере 98mV.
        Имеем среднее значение (100*63+98)/64 = 99,97mV, и при сдвиге вправо на 6 бит числа (100*63+98) = 6398 = bx1100011111110 >>6 = bx1100011 = 99mV, что довольно прилично отличается от 99,97mV. »

        Вы неправильно считаете, мы не милливольты усредняем, а итог с АЦП, который имеет своё однозначное квантование.
        У нас на 5 вольт 10 разрядов, следовательно :
        adc:1024 = 5000 mV
        adc: 20 = 97,65.. mV
        adc: 21 = 102,53.. mV
        теперь допустим, что 63 значения будет приходить 20 (97,65 mV) и одно придет 21 (102,53 mV), то получим на выходе:
        1281 = 10100000001 >> 6 = 20 (97,65 mV).
        Что и требовалось, а именно, одно ошибочное значение было нивелировано.

        1. А теперь в 63 случаях вы получили adc: 21, и в одном 20. В итоге получили 1343 >>6 = 20, упс, всего один отсчет adc:21 смазал истинность результата. Неважно что усреднять, математика — она такая математика, ей все равно.

          1. Да, в этом с вами я согласен, тут попадаем уже на грубость деления целого и всё равно теряем.

Добавить комментарий

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha