Easy_I2C AVR ASM практикум начало

AVR
ОТРЕДАКТИРОВАНО 22.12.2013 благодаря комментариям.
I2C это достаточно важный раздел который я не изучил в AVR Mega на ASMе. Вот и решил восполнить этот пробел. В этом посте не будет прикольных графиков, фоток, схем зато будет много унылого кода на ASMe. Врать не буду но времени ушло на изучение больше 2-х месяцев. Причин много — это и работа, и работа, и работа, и (ремонт на кухне)*100500, и изготовление отладочной платы с PCF8574+PCF8574+MCP23016+DS1307+AT24C512+AT24C512+PCF8591 поскольку многое хотелось попробовать.
Писать программный I2C я не решился поэтому все строилось на прерываниях. У работы на прерываниях есть много преимуществ и легко прикручивается к диспетчеру заданий(RTOS). За основу были взяты статьи учебного курса DIhalta и его помощников. Ну а куда же деться от книги А. В. Евстифеева которая стала настольной заменив Камасутру.
У DiHalta есть пример, но он написан на Си, а мне это мало о чем говорит, но тем не менее я от туда кое какие имена стащил.
Данная статья рассчитана на тех кто уже начал изучать I2C, на тех кто разобрался что такое sda scl ack nack TWCR TWDR TWBR. В общем это статья о зародыше практического применения I2C
И еще кое что, но не сочтите за рекламу. Если у вас много времени то конечно можно обратиться на местный форум за помощью и кто-то на вроде AlexPM даст достаточно грамотные советы (я искренне удивляюсь знаниям многих форумчан и даже завидую), а если времени нет то можно все быстро отладить самому имея недорогой логический анализатор (который покупается 1 раз а помогает ну очень сильно). Я сам его просто купил. У меня LOGIC-U.
Пока прерывание написано только для условий когда контроллер выступает в роли Мастера.
Теперь поясню какие задания на мой взгляд должно выполнять прерывание — 2 группы: отправка и прием данных
Каждая группа имеет 3 варианта:
1) простое обращение к устройству (например PCF8574)
2) обращение к устройству с предварительной записью номера регистра/страницы размером 1byte (например MCP23016 DS1307)
3) обращение к устройству с предварительной записью номера регистра/страницы размером 2byte (например AT24Cxx)
Итак начинаю с регистров оперативы которые обязательны для работы прерывания
;всего 12 байт
i2c_SlaveAddress:		.byte	1               ;РЕГИСТР АДРЕСА УСТРОЙСТВА В СЕТИ
I2C_err:			.byte	1		;РЕГИСТР ОШИБОК
do_I2C:				.byte	1		;РЕГИСТР ЗАДАНИЯ
i2c_index_DATA:			.byte	1		;РЕГИСТР ХРАНЯЩИЙ СМЕЩЕНИЕ ОТНОСИТЕЛЬНО НАЧАЛА БУФЕРА ПЕРЕДАВАЕМЫХ ДАННЫХ
i2c_index_DATA_END:		.byte	1		;РЕГИСТР ХРАНЯЩИЙ ЧИСЛО БАЙТ НА ОТПРАВКУ/ПРИЕМКУ
i2c_index_PageAddrL:    	.byte	1		;РЕГИСТР ХРАНЯЩИЙ НОМЕР МЛАДШЕЙ СТРАНИЦЫ ДАННЫХ
i2c_index_PageAddrH:        	.byte	1		;РЕГИСТР ХРАНЯЩИЙ НОМЕР СТАРШЕЙ СТРАНИЦЫ ДАННЫХ
i2c_busy:			.byte	1		;РЕГИСТР работы (занятости) шины и2ц
i2c_Adress_Buffer_in:    	.byte	2		;здесь храним адрес того буфера в который будем сохранять принятые данные
i2c_Adress_Buffer_out:    	.byte	2		;здесь храним адрес того буфера из которого будем отправлять данные 

Дальше стоит описать макрос который запускает прием или передачу данных по шине для наглядного понимания приведу сразу строки из проекта

I2C_GO_START_in         DS1307,i2c_sawsarp,Time_status,2,100    (макрос приема)
I2C_GO_START_out        PCF8574led,i2c_sawp,Led_reg,1,400    (макрос передачи)

I2C_GO_START_in и I2C_GO_START_out это названия макросов
DS1307 и PCF8574led адреса устройств к которым мы обращаемся (у меня это 0b11010000 и 0b01000110) здесь необходимо сказать что все адреса обязательно прописываются с младшим битом =0 те на запись а в прерывании все поправится как надо само сабой
i2c_sawsarp и i2c_sawp это задание по которому работает прерывание (тут у нас 2 группы по 3 варианта i2c_sarp, i2c_sawsarp, i2c_saw2sarp, i2c_sawp, i2c_sawsawp, i2c_saw2sawp)
Time_status и Led_reg это названия регистров в оперативке которые принимают/отправляют данные.
Следующий параметр это кол-во байт на прием/отправку
Последний параметр указывает на скорость с которой мы будем гонять биты по шине в кГц формула там незамысловатая и при частоте кварца 8 МГц имеет ограничение на скорость ниже 16 кГц те меньше 16 задать нельзя. Вообще я гонял пока что до 480 кГц (именно 480 кГц по показаниям лог анализатора) причем DS1307 про которую в даташите указано only 100 нормально тянет на этой скорости.
И еще один важный момент: если идет чтение/запись с предварительной записью номера регистра/страницы размером 1byte/2byte то перед макросом должна быть запись в регистр Х того самого номера регистра/страницы к которому мы обращаемся те для чтения например минут и часов из DS1307 в буфер оперативы под именем Time_status нам надо сделать так

