;**** Использование AT90S8515 в качестве ведущего устройства I2C ********
;*
;* Чистая программная реализация ведущего устройство I2C с помощью AT90S8515.
;* Этот Master через равномерные промежутки времени опрашивает температуру с
;* подключенного к шине I2C ведомого датчика LM75 и выводит ее по той же шине 
;* на ведомую микросхему SAA1064. Для наглядности используем систему с одним
;* ведущим устройством.
;* Функцию реализуем без использования прерываний AVR.
;* Таким образом, все прерывания можно использовать для других целей.
;* Пример написан для микроконтроллера AT90S8515, однако, поскольку, из всей 
;* аппаратной периферии используется только T/C0, он применим для всех
;* микроконтроллеров семейства AVR.
;* Тактовая частота микроконтроллеров AVR: 4 МГц (Стандарт STK200).
;*
;* Подпрограммы, которые могут быть использованы в других случаях применения:
;*
;* > ReStart:	Создает новое условие старта; должна следовать непосредственно
;* 		перед подпрограммой "Start".
;* > Start:	Передает условие старта и адрес ведомого устройства, хранимый
;* 		в "dbyt"; должна следовать непосредственно
;* 		перед подпрограммой "WriteByte".
;* > WriteByte: Передает ведомому устройству байт, хранимый в "dbyt"; 
;* 		при считывании значения с ведомого устройства должна следовать 
;*		непосредственно перед подпрограммой "GetAck"
;* > GetAck:	Считывает бит квитирования.
;* > ReadByte: Считывает байт с ведомого устройства, записываемый в "dbyt";
;* 		при выдаче бита квитирования ведущим устройством должна 
;*		следовать непосредственно перед подпрограммой "SetAck"
;* > SetAck:	Выводит бит квитирования от ведущего устройства.
;* > Stopp:	Завершает передачу выдачей условия завершения.
;*
;*************************************************************************

.nolist
.include "8515def.inc"
.list

.def	tmp1  = r16	; Рабочий регистр
.def	time  = r17	; Счетчик цикла задержки
.def	dbyt  = r18	; Байт данных для передачи по шине I2C
.def	Flg   = r19	; Флаги программы
.def	Dig43 = r20	; Цифры 4 и 3 для вывода
.def	Dig21 = r21	; Цифры 2 и 1 для вывода

; Флаги в регистре Flg:
.equ	Ack   = 0	; Флаг квитирования = разряд 0
.equ	DP1   = 4	; Десятичная точка цифры 1
.equ	DP2   = 5	; Десятичная точка цифры 2
.equ	DP3   = 6	; Десятичная точка цифры 3
.equ	DP4   = 7	; Десятичная точка цифры 4

; Входы/выходы I2C:
.equ  SDA     = PB0	; SDA = порт B / разряд 0
.equ  SCL     = PB1	; SCL = порт B / разряд 1
; Адреса ведомых устройств I2C:
.equ  DispAdr = $76	; Адрес SAA1064 (Вывод ADR (1) - Vcc)
.equ  TempAdr = $90	; Адрес LM75

.macro SCL_Up	; Нарастающий фронт SCL
cbi	DDRB,SCL	; SCL - высокоомный
.endmacro
.macro SCL_Dwn	; Ниспадающий фронт SCL
sbi	DDRB,SCL	; SCL = лог. 0
.endmacro
.macro SDA_Hi	; Установка SDA в 1
cbi	DDRB,SDA	; SDA - высокоомный
.endmacro
.macro SDA_Lo	; Установка SDA в 0
sbi	DDRB,SDA	; SDA = лог. 0
.endmacro

.cseg

rjmp  Initial	; После сброса – к главной программе

Wait250:	; Задержка на 250 мс
ldi   tmp1,0	; Загружаем интервал времени
out   TCNT0,tmp1	; Инициализируем T/C0
ldi   tmp1,1<<TOV0	; Загружаем битовую позицию флага TOV0
out   TIFR,tmp1	; Сбрасываем флаг TOV0
ldi   time,4	; 4 обращения к таймеру
Wait:
in    tmp1,TIFR	; Извлекаем содержимое регистра TIFR
sbrs  tmp1,TOV0	; Пропускаем следующую команду, если произошло переполнение таймера
rjmp  Wait	; Дальше ожидаем переполнения
ldi   tmp1,1<<TOV0	; Загружаем битовую позицию флага TOV0
out   TIFR,tmp1	; Сбрасываем флаг TOV0
dec   time
brne  Wait	; 4 прохода = 250 мс
ret

