Наш канал в telegram

Программа для контроллера 1-wire-шлюза (режим 1-wire-master из терминалки ПК)

Программа, рассмотренная в этой статье, разработана для контроллера 1-wire-шлюза (шлюз, как вы помните, реализован на ATTiny2313). Эта программа позволяет из терминальной программы персонального компьютера общаться через шлюз с различными 1-wire устройствами в качестве Master-а.

Для реализации обмена данными по 1-wire между контроллером и подключаемым устройством были использованы стандартные, написанные нами ранее процедуры (только задержки в них изменены, с учётом того, что здесь у нас кварц на 20 МГц, а не на 8).

Наша программа умеет делать следующие вещи: 1) по команде с компьютера формировать на шине 1-wire сигнал Reset, определять был ли получен ответ (Presence) от слэйва на этот сигнал и сообщать об этом компьютеру; 2) отправлять принятую с компа информацию по шине 1-wire; 3) считывать информацию с шины 1-wire и отправлять её компу. Причём, информацию по шине 1-wire наш шлюз умеет отправлять и принимать как побайтно, так и побитно.

Протокол обмена с компьютером состоит из сообщений, размером в 1 байт. Обмен всегда начинается с какой-либо команды от компьютера. Формат обмена для каждой команды строго определён и описан ниже.

Итак, команды используются такие:

  1. 01h — Reset. После получения такой команды — шлюз формирует на шине 1-wire сигнал сброса, потом проверяет — был ли на шине в ответ на сброс сформирован сигнал Presence и в зависимости от этого отправляет компьютеру код 02h (если Presence не был сформирован) или 03h (если Presence был сформирован).
  2. 02h — Send. Эта команда означает, что следующий принятый байт будет отправлен шлюзом по шине 1-wire. После принятия команды Send шлюз посылает назад компьютеру код 01h (готовность), после чего переходит к ожиданию байта, который нужно будет отправить по 1-wire. Далее, получив от компьютера этот байт, шлюз отправляет его по 1-wire и снова посылает копьютеру код 01h, который означает, что команда выполнена успешно и можно посылать шлюзу следующую команду.
  3. 03h — Read. Получив эту команду, шлюз считывает 1 байт с шины 1-wire и отправляет этот байт компьютеру.
  4. 04h — Send_One_Bit. Эта команда аналогична команде Send, за исключением того, что по шине 1-wire отправляется не весь принятый от компьютера байт, а только его младший бит.
  5. 05h — Read_One_Bit. После получения этой команды — шлюз считывает с шины 1-wire 1 бит и, в зависимости от значения этого бита, отправляет компьютеру код 0Fh (если бит равен нулю) или код 8Fh (если бит равен единице).

В случае, если шлюз принимает неизвестную ему команду, — он отсылает назад компьютеру код FFh.

Вот пожалуй и всё описание (если возникнут вопросы — не стесняйтесь, задавайте на форуме), а теперь перейдём к алгоритму и программе.

Алгоритм:

Алгоритм работы 1-wire-шлюза

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

  1. PB2 — линия 1-wire
  2. PD0 — линия Rx
  3. PD1 — линия Tx
Текст программы под катом

.device ATtiny2313
.include "tn2313def.inc"
.list
;-- определяем свои переменные
.def     w=r16           ; это будет наш аккумулятор
.def     Flags=r17       ; флаги 1-wire
.def     Transmit_R=r18  ; байт для передачи
.def     Recieve_R=r20   ; принятый байт
.def     Bit_counter=r21 ; счётчик переданных/принятых бит
 
