attiny817 twi manual

Как работать с 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
Что это значит? В обычных AVR автомат аппаратный, заточенный под асинхронное использование — всё дрочится в железе. Всё, что требуется от программы — прерывание и свитч по коду состояния. В нужном кейсе остаётся просто достать байтик из буфера, забрать в буфер или, скажем, дать повторный старт при срабатывании арбитража. Мне это куда больше нравится, чем самому хранить состояние и проверять зоопарк флажков…
Да блин, там не автомат, а полуфабрикат. Автомат это когда сунул адрес, указал буфер или байт, а дальше оно само, а ты только получил флаги итогового результата, что пошло так или не так. Потому то никто в авр этим автоматом и не пользуется и городит все программно на тупом ожидании флага в лучшем случае, заебавшись еще на чтении даташита. В русскоязычном интернете похоже только я и заморочился описанием и кодом для автомата полностью аппаратного.
Ну, насчёт никто не пользуется, это вы, сэр, пожалуй погорячились… Но там и вправду приколов хватает.
Ну я вот в публичных проектах, сколько не смотрел, ни разу не видел полноценную реализацию. Везде тупое ожидание флага в цикле.
У NXP I2C красиво сделан (авторы как-никак :) ). Во всяком случае в 8-битниках. Вход в прерывание получился как-то так:
Ну и далее ajmp на процедуры, соответствующие кодам состояния.
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
;..........................
; 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
...
Это несколько ограничивает возможности интерфейса с другой стороны. Например, можно было задействовать TWI как еще один UART. Я делал устройство, в котором было несколько «бродкастовых» адресов на шине, и ACK отдавал только один slave а остальные «молча» принимали данные. Это, конечно, нестандартный хак, но такая возможность была. Палка, как говорится, имеет 2 конца…
- coredumped
- 01 августа 2018, 09:55
- ↑
- ↓
Комментарии (16)
RSS свернуть / развернуть