В этой статье приводится пример простейшей программы управления микроконтроллером для цифрового генератора опорного напряжения (генератор у нас, если помните, сделан на ATtiny13).
Что наш генератор умеет с такой программой? С такой программой он умеет устанавливать любое выходное напряжение в пределах от 0 до 5 Вольт с шагом 19,6 мВ, умеет изменять выходное напряжение при нажатии на кнопки (одна кнопка — для увеличения выходного напряжения, другая — для уменьшения), а также ограничивать минимальное и максимальное значение выходного напряжения (то есть диапазон 0-5В можно сузить, скажем, до величины 0.98-4.9В).
В контроллере при нажатии на кнопки изменяется не сразу сам выход, а сначала изменяется внутренняя переменная (setup — уставка), к которой затем постепенно подтягивается выход. Сделано это для того, чтобы можно было менять выход с заданной плавностью, независимо от скорости изменения уставки, поскольку уставку можно и вообще мгновенно задавать, скажем по интерфейсу или с пульта.
Из интересного отмечу также алгоритм обработки нажатий кнопок. В данном случае вам не нужно жать кнопку столько же раз, на сколько шагов нужно изменить выход. Всё то время, пока вы держите кнопку нажатой — изменяется уставка (и вместе с ней, естественно, — выходное напряжение). Причём изменение уставки реализовано с двумя скоростями — сразу после нажатия на кнопку уставка изменяется медленно, а при удержании кнопки дольше определённого времени — она начинает изменяться гораздо быстрее. (Пришлось сделать такую фишку, поскольку шагов много и тыкать 256 раз на кнопку или держать её 3 часа нажатой — довольно утомительно).
Больше в общем-то никаких наворотов, типа управления от ИК-пульта или передачи данных по интерфейсу, в проге нет, поскольку это всего лишь простейший тестовый вариант, поэтому перейдём сразу к алгоритму и программе.
Для того, чтобы определить что нужно и чего не нужно делать программе в текущем цикле — нам понадобятся три флага, под которые мы выделим специальный регистр (flags). Установленный нулевой бит этого регистра будет говорить о том, что мы находимся в режиме изменения задания, установленный первый бит — о том, что выходное напряжение не равно уставке и его нужно корректировать, установленный второй бит — о том, что мы держим кнопку уже достаточно долго и уставку нужно менять с повышенной скоростью.
Алгоритм:
.device ATtiny13 .include "tn13def.inc" .list ;-- определяем свои переменные .def w=r16 ; это будет наш аккумулятор .def setup=r17 ; это задание .def current=r18 ; здесь текущее значение ширины импульсов .def maximum=r19 ; максимальное значение .def minimum=r20 ; минимальное значение .def flags=r21 ; флаги ;(0-идёт изменение задания, 1-выход не равен уставке, 2 - slow/fast) .def setup_counter=r22 ; счётчики для паузы между опросом кнопок .def setup_counter2=r23 .def setup_counter3=r24 .def output_counter=r25 ; счётчики для паузы между изменениями выхода .def output_counter2=r26 .def counter_sch=r27 ; cчётчик для определения удержания кнопок ;-------------------------- .equ slow_setup=5 ; задержка на изменение уставки медленная .equ fast_setup=1 ; задержка на изменение уставки быстрая .equ output_time=1 ; задержка на изменение выхода .equ s_maximum=238 ; начальное ограничение максимального выхода .equ s_minimum=24 ; начальное ограничение минимального выхода .equ s_setup=128 ; начальное значение уставки ;--- Используемые выходы ; PB0 - выход ШИМ, PB1 - вход КН2, PB2- вход КН1, PB3 - выход, PB4 - вход ;--- начало программного кода .cseg .org 0 rjmp Init ; переход на начало программы ;-- вектора прерываний reti ; INT0 rjmp Pch ; Pin Change reti ; Timer reti ; EEPROM reti ; comparator reti ; timer compare match A reti ; timer compare match B reti ; watchdog reti ; ADC ;-- начало программы Init: ldi w,RAMEND ; устанавливаем указатель вершины out SPL,w ; стека на старший байт RAM sbi ACSR,7 ; выключаем компаратор ldi w,0b00001001 ; определяем входы и выходы порта (1-выход, 0-вход) out DDRB,w ldi w,0b11100110 ; включаем подтягивающие резисторы out PORTB,w ; и определяем начальное состояние выходов ;-- записываем начальные значения ldi maximum,s_maximum ldi minimum,s_minimum ldi setup,s_setup ; ставим начальное задание ldi output_counter,255 ldi output_counter2,output_time ;-- включаем ШИМ на выходе PB0 (OC0A) ldi w,128 ; записываем начальную ширину импульсов out OCR0A,w ldi w,0b10000011 ; fastPWM, set at TOP, clear at compare out TCCR0A,w ldi w,0b00000001 ; fastPWM, no_prescale out TCCR0B,w ;-- разрешить прерывание от входов ldi w,0b00100000 out GIMSK,w ; разрешаем прерывание от входов ;-- разрешить прерывания на входах PB1, PB2 Start: ldi w,0b00100000 out GIFR,w ; сбросить флаг прерываний от входов ldi w,0b00000110 out PCMSK,w ; разрешаем прерывания на входах PB2, PB1 sei ; разрешить глобальные прерывания ;---------------------------------------------------------------------- Scan: sbrc flags,0 ; если флаг сброшен - пропускаем команду rcall Setup_update sbrc flags,1 ; если флаг сброшен - выход не нужно изменять rcall Change_output rjmp Scan ;--- Меняем выход --------------- Change_output: dec output_counter brne End_cho ; если не отсчитали нужный интервал - прыгаем ldi output_counter,255 dec output_counter2 brne End_cho ; если не отсчитали нужный интервал - прыгаем ;--- Отсчитали время, через которое можно менять выход in current,OCR0A ; читаем текущее установленное значение cp current,setup ; current - setup breq Equal ; текущее значение равно уставке brcc SetupIsLower; если переноса не было - setup меньше SetupIsHigher: inc current ; увеличиваем out OCR0A,current ; устанавливаем новое значение rjmp Exit SetupIsLower: dec current out OCR0A,current ; пишем новое значение rjmp Exit Equal: cbr flags,0b00000010 ; выход равен уставке, сбрасываем флаг rjmp End_cho ; и выходим Exit: ldi output_counter,255 ldi output_counter2,output_time End_cho: ret ;--- Меняем уставку ----------- Setup_update: dec setup_counter brne End_chs ; если счётчик не отсчитал интервал - прыгаем ldi setup_counter,255 dec setup_counter2 brne End_chs ; если счётчик не отсчитал интервал - прыгаем ldi setup_counter2,255 dec setup_counter3 brne End_chs ; если счётчик не отсчитал интервал - прыгаем Setup_change: sbis PINB,1 ; если первая кнопка не нажата - пропустить rjmp Dec_setup sbis PINB,2 ; если вторая кнопка не нажата - пропустить rjmp Inc_setup cbr flags,0b00000001 ; отменяем изменение задания rjmp End_chs Dec_setup: dec setup cp setup,minimum brlo SetMin ; если новое задание меньше минимального - ставим минимум rjmp ExitSetup SetMin: mov setup,minimum rjmp ExitSetup Inc_Setup: inc setup cp maximum,setup brlo SetMax ; если максимум меньше нового задания - ставим максимум rjmp ExitSetup SetMax: mov setup,maximum ExitSetup: sbr flags,0b00000010 ; выход нужно изменить ldi setup_counter, 255 ; готовимся отсчитывать ldi setup_counter2,255 ; следующий интервал sbrc flags,2 ; если флаг поднят - fast режим rjmp Fast_change Slow_change: inc counter_sch ; увеличиваем счётчик изменений задания sbrc counter_sch,2 ; если 2-й бит поднят - перекл. в быстрый режим sbr flags,0b00000100 ; fast change ldi setup_counter3,slow_setup rjmp End_chs Fast_change: ldi setup_counter3,fast_setup End_chs: ret ;-- ОБРАБОТЧИК ПРЕРЫВАНИЯ ПО ВХОДУ Pch: sbr flags,0b00000001 ; ставим флаг изменения задания ldi setup_counter,255 ldi setup_counter2,1 ldi setup_counter3,1 clr counter_sch ; обнуляем счётчик изменений cbr flags,0b00000100 ; slow change reti ; и выходим |
Вот и вся программа. Чтобы всё корректно работало — в контроллере должны быть «запрограммированы» фьюзы SPIEN, SUT0 и CKSEL0 (то есть в PonyProg напротив них должны стоять галочки).