Wait5us:	; Цикл задержки 5 мкс при f = 4 МГц
ldi   time,4
W5:
dec   time	; 4 x 3 такта + nop + rcall + ret
brne  W5	;   = 20 тактов
nop
ret

Wait4us:	; Цикл задержки 4 мкс при f = 4 МГц
ldi   time,3
W4:
dec   time	; 3 x 3 такта + rcall + ret
brne  W4	;   = 16 тактов
ret

ReStart:	; Повторяем условие старта
SCL_Dwn	; Ниспадающий фронт SCL
nop
SDA_Hi	; Установка SDA в 1
nop
rcall wait5us	; Цикл задержки
nop
SCL_Up	; Нарасающий фронт SCL
RS1:
sbis  PinB,SCL	; Состояние ожидания ведомого устройства ?
rjmp  RS1
rcall wait5us	; Цикл задержки

Start:				
SDA_Lo	; SDA = 0: условие старта
rcall wait4us	; Цикл задержки

WriteByte:	; Запись байта в ведомое устройство
sec	; Флаг переноса C = 1
rol   dbyt	; Сдвигаем влево, C->LSB, MSB->C
rjmp  bit1	; Особая обработка разряда 1
WriteBit:
lsl   dbyt	; Если dbyt пустой, ...
bit1:
breq  GetAck	; ... то передача завершена
SCL_Dwn	; Ниспадающий фронт SCL
brcc  WriteLow	; Переход, если флаг C = 0
nop	; Чтобы уравновесить время выполнения
SDA_Hi	; Установка SDA в 1
rjmp  SCL_High
WriteLow:
SDA_Lo	; Установка SDA в 0
rjmp  SCL_High	; Чтобы уравновесить длительность такта
SCL_High:
rcall wait4us	; Цикл задержки
SCL_Up	; Ниспадающий фронт SCL
WB1:
sbis  PinB,SCL	; Состояние ожидания ведомого устройства ?
rjmp  WB1
rcall wait5us	; Цикл задержки
rjmp  WriteBit

GetAck:	; Считывание бита квитирования от ведомого
SCL_Dwn	; Ниспадающий фронт SCL
SDA_Hi	; SDA – высокоомное состояние
rcall wait5us	; Цикл задержки
SCL_Up	; Нарастающий фронт SCL
GA1:
sbis  PinB,SCL	; Состояние ожидания ведомого устройства 
rjmp  GA1
cbr   Flg,1<<Ack	; Флаг Ack = 0
sbic  PinB,SDA	; Если SDA = 1,
sbr   Flg,1<<Ack	;   флаг Ack = 1
rcall wait4us	; Цикл задержки
ret

ReadByte:
ldi   dbyt,1	; Назначаем в качестве счетчика битов
RB1:
SCL_Dwn	; Ниспадающий фронт SCL
rcall wait5us	; Цикл задержки
SCL_Up	; Нарастающий фронт SCL
RB2:
sbis  PinB,SCL	; Состояние ожидания ведомого устройства ?
rjmp  RB2
rcall wait5us	; Цикл задержки
clc	; Флаг C = 0
sbic  PinB,SDA	; Если SDA = 1
sec	;   Carry = 1
rol   dbyt	; Сдвигаем прочитанный бит в байте
brcc  RB1	; Переход, если чтение не завершено

SetAck:	; Вывод бита квитирования
SCL_Dwn	; Ниспадающий фронт SCL
sbrs  Flg,Ack	; Пропускаем следующую команду, если Ack=1
rjmp  SA1	; Переход, если Ack = 0
SDA_Hi	; Установка SDA в 1
rjmp  SA2
SA1:
SDA_Lo	; Установка SDA в 0
SA2:
rcall wait5us	; Цикл задержки
SCL_Up	; Нарастающий фронт SCL
SA3:
sbis  PinB,SCL	; Состояние ожидания ведомого устройства ?
rjmp  SA3
rcall wait5us	; Цикл задержки
ret

Stopp:	; Вывод условия завершения
SCL_Dwn	; Ниспадающий фронт SCL
SDA_Lo	; Установка SDA в 0
rcall wait5us	; Цикл задержки
SCL_Up	; Установка SCL в 1
rcall wait5us	; Цикл задержки
SDA_Hi	; Нарастающий фронт SCL
rcall wait5us	; Цикл задержки
ret