.DSEG
Time_status:         .byte   12  ;12 байт для обмена между мегой и часиками
....
.CSEG
.....
LDX 0x01        ;минуты хранятся в регистре 0х01
I2C_GO_START_in         DS1307,i2c_sawsarp,Time_status,2,100
....

Имя и размер буфера(ов) на прием/передачу можно сделать каким угодно но размещен он должен быть в оперативке
Идем дальше. Теперь расскажу как я организовал разрешение на запуск шины. У меня в оператве есть регистр

i2c_want_job:			.byte	1

Таким образом у меня 8 бит — 8 флагов на запрос включения шины. Нулевой бит(флаг) я закрепил за PCF8574key, второй за PCF8574led а третий бит за DS1307. Дальше я опрашиваю каждый бит i2c_want_job и если бит =1 то сначала проверяется регистр i2c_busy (который отвечает за то что шина занята на прием/передачу, устанавливается и сбрасывается он самостоятельно и что-то в него записывать не надо ), если i2c_busy=0 то тогда можно запускать задание на прием/передачу. Поскольку PCF8574key достаточно важная микросхема то именно ей я и отдал самый младший бит а это значит самый высокий приоритет. Кстати этот бит ставится по внешнему прерыванию INT0 от самой PCF8574key — очень удобно
Теперь надо сказать то что я держу в файле Job_name_I2C
;имена микросхем
.equ PCF8574key		=	0b01001110
.equ PCF8574led		=	0b01000110
.equ MCP23016lcd	=	0b01000000
.equ DS1307		=	0b11010000
;далее идут указания битов в регистре i2c_want_job
.equ key_flag=0
.equ led_flag=2
.equ time_flag=3
;имена режимов
.equ i2c_sarp	= 0b00000000	// Start-Addr_R-Read-Stop  							Это режим простого чтения. Например из слейва или из епрома с текущего адреса
.equ i2c_sawp	= 0b10000000		// Start-Addr_W-Write-Stop 							Это режим простой записи. 

.equ i2c_sawsawp    = 0b10000001		// Start-Addr_W-WrPageAdr-Write-Stop 	Это режим с предварительной записью нужного адреса страницы в 1 байт  а потом запись
.equ i2c_saw2sawp	= 0b10000011		// Start-Addr_WrPageAdrH-WrPageAdrL-Write-Stop 	Это режим с предварительной записью нужного адреса страницы в 2 байт  а потом запись

.equ i2c_sawsarp	= 0b00000001		// Start-Addr_W-WrPageAdr-rStart-Addr_R-Read-Stop 	Это режим с предварительной записью нужного адреса страницы в 1 байт  а потом чтение
.equ i2c_saw2sarp	= 0b00000011		// Start-Addr_WrPageAdrH-WrPageAdrL-rStart-Addr_R-Read-Stop 	Это режим с предварительной записью нужного адреса страницы в 2 байт  а потом чтение

.equ i2c_Err_msk	=	0b00001111		// Маска кода ошибок
.equ i2c_Err_NO		=	0b00000000		// All Right!		-- Все окей, передача успешна. 
.equ i2c_ERR_NA		=	0b00000100		// Device No Answer 	-- Слейв не отвечает. Т.к. либо занят, либо его нет на линии.
.equ i2c_ERR_LP		=	0b00001000		// Low Priority		-- нас перехватили собственным адресом, либо мы проиграли арбитраж
.equ i2c_ERR_NK		=	0b00000010		// Received NACK. End Transmittion. -- Был получен NACK. Бывает и так.
.equ i2c_ERR_BF		=	0b00000001		// BUS FAIL 		


А теперь само прерывание размером 400 байт(примерно) но при желании можно урезать если понимать что надо урезать
PUSHF		;SREG+R16(temp)
push r18	;используется только с TWDR
PUSHXYZ
push r0

clr r0
ldi ZH, high(twi_table)
ldi ZL, low(twi_table)
READR temp,TWSR		;читаем код состояния  .if @1<0x40 IN	@0,@1	 .else	LDS	@0,@1	 .endif
lsr temp	;сдвигаем на 3 младших бита		
lsr temp
lsr temp
ADZR temp	;Z=Z+temp
icall			

pop r0
POPXYZ
pop r18
POPF

