Программная реализация мастер-абонента шины I2C в режиме single-master (библиотека процедур для PIC)

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

Итак, рассматриваемый режим single-master является самым простейшим случаем, поскольку в этом случае наш контроллер является единственным устройством, которое может управлять линией Clock и формировать старт- и стоп-условия. Соответственно, функции арбитража реализовывать не нужно, никаких коллизий и так возникнуть не может. Всё, что нужно нашему мастер-абоненту для обмена данными со слэйв-устройствами — это уметь делать следующие вещи:

  1. формировать на шине "Старт"-условие;
  2. формировать на шине "Стоп"-условие;
  3. формировать и посылать пакет данных из 8 бит с обработкой ответа (подтверждения) от "Slave"-устройства;
  4. принимать пакет данных из 8 бит с посылкой бита подтверждения и без посылки бита подтверждения.

Каждую из перечисленных "обязанностей" удобно реализовать в виде отдельной процедуры. Для удобства восприятия назовём их, например так: Start_uslovie, Stop_uslovie, Send_byte, Recieve_byte.

Поскольку подключаемые к I2C устройства должны имитировать выходы "с открытым коллектором", то для реализации этих процедур нам понадобятся ещё 4 процедуры, которые будут непосредственно управлять соответствующими линиями портов, имитируя на них выходы "с открытым коллектором" (по две на каждую линию — Clock и Data). Их назовём так: Clock_null, Clock_one, Data_null, Data_one.

Кроме того, наше "Master"-устройство должно отслеживать такое событие, как "удержание" "Slave"-устройством линии Clock (когда оно по каким-либо причинам "растягивает" передачу данных).

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

Для удобства нам понадобятся 4 пользовательских регистра: BTS (byte to send) — сюда будет записываться байт, который мы хотим послать, RDB (recieved byte) — сюда будет записываться принятый байт, Bit_counter — будет использоваться в качестве счётчика принятых и посланных бит и последний регистр — I2C_Flags — здесь будут храниться наши служебные флаги.

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

Так вот, бит 0 регистра I2C_flags — флаг подтверждения (ACK) и работать он будет следующим образом. Когда мастер принимает данные: если этот бит равен 0, то мастеру нужно выдать подтверждение приёма байта, а если он установлен в 1, то подтверждение выдавать не нужно. Когда мастер посылает данные: если этот бит установился в 0 — подтверждение получено, если в 1 — подтверждение не получено.

Алгоритмы: Программные коды: Комментарии:
Установка "0" на линии CLOCK
Алгоритм установки нуля на линии Clock (I2C)
Clock_null
  bcf  Port_reg,Clock_line
  bsf  Status,5
  bcf  Tris_reg,Clock_line
  bcf  Status,5
  return
Для установки линии в ноль — необходимо переключить её на "выход" и записать в защёлку 0.

Установка "1" на линии CLOCK

Алгоритм установки единицы на линии Clock (I2C)

Clock_one
  bsf  Status,5
  bsf  Tris_reg,Clock_line
  bcf  Status,5
  return
Для установки линии в Z-состояние — достаточно переключить её на вход.

Установка "0" на линии DATA

алгоритм аналогичен установке нуля на линии Clock

Data_null
  bcf  Port_reg,Data_line
  bsf  Status,5
  bcf  Tris_reg,Data_line
  bcf  Status,5
  return
Для установки линии в ноль — необходимо переключить её на "выход" и записать в защёлку 0.

Установка "1" на линии DATA

алгоритм аналогичен установке единицы на линии Clock

Data_one
  bsf  Status,5
  bsf  Tris_reg,Data_line
  bcf  Status,5
  return
Для установки линии в Z-состояние — достаточно переключить её на вход.

В шапке программы необходимо директивой equ указать — к каким именно выводам подключены линии Clock и Data. Для этого нужно включить в шапку следующий код:

  Clock_line  equ   <i>номер канала вывода</i>
  Data_line   equ   <i>номер канала вывода</i>