InitDisp:	; Передача байта управления в SAA1064
ldi   dbyt,DispAdr	; Адрес SAA1064, направление - запись
rcall Start	; Передаем условие старта + адрес ведомого
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
ldi   dbyt,$00	; Подадрес – регистр управления
rcall WriteByte	;   Передаем
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
ldi   dbyt,$77	; Максим. ток, вывод цифр, динамический режим
rcall WriteByte	;   Передаем
ret

OutDisp:	; Вывод 4-х цифр в SAA1064
ldi   dbyt,DispAdr	; Адрес SAA1064, направление - запись
rcall Start	; Передаем условие старта + адрес ведомого
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
ldi   dbyt,$01	; Подадрес = Digit 1
rcall WriteByte	;   Передаем
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
mov   tmp1,Dig21	; Цифры 1 (младшая) и 2 (старшая)
andi  tmp1,$0F	; Очищаем старший полубайт (цифра 2)
rcall Get7Segm	; Загружаем 7-сегментный код младшего полубайта
sbrc  Flg,DP1	; Пропускаем следующую команду, если в цифре 1 нет десятичной точки
sbr   dbyt,1<<7	; Устанавливаем десятичную точку
rcall WriteByte	; Передаем цифру 1
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
mov   tmp1,Dig21	; Цифры 1 (младшая) и 2 (старшая)
swap  tmp1	; Меняем местами младшую и старшую цифры
andi  tmp1,$0F	; Очищаем старший полубайт (цифра 1)
rcall Get7Segm	; Загружаем 7-сегментный код младшего полубайта
sbrc  Flg,DP2	; Пропускаем следующую команду, если в цифре 2 нет десятичной точки
sbr   dbyt,1<<7	; Устанавливаем десятичную точку
rcall WriteByte	; Передаем цифру 2
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
mov   tmp1,Dig43	; Цифры 3 (младшая) и 4 (старшая)
andi  tmp1,$0F	; Очищаем старший полубайт (цифра 4)
rcall Get7Segm	; Загружаем 7-сегментный код младшего полубайта
sbrc  Flg,DP3	; Пропускаем следующую команду, если в цифре 3 нет десятичной точки
sbr   dbyt,1<<7	; Устанавливаем десятичную точку
rcall WriteByte	; Передаем цифру 3
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
mov   tmp1,Dig43	; Цифры 3 (младшая) и 4 (старшая)
swap  tmp1	; Меняем местами младшую и старшую цифру
andi  tmp1,$0F	; Очищаем старший полубайт (цифра 3)
rcall Get7Segm	; Загружаем 7-сегментный код младшего полубайта
sbrc  Flg,DP4	; Пропускаем следующую команду, если в цифре 4 нет десятичной точки
sbr   dbyt,1<<7	; Устанавливаем десятичную точку
rcall WriteByte	; Передаем цифру 4
ret

Get7Segm:
ldi   dbyt,0
out   EEARH,dbyt	; Старший байт адреса EEPROM = 0
out   EEARL,tmp1	; Младший байт адреса EEPROM = tmp1
ldi   tmp1,$01	; Устанавливаем разрешение чтения
out   EECR,tmp1	; Начинаем доступ к EEPROM на чтение
in    dbyt,EEDR	; Считанный байт - в tmp1
ret