reti
;======================================================================================
twi_table:
rjmp twi_00 ; 0x00 = FAIL
rjmp twi_08 ; 0x08 = START
rjmp twi_10 ; 0x10 = RESTART
rjmp twi_18 ; 0x18 = SLA+W ACK
rjmp twi_20 ; 0x20 = SLA+W NOACK
rjmp twi_28 ; 0x28 = SEND ACK
rjmp twi_30 ; 0x30 = SEND NOACK
rjmp twi_38 ; 0x38 = COLLISION
rjmp twi_40 ; 0x40 = SLA+R ACK 
rjmp twi_48 ; 0x48 = SLA+R NOACK
rjmp twi_50 ; 0x50 = RECV ACK
rjmp twi_58 ; 0x58 = RECV NACK

twi_00:		;Bus_fail
	ldi temp,i2c_ERR_BF	
	sts I2C_err, temp	;записываем код ошибки в регистр ошибок		
	sts i2c_busy,r0   ;снимаем флаг занятости передачи чтобы разрешить другим задачам воспользоваться шиной I2C
	outi TWCR,0<<TWSTA|1<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE	;
ret


twi_08:	; Ok_start
twi_10:	; Duble_start
	
	LDS  R18,i2c_SlaveAddress	;возьмем адрес нашего устройства причем в адрессе уже прописан 0 на конце те готов к передаче (Addr+W) 						
	lds  TEMP,do_I2C			;берем тех задание которое надо выполнить
	tst temp			;проверяем  на 0, если не 0 то у нас идет запись (Addr+W) и перепрыгиваем 
	brne twi_10_1
        ori  r18,0x01			;иначе к адресу добавляем 1 в 0-ой бит те Шлем Addr+R
	twi_10_1 out TWDR,r18
	outi TWCR,0<<TWSTA|0<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;  	// Go!
ret


twi_18:	;	MSlaW_ack:	получили подтверждение поле посылки адресса
	lds  TEMP,do_I2C			;берем тех задание которое надо выполнить и 
	cpi temp,i2c_sawp			;первым делом проверим 7 бит нашего задания через сравнение и если он единственный то шлем тупо наши данные
	brne MSlaW_ack_M1			;а иначе перескакиваем в раздел посылки первой страници адреса а может и единственной					
						
	send_data01:	 
	RAM_DATA_INZ  i2c_Adress_Buffer_out	;берем в Z адрес нашего буфера на отправку
	lds temp,  i2c_index_DATA	;заберем размер смещения от начала буфера 
	ADZR temp					;прибавим к Z значение						
	LD  R18,Z			;читаем значение по адрессу + смещение
	inc temp				;сразу увеличим смещение
	sts i2c_index_DATA, temp	 ;и сохраним назад 									
	out TWDR,r18	;загружаем данные в регистр отправки
	outi TWCR,0<<TWSTA|0<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;  	// Go!
	ret
				
	MSlaW_ack_M1:	;сейчас в ТЕМПЕ у нас наш код задания и проверив бит номер 1 мы сразу поймем какой у нас адресс : если он =1 то 2 байта а если =0 то 1 байт - поехали  
						
	sbrs temp,1			;если первый бит установлен то пропустим следующую команду а если сброшен то 
	rjmp MSlaW_ack_M11	;прыгаем на 5строк ниже отправлять младший адрес поскольку он единственный
	LDS R18,i2c_index_PageAddrH	;цепляем старший байт 					 
	out TWDR,r18					;на передачу в шину и отправляем
	outi TWCR,0<<TWSTA|0<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;  	// Go!
	ret 

	MSlaW_ack_M11:
	;первым делом сбрасываем 2 младших бита в do_I2C которые отвечают за наличие адреса в посылке
	;почему 2? да потому что мы здесь могли очутиться из раздела /*28*/	при посылке второго но младшего байта
	; те старший послали из этого раздела (на 5 строк выше) и опять пришли в /*18*/	из /*28*/... короче надо немного поразмыслить	
	andi temp, ~i2c_saw2sarp
	STS do_I2C,temp  ;вот теперь наше задание по отправке адреса выполнено и совесть 2-х младших битов чиста
	LDS R18,i2c_index_PageAddrL	;цепляем младший байт				 
	out TWDR,r18					;на передачу в шину и отправляем
	outi TWCR,0<<TWSTA|0<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;  	// Go!

ret


twi_20:		;MSlaW_nack:	нам ответили НАСКом поэтому заканчиваем передачу		
	sts i2c_busy,r0   ;снимаем флаг занятости передачи чтобы разрешить другим задачам воспользоваться шиной I2C								
	ldi temp,i2c_ERR_NA	
	sts I2C_err,temp		;записываем код ошибки
	outi TWCR,0<<TWSTA|1<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;   	// STOP
ret


