Вольтметер на Attiny2313 или попытка вольтметра

Начав копаться с микроэлектроникой я начал с симуляторов. Но как правило это быстро надоедает и хочется попробовать тоже самое но в реальности, то есть чтобы устройство можно было пощупать, потрогать, посмотреть как оно на самом деле. В нашем городе в своё время смог нарыть только МК AtTiny2313 за 40 рублей (в других магазинах у нас же он стоит и 100 и 120 рублей), AtMega’у мне найти не удалось. Попробовав всякие «безделушки» на нём, вроде управления светодиодиками захотелось чего то большего, и вот лазяя по инету наткнулся на статью про вольтметр на сайте дихалта (изиелектроникс). Но! В AtTiny2313 нет АЦП… Но опять же «Но!» =) на всё том же сайте есть статья как сделать из аналогового компаратора и ШИМа этот самый АЦП. Сначала я подумал, а может легче купить атмегу? Но чета влом было искать их у нас, а по инету штучно не продает, или по крайней мере я об этом не знаю. И так, в итоге вольтметр получился, только вот числовые значения надо будем подгонять под пороговые. Так как у этого МК нет отдельного входа для определения границы измеряемого напряжения, оно равно напряжения питания МК. При создании вольтметра использовалось: AtTiny2313, экранчик от DVD-плеера и несколько самых обычных радиодеталей.

Схема самого устройства в Proteus:

Тут добавлен осциллограф чтобы отслеживать ШИМ. Вся проблема была более менее получить нормальный сглаженный выход с ШИМ, именно поэтому в схеме используется вторая RC-цепочка для сглаживания сигнала. Ниже будет показан выход с ШИМ и его сглаживание двумя RC-цепочками.

Структура файлов:

- voltmeter.asm
- - - init.asm
- - - interrupt.asm
- - - subprograms.asm
- - - macros.asm

Как видно из структуры файлов всё начинается с «voltmeter.asm», там происходит определение констант, именование регистров и код основной проги. По именам остальных файлов не сложно догадаться для чего они и всё же: инит — инициализация юарт, шим, таймера, аналогового компаратора и настройка портов; интеррапт — обработчики прерываний; макрос — тут ясно, мои макросы; субпрограмм — аналогично с макросами, мои подпрограммы.
Разберем основной файл. Там заложены основные идеи и всё самое интересное происходит там, не считая прерываний. Смысл такой, сначала объявляем нужные нам константы и переменные:

;/////////////////
;/// Константы ///
;/////////////////

;Настройки RS-232
.equ XTAL = 4000000
.equ BAUDRATE = 9600  
.equ bauddivider = XTAL/(16*BAUDRATE)-1

;Настройки измерений
.equ zaderzhka_pwm = 0 ;задержка изменения выхода ШИМ
.equ step_dec_pwm = 5 ;шаг изменения выхода ШИМ

.equ volt_correct = 20 ;корректировка погрешности аналогового компаратора

;Частота обновления значения на экране
.equ timeBinBcd_H = 0x00+0x01
.equ timeBinBcd_L = 0x0F


;//////////////////
;/// Переменные ///
;//////////////////

;Временные 
.def t1 = R16;
.def t2 = R17;

;ШИМ
.def lev_pwm = R18 ;уровень
.def counter_zaderzhka = R19 ;длительность уровня(задержка изменения уровня) 

;Значения напряжения на входе аналогово компаратора
.def LED_bin_value = R20 ;в данный момент не используется так как 
			 ;потребовалось  увеличить разрядность

.def LED_bin_value_H = R25 ;двоичный формат
.def LED_bin_value_L = R20

.def LED7segment_1 = R21 ;десятичный формат
.def LED7segment_2 = R22

;Частота обновления напряжения на сегментах экрана
.def TimerBinBcd_H = R23 
.def TimerBinBcd_L = R24


Затем как обычно начиная программу указываем нужные нам прерывания, а на остальные ставим заглушки. Далее мы попадаем на метку RESET, что соответствует только что включенному устройству. Самое главное первым делом не забыть инициализировать стек, иначе с устройством будет твориться что-то странное, причем даже в отладчике (я пользуюсь AVR Studio). Затем идет инициализация ШИМа, точнее совсем уж сначала =) я задал уровень и задержку, всё по нулям. Это означает что уровень ШИМа будет расти от нуля, и изменяться, то есть увеличиваться каждый раз когда приходит прерывание. Длительность и шаг ШИМа настраивается в константах, кури вставку кода выше. Там всё подробно написано. Далее инициализируются порты (входы\выходы), работа ШИМа и аналоговый компаратор. С этим всё, если посмотреть в код, можно увидеть закоменченные вызовы инициализации экрана и UART, не обращайте на них внимания. Я думал вообще подрубить его к компу и прогу накидать чтобы смотреть график, но не стал, и так нахимичился пока делал АЦП из ШИМа и UARTа, так что кому надо, можете посидеть над этим. Ну вот и всё с началом работы, далее разрешаются прерывания и записываются начальные значения для таймера по которому обновляется значение на экране.

