attiny817 twi manual

AVR

Как работать с TWI новых тинь?


К сожаленю, даташит на процессоры стеснительно умалчивает об этой мелочи. Немного описаны биты в регистрах, состояния интерфейса — и всё. Стартовые решения по этой теме тоже отсутствуют, в аппликешн нотах — гордо перемаркированные старые атмеловские аппноты, совершенно неподходящие к новой аппаратуре.
Наверное, в Микрочипе ждут, пока Горнист за них мануал напишет. Ну ладно, я могу попробовать, на примере чтения термометра MCP9800. Простенько, с тормозами, без прерываний. Но надеюсь, что и этого будет достаточно.

1) Инит.
Инит нового i2C достаточно прост. Пишем константу для желаемого битрейта в регистр TWI0_MBAUD, для ста килобит при частоте ядра 20MHz это будет 90.
Можно просто считать по формуле константа=(частота_ядра/2*желаемый_битрейт)-10. Я пробовал, получается похоже.
Затем в регистр TWI0_MCTRLA пишем 0b00000011 — комбинацию из Smart Mode Enable и Enable TWI Master.
После этого i2C переходит в неопределённое состояние, и попытка работы с ним вызовет ошибку. Из неопределённого состояния он через некоторое время сам перейдёт в состояние Idle по таймауту, или когда увидит на шине STOP от другого мастера.
Если у вас один мастер на шине, как в нашем случае — можно принудительно перевести шину в состояние IDLE, записав 0b00000001 в регистр TWI0_MSTATUS.
А если у вас мультимастер конфигурация, после разрешения мастера надо читать TWI0_MSTATUS пока два младших бита не примут состояние 01, и тогда шина готова к работе. Вообще, это полезный трюк — убедиться, что шина свободна, перед тем, как начинать транзакцию.

2) Старт.
Следите за руками — просто берём адрес слейва с битом записи, и кладём его в регистр TWI0_MADDR.

Дальше оно само сделает старт, само передаст адрес, само поймает бит подтверждения. Круто, правда?

После того, как всё это произойдёт — в регистре TWI0_MSTATUS установится один из флагов — или Read Interrupt Flag TWI_RIF(бит 7) или Write Interrupt Flag TWI_WIF(бит 6). Всё зависит от бита записи в адресе слейва. Если просили запись — появится бит 6, если чтение — бит 7.
Одновременно с ними в бите 4 будет принятый ACK/NAK от слейва, 0=ACK, 1=NAK.

В итоге, чтобы сделать операцию Master Transmit — надо записать адрес SLA+W в регистр TWI0_MADDR, затем читать TWI0_MSTATUS пока бит 6 не станет единицей. Бит 4 скажет нам, ответил слейв или нет.

3) Передача.
Просто пишем данные для передачи в регистр TWI0_MDATA. Ждём пока бит 6 регистра TWI0_MSTATUS не станет единицей.

4) Стоп.
Пишем 0b00000011 (STOP) в регистр TWI0_MCTRLB.
Ну и всё.

Теперь Master Recieve.
1) делаем старт — пишем SLA+W в регистр TWI0_MADDR, ждём Write Interrupt Flag

2) пишем адрес чтения — записываем его в TWI0_MDATA, ждём Write Interrupt Flag

3) Делаем повторный старт — SLA+R пишем в TWI0_MADDR.
Тут начинаются различия. После записи SLA+R оно делает START, предаёт адрес с битом чтения, получает ACK/NAK, и само читает один байт с шины. После этого устанавливается флаг Read Interrupt Flag.

4) читаем очередной байт. Просто читаем TWI0_MDATA. Сам факт чтения регистра TWI0_MDATA вызывает отправку ACK слейву, чтение очередного байта, и появление флага Read Interrupt Flag когда все операции завершатся.

Подытоживая — для чтения произвольного количества байт из слейва просто ждём повления Read Interrupt Flag, потом читаем WI0_MDATA. Повторяем, пока не надоест.