twi_28:	;MData_out_ack

	lds  TEMP,do_I2C			;берем тех задание которое надо выполнить						
	cpi  TEMP,i2c_sawp		;если мы отправляем данные то идем  
	breq MData_out_ack_M1	;дальше отправлять с проверкой конца данных						
	cpi  TEMP,i2c_sarp	;если мы здесь очутились после отправки  адреса страници данных (раздел 18) для приемки те 2младших бита сброшены а 7 бит =0 
	breq MData_out_ack_M2	;то идем отправлять ПОВторный СТАРТ
	;иначе мы здесь поскольку надо отправить адрес младшей страницы ведь старшую мы уже отправили в разделе 18
	;а отправляем вот таким самым сложным способом
	rjmp MSlaW_ack_M11	;который кстати находится тоже в разделе 18
									
	MData_out_ack_M1:	;ЗДЕСЬ МЫ ОТПРАВЛЯЕМ ОЧЕРЕДНОЙ БАЙТ ДАННЫХ ЕСЛИ ОН НЕ ЛИШНИЙ
	;для начала проверим кончились ли байты для отправки
	lds temp, i2c_index_DATA	;берем смещение те сколько байт уже отправили 
	lds r18, i2c_index_DATA_END		;берем число байтов на отправку
	cp temp,r18									
	brne MData_out_ack_M1_01	;если не достигли конца то переходим к отправке
	;а иначе останавливаем трансляцию
	outi TWCR,0<<TWSTA|1<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;   	// STOP			
	sts i2c_busy,r0   ;снимаем флаг занятости передачи чтобы разрешить другим задачам воспользоваться шиной I2C
	ret
					
	MData_out_ack_M1_01:	 rjmp send_data01 ;прыгаем в раздел 18 где у нас есть фрагмент отправки байта из буфера
								 
	MData_out_ack_M2:	;ЗДЕСЬ МЫ ОТПРАВЛЯЕМ ПОВторный СТАРТ поскольку мы расчитываем принимать данные
			outi TWCR,1<<TWSTA|0<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;   	// Duble_start
ret


twi_30:	;MData_out_nack 
	ldi temp,i2c_ERR_NK  ;отправили данные а нам ответили НАСКом
	sts I2C_err,temp
	sts i2c_busy,r0	;снимаем флаг занятости передачи чтобы разрешить другим задачам воспользоваться шиной I2C
	outi TWCR,0<<TWSTA|1<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;   	// STOP
ret


twi_38:		;Luser_line 
	ldi temp,i2c_ERR_LP    ;у нас отобрали шину И2С
	sts I2C_err,temp
	sts i2c_busy,r0		;снимаем флаг занятости передачи чтобы разрешить другим задачам воспользоваться шиной I2C
	outi TWCR,0<<TWSTA|1<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;   	// STOP		
ret


twi_40:	;MSlaR_ack:	Послали SLA+R получили АСК. А теперь будем получать байты
	;для начала узнаем больше одного байта нам надо принять или только 1						
	lds temp, i2c_index_DATA_END
	cpi temp,1					 
	brne MSlaR_ack_M2				;не 1 байт то переходим MSlaR_ack_M2
	outi TWCR,0<<TWSTA|0<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE ;   	иначе ловим байт и посылаем НАСКом
ret
					
	MSlaR_ack_M2:	;разрешаем прием первого байта и отвечаем АСКом 
	outi TWCR,0<<TWSTA|0<<TWSTO|1<<TWINT|1<<TWEA|1<<TWEN|1<<TWIE ;
ret


twi_48:	;MSlaR_nack	Послали SLA+R, но получили NACK. Видать slave занят или его нет дома
	ldi temp,i2c_ERR_NA
	sts I2C_err,temp
	sts i2c_busy,r0	;снимаем флаг занятости передачи чтобы разрешить другим задачам воспользоваться шиной I2C
	outi TWCR,0<<TWSTA|1<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE;   	// STOP 

ret


twi_50:	;MData_in_ack Приняли байт.
	in R18,TWDR				;заберем наш байт
	RAM_DATA_INZ  i2c_Adress_Buffer_in	;берем в Z адрес нашего буфера приемника
	lds temp, i2c_index_DATA	;заберем размер смещения от начала буфера 
	ADZR temp					;прибавим к Z
	st  Z,R18				;сохраняем принятый байт в буфер
	inc temp				;увеличиваем смещение
	sts i2c_index_DATA, temp      ;и сохраняем
	;теперь проверим последний ли следующий байт и если последний то мы на него ответим НАСКом
	inc temp	;
	lds r18, i2c_index_DATA_END		;берем число байтов на отправку
	cp temp,r18	
	brne MData_in_ack_M2		;прыгаем по метке если не равны
	outi TWCR,0<<TWSTA|0<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE ;если ловим последний байт то посылаем НАСКом
	ret
	MData_in_ack_M2: 
		outi TWCR,0<<TWSTA|0<<TWSTO|1<<TWINT|1<<TWEA|1<<TWEN|1<<TWIE;  	// Go!
ret


twi_58:	;MData_in_nack Вот мы взяли последний байт, сказали NACK слейв обиделся и отпал.
	in R18,TWDR				;заберем наш байт
	RAM_DATA_INZ  i2c_Adress_Buffer_in	;берем в Z адрес нашего буфера приемника
	lds temp, i2c_index_DATA	;заберем размер смещения от начала буфера
	ADZR temp					;прибавим к Z
	st  Z,R18				;сохраняем принятый байт в буфер
	outi TWCR,0<<TWSTA|1<<TWSTO|1<<TWINT|0<<TWEA|1<<TWEN|1<<TWIE ;СТОП		
	sts i2c_busy,r0	;снимаем флаг занятости передачи чтобы разрешить другим задачам воспользоваться шиной I2C
