Видео:Лекция 308. Шина I2CСкачать
Программа для контроллера I2C-шлюза (режим I2C-master из терминалки ПК)
Программа, рассмотренная в этой статье, разработана для контроллера I2C-шлюза (шлюз у нас реализован на ATTiny2313). Эта программа позволяет из терминальной программы персонального компьютера общаться с I2C устройствами в режиме Master.
Программа полностью написана на ассемблере, в конце статьи выложены исходники (с комментариями) и прошивка.
Для реализации обмена данными по I2C между контроллером и подключаемым устройством была использована стандартная, написанная нами ранее библиотека процедур (по этой ссылке можно посмотреть алгоритмы и исходники библиотеки).
Протокол обмена с компьютером был придуман на ходу, ориентируясь на то, что он должен быть максимально простым, удобным и быстрым, тут каждый может придумать что-то своё. Я рассуждал следующим образом:
При работе с терминальной программой нам удобно посылать, принимать и отображать информацию байтами (по 8 бит), следовательно размер сообщений, которыми обмениваются ПК и шлюз должен быть кратен одному байту. Если внимательно почитать описание протокола I2C, то можно заметить, что общение по шине I2C требует 9 бит, из которых 8 бит посылает передатчик и один бит — приёмник (Ack), кроме того нужно как-то кодировать посылку старт- и стоп-условия и управлять линией Chip Select. Исходя из этого было принято решение, что обмен данными между компьютером и контроллером шлюза будет состоять из сообщений, размером в 1 или 2 байта. Первый байт будет служебным, в нём комп будет сообщать шлюзу что ему делать (и в том числе нужно ли посылать Ack при приёме), а шлюз будет сообщать компу что он сделал (и в том числе был ли принят Ack при отправке байта). Второй байт будет использоваться только в сообщении шлюза о приёме данных и в сообщении компа о необходимости отправить данные и будет содержать эти самые данные (которые шлюз принял по I2C или которые ему надо по I2C отправить).
Служебные сообщения мы закодируем следующим образом:
- При передаче сообщения в направлении ПК->Шлюз:
- 10h — сформировать start-условие
- 11h — сформировать stop-условие
- 12h — отправить байт по I2C (следующий переданный компьютером байт будет отправлен шлюзом по I2C)
- 13h — принять байт по I2C, выдать Ack
- 14h — принять байт по I2C, не выдавать Ack
- 15h — переключить линию CS в низкий уровень
- 16h — переключить линию CS в высокий уровень
- При передаче сообщения в направлении Шлюз->ПК:
- 10h — сформировал start-условие
- 11h — сформировал stop-условие
- 12h — послал байт по I2C, не получил Ack (следующий принятый ПК байт — это байт, который шлюз послал по I2C)
- 13h — послал байт по I2C, получил Ack (следующий принятый ПК байт — это байт, который шлюз послал по I2C)
- 14h — принял байт по I2C (следующий принятый ПК байт — это байт, который шлюз принял по I2C)
- 15h — установил низкий уровень на линии CS
- 16h — установил высокий уровень на линии CS
- FFh — от ПК получена неизвестная команда
Вот пожалуй и всё описание (за дополнительными объяснениями — добро пожаловать на форум), а теперь перейдём к алгоритму и программе.
Итак, в аппаратной части мы имеем:
- PB0 — линия Clock
- PB2 — линия Data
- PD5 — линия CS
- PD0 — линия Rx
- PD1 — линия Tx
.device ATtiny2313 .include «tn2313def.inc» .list ;— определяем свои переменные .def w=r16 ; это будет наш аккумулятор .def Bit_counter=r17 ; счётчик бит .def I2C_flags=r18 ; флаги I2C ;— флаг 0 — ack, при сброшенном флаге — посылаем ack ;— флаг 1 — rcv, поднятый флаг — признак ожидания второго байта от компа ;— (если флаг поднят — значит комп передаёт шлюзу ;— двухбайтное сообщение и мы ждём второй байт) .def BTS=r19 ; байт для передачи .def RDB=r20 ; принятый байт ;— определяем константы (и даём им имена) .equ Clock=0 ; PortB0/PinB0 — clock .equ Data=2 ; PortB2/PinB2 — data .equ CS=5 ; PinD5 — выход Chip Select ; кроме того, мы используем линии Rx (PD0), Tx (PD1) ;— начало программного кода .cseg .org 0 rjmp Init ; переход на начало программы (вектор сброса) ;— дальше идут вектора прерываний reti ; внешнее прерывание INT0 reti ; внешнее прерывание INT1 reti ; Input capture interrupt 1 reti ; Timer/Counter1 Compare Match A reti ; Overflow1 Interrupt reti ; Overflow0 Interrupt rjmp RX_INT ; USART0 RX Complete Interrupt reti ; USART0 Data Register Empty Interrupt reti ; USART0 TX Complete Interrupt reti ; Analog Comparator Interrupt reti ; Pin Change Interrupt reti ; Timer/Counter1 Compare Match B reti ; Timer/Counter0 Compare Match A reti ; Timer/Counter0 Compare Match B reti ; USI start interrupt reti ; USI overflow interrupt reti ; EEPROM write complete reti ; Watchdog Timer Interrupt ;— конфигурирование контроллера Init: ldi w,RAMEND ; устанавливаем указатель вершины out SPL,w ; стека на старший байт RAM sbi ACSR,ACD ; выключаем компаратор ;— инициализируем порты ser w ; w=0xFF out DDRA,w ; настраиваем порт A. все линии на выход, clr w ; w=0x00 out PORTA,w ; на всех линиях ноль ldi w,0b11111010 ; настраиваем порт B. out DDRB,w ; PB0, PB2 — входы, остальные — выходы clr w ; определяем нач.состояние выходов и подтяжки на входах out PORTB,w ; (выходы — нули, подтяжек нет) ldi w,0b11110100 ; настраиваем порт D out DDRD,w ; PD0, PD1, PD3 — входы, остальные — выходы clr w ; определяем нач.состояние выходов и подтяжки на входах out PORTD,w ; (выходы — нули, подтяжек нет) ;— инициализируем UART out UBRRH,w ; UBRRR в нашем случае (кварц 20МГц, скорость 115200) ldi w,10 ; равен 10, т.е. UBRRH=0, UBRRL=10 out UBRRL,w ldi w,0b00001110 ; поднимаем биты USBS, UCSZ1:0 out UCSRC,w ; формат: 1 старт, 8 данные, 2 стоп sbi UCSRB,TXEN ; включить передатчик nop sbi UCSRB,RXEN ; включить приёмник ;— разрешаем прерывание от приёмника sbi UCSRB,RXCIE ; включить прерывания от приёмника ;— разрешаем глобальные прерывания sei ;— ждём прерывания — Wait_data: rjmp Wait_data ;——————————————————— ;— Обработчик прерывания от приёмника — RX_INT: sbrs I2C_flags,1 ; если флаг поднят — приняли ожидаемый для отправки байт rjmp RX_one_int ; если не ждали байт для отправки — прыгаем cbr I2C_flags,0b00000010 ; сбрасываем флаг ожидания байта in XL,UDR ; читаем байт из приёмника в XL ;— теперь у нас в ZH — команда отправки (0x12), в ZL — байт I2C Com_0x12_2: mov BTS,XL ; посылаем байт rcall Send_Byte ldi w,0x12 sbrs I2C_flags,0 ; если подтв-я не было, — пропустить следующую команду ldi w,0x13 out UDR,w ; сообщаем компу о результатах wait_transmit1: sbis UCSRA,UDRE rjmp wait_transmit1 ; ждём, когда контроллер закончит передачу out UDR,XL ; и посылаем копию переданного байта reti RX_one_int: in XH,UDR ; читаем байт из приёмника в XH cpi XH,0x10 ; команда 0x10? breq Com_0x10 cpi XH,0x11 ; команда 0x11? breq Com_0x11 cpi XH,0x12 breq Com_0x12_1 cpi XH,0x13 breq Com_0x13 cpi XH,0x14 breq Com_0x14 cpi XH,0x15 breq Com_0x15 cpi XH,0x16 breq Com_0x16 Com_err: ser w out UDR,w ; посылаем сигнал об ошибочной команде reti ;— действия для разных команд — Com_0x12_1: sbr I2C_flags,0b00000010 ; поднимаем флаг, что ждём байт для отправки reti ; выходим и разрешаем прерывания ;— обработчики команд — Com_0x10: rcall Start_uslovie ; посылаем старт-условие ldi w,0x10 out UDR,w ; сообщаем компу, что послали старт-условие reti Com_0x11: rcall Stop_uslovie ; посылаем стоп-условие ldi w,0x11 out UDR,w ; сообщаем компу, что послали стоп-условие reti Com_0x13: sbr I2C_flags,0b00000001 ; ack — не нужен rjmp read_next Com_0x14: cbr I2C_flags,0b00000001 ; ack — нужен read_next: rcall Recieve_Byte ldi w,0x14 out UDR,w ; сообщаем компу, что считали байт wait_transmit2: sbis UCSRA,UDRE rjmp wait_transmit2 ; ждём, когда контроллер закончит передачу out UDR,RDB ; и посылаем считанный байт reti Com_0x15: cbi PORTD,CS ; сбрасываем Chip Select out UDR,XH ; сообщаем об этом компу reti Com_0x16: sbi PORTD,CS ; поднимаем Chip Select out UDR,XH ; сообщаем об этом компу reti ;— Процедуры I2C ————————————— ;— сигнал ack — инвертированный, т.е. если он ———— ;— ноль — есть ack, если 1 — нет ack ——————— Clock_null: ; установка нуля на линии Clock sbi DDRB,Clock ; переключаем ногу на выход ret ; (ноль в защёлку мы записали ещё при старте) Clock_one: ; установка единицы на линии Clock cbi DDRB,Clock ; переключаем ногу в Z-состояние ret Data_null: ; установка нуля на линии Data sbi DDRB,Data ; переключаем ногу на выход ret ; (ноль в защёлку мы записали ещё при старте) Data_one: ; установка единицы на линии Data cbi DDRB,Data ; переключаем ногу в Z-состояние ret ;— start ———- Start_uslovie: ; формирование старт-условия rcall Clock_one rcall Pause_tbuf ; «свободная шина» (4,7 мкс) rcall Data_null rcall Pause_thdsta ; «фиксация старт-условия» (4 мкс) rcall Clock_null ret ;— stop ———— Stop_uslovie: ; формирование стоп-условия rcall Data_null rcall Clock_one wait_clock_p: sbis PINB,Clock ; проверяем шину Clock rjmp wait_clock_p ; ждём, пока отпустится шина Clock rcall Pause_tsusto ; «готовность стоп-условия» (4 мкс) rcall Data_one ret ;— transmit ——- Send_Byte: cbr I2C_flags,0b00000001 ; сбрасываем флаг подтверждения ldi Bit_counter,8 ; устанавливаем счётчик бит next_bit_s: sbrc BTS,7 rcall Data_one ; если передаваемый бит = 1 sbrs BTS,7 rcall Data_null ; если передаваемый бит = 0 rcall Pause_tsudat ; «готовность данных» (250 нс) rcall Clock_one wait_clock_s1: sbis PINB,Clock rjmp wait_clock_s1 rcall Pause_thigh ; длительность полупериода считывания (4 мкс) rcall Clock_null lsl BTS ; сдвиг влево dec Bit_counter ; уменьшаем счётчик brne next_bit_s ; если счётчик не равен нулю — шлём ещё rcall Data_one ; если всё — отпускаем линию Data rcall Pause_tlow ; длительность полупериода установки (4 мкс) rcall Clock_one wait_clock_s2: sbis PINB,Clock ; проверяем — отпустилась ли линия Clock rjmp wait_clock_s2 sbic PINB,Data ; проверяем «ack» sbr I2C_flags,0b00000001 ; если нет — поднимаем флаг rcall Pause_thigh ; длительность полупериода считывания (4 мкс) rcall Clock_null ; снова занимаем шину ret ;— recieve ——— Recieve_Byte: clr RDB ; очищаем приёмник ldi Bit_counter,8 ; устанавливаем счётчик next_bit_r: lsl RDB ; сдвигаем влево приёмный регистр rcall Data_one ; отпускаем линию Data rcall Pause_tlow ; длительность полупериода установки (4 мкс) rcall Clock_one ; отпускаем clock wait_clock_r1: sbis PINB,Clock ; ждём когда отпустится clock rjmp wait_clock_r1 sbic PINB, Data ; если Data=1 — пишем в младший бит приёмника 1, sbr RDB,0b00000001 ; если нулю — пропускаем эту команду rcall Pause_thigh ; длительность полупериода считывания (4 мкс) rcall Clock_null ; роняем clock dec Bit_counter ; уменьшаем счётчик brne next_bit_r ; если результат не равен нулю — читаем дальше sbrs I2C_flags,0 rcall Data_null ; если надо посылать ack — роняем Data sbrc I2C_flags,0 rcall Data_one ; если не надо посылать ack — отпускаем Data rcall Pause_tlow ; длительность полупериода установки (4 мкс) rcall Clock_one ; отпускаем Clock wait_clock_r2: sbis PINB,Clock ; ждём, пока установится Clock rjmp wait_clock_r2 rcall Pause_thigh ; даём время slav-у увидеть наш ack ; (или его отсутствие) rcall Clock_null ; роняем Clock ret ;— Конец процедур I2C ——————————- ;— Задержки для частоты шины 100 кГц —— ;— свободная шина (4,7 мкс) — Pause_tbuf: ldi w,27 wait_tbuf: dec w brne wait_tbuf ret ;— фиксация старт-условия (4 мкс) — Pause_thdsta: ldi w,23 wait_thdsta: dec w brne wait_thdsta ret ;— готовность стоп-условия (4 мкс) — Pause_tsusto: ldi w,23 wait_tsusto: dec w brne wait_tsusto ret ;— готовность данных (250 нс) — Pause_tsudat: nop ret ;— длительность полупериода считывания (4 мкс) — Pause_thigh: ldi w,23 wait_thigh: dec w brne wait_thigh ret ;— длительность полупериода установки (4 мкс) — Pause_tlow: ldi w,23 wait_tlow: dec w brne wait_tlow ret ;———————————————————
Читайте также: Зимние шины yokohama geolandar 225 65 r17
Для правильной работы шлюза в контроллере должны быть «запрограммированы» следующие фьюзы: SPIEN, SUT0
Приведу небольшой пример работы со шлюзом:
Пусть мы хотим записать AAh в микруху SDA2546 по адресу 00h. Открываем даташит и смотрим как с этой микрухой общаться. В даташите написано, что для записи в микруху надо сначала послать сообщение CS/E: «1 0 1 0 0 A8 CS 0 0» (которое при A8=0 и CS=0 в шестнадцатиричном виде выглядит как A0h), после получения Ack послать адрес, по которому мы хотим писать (т.е. 00h), и далее после получения Ack послать сам байт, который должен быть записан по этому адресу (т.е. AAh). Непосредственно запись произойдёт после формирования стоп-условия.
Итак, заходим в терминалку, выбираем порт и скорость обмена (скорость у нас 115200), подключаемся и начинаем общаться со шлюзом:
— отправляем шлюзу 15h | // (установить CS=0) |
— получаем от шлюза 15h | // он сообщает, что установил CS=0 |
— отправляем шлюзу 10h | // просим шлюз сформировать старт-условие на шине I2C |
— получаем от шлюза 10h | // отчёт о выполнении нашей просьбы |
— отправляем шлюзу 12h A0h | // просим шлюз отправить по I2C байт A0h |
— получаем от щлюза 13h A0h | // шлюз сообщает, что отправил байт A0h и получил подтверждение |
— отправляем шлюзу 12h 00h | // посылаем по I2C адрес, по которому хотим писать |
— получаем от шлюза 13h 00h | |
— отправляем шлюзу 12h AAh | // посылаем по I2C байт, который хотим записать |
— получаем от шлюза 13h AAh | |
— отправляем шлюзу 11h | // просим шлюз сформировать стоп-условие |
— получаем от шлюза 11h | // отчёт, что стоп-условие сформировано |
Вот и всё, в результате этих действий в нашей SDA2546 по адресу 00h окажется записано AAh.
Update. После небольших исследований, один из форумчан нашёл в данной программе ошибку, которая заключается в том, что при старте случайным образом может всё работать нормально, а может улететь по I2C первый же принятый байт. Для правильной работы программы нужно добавить в секцию Init команду clr I2C_flags, например, перед строкой «;— инициализируем порты». Прошивку, естественно, тоже нужно перекомпилировать.
Видео:Введение в шину I2CСкачать
Программа для контроллера I2C-шлюза (режим I2C-slave из терминалки ПК)
Итак, продолжаем эксперименты с собранным ранее I2C-шлюзом (который, как вы помните, у нас реализован на ATTiny2313). В этой статье мы рассмотрим полностью программную реализацию режима I2C-Slave, который позволит нашему девайсу из терминальной программы персонального компьютера прикидываться любым Slave-устройством, а также просто подглядывать за обменом данными на шине I2C (то есть работать как сниффер).
Прога, как всегда, на асме, в конце статьи, как всегда, исходники (с комментариями) и прошивка.
Протокол обмена шлюза с компьютером состоит из однобайтных сообщений, в которых передаются служебные (команды шлюзу — что нужно делать, или сообщения компьютеру о том, что сделано) или информационные данные (информация, которую нужно передать или которая получена по шине I2C).
Служебные сообщения мы закодируем следующим образом:
- При передаче сообщения в направлении ПК->Шлюз:
- 20h — отправить байт по I2C (следующий переданный компьютером байт будет отправлен шлюзом по I2C)
- 21h — принять байт по I2C (следующий принятый компом байт — это тот байт, который шлюз считал с шины)
- 22h — не посылать Ack
- 23h — послать Ack
- 24h — запросить состояние входов порта D (например, для определения уровня на входе Chip Select), следующий принятый компом байт — это байт состояния входов порта D (PIND)
- При передаче сообщения в направлении Шлюз->ПК:
- 20h — на шине произошло start-условие
- 21h — на шине произошло stop-условие
- 22h — не получили (не послали) Ack
- 23h — получили (послали) Ack
- 24h — ждём дальнейших указаний
- FFh — от ПК получена неизвестная команда
- 02h — произошла реинициализация шлюза
Читайте также: Грязевая шина для дастера
Прежде чем рисовать алгоритм, — напишу немного текстухи, — чтобы было понятнее как это всё работает и как сделан режим I2C-Slave. Работа нашего девайса основана на том, что Slave устройство в протоколе I2C не совсем бесправно, — оно может растягивать обмен, удерживая на низком уровне линию Clock. Таким образом, в те моменты, когда мастер роняет линию clock в ноль, мы можем захватить эту линию и удерживать её на низком уровне до тех пор, пока не «поговорим» с компьютером.
Далее, для того, чтобы определять на какой стадии обмена данными находится шлюз, — мы, во-первых, используем флаг T регистра SREG, в котором сохраняем текущее состояние линии Clock (это позволяет в дальнейшем определить от какой линии произошло прерывание — от Clock или от Data) и, во-вторых, создали в программе свой собственный регистр флагов (I2C_flags). В нём мы юзаем 3 флага:
Нулевой бит регистра I2C_flags установливается в 1 после обнаружения start-условия и используется при отправке первого Ack (после получения первого байта). Используется он следующим образом: если мы посылаем «Ack», то флаг сбрасывается и мы продолжаем обмен, если же мы посылаем «No_Ack», то шлюз реинициализируется и ждёт нового старт-условия на шине. Это для случая, когда на шине несколько slave-устройств, а мы хотим эмулировать только какое-то одно (первый байт после старт-условия — это адрес устройства, и, если обращаются не к нам, то мы последующий обмен игнорируем и ждём нового старт-условия).
Первый бит регистра I2C_flags — это направление передачи данных, когда он установлен в 1 — шлюз будет посылать данные мастеру, когда он сброшен в ноль — шлюз будет читать данные от мастера.
И, наконец, второй бит регистра I2C_flags сообщает о том, что мы читаем — данные или Ack (чтобы знать сколько бит нам с шины читать — 8 или 1).
Наша прога делает следующее. Сначала мы инициализируем шлюз для чтения байта, флаг T устанавливаем равным 1, настраиваем прерывание от Pin_Change, разрешаем прерывания и переходим к циклической проверке обоих линий. Если на обоих линиях высокий уровень, то разрешаем прерывание от линии Data, если нет — запрещаем прерывание от любой линии. Таким образом первое прерывание у нас в любом случае должно произойти от изменения уровня на линии Data с высокого уровня на низкий.
Далее, в прерывании мы первоначально попадём в обработчик старт-условия, там мы разрешаем прерывание от Clock, настраиваемся на приём от мастера первого байта, выставляем нужные флаги и переходим к процедуре ожидания (снова устанавливаем глобальный флаг разрешения прерываний и нифига не делаем). В протоколе определено время фиксации старт-условия, оно зависит от скорости, но главное, что оно есть и в течении этого времени нельзя менять уровни на линиях Clock и Data. За это время мы должны успеть выполнить весь обработчик start-условия и снова разрешить прерывания.
Далее, при прерывании мы (с помощью флага T) определяем от какой линии произошло прерывание и, соответственно, на какой стадии обмена мы находимся. Если помните описание протокола I2C — во время высокого уровня на линии clock приёмник читает данные, во время низкого уровня на линии clock — передатчик выставляет данные на шину, соответственно, когда мы определяем, что уровень на линии clock изменился с 1 на 0 — мы захватываем линию clock (сами роняем её в ноль), запрещаем прерывание от изменения уровня на линии Data и занимаемся своими делами, после чего отпускаем линию clock и ждём прерывания от её изменения.
Пока clock не изменится с 0 на 1 — снова разрешать прерывания от линии Data нельзя, поскольку уровень на линии Data при низком уровне на линии clock может в любой момент измениться (в это время передатчик выставляет данные на шину). Когда мы определяем, что уровень на линии clock изменился с 0 на 1 — мы сначала делаем все свои дела (читаем если нужно байт или сигнал Ack), а потом снова разрешаем прерывание от линии Data (благо посылать старт- и стоп-условие запрещено сразу после изменения уровня на линии Clock, так что некоторый запас времени, на то, чтобы позаниматься своими делами, не рискуя пропустить старт или стоп условие, у нас есть).
Вот такая концепция, дальше смотрим алгоритм и прогу, в случае необходимости, — пишем вопросы на форум.
Итак, в аппаратной части мы имеем:
- PB0 — линия Clock
- PB2 — линия Data
- PD5 — линия CS
- PD0 — линия Rx
- PD1 — линия Tx
.device ATtiny2313 .include «tn2313def.inc» .list ;— определяем свои переменные .def w=r16 ; это будет наш аккумулятор .def Bit_counter=r17 ; счётчик бит .def I2C_flags=r18 ; флаги I2C ;— флаг 0 — признак, что только что было старт-условие ;— флаг 1 — направление (приём: 0, передача: 1) ;— флаг 2 — признак чтения ack .def BTS=r19 ; байт для передачи .def RDB=r20 ; принятый байт .def Hi_PCIE=r21 ; сюда запишем GIMSK с поднятым PCIE .def Clr_reg=r22 ; здесь будет просто ноль .def PCMSK_D_Set=r23 ; тут PCMSK c установленным флагом ; для прерывания от линии Data .def PCMSK_C_Set=r24 ; тут PCMSK c установленным флагом ; для прерывания от линии Clock .def PCMSK_CD_Set=r25; тут PCMSK c флагами для ; прерываний от линий Data и Clock ;— определяем константы (и даём им имена) .equ Clock=0 ; PortB0/PinB0 — clock .equ Data=2 ; PortB2/PinB2 — data .equ CS=5 ; PinD5 — вход Chip Select ; кроме того, мы используем линии Rx (PD0), Tx (PD1) ;— начало программного кода .cseg .org 0 rjmp Init ; переход на начало программы (вектор сброса) ;— дальше идут вектора прерываний ;— если не используем — пишем reti, иначе — переход к обработчику reti ; внешнее прерывание INT0 reti ; внешнее прерывание INT1 reti ; Input capture interrupt 1 reti ; Timer/Counter1 Compare Match A reti ; Overflow1 Interrupt reti ; Overflow0 Interrupt reti ; USART0 RX Complete Interrupt reti ; USART0 Data Register Empty Interrupt reti ; USART0 TX Complete Interrupt reti ; Analog Comparator Interrupt rjmp PCInt ; Pin Change Interrupt reti ; Timer/Counter1 Compare Match B reti ; Timer/Counter0 Compare Match A reti ; Timer/Counter0 Compare Match B reti ; USI start interrupt reti ; USI overflow interrupt reti ; EEPROM write complete reti ; Watchdog Timer Interrupt ;— начало программы — Init: ldi w,RAMEND ; устанавливаем указатель вершины out SPL,w ; стека на старший байт RAM sbi ACSR,ACD ; выключаем компаратор ;— инициализируем порты ser w ; w=0xFF out DDRA,w ; настраиваем порт A. все линии на выход clr w ; w=0x00 out PORTA,w ; на всех линиях ноль ldi w,0b11111010 ; настраиваем порт B. out DDRB,w ; PB0, PB2 — входы, остальные — выходы clr w ; определяем начальное состояние out PORTB,w ; (выходы — нули, подтяжек нет) ldi w,0b11010100 ; настраиваем порт D out DDRD,w ; PD0,1,3,5 — входы, остальные — выходы clr w ; определяем начальное состояние out PORTD,w ; (выходы — нули, подтяжек нет) ;— инициализируем UART out UBRRH,w ; UBRRR для кварца 20МГц и скорости 115200 ldi w,10 ; равен 10, т.е. UBRRH=0, UBRRL=10 out UBRRL,w ldi w,0b00001110 ; поднимаем биты USBS, UCSZ1:0 out UCSRC,w ; формат: 1 старт, 8 данные, 2 стоп sbi UCSRB,RXEN ; включить приёмник in w,UDR ; считать из него мусор sbi UCSRB,TXEN ; включить передатчик ;— инициализируем вспомогательные регистры clr Clr_reg ldi Hi_PCIE,0b00100000 ldi PCMSK_D_Set,0b00000100 ldi PCMSK_C_Set,0b00000001 ldi PCMSK_CD_Set,0b00000101 ;— включить прерывание от pin change out GIMSK,Hi_PCIE ;— Для реинициализации — Reset: out PCMSK,Clr_reg; выключить прерывания от clock и data ldi w,0x02 ; сообщаем, что был reset out UDR,w ;— проверяем флаги от прерываний in w,EIFR ; читаем регистр sbrc w,5 ; пропустить команду, если PCIF=0 out EIFR,Hi_PCIE ; сбрасываем PCIF, если он установлен ;— cbi DDRB,Data ; отпускаем Data (=1) set ; инициализируем флаг T cbi DDRB,Clock ; отпускаем Clock (=1) ;— разрешаем глобальные прерывания sei ;— если обе линии свободны — разрешаем прерывание от Data — Wait_start: in w,PINB ; читаем входы PINB com w ; инвертируем andi w,0b00000101; если инверсные clock и data=0 — результат 0 breq Free_bus ; если флаг Z=1, то прыгать Not_free_bus: out PCMSK,Clr_reg; выключить прерывания от всех линий rjmp Wait_start Free_bus: out PCMSK,PCMSK_D_Set; включить прерывание от линии Data rjmp Wait_start ;—————————— ;— циклы ожидания после выполнения программы в C01 и C10 Wait_C: pop w ; выгружаем из стека адрес возврата pop w sei Wait: rjmp Wait ;——————————————- ;— Обработчик прерывания от pin change — PCInt: sbis PINB,Clock ; если clock=1 — пропустить команду rjmp Clock_Low ;——————————————- ;— Если на линии Clock высокий уровень — Clock_Hi: brts C1_no_changes ; если флаг T=1, то прыгаем ;——————————————— ;— Уровень на линии clock изменился с 0 на 1 C01: set ; сохраняем новое состояние линии clock sbrc I2C_flags,1;если флаг 1=0 — пропускаем 1 команду rjmp C01_Exit C01_Read: sbrc I2C_flags,2;если флаг 2=0 — пропускаем 1 команду rjmp C01_Read_Ack C01_Read_Byte: lsl RDB ; сдвиг влево sbic PINB,Data ; если Data=0 — пропускаем 1 команду sbr RDB,0b00000001 ; поднять нулевой бит dec Bit_counter brne C01_Exit ; если не считали 8 бит — просто выходим, out UDR,RDB ; если считали — шлём их на комп и выходим C01_Exit: out PCMSK,PCMSK_CD_Set; включить прерыв. от Data и Clock rjmp Wait_C ;———————- C01_Read_Ack: sbic PINB,Data ; если Data=0, пропускаем след-ю команду rjmp C01_Read_Ack_NotOk C01_Read_Ack_Ok: ldi w,0x23 ; говорим компу, что на линии есть Ack out UDR,w rjmp C01_Exit C01_Read_Ack_NotOk: ldi w,0x22 ; говорим компу, что на линии нет Ack out UDR,w rjmp C01_Exit ;————————————— ;— Уровень на линии clock не изменился C1_no_changes: ; линия clock=1 и прерывание не от неё ;— значит это старт или стоп sbic PINB,Data ; если data=0 — пропустить команду rjmp Stop_uslovie Start_uslovie: ldi Bit_Counter,8; один пакет = 8 бит от передатчика out PCMSK,PCMSK_CD_Set ; добавляем прерывание от Clock ldi w,0x20 ; сообщим, что получили start-условие out UDR,w ldi I2C_flags,0b00000001 ; чтение/было старт-условие rjmp Wait_C Stop_uslovie: ldi w,0x21 ; сообщаем компу что получили stop-усл. out UDR,w pop w ; выгружаем адрес возврата из стека pop w rjmp Reset ;————————————— ;— Если на линии Clock низкий уровень Clock_Low: brts C10 ; если флаг T=1, то прыгаем ;————————————— ;— Уровень на линии clock не изменился C0_no_changes: ; теоретически такого не должно произойти, ; поскольку мы выключаем прерывания от Data ; при переключении Clock 1->0 ldi w,0xFF ; сообщаем компу об ошибке out UDR,w pop w ; выгружаем адрес возврата из стека pop w rjmp Reset ; реинициализация ;——————————————— ;— Уровень на линии clock изменился с 1 на 0 C10: sbi DDRB,Clock ; зажимаем clock (чтоб всё успеть) cbi DDRB,Data ; отпускаем Data (=1) clt ; записываем новое состояние clock out PCMSK,PCMSK_C_Set ; убираем прерывание от Data in w,EIFR ; читаем регистр sbrc w,5 ; пропустить команду, если PCIF=0 out EIFR,Hi_PCIE; сбрасываем PCIF ;— проверяем — читаем или пишем? — sbrs I2C_flags,1;если флаг 1=1 — пропускаем rjmp C10_Read ;— Пишем — C10_Write: tst Bit_counter ; если записали 8 бит, ; то установится флаг Z brne C10_Write_Next ; если нет — выходим, иначе: C10_End_Write: ldi I2C_flags,0b00000100 ; чтение ack rjmp C10_Exit C10_Write_Next: lsr BTS sbrs BTS,0 ; если бит 0 в BTS=1 — проп. 1 команду sbi DDRB,Data ; Data=0 dec Bit_counter ; уменьшаем счётчик C10_Exit: cbi DDRB,Clock ; отпускаем clock rjmp Wait_C ;— Читаем — C10_Read: sbrs I2C_flags,2; если читали ack — пропускаем 1 команду rjmp C10_Read_Byte ;— стоит режим чтения Ack (значит мы его только что ;— прочли и ждём команды от компа что делать дальше) C10_Ready: sbis UCSRA,UDRE ; если буфер передатч. пуст — пропуск.1 команду rjmp C10_Ready ; дожидаемся, пока предыдущее сообщение уйдёт ldi w,0x24 ; сообщаем компу, что готовы принимать команды out UDR,w Wait_Comp_Answer: sbis UCSRA,RXC ; если пришли данные от компа — пропуск.1 команду rjmp Wait_Comp_Answer in w,UDR ; читаем — что пришло от компа cpi w,0x20 breq Com_Send_Byte ; переходим к команде «послать байт» cpi w,0x21 breq Com_Read_Byte ; переходим к команде «считать байт» cpi w,0x22 breq Com_NoAck ; переходим к команде «не посылать Ack» cpi w,0x23 breq Com_Ack ; переходим к команде «послать Ack» cpi w,0x24 breq Com_PIND_Status ; переходим к команде «PIND Status?» ;— если пришла какая-то другая команда — сообщ.компу и ресетимся ldi w,0xFF out UDR,w Exit_Reset: pop w pop w rjmp Reset ;— Выполнение разных команд — ;— готовимся писать байт Com_Send_Byte: ldi I2C_flags,0b00000010 ; направление — передача ldi Bit_Counter,7 Wait_BTS: sbis UCSRA,RXC ; если есть данные от компа — пропускаем 1 команду rjmp Wait_BTS in BTS,UDR ; читаем байт для передачи ;— посылаем первый бит cbi DDRB,Data ; Data=1 sbrs BTS,0 ; если бит 0 в BTS=1 — пропускаем команду sbi DDRB,Data ; Data=0 ;— и выходим rjmp C10_Exit ;— готовимся читать байт Com_Read_Byte: ldi Bit_Counter,8 ldi I2C_flags,0b00000000 ; направление — чтение rjmp C10_Exit ;— шлём в шину NoAck Com_NoAck: sbrc I2C_flags,0; если приняли первый байт после старта rjmp Exit_Reset ; выходим до нового start-условия ldi I2C_flags,0b00000100 ; направление — чтение/читаем Ack rjmp C10_Exit ;— шлём в шину Ack Com_Ack: sbi DDRB,Data ; Data=0 (Ack) ldi I2C_flags,0b00000100 ; направление — чтение/читаем Ack rjmp C10_Exit ;— шлём на комп состояние входов Com_PIND_Status: in w,PIND ; читаем входы порта D out UDR,w ; шлём на комп rjmp C10_Ready ;— Не стоит режим чтения ack (значит мы читаем байт) — C10_Read_Byte: tst Bit_counter ; если прочитали 8 бит — установится флаг Z brne C10_Exit ; если нет — выходим, иначе: rjmp C10_Ready ;———————————————————
Читайте также: Шины для нового хендай туссан
Для правильной работы шлюза в контроллере должны быть «запрограммированы» следующие фьюзы: SPIEN, SUT0
Небольшой пример работы этой проги:
Пусть у нас есть мастер, который хочет считать байт по адресу 12h из микросхемы памяти 24С02, у которой адресные пины A0, A1, A2 подключены на общий провод (т.е. её 7-ми битный адрес равен 1010000), а мы хотим этой самой микрухой прикинуться и сказать мастеру, что по этому адресу у нас записан байт AAh. Открываем даташит и смотрим как с этой микросхемой памяти общаться (то есть смотрим — что мы будем получать от мастера и как должны ему отвечать). В даташите написано, что для чтения по произвольному адресу мастер должен сначала, после подачи старт-условия, адресовать нас для записи, передать адрес, потом послать повторное старт-условие, адресовать нас для чтения и потом уже прочитать байт.
Итак, заходим в терминалку, выбираем порт и подключаемся на скорости 115200. Описанный выше сценарий будет выглядеть в терминалке следующим образом:
(принимаемые от шлюза данные: ):
Добавить комментарий Отменить ответ
Для отправки комментария вам необходимо авторизоваться.
🌟 Видео
Подключение нескольких устройств, датчиков по I2C (АйТуСи) шинеСкачать
Шина данных i2c - декодируем/синхронизируем с помощью осциллографа Lecroy!Скачать
Видеоуроки по Arduino. I2C и processing (7-я серия, ч1)Скачать
Урок 24. Узнаём адреса устройств на шине I2CСкачать
Шина I2C.Скачать
Подключение нескольких устройств по шине i2cСкачать
Установщик адресов Flash-i2cСкачать
Логический анализатор шины i2cСкачать
Лекция "Интерфейсы (часть II). I2C. 1-Wire"Скачать
Урок 26.3 Соединяем две arduino по шине I2C #iarduinoСкачать
Управляем LCD1602 через PCF8574 по шине i2cСкачать
Что такое I2C ??? Подключаем GY-521 и Oled 96*16 к STM 32Скачать
Урок №7. Подключаем шину I2C в микроконтроллере Atmega8.Скачать
Логический LIN пробник, цифровой тестер лин, к лайн шины автомобиля. На Ардуино, OLED I2C, TJA 1020Скачать
Урок 9. Адреса модулей на шине I2C. Arduino (что такое I2C, адресация, как изменить адрес модуля)Скачать
I2C интерфейсСкачать
I2C. Краткая теория с примером. STM32 CubeIDE.Скачать
I2C хаб — как подключить одинаковые I²C модули к одной шине. Управляем IMU, матрицами, NFC и RTC.Скачать