5) Надоело читать, спасибо, больше не надо.
Тут тоже просто — дожидаемся Read Interrupt Flag, затем в TWI0_MCTRLB записываем 0b00000111 (комбинация NAK+STOP), и только потом считываем пришедший байт из регистра TWI0_MDATA. Порядок этих действий важен, его нарушение ведёт к сбою в работе I2C.

Если мы сделали как написано — у нас есть последний байт от слейва, больше ничего ждать не надо, программа может заниматься своими делами. Здесь и таится один подводный камушек — интерфейс тоже продолжает заниматься своими делами. Он отсылает слейву NAK, затем делает STOP, и всё это длится около 40 микросекунд.

А по завершению никакой флаг прерывания не устанавливается. В большинстве случаев это не является проблемой, допустим, если сразу после команды «STOP» записать новый адрес в TWI0_MADDR, сначала сформируется stop-состояние, повисит пару десятков микросекунд, потом сформируется start-состояние, потом начнёт передаваться адрес. Для программы вся разница будет в том, что Write Interrupt Flag появится через 150 микросекунд, а не через 120.

Проблема в том, что мы оставили в регистре TWI0_MCTRLB третий бит в единице. И при следующей операции Master receive он отдаст слейву NAK после первого считанного байта. А если попытаться очистить TWI0_MCTRLB до окончания формирования STOP — то стоп не сформируется. Эта проблема тоже легко решается — надо перед переходом в режим Master receive очистить TWI0_MCTRLB.

А в остальном, новый TWI работает весьма даже неплохо. Только режимы Slave ещё не опробовал — пока необходимости не возникало.

.macro outi1
ldi	r16,@1
sts	@0,r16
.endm

		outi1	TWI0_MBAUD,95		;=((20MHz/(2*100KHz))-10)
		outi1	TWI0_MCTRLA, 0b00000011	; smen|enable
		outi1	TWI0_MSTATUS,0b00000001	; force status=>idle
		outi1	TWI0_MADDR,  0b10011010	; sla/w
		rcall	waitmwif
		sbrc	r16,4
		rjmp	_iic_in_err
		outi1	TWI0_MDATA,  0b00000001	; Pointer
		rcall	waitmwif
		outi1	TWI0_MDATA,  0b01100000	; Config
		rcall	waitmwif
_iic_in_err:	outi1	TWI0_MCTRLB, 0b00000011	; stop

ReadTerm:	outi1	TWI0_MADDR,  0b10011010	; sla/w
		rcall	waitmwif
		outi1	TWI0_MDATA,  0b00000000	; Pointer
		rcall	waitmwif
		outi1	TWI0_MCTRLB, 0b00000000	; no cmd, just clear ACKACT bit
		outi1	TWI0_MADDR,  0b10011011	; sla/r
		rcall	waitmrif	
; Вот здесь первый байт считан, клок притянут к земле, пауза.
; Забираем первый байт.  Это вызывовет автоматическое  формирование ACK и считывание второго байта.
		lds	r16, TWI0_MDATA
		sts	anydata,r16
		rcall	waitmrif	
; А вот тут оно считало второй байт, и дальше надо действовать именно в таком порядке - записать команду 
		outi1	TWI0_MCTRLB, 0b00000111	; ACKact=1(NAK) + STOP
		lds	r16, TWI0_MDATA
		sts	anydata+1,r16
		ret

; Разное вспомогательное
; Ожидание флага Write Interrupt
waitmwif:	lds	r16,TWI0_MSTATUS
		sbrs	r16,6
		rjmp	waitmwif
		ret		
; Ожидание флага Read Interrupt
waitmrif:	lds	r16,TWI0_MSTATUS
		sbrs	r16,7
		rjmp	waitmrif
		ret
  • +6
  • 30 июля 2018, 19:31
  • Gornist

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