ret
	

Писать в этом посту наверное больше нечего
архив с файлами удалил поскольку нашлось пара ошибок но в посту их исправил. В следующих статьях файлы уже с исправлениями
В ближайшее время напишу как это все работает с реальными примерами на вышеупомянутых микросхемах
Если есть вопросы или пожелания то пишите, как смогу отвечу… Пошел я дальше ремонт на кухне делать.
  • +1
  • 16 декабря 2013, 23:23
  • deses

Комментарии (37)

RSS свернуть / развернуть
500 байт прерывание — я застрелюсь лучше.

CPI TEMP,0x28
BREQ MData_out_ack
CPI TEMP,0x08
BREQ Ok_start                   
CPI TEMP,0x18
BREQ MSlaW_ack
CPI TEMP,0x10
BREQ Duble_start
CPI TEMP,0x00
BREQ Bus_fail
CPI TEMP,0x20
BREQ MSlaW_nack         
CPI TEMP,0x30
BREQ MData_out_nack
CPI TEMP,0x38
BREQ Luser_line
RJMP EXIT_INTERRUPT_TWI

Это убивает
+1
Уважаемый ILYAUL я не по наслышке знаю ваш темперамент и ту палемику которую Вы способны развести на ровном месте (я просматриваю форум иногда), и Вы не поверите но я почему то был уверен что вот такой Ваш комент будет первым в этой теме. А что собственно Вас смущает в CPI и BREQ или 500 байт… нет не надо отвечать я не хочу палемики. Если есть желание то скинте мне в личку Ваш уникальный код прерывания размером 21 бит (но Си я не понимаю и даже Goto… не воспринимаю)
0
TWI не делают на прерывании- это нонсенс. (очень медленный интерфейс по сравнению с ресурсами проца. И тем более столько сидеть в прерывании. К тому же для каждой шага обмена существует свой индивидуальный код т.ч. проверять все ошибки при выполнении определенного действия — тратить время.

;**************************************************
;*       ;;+INIT and write in  DS1337;;*	  	  *
;**************************************************
DS37INIT:
		ldi		count,BUFINTDS		;= Загружаем размер буфера InitDS1337
		rcall	TWI_START			;= стартуем
		ldi		temp,DS37SlaveAddr
		rcall	DATA				;= посылаем SlaveAddress
		cpi		temp,SLVNORMW		;- Если не $18 снова на START
		brne	RetInit				;= повтор
		clr		temp				;= Установка на регистр $00 DS1337 (секунды )
		rcall	DATA
		cpi		temp,NORMDATW		;= Проверка на ACK
		brne	RetInit				;-Если не $28 снова на START
FLASH_or_SRAM:
		sbrc	Flags1,fl_SRAM_FLASH
		rjmp	SRAM_DS1337
		lpm		temp,Z+				;= извлекаем данные
INIT_DS37:
		rcall	DATA				;= Вызываем подпрограмму отсылки
		cpi		temp,NORMDATW		;= Проверка на ACK
		brne	RetInit				;- Если не $28 снова на START
		dec		count				;= вычитаем счётчик данных для DS1337
		brne	FLASH_or_SRAM
		rjmp	OutINTDS37			;+ Инициализация закончена.
RetInit:
		rcall	STOP				;* STOP - Not GOOT
		rjmp	DS37INIT			;= снова на СТАРT
OutINTDS37:
		rcall	STOP				;/ STOP - GOOD
		ret
SRAM_DS1337:
		ld		temp,Z+
		rjmp	INIT_DS37

;**************************************************
;*   		;;+Проверка сбоя часов;;*   		  *
;**************************************************
TIME_ERROR:
		rcall	TWI_START			;+ стартуем
		ldi		temp,DS37SlaveAddr
		rcall	DATA				;+ посылаем SlaveAddress
		cpi		temp,SLVNORMW		;- Если не $18 снова на START
		brne	RESTART				;+ начинаем сначала
		ldi		temp,0x0F			;- Устанавливаем адрес статус регистра DS1337
		rcall	data				;+ Посылаем адрес регистра статуса
		cpi		temp,NORMDATW		;+ Проверяем
		brne	RESTART				;- если не $28 повтор
		rcall	TWI_START			;+ Ещё раз cтартуем
		ldi		temp,DS37SlaveAddr+1
		rcall	DATA				;- посылаем SlaveAddress на чтение
		cpi		temp,SLVNORMR		;+ Проверяем
		brne	RESTART				;- если не $40 повтор
		rcall	DATA				;+ Вызываем подпрограмму
		cpi		temp,NORMDATR		;- Если не $50
		brne	RESTART				;+ начинаем сначала
		lds		temp,TWDR			;- Если $50 считываем данные
		sbrs	temp,MsbOne			; Проверяем установлен старший бит
		rjmp	ERR_TIME_END
		sbr		Flags,1<<fl_Err_Time
ERR_TIME_END:
		ret
RESTART:
		rcall	STOP
		rjmp	TIME_ERROR
;**************************************************
;*    ;;/Программа чтения данных из DS1337;;*	  *
;**************************************************
TIME_Read:
		BeginInt					;- Заталкиваем в стек
		ldwi	Z,BUFDS1337			;+ Заносим начальный адрес SRAM DS37 в регистр (Z)
		ldi		count,BUFINTDS-1	;- Загружаем размер буфера InitDS1337
;* Сбрасываем флаг A2F
TIME_START:
		rcall	TWI_START			;+ стартуем
		ldi		temp,DS37SlaveAddr
		rcall	DATA				;+ посылаем SlaveAddress
		cpi		temp,SLVNORMW		;- Если не $18 снова на START
		brne	TIME_RESTART		;+ начинаем сначала
		ldi		temp,0x0F			;- Устанавливаем адрес статус регистра DS1337
		rcall	data				;+ Посылаем адрес регистра статуса
		cpi		temp,NORMDATW		;+ Проверяем
		brne	TIME_RESTART		;- если не $28 повтор
		clr		temp				
		rcall	data				;- Очищаем регистр статуса
		cpi		temp,NORMDATW		;+ Проверяем
		brne	TIME_RESTART		;- если не $28 повтор
TIME_Read1:
		rcall	TWI_START			;+ Ещё раз cтартуем
		ldi		temp,DS37SlaveAddr+1
		rcall	DATA				;- посылаем SlaveAddress на чтение
		cpi		temp,SLVNORMR		;+ Проверяем
		brne	TIME_RESTART		;- если не $40 повтор
TIME_Read4:
		rcall	DATA				;+ Вызываем подпрограмму
		cpi		temp,NORMDATR		;- Если не $50
		brne	TIME_RESTART		;+ начинаем сначала
		lds		temp,TWDR			;- Если $50 считываем данные
		st		Z+,temp				;+ и записываем в ОЗУ
		dec		count				;- вычитаем счётчик
		brne	TIME_Read4			;+ нет - след. данные
		rcall	DSNACK
		rcall	STOP				;/ - STOP GOOD
		EndInt						;- Выталкиваем из стека
		sbr		Flags,1<<fl_Minut	;+ Устанавливаем флаг минуты
		reti
TIME_RESTART:
		rcall	STOP				;- формируем STOP
		rjmp	TIME_START			;+ или очищать рег. статуса

;|*************************************************
;+         		;;/Подпрограммы;;+				  *
;|*************************************************

;**************************************************
;*      ;;+Подпрограмма Init TWI_START;;*         *
;*   ;;+Формирование START и проверка ответа.;;*  *
;**************************************************
TWI_START:
		ldi		temp,(1<<TWINT)|(1<<TWSTA)|(1<<TWEN)
		sts		TWCR,temp			;= формирование режима START
waitST:
		lds		temp,TWCR			;= Проверка бита TWINT
		sbrs	temp,TWINT
		rjmp	waitST				;= Не получен -цикл
		lds		temp,TWSR			;= Проверка кода ответа
		cpi		temp,START
		breq	RETURN				;= Правильный код -выход из подпрограммы
		cpi		temp,REPSTART
		brne	TWI_START			;= Нет - повтор.
RETURN:
		ret
;**************************************************
;*   ;;+Подпрограмма IN/OUT данных в DS;;* 		  *
;**************************************************
DATA:
		sts		TWDR,temp
		ldi		temp,(1<<TWINT)|(1<<TWEN)|(1<<TWEA)
		sts		TWCR,temp			;= режим данные
waitDT:
		lds		temp,TWCR			;= Проверка бита TWINT
		sbrs	temp,TWINT
		rjmp	waitDT
		lds		temp,TWSR			;= получаем код передачи
		ret
;**************************************************
;*      ;;+Подпрограмма формирование STOP;;*      *
;**************************************************
STOP:
		ldi		temp,(1<<TWINT)|(1<<TWSTO)|(1<<TWEN)
		sts		TWCR,temp			;= Формирование режима STOP
waitTWIStop:
		lds		temp,TWCR			;= Считываем регистр управления
		sbrc	temp,TWSTO			;= Проверяем бит STOP
		rjmp	waitTWIStop			;= Не установлен - повторяем проверку
		ret
;**************************************************
;*              ;;+Формируем NACK;;*              *
;**************************************************
DSNACK:
		ldi		temp,(1<<TWINT)|(1<<TWEN)|(0<<TWEA)
		sts		TWCR,temp			;= формируем режим NACK
waitDT1:
		lds		temp,TWCR
		sbrs	temp,TWINT			;= Проверка бита TWINT
		rjmp	waitDT1				;= Ждём
		lds		temp,TWSR			;= получаем код ответа
		cpi		temp,NACK			;- Если не $58
		brne	TIME3				;= Всё сначала - обидно!
		lds		temp,TWDR			;= Если $58 считываем данные
		st		Z,temp				;= и записываем в ОЗУ
		ret
TIME3:
		rcall	STOP				;= формируем STOP
		rjmp	TIME_Read1			;= начинаем заново

;|*************************************************
0
мило. Сунули код в автомат и затупили до ответа с шины. То есть перестали обрабатывать события в реальном времени.
+1
О каком реальном времени Вы говорите? Не очень понял.
Прошла минута, отработал INT0:
sbr Flags,1<<CLOCK
reti

Дальше по флагу
rcall TIME_Read
0
Архитектура AVR для прерывания TWI не содержит однозначности в отличие от других прерываний USART, TIMERs, ADC и прочего. Которые могут обрабатываться достаточно быстро.
ИМХО, его в крайнем случае можно использовать только для отправки, получения данных (т.к. код при этом одинаков) а всё остальное за рамками прерывания.
0
Хорошо давайте обменяемся цифирками. Вопрос таков: сколько машинных тактов Вам понадобится чтобы обратится к микросхеме PCF8574 (GPIO) и прочитать состояние её выводов? У меня получилось 236 тактов. Это при том что достаточно большой вес прерыванию придает возможность отправлять/принимать потоком до 65535 байт и я не просто так это сделал — мне не достаточно отправлять до 255 байт поскольку в планах отправки весом больше 1 кило. Ну вы пока посчитайте и ответе на вопрос.
0
Т.е Вы предлагаете мне сейчас изучить DS на совершенно неизвестную для меня микросхему, написать для нее код и посчитать такты :) Перед Вами код для DS1337 прямая родственница DS1307 и судя по всему ВЫ ее знаете. Посчитайте, даже предположив, что TIME_READ это код прерываниия. Т.е другое прерывание в этот момент не возможно. Будем считать что ошибок на шине нет т.е нет повторов.
st              Z+,temp                         ;+ и записываем в ОЗУ
                dec             count                           ;- вычитаем счётчик