Компилятор просто заменит все записи Clock_line и Data_line на соответствующие числа (номера каналов выводов).

Кроме того, этой же директивой в шапке надо указать адреса регистра порта и регистра выбора направления каналов порта:

  Port_reg  equ  <i>адрес регистра порта</i>                      (для PIC12 это будет адрес регистра GPIO)
  Tris_reg  equ  <i>адрес регистра направления каналов порта</i>  (для PIC12 сюда пишем адрес TRISIO)

Формирование Старт-условия

Алгоритм посылки старт-условия (I2C)

Start_uslovie
  Call  Clock_one
  Call  Data_null
  Call  Clock_null
  return

Для посылки старт-условия достаточно на свободной шине (когда обе линии притянуты к 1) уронить в ноль линию Data.

Первая команда (Clock_one) нужна на тот случай, если мы подаём повторное старт-условие без подачи стоп-условия.

Формирование Стоп-условия

Алгоритм посылки стоп-условия (I2C)

Stop_uslovie
  call  Data_null
  call  Clock_one
wait_clock_p
  btfss Port_reg,Clock_line
  goto  wait_clock_p
  call  Data_one
  return

Для посылки стоп-условия необходимо при отпущенной линии Clock перевести линию Data из нуля в единицу.

Для этого надо в первой половине тактового импульса (когда линия Clock притянута к нулю) притянуть к нулю линию Data (чтобы во второй половине тактового импульса было что отпускать).

Пересылка байта

Алгоритм посылки одного байта по I2C

Send_Byte
  bcf   I2C_flags,0
  movlw .8
  movwf Bit_counter
next_bit_s
  btfsc BTS,7
  Call  Data_one
  btfss BTS,7
  call  Data_null
  Call  Clock_one
wait_clock_s1
  btfss Port_reg,Clock_line
  goto  wait_clock_s1
  call  Clock_null
  rlf   BTS,1
  decfsz Bit_counter,1
  goto  next_bit_s
  call  Data_one
  call  Clock_one
wait_clock_s2
  btfss Port_reg,Clock_line
  goto  wait_clock_s2
  btfsc Port_reg,Data_line
  bsf   I2C_flags,0
  call  Clock_null
  return

В девятом такте (18-я строка) процедура Data_one вызывается для того, чтобы slave мог выставить бит подтверждения). Если линию не отпустить, то будет непонятно, кто установил на линии ноль — вы в предыдущем такте или slave-устройство в текущем такте.

Приём байта

Алгоритм приёма одного байта по I2C

Recieve_Byte
  clrf  RDB
  movlw .8
  movwf Bit_counter
next_bit_r
  bcf   Status,0
  rlf   RDB,1
  call  Data_one
  call  Clock_one
wait_clock_r1
  btfss Port_reg,Clock_line
  goto  wait_clock_r1
  btfsc Port_reg, Data_line
  bsf   RDB,0
  call  Clock_null
  decfsz Bit_counter
  goto  next_bit_r
  btfss I2C_flags,0
  call  Data_null
  btfsc I2C_flags,0
  call  Data_one
  call  Clock_one
wait_clock_r2
  btfss Port_reg,Clock_line
  goto  wait_clock_r2
  call  Clock_null
  return
Процедура принимает 8 бит данных от slave-устройства, а в девятом такте посылает или не посылает бит подтверждения (в зависимости от значения специального флага в нашем регистре флагов).

Замечание 1. Поскольку устройства I2C имеют выходы с открытым коллектором, то для линий шины I2C выражения "отпустить линию" и "установить линию в 1" означают одно и тоже, так что не пугайтесь, если встретите в тексте разные варианты (и не надо путать установку линии в "1" с установкой "1" на выходе контроллера).

Замечание 2. При тактовой частоте внутреннего генератора PIC-контроллеров (4 МГц) эти процедуры укладываются в тайминги (в некоторых местах — впритык) и отлично работают, но если тактовая частота будет выше или если вы захотите уменьшить скорость обмена, то в процедуры придётся вносить дополнительные задержки.

Добавить комментарий