;////////////////////////////////
;/// Инициализация устройства ///
;////////////////////////////////

RESET:

ldi t1, low(RAMEND); Main program start
out SPL,t1 ;Set Stack Pointer to top of RAM

LDI lev_pwm, 0
LDI counter_zaderzhka, 0

RCALL ports_init
RCALL pwm_init
RCALL analog_comp_init
;RCALL disp_init
;RCALL uart_init

SEI ;Разрешение прерываний

LDI TimerBinBcd_H, timeBinBcd_H
LDI TimerBinBcd_L, timeBinBcd_L


Подобрались к выполнению основного цикла программы, тут я бы выделил 3 этапа: 1) обновление на экране старшего разряда (свечение циферки слева); 2) обновление на экране младшего разряда (свечение циферки справа); 3) изменение значения для высвечивания на экране.
На счет всех этих свечений и обновлений дело обстоит так. ШИМ постоянно меняет своё значение на выходе. Допустим у нас напряжение от 0 до 5 вольт, допустим оно изменяется за 0.3 секунды за 256 итераций(5 вольт делим на 256 получаем +0.019607843 вольта за каждую итерацию). Теперь главное не запутаться. Теперь перейдем к аналоговому компаратору, так как выход с ШИМа через RC-цепочки идет именно на него. Так вот, аналоговый компаратор, каждые 0.3 секунды выдает пару, тройку а может и десяток раз прерывание. В идеальном случае(читай в идеальных условиях) аналоговый компаратор должен выдать одно прерывание за 0.3 секунды, потому что входное нарпяжение должно один раз совпасть с напряжение на ШИМ. На ШИМе оно ведь всегда уменьшает(или всегда увеличивается) и по идее не равно предыдущему значению. Но! У нас не идеальный мир и есть погрешности, вот из-за этих погрешностей аналоговый компаратор несколько раз выдает прерывания из-за того что сигнал с ШИМа скачет. На картинках ниже это видно. Так, как работает аналоговый компаратор тоже разобрались. Еще раз(допустим у нас идеальный мир): ШИМ меняет значение от 0 до 5 вольт за 0.3 секунды в 255 интераций, аналоговый компаратор за эти 0.3 секунды вызывает один раз своё прерывание в том случае если напряжение с ШИМ и измеряемое напряжения равны.
В этом прерывании это значение записывается в память микроконтроллера (об этом ниже). Теперь возвращаясь в основную программу узнаем, что значения на экране НЕ МЕНЯЮТСЯ МГНОВЕННО с изменением напряжения на входе компаратора. А меняются тогда, когда это разрешит «искусственный» таймер. То есть, значение со входа компаратора меняется один раз в 0.3 секунды, как бы получается что и значение на экране должно меняться раз в 0.3 секунды, если напряжение конечно поменялось. НО! У нас, опять таки, не идеальный мир и за эти 0.3 секунды аналоговый компаратор может сказать что напряжение изменилось десяток раз. И что? Обновлять циферки на экране будем каждый раз, как нам об этом скажет аналоговый компаратор? А вот и нифига, представьте какой мерцание будет у вас на экранчике. Вы даже циферки разглядеть не сможете, представьте одновременно горят цифра 6 и 7, в итоге вы увидите 8 (восьмерку) на экране, потому что человеческий глазичек не успеет разглядеть эту разницу и сольет цифры 6 и 7 вместе. Ну может даже глаза тут не причем будут, а сам сегмент экранчика не успеет затушить цифру 6 и зажечь циферку 7. Так что вот так, для этого и сделан «искусственный» таймер, который меняет циферки (значения измеренного напряжения) на экране гораздо реже, чем делает это аналоговый компаратор с данными (опять же, со значениями измеренного напряжения) в памяти микроконтроллера.
Сначала думал привести код по частям с описанием, но получилось что описание я накатал не отрываясь от клавы. Теперь выкладываю код целиком с основным циклом программы, ибо нефиг тут уже чета по отдельности описывать, выше всё уже накатал. =)