Это счетчик байтов которые необходимо отправить.
0
rcall   TWI_START                       ;+ стартуем
                ldi             temp,DS37SlaveAddr
                rcall   DATA                            ;+ посылаем SlaveAddress
                cpi             temp,SLVNORMW           ;- Если не $18 снова на START
                brne    TIME_RESTART            ;+ начинаем сначала
                ldi             temp,0x0F                       ;- Устанавливаем адрес статус регистра DS1337
                rcall   data                            ;+ Посылаем адрес регистра статуса
                cpi             temp,NORMDATW           ;+ Проверяем
                brne    TIME_RESTART            ;- если не $28 повтор
                clr             temp                            
                rcall   data                            ;- Очищаем регистр статуса
                cpi             temp,NORMDATW           ;+ Проверяем
                brne    TIME_RESTART            ;- если не $28 повтор

Вот это из кода надо выбросить т.к. он очищает регистр статуса ибо часы у меня настроены на вывод один раз в минуту и прерывание в микросхеме необходимо сбрасывать иначе последующие не получите.
0
BeginInt EndInt макросы заталкивающие/выталкивающие temp и count и сохраняет SREG.
0
ОЙ я перепутал 111 машинных тактов уходит на обращение и чтение ног у PCF8574
0
Тобиш беглым взглядом я понял что от команды старт и до команды стоп на шине мы не можем выйти от сюда? Или в контроллере нет места прописать другие задания? ИМХО если есть вектор прерывания то его надо использовать поскольку это правильно.
0
TWI не делают на прерывании- это нонсенс.
Как это не делают? Всё наоборот — именно по прерываниям и надо делать.
очень медленный интерфейс
Тем более — завершилась очередная операция I2C — прерывание и так пока цикл обмена не закончится.
0
TWI не делают на прерывании- это нонсенс.
ЛОЛШТО? А на чем же его делать тогда? На прерываниях там все как раз очень мило и замечательно получается. Автомат сразу же получается из коробки, его нам сама архитектура на блюдечке дает. Состояния проверять не надо — табличный переход может решить все проблемы быстродействия. А обработчик может быть хоть мегабайт, это не имеет значения, главное как быстро он выполняется.
+1
Это каким образом нам прерывание TWI дает возможность не проверять ответ адресата? При условии работы на шине разнотипных устройств, со своими командами общения, до каких величин вырастает таблица? Что может быть быстрее приведённого мной выше обработчика INT0, который можно замаскировать и выполнить в свободное от основной работы время? Для старших микросхем ему даже RJMP не нужен.
На самом деле по тому же принципу можно использовать само прерывание TWI т.е. просто флаг. И обработчик TWI в MAIN, RCALL, ICALL ,RET/ При этом только критические секции скрыть СLI.
0
Что значит не проверять ответ адресата? В слейве TWI проверяет свой адрес и если не он — автомат обрывается, а весь обмен игнорируется до стопа или повстарта. Ну и если уж сильно надо, то никто не запрещает отключить прерывание TWI когда не нужно.
0
Ветки-то короткие, а проходить будем за раз только одну. Так что пофиг на размер, вообще-то.
0
Cам было дело разбирался с этим интерфейсом и у меня есть некоторые рекомендаци.
Используйте прерывания, что-бы не говорили. Если пишете на asm (это мой вариант), то используйте его силу на полную, а именно воспользуйтесь такой вот конструкцией:


