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

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