;-- определяем константы (и даём им имена)
.equ     line_1_wire=2   ; PortB2/PinB2 - на этой ноге сделан порт 1-wire
.equ     Port_reg=PORTB
.equ     Pin_reg=PINB
.equ     DDR_reg=DDRB
;----------------------------
.equ SEND_BIT=0
.equ READ_BIT=1
.equ SEND_RESET=2
.equ FREE_BUS=3
.equ SLAVE_IS=4
;-- начало программного кода
.cseg
.org 0
rjmp Init        ; переход на начало программы (вектор сброса)
;-- дальше идут вектора прерываний
;-- если не используем - пишем reti, иначе - переход на начало обработчика
reti         ; внешнее прерывание INT0
reti         ; внешнее прерывание INT1
reti         ; Input capture interrupt 1
reti         ; Timer/Counter1 Compare Match A
reti         ; Overflow1 Interrupt
reti         ; Overflow0 Interrupt
rjmp RX_INT  ; USART0 RX Complete Interrupt
reti         ; USART0 Data Register Empty Interrupt
reti         ; USART0 TX Complete Interrupt
reti         ; Analog Comparator Interrupt
reti         ; Pin Change Interrupt
reti         ; Timer/Counter1 Compare Match B
reti         ; Timer/Counter0 Compare Match A
reti         ; Timer/Counter0 Compare Match B
reti         ; USI start interrupt
reti         ; USI overflow interrupt
reti         ; EEPROM write complete
reti         ; Watchdog Timer Interrupt
;-- начало программы
Init:   ldi w,RAMEND     ; устанавливаем указатель вершины
out SPL,w        ; стека на старший байт RAM
sbi ACSR,ACD     ; выключаем компаратор
;-- инициализируем порты
ser w            ;    w=0xFF
out DDRA,w       ; настраиваем порт A. все линии на выход
out DDRD,w       ; настраиваем порт D. все - выходы
clr w            ;    w=0x00
out PORTA,w      ; на всех линиях ноль
out PORTD,w
ldi w,0b11111011 ; настраиваем порт B. PB2 - вход, остальные - выходы
out DDRB,w       ;
clr w            ; начальное состояние выходов и подтяжки на входах
out PORTB,w      ; (выходы - нули, подтяжек нет)
;-- инициализируем UART
out UBRRH,w      ; UBRR (для кварца 20 МГц и скорости 115200)
ldi w,10         ; равен 10, т.е. UBRRH=0, UBRRL=10
out UBRRL,w
ldi w,0b00001110 ; поднимаем биты USBS, UCSZ1:0
out UCSRC,w      ; формат: 1 старт, 8 данные, 2 стоп
sbi UCSRB, TXEN  ; включить передатчик
nop
sbi UCSRB, RXEN  ; включить приёмник
;-- разрешаем прерывания от приёмника
sbi UCSRB,RXCIE
;-- инициализируем таймер
clr w
out TCCR0A,w  ; таймер отключен от выводов OC0A(B)
out TCNT0,w   ; сбрасываем таймер
out TIMSK,w   ; запрещаем все прерывания от него
ser w
out TIFR,w    ; сбрасываем флаги таймера
;-- сообщаем компу, что загрузились и ждём команду
ldi w,0x01
out UDR,w
;-- разрешаем глобальные прерывания
sei
;-- ждём у моря погоды ---
Wait_data:
rjmp Wait_data
;************************************************
;------------------------------------------------
;--- Прерывание от UART -------------------------
RX_INT: in   w,UDR     ; читаем байт из приёмника в w
cpi  w,0x01    ; принятый байт равен 1?
breq Reset
cpi  w,0x02    ; принятый байт равен 2?
breq Send
cpi  w,0x03    ; принятый байт равен 3?
breq Read
cpi  w,0x04    ; принятый байт равен 4?
breq Send_One_Bit
cpi  w,0x05    ; принятый байт равен 5?
breq Read_One_Bit
;-- Сообщаем что приняли неизвестную команду и выходим
Error_Command:
ldi  w,0xFF
out  UDR,w
reti
;--- ОБРАБОТЧИКИ КОМАНД -------------------------
Reset:
rcall Send_Reset_proc
ldi   w,0x02           ; никого нет
sbrc  Flags,SLAVE_IS
ldi   w,0x03           ; есть слэйв
out   UDR,w            ; шлём компу результат
reti
;------------------------------
Send:   ldi w,0x01             ; сообщаем компу, что ждём байт для передачи
out UDR,w
ldi Bit_counter,8      ; готовимся передать 8 бит
Wait_byte:
sbis UCSRA,RXC
rjmp Wait_byte
in   Transmit_R,UDR    ; пишем байт для передачи в Transmit_R
Send_next_bit:
rcall Send_Bit_proc
lsr  Transmit_R
dec  Bit_counter
brne Send_next_bit     ; если передали не все биты - прыгаем
ldi w,0x01             ; сообщаем компу, что передали все биты
out UDR,w
reti                   ; и выходим
;------------------------------
Read:   ldi  Bit_counter,8      ; готовимся прочитать 8 бит
Read_next_bit:
rcall Read_Bit_proc
dec  Bit_counter
brne Read_next_bit     ; если прочитали не все биты - читаем следующий
out  UDR, Recieve_R    ; посылаем компу то, что прочитали
reti                   ; и выходим
;------------------------------
Send_One_Bit:
ldi w,0x01             ; сообщаем компу, что ждём байт,
out UDR,w              ; содержащий бит для передачи
Wait_byte2:                    ; передавать будем только младший бит этого байта
sbis UCSRA,RXC
rjmp Wait_byte2
in   Transmit_R,UDR    ; пишем байт для передачи в Transmit_R
rcall Send_Bit_proc
ldi w,0x01             ; сообщаем компу, что передали бит
out UDR,w
reti                   ; и выходим
;------------------------------
Read_One_Bit:
ldi Recieve_R,0b00011110
rcall Read_Bit_proc
out  UDR, Recieve_R    ; посылаем компу то, что прочитали (1 - 8F, 0 - 0F)
reti                   ; и выходим
;************************************************
;------------------------------------------------
;--- Процедуры 1-Wire ---------------------------
_1_wire_null:
sbi  DDR_reg,line_1_wire
ret
_1_wire_Z:
cbi  DDR_reg,line_1_wire
ret
;------------------------------------------------
Send_Reset_proc:
sbr Flags, 1<<SEND_RESET
cbr Flags, 1<<SLAVE_IS
;--- настраиваем таймер ---
ldi w,177             ; начальное значение таймера (255-78)
out TCNT0,w
ldi w,216             ; +39 тиков
out OCR0A,w
ldi w, 221            ; +5 тиков
out OCR0B,w
;--- конец настройки таймера ---
ldi w,0b00000100
out TCCR0B,w          ; запускаем таймер с предделителем 1:256
rcall _1_wire_null    ; формируем низкий уровень на шине
;--- ждём отсчёта первого интервала ---
M1:         in w,TIFR
sbrs w,OCF0A          ; если флаг установлен - пропустить команду
rjmp M1
rcall Occured_OCF0A   ; первое действие
;--- ждём отсчёта второго интервала ---
M2:         in w,TIFR
sbrs w,OCF0B          ; если флаг установлен - пропустить команду
rjmp M2
rcall Occured_OCF0B   ; второе действие
;--- ждём отсчёта третьего интервала ---
M3:         in w,TIFR
sbrs w,TOV0           ; если флаг установлен - пропустить команду
rjmp M3
rcall Occured_TOV0    ; третье действие
cbr Flags, 1<<SEND_RESET ; флаг передачи сигнала "Reset"
ret                   ; и выходим
Send_Bit_proc:
sbr Flags, 1<<SEND_BIT
;--- настраиваем таймер ---
ldi w,85              ; начальное значение таймера (255-170)
out TCNT0,w
ldi w,89              ; +4 тика
out OCR0A,w
ldi w, 241            ; +156 тиков
out OCR0B,w
;--- конец настройки таймера ---
ldi w,0b00000010
out TCCR0B,w          ; запускаем таймер с предделителем 1:8
rcall _1_wire_null    ; формируем низкий уровень на шине
;--- ждём отсчёта первого интервала ---
M4:         in w,TIFR
sbrs w,OCF0A       ; если флаг установлен - пропустить команду
rjmp M4
rcall Occured_OCF0A   ; первое действие
;--- ждём отсчёта второго интервала ---
M5:         in w,TIFR
sbrs w,OCF0B       ; если флаг установлен - пропустить команду
rjmp M5
rcall Occured_OCF0B   ; второе действие
;--- ждём отсчёта третьего интервала ---
M6:         in w,TIFR
sbrs w,TOV0        ; если флаг установлен - пропустить команду
rjmp M6
rcall Occured_TOV0    ; третье действие
cbr Flags, 1<<SEND_BIT  ; сбрасываем флаг передачи бита
ret                   ; и выходим
Read_Bit_proc:
sbr Flags, 1<<READ_BIT
;--- настраиваем таймер ---
ldi w,85              ; начальное значение таймера (255-170)
out TCNT0,w
ldi w,89              ; +4 тика
out OCR0A,w
ldi w, 119            ; +34 тика = 14,55 мкс (+35 тиков = 14,95 мкс)
out OCR0B,w
;--- конец настройки таймера ---
ldi w,0b00000010
out TCCR0B,w          ; запускаем таймер с предделителем 1:8
rcall _1_wire_null     ; формируем низкий уровень на шине
;--- ждём отсчёта первого интервала ---
M7:         in w,TIFR
sbrs w,OCF0A       ; если флаг установлен - пропустить команду
rjmp M7
rcall Occured_OCF0A   ; первое действие
;--- ждём отсчёта второго интервала ---
M8:         in w,TIFR
sbrs w,OCF0B       ; если флаг установлен - пропустить команду
rjmp M8
rcall Occured_OCF0B   ; второе действие
;--- ждём отсчёта третьего интервала ---
M9:         in w,TIFR
sbrs w,TOV0        ; если флаг установлен - пропустить команду
rjmp M9
rcall Occured_TOV0    ; третье действие
cbr Flags, 1<<READ_BIT ; сбрасываем флаг чтения бита
ret                   ; и выходим
;--------------------------------------------
Occured_OCF0A:
sbrc Flags, SEND_RESET
rjmp Send_Reset_OCR0A
sbrc Flags, SEND_BIT
rjmp Send_Bit_OCR0A
sbrc Flags, READ_BIT
rjmp Read_Bit_OCR0A
ret
Send_Reset_OCR0A:   ; первое действие для Send_Reset - отпустить шину
rcall _1_wire_Z
ret
Send_Bit_OCR0A:   ; первое действие для Send_Bit - установить бит
sbrc Transmit_R,0 ; если передаём ноль - ничего делать не нужно
rcall _1_wire_Z   ; если передаём единицу - отпускаем шину
ret
Read_Bit_OCR0A:   ; первое действие для Read_Bit - отпустить шину
rcall _1_wire_Z
ret
 
