Управление шиной i2c с компьютера

Видео:Лекция 308. Шина I2CСкачать

Лекция 308.  Шина I2C

Программа для контроллера I2C-шлюза (режим I2C-master из терминалки ПК)

Программа, рассмотренная в этой статье, разработана для контроллера I2C-шлюза (шлюз у нас реализован на ATTiny2313). Эта программа позволяет из терминальной программы персонального компьютера общаться с I2C устройствами в режиме Master.

Программа полностью написана на ассемблере, в конце статьи выложены исходники (с комментариями) и прошивка.

Для реализации обмена данными по I2C между контроллером и подключаемым устройством была использована стандартная, написанная нами ранее библиотека процедур (по этой ссылке можно посмотреть алгоритмы и исходники библиотеки).

Протокол обмена с компьютером был придуман на ходу, ориентируясь на то, что он должен быть максимально простым, удобным и быстрым, тут каждый может придумать что-то своё. Я рассуждал следующим образом:

При работе с терминальной программой нам удобно посылать, принимать и отображать информацию байтами (по 8 бит), следовательно размер сообщений, которыми обмениваются ПК и шлюз должен быть кратен одному байту. Если внимательно почитать описание протокола I2C, то можно заметить, что общение по шине I2C требует 9 бит, из которых 8 бит посылает передатчик и один бит — приёмник (Ack), кроме того нужно как-то кодировать посылку старт- и стоп-условия и управлять линией Chip Select. Исходя из этого было принято решение, что обмен данными между компьютером и контроллером шлюза будет состоять из сообщений, размером в 1 или 2 байта. Первый байт будет служебным, в нём комп будет сообщать шлюзу что ему делать (и в том числе нужно ли посылать Ack при приёме), а шлюз будет сообщать компу что он сделал (и в том числе был ли принят Ack при отправке байта). Второй байт будет использоваться только в сообщении шлюза о приёме данных и в сообщении компа о необходимости отправить данные и будет содержать эти самые данные (которые шлюз принял по I2C или которые ему надо по I2C отправить).

Служебные сообщения мы закодируем следующим образом:

  1. При передаче сообщения в направлении ПК->Шлюз:
  2. 10h — сформировать start-условие
  3. 11h — сформировать stop-условие
  4. 12h — отправить байт по I2C (следующий переданный компьютером байт будет отправлен шлюзом по I2C)
  5. 13h — принять байт по I2C, выдать Ack
  6. 14h — принять байт по I2C, не выдавать Ack
  7. 15h — переключить линию CS в низкий уровень
  8. 16h — переключить линию CS в высокий уровень
  1. При передаче сообщения в направлении Шлюз->ПК:
  2. 10h — сформировал start-условие
  3. 11h — сформировал stop-условие
  4. 12h — послал байт по I2C, не получил Ack (следующий принятый ПК байт — это байт, который шлюз послал по I2C)
  5. 13h — послал байт по I2C, получил Ack (следующий принятый ПК байт — это байт, который шлюз послал по I2C)
  6. 14h — принял байт по I2C (следующий принятый ПК байт — это байт, который шлюз принял по I2C)
  7. 15h — установил низкий уровень на линии CS
  8. 16h — установил высокий уровень на линии CS
  9. FFh — от ПК получена неизвестная команда

Вот пожалуй и всё описание (за дополнительными объяснениями — добро пожаловать на форум), а теперь перейдём к алгоритму и программе.

Итак, в аппаратной части мы имеем:

  1. PB0 — линия Clock
  2. PB2 — линия Data
  3. PD5 — линия CS
  4. PD0 — линия Rx
  5. 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-шлюза (режим I2C-slave из терминалки ПК)

Итак, продолжаем эксперименты с собранным ранее I2C-шлюзом (который, как вы помните, у нас реализован на ATTiny2313). В этой статье мы рассмотрим полностью программную реализацию режима I2C-Slave, который позволит нашему девайсу из терминальной программы персонального компьютера прикидываться любым Slave-устройством, а также просто подглядывать за обменом данными на шине I2C (то есть работать как сниффер).

Прога, как всегда, на асме, в конце статьи, как всегда, исходники (с комментариями) и прошивка.