IRQ_TWI:
...
ldi ZH, high(twi_table)
ldi ZL, low(twi_table)
lds r16, TWSR
lsr r16
lsr r16
lsr r16
add ZL, r16
adc ZH, r0 ; r0 = 0
icall
...
reti

twi_table:
rjmp twi_00 ; 0x00 = FAIL
rjmp twi_01 ; 0x08 = START
rjmp twi_02 ; 0x10 = RESTART
rjmp twi_03 ; 0x18 = SLA+W ACK
rjmp twi_04 ; 0x20 = SLA+W NOACK
rjmp twi_05 ; 0x28 = SEND ACK
rjmp twi_06 ; 0x30 = SEND NOACK
rjmp twi_07 ; 0x38 = COLLISION
rjmp twi_08 ; 0x40 = SLA+R ACK 
rjmp twi_09 ; 0x48 = SLA+R NOACK
rjmp twi_10 ; 0x50 = RECV ACK
rjmp twi_11 ; 0x58 = RECV NACK

twi_00:
...
ret

twi_01:
...
ret

...

twi_11:
...
ret


Думаю что она делает, понять не сложно :)
0
Только с учетом что ICALL «умеют» использовать не все микросхемы AVR
0
RET умеют все. Этого более чем достаточно.
0
так там, по-ходу, фича как раз в icall — он же с z-парой работает. и именно из-за этого убирается куча cpi/brne. а icall таки да — не вот всех камнях есть
0
куча cpi/brne