Occured_OCF0B:
sbrc Flags, SEND_RESET
rjmp Send_Reset_OCR0B
sbrc Flags, SEND_BIT
rjmp Send_Bit_OCR0B
sbrc Flags, READ_BIT
rjmp Read_Bit_OCR0B
ret
Send_Reset_OCR0B:   ; второе действие для Send_Reset - прочитать "Presence"
sbis Pin_reg,line_1_wire  ; если 1 - пропускаем команду
sbr Flags, 1<<SLAVE_IS
ret
Send_Bit_OCR0B:   ; второе действие для Send_Bit - отпустить шину
rcall _1_wire_Z
ret
Read_Bit_OCR0B:   ; второе действие для Read_Bit - прочитать бит с шины
lsr Recieve_R ; сдвигаем регистр вправо, а в старший пишем ноль
sbic Pin_reg, line_1_wire ; если 0 - пропускаем команду
sbr Recieve_R,1<<7
ret
 
Occured_TOV0:
sbrc Flags, SEND_RESET
rjmp Send_Reset_TOV0
sbrc Flags, SEND_BIT
rjmp Send_Bit_TOV0
sbrc Flags, READ_BIT
rjmp Read_Bit_TOV0
ret
Send_Reset_TOV0:   ; третье действие для Send_Reset - конец процедуры
Send_Bit_TOV0:     ; третье действие для Send_Bit - конец тайм-слота
Read_Bit_TOV0:     ; третье действие для Read_Bit - конец тайм-слота
clr w
out TCCR0B,w          ; останавливаем таймер
ser w
out TIFR,w            ; сбрасываем флаги таймера,
ret
;---------------------------------------------------------

