Программа, рассмотренная в этой статье, разработана специально для самодельного ИК-пульта дистанционного управления на контроллере ATtiny13. Если вы измените аппаратную часть (например, будете использовать другой порядок ног, повесите внешний генератор), то программу также нужно будет переделать.
Прежде чем разбираться с программой, наверное, каждому будет полезно посмотреть описание протокола NEC.
Код как всегда по религиозным мотивам написан на чистом ассемблере и выложен в полном объёме (в конце статьи лежат сорцы и прошивка). Чтобы было проще во всём этом разбираться — добавлю некоторое количество словесного описания (хотя код тоже дан с комментами).
Итак, в данном случае для отсчёта времени импульса и паузы используется встроенный в ATtiny13 таймер TMR0. Кроме этого, в алгоритме используются два специальных управляющих регистра (это обычные регистры общего назначения, которые мы, в данном случае, используем для управления передачей). Один из них — счётчик количества передаваемых импульсов и пауз, причём отсчёт начинается с паузы стартовой последовательности (нулевое значение счётчика). Соответственно, если значение счётчика нечётное — мы должны передать импульс, а если чётное — паузу.
Второй регистр служит для указания позиции передаваемого бита (в нужной позиции у нашего регистра взведён бит) и сдвигается влево каждый раз при передаче паузы. Поскольку данные передаются младшим битом вперёд, то сначала в этом регистре установлен нулевой бит. После того, как регистр сдвинется влево 8 раз, указатель адреса передаваемого байта устанавливается на следующий предназначенный для передачи байт, а указатель позиции передаваемого бита снова позиционируется на нулевой бит.
Кстати, если интересно, — можете посмотреть реализацию аналогичного алгоритма для pic12F629. Кроме того, что для управления ИК-диодом используется другая нога, есть только два небольших отличия. Во-первых, раз уж разработчики AVR подарили нам один пользовательский бит в служебном регистре (бит T в регистре SREG), то грех было им не воспользоваться. Он был использован для того, чтобы отделить состояние когда мы передаём данные, от состояния когда мы нифига не делаем и отсчитываем паузу межпакетного интервала. В результате пауза межпакетного интервала реализована несколько иначе, чем в пике. Во-вторых, для отсчёта импульсов разной длины использовался разный коэффициент предделителя таймера TMR0. Можно было конечно так же, как и в случае с пиком, использовать один и тот же коэффициент, но в случае c ATtiny это никак не увеличивает код, зато позволяет точнее настраивать временнЫе интервалы.
На этом закончим вступительную часть и перейдём к алгоритму и программе.
Алгоритм:
Итак, пусть в аппаратной части мы имеем:
входы: PB4 — кнопка SB1, PB2 — кнопка SB2, PB0 — кнопка SB3, PB1 — кнопка SB4
выходы: PB3 — вывод информации по протоколу NEC.
RESET внешне подтянут к питанию; используется внутренний генератор на 9,6 МГц.
.device ATtiny13 .include "tn13def.inc" .list ;-- определяем свои переменные .def w=r16 ; это будет наш аккумулятор .def T_Cr=r17 ; таймер несущей .def C_imp=r18 ; счётчик кол-ва импульсов .def N_bit=r19 ; номер посылаемого бита .def B1=r20 ; первый байт для передачи .def B2=r21 ; второй байт для передачи .def B3=r22 ; третий байт для передачи .def B4=r23 ; четвёртый байт для передачи ;-- определяем константы .equ Cr1=22 ; длительность 1 несущей .equ Cr0=64 ; длительность 0 несущей .equ Th=239 ; 255-длительность высокого уровня (252) .equ Tl1=199 ; 255-длительность низкого уровня 1-го бита (242) .equ Tl0=231 ; 255-длительность низкого уровня 0-го бита (250) .equ Tsl=213 ; 255-длит-ть низкого уровня в стартовой послед-ти .equ Tsh=172 ; 255-длит-ть высокого уровня в стартовой послед-ти .equ MP=10 ; кол-во интервалов для межпакетной паузы ; (MP=10 соответствует интервалу около 240 мс) .equ AL=0b00000100 ; адрес 04h (заданный младший байт адреса) .equ AH=0b11001011 ; адрес CBh (заданный старший байт адреса) .equ CMD_1=0x4A ; код команды 1 .equ CMD_2=0x4B ; код команды 2 .equ CMD_3=0x4C ; код команды 3 .equ CMD_4=0x4D ; код команды 4 ;-- Используемые регистры ; GTCCR - управление таймером (обнуление предделителя) ; TCNT0 - регистр таймера ; TCCR0B - управление таймером (биты 0-2: 000 - стоп, 100 - включен с ; делителем на 256, 101 - включен с делителем на 1024) ; TIMSK0 - прерывания от таймера (бит 1=1 - прерывание при переполнении) ; SREG - глобальное разреш.прерываний (бит 7=1 - прерывания разрешены) ;-- начало программного кода .cseg .org 0 rjmp Init ; переход на начало программы ;-- вектора прерываний reti ; INT0 reti ; Pin Change rjmp Timer ; 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,0b00001000 ; определяем входы и выходы порта out DDRB,w ldi w,0b11110111 ; включаем подтягивающие резисторы out PORTB,w ; и определяем начальное состояние выходов ;-- Формируем младший и старший байт адреса ldi B1,AL ; в первый байт пишем младший байт адреса ldi B2,AH ; во второй байт - старший байт адреса ;-- Сканирование клавиатуры Scan: sbis PINB,4 ; если на входе PB4 низкий уровень - нажата КН1 rjmp Tx_CMD1 sbis PINB,2 ; если на входе PB2 низкий уровень - нажата КН2 rjmp Tx_CMD2 sbis PINB,0 ; если на входе PB0 низкий уровень - нажата КН3 rjmp Tx_CMD3 sbis PINB,1 ; если на входе PB1 низкий уровень - нажата КН4 rjmp Tx_CMD4 rjmp Scan ;-- формирование младшего байта пакета Tx_CMD1: ldi B3,CMD_1 ; записываем в третий байт номер команды 1 rjmp Form_Com Tx_CMD2: ldi B3,CMD_2 ; записываем в третий байт номер команды 2 rjmp Form_Com Tx_CMD3: ldi B3,CMD_3 ; записываем в третий байт номер команды 3 rjmp Form_Com Tx_CMD4: ldi B3,CMD_4 ; записываем в третий байт номер команды 4 Form_Com: mov B4,B3 ; записываем в четвёртый байт номер команды com B4 ; инвертируем его ;-- Сбрасываем счётчик импульсов, загружаем в таймер время передачи ;-- высокого уровня первого полубита и включаем прерывания от таймера clr C_imp ; очистить счётчик импульсов ldi w,Tsh out TCNT0,w ; загр.в таймер время высокого уровня старт бита ldi w,0b00000001 out GTCCR,w ; сбросить предделитель ldi w,0b00000101 out TCCR0B,w ; запустить таймер с делителем на 1024 ldi w,0b00000010 out TIMSK0,w ; разрешить прерывания от таймера sei ; разрешить немаскированные прерывания ;--------------------------------------------------------- ;--- ПОДПРОГРАММЫ ПЕРЕДАЧИ ВЫСОКОГО И НИЗКОГО УРОВНЕЙ ---- ;--- передача высокого уровня ---------------------------- Hlevel: ldi T_Cr,Cr1 ; загрузить длительность импульса sbi PORTB,3 ; установить 1 на выходе Cr1_Y: dec T_Cr brne Cr1_Y ; точная подстройка частоты несущей cbi PORTB,3 ; установить 0 на выходе ldi T_Cr,Cr0 ; длительность паузы в импульсе Cr0_Y: dec T_Cr brne Cr0_Y ; точная подстройка частоты несущей rjmp Hlevel ;-- передача низкого уровня Llevel: nop rjmp Llevel ;--------------------------------------------------------- ;--- ОБРАБОТЧИК ПРЕРЫВАНИЯ ОТ ТАЙМЕРА -------------------- Timer: cbi PORTB,3 ; сбросить выход (флаг переполн.сбросится аппаратно) pop w ; выгрузить адрес из стека (2 байта), pop w ; чтобы указатель не сдвигался ldi w,0b00000000 out TCCR0B,w ; выключить таймер brts ETransf ; если T=1 - мы отсчитыв.интерв. для межпак.паузы ; если флаг не установлен - передаём данные tst C_imp ; проверить C_imp на ноль breq Send_Start0 ; если счётчик равен нулю - переход на Send_Start0 sbrc C_imp,0 ; если нулевой бит сброшен - пропускаем команду rjmp Send_HL ldi w,66 cpse C_imp,w ; если счётчик равен 66 - пропускаем следующ.команду rjmp Send_LL set ; ставим флаг, означающий, что мы закончили передачу ldi C_imp,MP ; загружаем в C_imp кол-во интерв.для межпак.паузы ETransf: dec C_imp breq EPause ; если отсчитали MP интервалов - отсчитали паузу clr w out TCNT0,w ; обнуляем таймер ldi w,0b00000101 out TCCR0B,w ; включаем таймер с делителем на 1024 sei rjmp Llevel ; и нифига не делаем, пока прерывание не случится EPause: clt ; сбрасываем флаг T rjmp Scan Send_LL: ld w,X ; читаем в w байт по адресу X and w,N_bit brne Send_L1 ; если флаг Z не установился, то передаваемый бит=1 Send_L0: ldi w,Tl0 out TCNT0,w ; загрузить в таймер время низкого уровня 0-го бита rjmp Next Send_L1: ldi w,Tl1 out TCNT0,w ; загрузить в таймер время низкого уровня 1-го бита Next: inc C_imp ; увеличить счётчик импульсов clc ; сбросить флаг переноса rol N_bit ; сдвинуть влево регистр N_bit brcc Next2 ; если флаг не установился - переходим rol N_bit ; ещё раз сдвигаем влево inc XL ; увеличиваем адрес указателя Next2: ldi w,0b00000100 out TCCR0B,w ; включаем таймер с делителем на 256 sei ; разрешаем прерывания rjmp Llevel ;--------------------------------------------------------- Send_HL: inc C_imp ldi w,Th out TCNT0,w ; загрузить в таймер время высокого уровня бита ldi w,0b00000100 out TCCR0B,w ; включаем таймер с делителем на 256 sei rjmp Hlevel ;--------------------------------------------------------- Send_Start0: inc C_imp clr N_bit ; очистить регистр N_bit sbr N_bit,0b00000001 ; и установить в нём нулевой бит ldi XL,0x14 ; загружаем в XL указатель на регистр r20 ldi w,Tsl out TCNT0,w ; загр.в таймер время низк.уровня старт.послед. ldi w,0b00000101 out TCCR0B,w ; включаем таймер с делителем на 1024 sei rjmp Llevel ;--------------------------------------------------------- |
Скачать готовую прошивку и asm-файл
Кроме этого должны быть запрограммированы следующие фьюзы: SPIEN, SUT0, CKSEL0. Запрограммированы — то есть сброшены в ноль, то есть в Pony Prog, например, напротив них должны стоять галочки.