т.е. breq, но в данном случае — не суть
0
А сунуть адрес в стек и сделать ret религия не позволяет?
0
да я понимаю, как можно организовать:) но речь-то шла именно про icall, т.е. именно про приведенный кусок кода
0
Там где нет icall и iic нет :) В лучшем случае огрызок интерфейса.
0
icall и iic нет

[записывает в избранное]
0
Целая дискуссия разгорелась из-за маленького icall'а :-).
Как правильно сказал наш уважаемый DIHALT, если нет icall мы его создадаим сами.
Позволю себе еще раз выложить «приведенный кусок кода», но уже без icall (правда с ijmp :-)).

IRQ_TWI:

ldi ZH, high(twi_table)
ldi ZL, low(twi_table)
lds r16, TWSR
lsr r16
lsr r16
lsr r16
add ZL, r16
adc ZH, r0; r0 = 0
rcall i_jmp

reti

i_jmp:
push ZL
push ZH
ret

twi_table:
rjmp twi_00; 0x00 = FAIL
rjmp twi_01; 0x08 = START
rjmp twi_02; 0x10 = RESTART
rjmp twi_03; 0x18 = SLA+W ACK
rjmp twi_04; 0x20 = SLA+W NOACK
rjmp twi_05; 0x28 = SEND ACK
rjmp twi_06; 0x30 = SEND NOACK
rjmp twi_07; 0x38 = COLLISION
rjmp twi_08; 0x40 = SLA+R ACK
rjmp twi_09; 0x48 = SLA+R NOACK
rjmp twi_10; 0x50 = RECV ACK
rjmp twi_11; 0x58 = RECV NACK

twi_00:

ret

twi_01:

ret



twi_11:

ret
0
Переписал прерывание и целый час разбирался почему не работает
оказалось все бонально
lds r16, TWSR
не для меги16
0
Ну да, достаточно читабельная сортировка
0
По тексту НАСК («а нам ответили НАСКом») — это NACK? поправьте или меня или текст.
0
Да
0
— Ваше политическое кредо?
— Всегда — восторженно ответил Полесов.
12 стульев.
0
Ну да НАСК это «русское» произношение «Нет ответа» NACK
0
Ну тогда уж скорее НАК или даже НАЦК (для «кул-хацкеров»)… )) Впрочем не очень это важно, конечно.
0
CPI TEMP,0x28
BREQ MData_out_ack
CPI TEMP,0x08
BREQ Ok_start                   
CPI TEMP,0x18
BREQ MSlaW_ack
CPI TEMP,0x10
BREQ Duble_start
CPI TEMP,0x00
BREQ Bus_fail
CPI TEMP,0x20
BREQ MSlaW_nack         
CPI TEMP,0x30
BREQ MData_out_nack
CPI TEMP,0x38


Вот так лучше не делать, это долго и муторно. Лучше сделать табличный переход, тогда от ближайшей ветви до дальней время выполнения будет одинаково.
0
табличный не лучше, т.к. для получения адреса в таблице нужны ещё логические операции.
лучше другие 2 варианта:
1) деление множеств пополам, либо
2) как у автора, только в порядке максимальной вероятности.
0
так я вроде и сделал в порядке максимальной вероятности а не тупо по-порядку
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.