Программа для контроллера 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.

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