Наш канал в telegram

Программа для самодельного ИК-пульта, протокол NEC (кодирование длиной паузы)

Программа, рассмотренная в этой статье, разработана специально для самодельного ИК-пульта дистанционного управления на контроллере ATtiny13. Если вы измените аппаратную часть (например, будете использовать другой порядок ног, повесите внешний генератор), то программу также нужно будет переделать.

Прежде чем разбираться с программой, наверное, каждому будет полезно посмотреть описание протокола NEC.

Код как всегда по религиозным мотивам написан на чистом ассемблере и выложен в полном объёме (в конце статьи лежат сорцы и прошивка). Чтобы было проще во всём этом разбираться — добавлю некоторое количество словесного описания (хотя код тоже дан с комментами).

Итак, в данном случае для отсчёта времени импульса и паузы используется встроенный в ATtiny13 таймер TMR0. Кроме этого, в алгоритме используются два специальных управляющих регистра (это обычные регистры общего назначения, которые мы, в данном случае, используем для управления передачей). Один из них — счётчик количества передаваемых импульсов и пауз, причём отсчёт начинается с паузы стартовой последовательности (нулевое значение счётчика). Соответственно, если значение счётчика нечётное — мы должны передать импульс, а если чётное — паузу.

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

Кстати, если интересно, — можете посмотреть реализацию аналогичного алгоритма для pic12F629. Кроме того, что для управления ИК-диодом используется другая нога, есть только два небольших отличия. Во-первых, раз уж разработчики AVR подарили нам один пользовательский бит в служебном регистре (бит T в регистре SREG), то грех было им не воспользоваться. Он был использован для того, чтобы отделить состояние когда мы передаём данные, от состояния когда мы нифига не делаем и отсчитываем паузу межпакетного интервала. В результате пауза межпакетного интервала реализована несколько иначе, чем в пике. Во-вторых, для отсчёта импульсов разной длины использовался разный коэффициент предделителя таймера TMR0. Можно было конечно так же, как и в случае с пиком, использовать один и тот же коэффициент, но в случае c ATtiny это никак не увеличивает код, зато позволяет точнее настраивать временнЫе интервалы.

На этом закончим вступительную часть и перейдём к алгоритму и программе.

Алгоритм:

Алгоритм реализации протокола NEC на AVR

Итак, пусть в аппаратной части мы имеем:

входы: PB4 — кнопка SB1, PB2 — кнопка SB2, PB0 — кнопка SB3, PB1 — кнопка SB4

выходы: PB3 — вывод информации по протоколу NEC.

RESET внешне подтянут к питанию; используется внутренний генератор на 9,6 МГц.

Текст программы под катом (в данной программе частота несущей 36 кГц)

.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, например, напротив них должны стоять галочки.

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