RSS свернуть / развернуть
Ухтыж? Т.е. теперь не нужно дрочить автомат по всем состояниям?
0
Что это значит? В обычных AVR автомат аппаратный, заточенный под асинхронное использование — всё дрочится в железе. Всё, что требуется от программы — прерывание и свитч по коду состояния. В нужном кейсе остаётся просто достать байтик из буфера, забрать в буфер или, скажем, дать повторный старт при срабатывании арбитража. Мне это куда больше нравится, чем самому хранить состояние и проверять зоопарк флажков…
0
Да блин, там не автомат, а полуфабрикат. Автомат это когда сунул адрес, указал буфер или байт, а дальше оно само, а ты только получил флаги итогового результата, что пошло так или не так. Потому то никто в авр этим автоматом и не пользуется и городит все программно на тупом ожидании флага в лучшем случае, заебавшись еще на чтении даташита. В русскоязычном интернете похоже только я и заморочился описанием и кодом для автомата полностью аппаратного.
0
Ну, насчёт никто не пользуется, это вы, сэр, пожалуй погорячились… Но там и вправду приколов хватает.
0
Ну я вот в публичных проектах, сколько не смотрел, ни разу не видел полноценную реализацию. Везде тупое ожидание флага в цикле.
0
У NXP I2C красиво сделан (авторы как-никак :) ). Во всяком случае в 8-битниках. Вход в прерывание получился как-то так:
i2c_irq:
    push    acc
    push    b
    push    dph
    push    dpl
    push    PSW
    mov     PSW,#008H; switch register bank
    mov a, I2STAT
    rr a
    rr a
    mov dptr, #I2Cvectortable
    jmp @a+dptr

Ну и далее ajmp на процедуры, соответствующие кодам состояния.
I2Cvectortable:	
		;00h->00h
		ajmp i2c_error
		;08h->02h - start
		ajmp i2c_putaddress
		;10h->04h - repeated start
		ajmp i2c_putaddress
                ;18h->06h - address+W sent, ACK received
		ajmp i2c_putdata
		;20h->08h - address+W sent, NAK received
                ajmp i2c_error
                ;..........................
0
Поди из AVR и скопировали. Тоже самое ведь, даже коды совпадают.
0
; Load event code
lds	R16,_SFR_MEM_ADDR(TWSR)
andi	R16,TW_MASK
lsr	R16
lsr	R16
lsr	R16

; Jump to handler
ldi	ZL,lo8(pm(twi_jump))
ldi	ZH,hi8(pm(twi_jump))
add	ZL,R16
adc	ZH,R17 ; =0
ijmp

twi_jump:
	rjmp	twi_error		; 0x00 TW_BUS_ERROR

	rjmp	twi_start_done		; 0x08 TW_START
	rjmp	twi_start_done		; 0x10 TW_REP_START
	rjmp	twi_xmit_byte		; 0x18 TW_MT_SLA_ACK
	rjmp	twi_no_reply		; 0x20 TW_MT_SLA_NACK
...
0
Скорее наоборот. I2C — филипсовское изобретение тех времён, когда об Атмеле ещё ничего не слышали. :)
Приведенный код сочинил я, при копировании сюда прозевал одну строчку — д.б.:
mov a, I2STAT
    anl a, #0F8h; reset 3 LSB 
0
0
Ну звеняйте, на каждый чих состояний шины ни один производитель не стал заморачиваться регистрами флагов прерываний и векторами переходов. Т.ч или флаг или состояние шины, что в принципе одно и тоже, не выигрывается ничего.
0
Ну вот судя по описанию из поста, тут все же сильно упростили по сравнению с классическим авр.
0
А по моему всё очень даже просто. И описание в даташите — сказка. Вот в стм32 я два дня плевался и матерился… «чтобы сбросить этот флажок прочитайте SR1 потом SR2, чтобы сбросить тот — прочитайте SR1 и запишите CR1» — что за индус мог до такого бреда додуматься?..
0
Это несколько ограничивает возможности интерфейса с другой стороны. Например, можно было задействовать TWI как еще один UART. Я делал устройство, в котором было несколько «бродкастовых» адресов на шине, и ACK отдавал только один slave а остальные «молча» принимали данные. Это, конечно, нестандартный хак, но такая возможность была. Палка, как говорится, имеет 2 конца…
0
0
Спасибо за статью.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.