;//////////////////////////
;/// Основная программа ///
;//////////////////////////

beg:

	;=======================
	;=== Старший сегмент ===
	;=======================

	;Выключение экрана
	CBI PORTB, 3
	CBI PORTB, 4

	;Обновление цифры старшего сегмента
	;LDI t1, 3
	MOV t1, LED7segment_1
	RCALL DecTo7LedPort

	;Включение старшего сегмента экрана
	CBI PORTB, 3
	SBI PORTB, 4

	;Горит старший сегмент экрана
	loop

	;=======================
	;=== Младший сегмент ===
	;=======================
	
	;Выключение экрана
	CBI PORTB, 3
	CBI PORTB, 4

	;Обновление цифры младшего сегмента
	;LDI t1, 5
	MOV t1, LED7segment_2
	RCALL DecTo7LedPort

	;Включение старшего сегмента экрана
	SBI PORTB, 3
	CBI PORTB, 4

	;Горит младший сегмент экрана
	loop
	
	;============================================
	;=== Получение нового значения для экрана ===
	;============================================

	;Насколько часто обновлять значение
	SUBI TimerBinBcd_L, 1
	BRNE end
	SUBI TimerBinBcd_H, 1
	BRNE not_nul_TimerBinBcd_H
	
;Забрать данные из аналогово компаратора и преобразовать их в двоично-десятичный код
binbcd:
	cli
	LDI TimerBinBcd_H, timeBinBcd_H

	MOV t1, LED_bin_value_L
	MOV t2, LED_bin_value_H
	RCALL bin2bcd2REG
	sei

not_nul_TimerBinBcd_H:
	LDI TimerBinBcd_L, timeBinBcd_L

end:
	;Возврат
	RJMP beg

Что же вышло. Вся отладка происходила в Proteus. После написание кода и постоянное тестирование его в proteus результат появился. Некоторые проблемы были с выводом циферок на индикаторы, сравнения напряжений на компаратора и пришлось подобрать номиналы резисторов и конденсаторов двух сглаживающих RC-цепочек. На первом скриншоте видно то о чем я писал выше, главная проблема которая была это множественное срабатывание аналогового компаратора на одном цикле ШИМа. Помните я приводил пример за 0.3 секунды 256 интераций? Так вот это оно самое, с ШИМа сигнал падает с максимального напряжения до нуля. За это время в идеальном случае аналоговый компаратор должен был сработать только один раз, а тут фигачет сколько ему захочется. Поясню что видим на экране виртуального осциллографа: желтым — выход ШИМ, голубым — первая RC-цепочка, красным — вторая RC-цепочка, зеленым — срабатывание аналогового компаратора. Первые два скриншота различают тем, что в первом я изменял входное напряжение. Это можно заметить по столбикам (срабатывает аналоговый компаратор) при разном напряжении идущим со сглаживающих RC-цепочек.



Собственно ради интереса сделал этих скриншота, ничего особенно нет, только на первом увеличен фрагмент с двумя срабатывании аналогового компаратора, а второй без. =) Второй показывает как сглаживается сигнал при уменьшении импульсов с ШИМа.

В заключение хотел бы немного уйти от темы и посоветовать всем программистам прочитать книгу «Совершенный код. Мастер-класс» С.Макконнелла, так как если бы я прочитал эту книгу до написания кода вольтметра, он был бы куда аккуратнее и понятнее. =) Так что исходники получайте такие какие есть, плюс кидаю видео как это всё работает.

Исходники в zip-архиве:
Проект AvrStudio плюс схема в Proteus

Видео первой попытки «вольтметра» на Attiny2313. Почему в ковычках? Потому что стоит переменный резистор, на который идет напряжение питания и это питание делиться на 5 пороговых точек. Вот и появляются цифры от 1 до 5. Причем к двум сегментам нет проводов, поэтому они не светятся =).

Запись опубликована в рубрике программирование, цифровая электроника. Добавьте в закладки постоянную ссылку.

3 комментария на «Вольтметер на Attiny2313 или попытка вольтметра»

  1. Алекс говорит:

    ну не плохо всё описал)) интересно))
    вот только ума не приложу — как сам с программой разобрался. долго возился?

    • alexei говорит:

      Да сам, сложно там немного, когда до этого разрабатал несколько хотя бы примитивных устройствах и сталкивался с передачей данных по проводам на таких устройствах =)

  2. gjowdry говорит:

    Спасибо за информацию!!!!!

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

*