GetTemp:	; Считываем температуру из LM75
ldi   dbyt,TempAdr	; Адрес LM75, направление - запись
rcall Start	; Передаем условие старта + адрес ведомого
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
ldi   dbyt,0	; Указатель LM75 (указывает на температуру)
rcall WriteByte	;   Передаем
sbrc  Flg,Ack	; Пропускаем следующую команду, если квитирование
ret	; Завершаем, если отрицательное квитирование
ldi   dbyt,TempAdr+1	; Адрес LM75, направление - чтение
rcall ReStart	; Передаем новое условие старта + адрес
sbrc  Flg,Ack	; Пропускаем следующую команду, если бит квитирования = 0
ret	; Завершаем, если отрицательное квитирование
rcall ReadByte	; Считываем байт, бит квитирования = 0
mov   Dig43,dbyt	; Сохраняем старший байт в буфер
sbr   Flg,1<<Ack	; Нет квитирования
rcall ReadByte	; Считываем байт, бит квитирования = 1
mov   Dig21,dbyt	; Сохраняем младший байт в буфер
rcall stopp	; Передаем условие завершения
Calc:
bst   Dig43,7	; Копируем знак в флаг T
brtc  Positiv	; Переход, если знак положительный
sbrs  Dig21,7	; Пропускаем следующую команду, если старший разряд в позиции после точки = 1
rjmp  GT1	; Переход, если старший разряд в позиции после точки = 0
com   Dig43	; Дополнение до единицы
ldi   Dig21,$05	; Позиция после точки -xx,5 °C (цифра 1)
rjmp  HexDez
GT1:
neg   Dig43	; Дополнение до двух
ldi   Dig21,$00	; Позиция после точки -xx,0 °C (цифра 1)
rjmp  HexDez
Positiv:
ldi   tmp1,$05	; Позиция после точки xx,5 °C (цифра 1)
sbrs  Dig21,7	; Пропускаем следующую команду, если старший разряд в позиции после точки = 1
ldi   tmp1,$00	; Позиция после точки -xx,0 °C (цифра 1)
mov   Dig21,tmp1	; Принимаем позицию после точки
HexDez:	; Шестнадцатеричное число в Dig43 < $64 (100)
ldi   tmp1,0	; Счетчик для BCD-преобразования
HD1:
subi  Dig43,10	; Вычитаем 10
brlo  HD2	; Переход, если результат отрицательный
inc   tmp1	; Старший полубайт BCD-числа
rjmp  HD1	; Деление еще не завершено
HD2:
subi  Dig43,-10	; Прибавляем 10 = остаток от деления
cpi   tmp1,0	; Ведущий нуль в старшем полубайте ?
brne  GT2	; Переход, если нет 
ldi   tmp1,$0E	; Пробел вместо ведущего нуля
GT2:
swap  Dig43	; Меняем местами старш. и младш. полубайты
or    Dig21,Dig43	; Старший полубайт  - в Dig21
ldi   Dig43,$F0	; Установка старшего полубайта (знак "-")
or    Dig43,tmp1	; Устанавливаем старший BCD-полубайт
brtc  HD3	; Переход к позиции знака
ret
HD3:
cbr   Dig43,1<<4	; $E= пробел, $F= минус
ret

Initial:
ldi   dbyt,Low(RAMEND)	; Функция вспомогательного регистра
out   SPL,dbyt
ldi   dbyt,High(RAMEND)
out   SPH,dbyt	; Инициализируем указатель стека
clr   dbyt	; Функция вспомогательного регистра
out   PortB,dbyt	; Обнуляем все разряды
out   PortD,dbyt	; Обнуляем все разряды
out   DDRB,dbyt	; Переключаем выходной драйвер в высокоомное состояние
out   DDRD,dbyt	; Переключаем выходной драйвер в высокоомное состояние
ldi   tmp1,5	; Загружаем делитель
out   TCCR0,tmp1	; Запускаем T/C0, коэффициент дел. = 1024
ldi   Flg,$01	; Сбрасываем в 0 все флаги, кроме Ack
sbr   Flg,1<<DP2	; Десятичная точка возле второй цифры
Haupt:
rcall InitDisp	; Инициализируем SAA1064
rcall stopp	; Выводим условие завершения
sbrc  Flg,Ack	; Пропускаем следующую команду, если получено квитирование
rjmp  Haupt	; Новая попытка, если нет квитирования
Next:
rcall GetTemp	; Считываем температуру из LM75
rcall OutDisp	; Показываем температуру
rcall stopp	; Завершаем вывод
sbrc  Flg,Ack	; Пропускаем следующую команду, если получено квитирование
rjmp  Haupt	; Новая попытка, если нет квитирования
rcall Wait250	; Задержка 250 мс
rjmp  Next	; Следующее значение

.eseg
HexTo7Segm:	; Таблица 7-сегментных кодов
.db   $3F,$06,$5B,$4F,$66,$6D,$7D,$07	; Шестнадцатеричные 0..7
.db   $7F,$6F,$77,$7C,$39,$5E,$00,$40	; Шестнадцатеричные 8..D,E=пробел,F=минус
;.db   $7F,$6F,$77,$7C,$39,$5E,$79,$71	; Шестнадцатеричные 8..F

