Всем привет! Начал разбираться с протоколом TWI(I2C) на авр, а именно atmega 8. Почитал статьи на изиэлектроник, написал обработку на прерываниях(подсматривая в пример), чтобы вникнуть. Начал тестить в протеус иии... Неизвестная бага не дает мне покоя. Старт производит, адрес слэйва передает(с битом на запись), адрес нужной ячейки передает, а уже при записи пишет ерунду, все время одно и то же, первый 0x00, второй 0xff, потом стоп(стоп запланирован) и какие бы байты я не писал в обработчике, передает 2 одинаковых 00 и ff... Ошибок не выдает, я поставил флаг на ошибки(1 на ноге), в нужное подпрерывание попадает ровно столько раз, сколько надо(тое ставил флаги туда), да и ставил флаги в другие обработчики прерываний(в которые не надо попадать в данном случае), но все норм. Я скину сюда часть кода, эквиваленты, озу нужные, макрос на отправку, и обработчики прерываний, которые используются для отправки байтов. Пишите, если нужна будет доп информация.
Кстати, все прогнал по трассировщику в студии, просто сделал rcall на нужную последовательность обработчиков
.equ F_CPU = 8000;частота процессора
.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
.DSEG
;"******" - нужно вносить изменения в основном цикле!!!!!!
I2C_SlaveAdres: .byte 1;регистр с адресом слэйва******************
I2C_busy: .byte 1; байт флага занятости шины и2с 1-занято! 0-свободно!
I2C_ERR: .byte 1;байт для ошибок
I2C_DO: .byte 1;задание, читать или записывать, 2 байта адрес или 1?**********
I2C_LOW_ADRES: .byte 1;младший байт адреса ячейки для чтения/записи(если адрес однобайтовый, то младший единственный)********
I2C_HIGH_ADRES: .byte 1;старший байт адреса ячейки для чтения/записи***************
I2C_KOL_BYTE: .byte 1; сколько байт нужно прочесть или записать? писать количетсво!!!!***********
I2C_BUFER: .byte 5; байты, которые хотим пропихнуть*************
I2c_ADRES_BUFER_IN: .byte 2;младший и старший байты адреса начала буфера данных для приема!***************
I2C_ADRES_BUFER_OUT: .byte 2;младший и старший байты адреса начала буфера данных для отправки!****************
I2C_INDEX_DATA: .byte 1;размер смещения,т.е. сколько байт уже отправлено, вначале отправки должен ранвться 0!
TWSI:;перрывание i2c
PUSHF;р16 и sreg в стэк
PUSH R17;в стек
PUSH R18
PUSH R0;в стек
PUSH R30;в стек
PUSH R31;в стек
CLR R0;нужен 0
LDI ZH,HIGH(TWI_TABLE);заносим в индексную пару адрес таблицы причины прерывания и2с
LDI ZL,LOW(TWI_TABLE);и младший байт
UIN R16,TWSR;кода прерывания в р16
LSR R16;сдвиг влево
LSR R16;еще
LSR R16;и еще
ADD ZL,R16;прибавляем значение к адресу таблицы
ICALL;переходим на причину прерывания
POP R31;достаем из стека z пару
POP R30;
POP R0;р0 из стека
POP R18
POP R17;р17 из стека
POPF;достаем р16 и sreg
RETI;выход из прерывания
TWI_TABLE:;таблица прерываний
RJMP TWI_0x00;0x00 Bus Fail Автобус сломался… эээ в смысле аппаратная ошибка шины. Например, внезапный старт посреди передачи бита.
RJMP TWI_0x08;0x08 Start Был сделан старт. Теперь мы решаем что делать дальше, например послать адрес ведомого
RJMP TWI_0x10;0x10 ReStart Был обнаружен повторный старт. Можно переключиться с записи на чтение или наоборот. От логики зависит.
RJMP TWI_0x18;0x18 SLA+W+ACK Мы отправили адрес с битом записи, а в ответ получили ACK от ведомого. Значит можно продолжать.
RJMP TWI_0x20;0x20 SLA+W+NACK Мы отправили адрес с битом записи, а нас послали NACK. Обидно, сгенерим ошибку или повторим еще раз.
RJMP TWI_0x28;0x28 Byte+ACK Мы послали байт и получили подтверждение, что ведомый его принял. Продолжаем.
RJMP TWI_0x30;0x30 Byte+NACK Мы послали байт, но подтверждение не получили. Видимо ведомый уже сыт по горло нашими подачками или он захлебнулся в данных.
;Либо его ВНЕЗАПНО посреди передачи данных украли инопланетяне.
RJMP TWI_0x38;0x38 Collision А у нас тут клановые разборки — пришел другой мастер,
;по хамски нас перебил, да так, что мы от возмущения аж заткнулись. Ничего I’l be back! До встречи через n тактов!
RJMP TWI_0x40;0x40 SLA+R+ACK Послали адрес с битом на чтение, а ведомый отозвался. Хорошо! Будем читать.
RJMP TWI_0x48;0x48 SLA+R+NACK Крикнули в шину «Эй ты, с адресом ХХХ, почитай нам сказки» А в ответ «Иди NACK!»
;В смысле на запрос адреса с битом чтения никто не откликнулся. Видимо не хотят или заняты. Также может быть никого нет дома.
RJMP TWI_0x50;0x50 Receive Byte Мы приняли байт. И думаем что бы ответить ведомому. ACK или NACK.
RJMP TWI_0x58;0x58 Receive Byte+NACK Мы приняли байт от ведомого и сказали ему «иди NACK!» И он обиженый ушел, освободив шину.
TWI_0x10: ;повторный старт
TWI_0x08:;произвели старт!
LDS R16,I2C_SlaveAdres;адрес слэйва
LDS R17,I2C_DO;что нужно, запись или чтение?
CPI R17,0
BRNE TWI_0x10_WR
ORI R16,1<<0
TWI_0x10_WR:
OUT TWDR,R16;отправляем адрес слейва по шине и2с
OUTI TWCR,0<<TWEA|0<<TWSTA|0<<TWSTO|1<<TWEN|1<<TWIE|1<<TWINT ;флаг преывания, блок тви и разрешаем прерывания
;TWINT флаг прерывания
;TWEA 1-ACK, 0-NACK
;TWSTA 1-start, 0-no start
;TWSTO 1-stop
;TWEN блок TWI включен
;TWIE - разрешаем прерывания TWI
RET;выход из подпрерывания
TWI_0x18:;подтверждение после посылки байта адреса и бита записи
LDS R16,I2C_DO;загружаем тех задание в регистр
SBRS R16,0;проверяем, нужно ли нам только записать байт? если да, то переход БЕЗ АДРЕСОВ!
RJMP TWI_0x18_sawp;чтение тут невозможно! т.к. на чтении будет другое подпрерывание
SBRC R16,0;проверяем, а не нужно ли нам послать адрес ячейки? в которую хотим писать
RJMP TWI_0x18_sawsawp;если нужно , то переход. Адрес ячейки состоит из одного байта! соответственно младшего
;если ничего не подошло, значит нужно отправить адрес состоящий из двух байт
;передачу начинаем со старшего байта, затем просто в тех задании поправим на пересылку оставшегося младшего байта
LDS R17,I2C_HIGH_ADRES;загружаем старший байт
ANDI R16,~(1<<0);обнуляем первый бит тех задания, тем самым переводим его на отправку еще одного байта адреса(младшего)
STS I2C_DO,R16
OUT TWDR,R17;загружаем старший байт адреса в тви
RET;выход из подпрерывания
TWI_0x18_sawsawp:;ЭТО КЛОООООН!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Запись только младшего байта адреса
LDS R17,I2C_LOW_ADRES;загружаем младший байт адреса ячейки слейва
DEC R16;первращаем sawsawp/sawsarp в просто в sawp или sarp!!!, т.е. загружаем в задание только запись! нужный байт адреса мы уже отослали! теперь только запись/чтение
STS I2C_DO,R16 ;закидываем в задания
OUT TWDR,R17;загружаем адрес ячейки в тви для записи
OUTI TWCR,0<<TWEA|0<<TWSTA|0<<TWSTO|1<<TWEN|1<<TWIE|1<<TWINT; передача младшего байта адреса!
RET;выход из подпрерывания
TWI_0x18_sawp:;ЭТО КЛОООООН!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! запись самих байтов
;(сюда я вставлял счетчик, сколько раз здесь бывает процессор, и все сходится)
LDS R16,I2C_KOL_BYTE;считываем, сколько байт нам нужно записать????
LDS R18,I2C_INDEX_DATA;счетчик, сколько байт скинули
CP R16,R18; кончились байты то?
BREQ TWI_0x18_STOP;если да, то переход и СТОП!!!! говорим слейву: хватит братан, но передаем последний байт)
LDS ZL,I2C_ADRES_BUFER_OUT //младший байт адреса буфера данных озу, откуда брать байты на отправку
LDS ZH,I2C_ADRES_BUFER_OUT+1 //старший байт адреса буфера данных озу, откуда брать байты на отправку
ADD ZL,R18;прибавляем к адресу количество байт, которые отправили
ADC ZH,R0
INC R18;увеличиваем счетчик на 1
STS I2C_INDEX_DATA,R18;отправляем в озу
LD R17,Z;берем один из.. байтов по адресу индексной пары
;(сюда я вставлял строчку LDI R17,1 но отправляет в итоге не одни 1, а опять таки 00 и ff)
OUT TWDR,R17;отправляем в регистр для передачи
OUTI TWCR,0<<TWEA|0<<TWSTA|0<<TWSTO|1<<TWEN|1<<TWIE|1<<TWINT ;передача очередного байта данных!
RET;выход из подпрерывания
TWI_0x18_STOP:;СТОПЭ братан слейв,наелся ты байтов, отдохни ЭТО КЛОООН!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
STS I2C_busy,R0;освобождаем линию тви
OUTI TWCR,1<<TWEA|0<<TWSTA|1<<TWSTO|1<<TWEN|1<<TWIE|1<<TWINT ;запись ОКОНЧЕНА!
RET;выход из подпрерывания
TWI_0x28:
LDS R16,I2C_DO;и так, м ыздесь потому что: 1-отправили адрес и нужна запись байтов в ячейки
;2- отправили адрес и нужно чтение и 3 - отправили старший байт памяти, нужно отправить младший.
CPI R16,i2c_sawp;нужна запись?
BREQ TWI_0x18_sawp;переход
SBRC R16,0;нужно записать оставшийся младший байт?
RJMP TWI_0x18_sawsawp;переход!
;а иначе повторный старт!!!!
OUTI TWCR,1<<TWEA|1<<TWSTA|0<<TWSTO|1<<TWEN|1<<TWIE|1<<TWINT ;повторный старт, т.к. чтение!
RET;выходи из подпрерывания
.MACRO I2C_OUT;НУЖНО ПРЕДВАРИТЕЛЬНО ЗАКИНУТЬ ОТПРАВЛЯЕМЫЕ ДАННЫЕ В И2С БУФЕР!!!
;0-адрес слейва, младший байт = 0!!!
; 1-запись или чтение? если с адресом, то сколькибитный?
;2-младший байт адреса регистра слэйва
;3-старший байт адреса регистра слейва(если не нужен, то 0)
;4-сколько байт нужно записать, прочитать?
;5-какая скорость передачи нужна?
;6-откуда брать байты?
CLR R16
STS I2C_INDEX_DATA,R16
LDI R16,1
STS I2C_BUSY,R16
OuTI I2C_SlaveAdres,@0
//OUTI TWAR,@0;заружаем адрес слэйва
LDI R16,@1;загружаем задание
STS I2C_DO,R16
.if @3>0x00
LDI ZL,LOW(@2);адрес регистра слэйва для приема/передачи
LDI ZH,HIGH(@3)
STS I2C_LOW_ADRES,ZL
STS I2C_HIGH_ADRES,ZH
.else
LDI ZL,LOW(@2)
STS I2C_LOW_ADRES,ZL
.endif
LDI R16,@4;загружаем значение количества байт для приема/передачи
STS I2C_KOL_BYTE,R16
OUTI TWSR,0<<TWPS1|0<<TWPS0;предделитель 1
LDI R16,((F_CPU/@5)-16)/2;расчетскорости приема/передачи данных
UOUT TWBR,R16
LDI ZL,LOW(@6)
LDI ZH,HIGH(@6)
STS I2C_ADRES_BUFER_OUT,ZL
STS I2C_ADRES_BUFER_OUT+1,ZH
OUTI TWCR,1<<TWINT|1<<TWEA|1<<TWSTA|0<<TWSTO|1<<TWEN|1<<TWIE;запуск! отправляем старт!
;TWINT флаг прерывания
;TWEA 1-ACK, 0-NACK
;TWSTA 1-start, 0-no start
;TWSTO 1-stop
;TWEN блок TWI включен
;TWIE - разрешаем прерывания TWI
.ENDM
Тело:
OUTI I2C_BUFER,1
OUTI I2C_BUFER+1,2
I2C_OUT 0xA2,i2c_sawsawp,0x03,0,2,200,I2C_BUFER - сюда больше не вернется
Main:
зацикливание
Rjmp main
П.С. вот что пишет i2c debuger в протеусе: S A2 A 03 A 00 A FF A P