[свернуть]

Для правильной работы шлюза в контроллере должны быть «запрограммированы» следующие фьюзы: SPIEN, SUT0

Скачать готовую прошивку и asm-файл

Приведу небольшой пример работы со шлюзом.

Пусть у нас есть обычный ключ-таблетка от подъездной двери (сразу скажу, что в примере ключ не от моего подъезда). Присоединяем шлюз к компьютеру, выводы 1-wire и GND нашего шлюза присоединяем к этой таблетке, заходим в терминалку, выбираем порт и скорость обмена (скорость у нас 115200), подключаемся и делаем следующее:

— отправляем шлюзу 01h // (послать сигнал Reset)
— получаем от шлюза 03h // шлюз сообщает, что увидел Presence от ключа
— отправляем шлюзу 02h // сообщаем шлюзу, что следующий байт нужно отправить по шине 1-wire
— получаем от шлюза 01h // шлюз говорит, что ждёт байт для передачи
— отправляем шлюзу 33h // посылаем ключу команду Read ROM
— получаем от шлюза 01h // шлюз сообщает, что команда отправлена
— отправляем шлюзу 03h // просим шлюз считать байт с шины 1-wire
— получаем от шлюза 01h // получаем первый прочитанный байт
— отправляем шлюзу 03h // просим шлюз считать байт с шины 1-wire
— получаем от шлюза 00h // получаем второй прочитанный байт
— отправляем шлюзу 03h // просим шлюз считать байт с шины 1-wire
— получаем от шлюза 15h // получаем третий прочитанный байт
— отправляем шлюзу 03h // просим шлюз считать байт с шины 1-wire
— получаем от шлюза 47h // получаем четвёртый прочитанный байт
— отправляем шлюзу 03h // просим шлюз считать байт с шины 1-wire
— получаем от шлюза D9h // получаем пятый прочитанный байт
— отправляем шлюзу 03h // просим шлюз считать байт с шины 1-wire
— получаем от шлюза 00h // получаем шестой прочитанный байт
— отправляем шлюзу 03h // просим шлюз считать байт с шины 1-wire
— получаем от шлюза 00h // получаем седьмой прочитанный байт
— отправляем шлюзу 03h // просим шлюз считать байт с шины 1-wire
— получаем от шлюза 62h // получаем восьмой прочитанный байт

В результате этих действий мы получаем все 8 байт, зашитых в ROM ключа-таблетки: 01 00 15 47 D9 00 00 62.

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