Протокол обмена шлюза с компьютером состоит из однобайтных сообщений, в которых передаются служебные (команды шлюзу — что нужно делать, или сообщения компьютеру о том, что сделано) или информационные данные (информация, которую нужно передать или которая получена по шине I2C).

Служебные сообщения мы закодируем следующим образом:

  1. При передаче сообщения в направлении ПК->Шлюз:
  2. 20h — отправить байт по I2C (следующий переданный компьютером байт будет отправлен шлюзом по I2C)
  3. 21h — принять байт по I2C (следующий принятый компом байт — это тот байт, который шлюз считал с шины)
  4. 22h — не посылать Ack
  5. 23h — послать Ack
  6. 24h — запросить состояние входов порта D (например, для определения уровня на входе Chip Select), следующий принятый компом байт — это байт состояния входов порта D (PIND)
  1. При передаче сообщения в направлении Шлюз->ПК:
  2. 20h — на шине произошло start-условие
  3. 21h — на шине произошло stop-условие
  4. 22h — не получили (не послали) Ack
  5. 23h — получили (послали) Ack
  6. 24h — ждём дальнейших указаний
  7. FFh — от ПК получена неизвестная команда
  8. 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, так что некоторый запас времени, на то, чтобы позаниматься своими делами, не рискуя пропустить старт или стоп условие, у нас есть).

Вот такая концепция, дальше смотрим алгоритм и прогу, в случае необходимости, — пишем вопросы на форум.

Итак, в аппаратной части мы имеем:

  1. PB0 — линия Clock
  2. PB2 — линия Data
  3. PD5 — линия CS
  4. PD0 — линия Rx
  5. 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 (АйТуСи) шине

Шина данных i2c - декодируем/синхронизируем с помощью осциллографа Lecroy!Скачать

Шина данных i2c - декодируем/синхронизируем   с помощью осциллографа Lecroy!

Видеоуроки по Arduino. I2C и processing (7-я серия, ч1)Скачать

Видеоуроки по Arduino. I2C и processing (7-я серия, ч1)

Урок 24. Узнаём адреса устройств на шине I2CСкачать

Урок 24. Узнаём адреса устройств на шине I2C

Шина I2C.Скачать

Шина I2C.

Подключение нескольких устройств по шине i2cСкачать

Подключение нескольких устройств по шине i2c

Установщик адресов Flash-i2cСкачать

Установщик адресов Flash-i2c

Логический анализатор шины i2cСкачать

Логический анализатор шины i2c

Лекция "Интерфейсы (часть II). I2C. 1-Wire"Скачать

Лекция "Интерфейсы (часть II). I2C. 1-Wire"

Урок 26.3 Соединяем две arduino по шине I2C #iarduinoСкачать

Урок 26.3 Соединяем две arduino по шине I2C #iarduino

Управляем LCD1602 через PCF8574 по шине i2cСкачать

Управляем LCD1602 через PCF8574 по шине i2c

Что такое I2C ??? Подключаем GY-521 и Oled 96*16 к STM 32Скачать

Что такое I2C ??? Подключаем GY-521 и Oled 96*16 к STM 32

Урок №7. Подключаем шину I2C в микроконтроллере Atmega8.Скачать

Урок №7. Подключаем  шину I2C в микроконтроллере Atmega8.

Логический LIN пробник, цифровой тестер лин, к лайн шины автомобиля. На Ардуино, OLED I2C, TJA 1020Скачать

Логический LIN пробник, цифровой тестер лин, к лайн шины автомобиля. На Ардуино, OLED  I2C, TJA 1020

Урок 9. Адреса модулей на шине I2C. Arduino (что такое I2C, адресация, как изменить адрес модуля)Скачать

Урок 9. Адреса модулей на шине I2C. Arduino (что такое I2C, адресация, как изменить адрес модуля)

I2C интерфейсСкачать

I2C интерфейс

I2C. Краткая теория с примером. STM32 CubeIDE.Скачать

I2C. Краткая теория с примером. STM32 CubeIDE.

I2C хаб — как подключить одинаковые I²C модули к одной шине. Управляем IMU, матрицами, NFC и RTC.Скачать

I2C хаб — как подключить одинаковые I²C модули к одной шине. Управляем IMU, матрицами, NFC и RTC.
Поделиться или сохранить к себе:
Технарь знаток