I²C (IIC, Inter-Integrated Circuit) — последовательная асимметричная шина для связи между интегральными схемами внутри электронных приборов. Разработан в компании Philips, а так как его использование требует платной лицензии, некоторые компании используют его переименованный аналог (TWI).

Производители законченных блоков и модулей, зачастую, используют именно I2C для общения с внешним миром (блоки телевизоров, магнитол, дисплеи, некоторые камеры в мобильных телефонах и т.п.), а микросхем периферии вообще на любой вкус: АЦП/ЦАП памяти разнообразные, часы реального времени, расширители вводо-выводов, гироскопы, акселерометры, компасы, драйверы светодиодов и матриц, ШИМ-контроллеры, синтезаторы частот и вообще, по-моему, всё что душе угодно.

Данные передаются по двум проводам — двунаправленная линия данных SDA и линия тактирования SCL, которая используется для синхронизации приема и передачи данных. Инициирует передачу только ведущий(master), он же генерирует такты синхронизации. Ведомый (slave), тем временем, шлет сигналы подтверждения при приеме байта, либо передает запрошенный байт или байты указанного адресного пространства. Всего на одной двупроводной шине может быть до 127 устройств.

Связь устройств по интерфейсу I2C на уровне электрических сигналов осуществляется с учетом следующих 4 правил:

  1. ВЫСОКИЙ уровень на линии данных SDA или на линии тактирования SCL не может быть установлен непосредственно устройством I2C. Все устройства I2C должны иметь выходы с открытым коллектором (или открытым стоком). ВЫСОКИЙ уровень напряжения на линиях формируется внешними подтягивающими резисторами.
  2. Информация на линии данных SDA считывается только при ВЫСОКОМ уровне на линии тактирования SCL.
  3. Информация на линию данных SDA выставляется только при НИЗКОМ уровне на линии тактирования SCL.
  4. Если шина I2C в текущий момент не используется, то на линиях должен быть установлен ВЫСОКИЙ уровень сигнала (логическая 1).

Основной формат кадра обмена по I2C состоит из 11 бит. Сначала следует старт-бит, затем 8 бит данных, далее бит подтверждения приема, затем стоп-бит. Форма представления старт- и стоп-битов нарушает записанное выше правило 3. Старт-бит определяется наличием спадающего фронта на лини SDA при ВЫСОКОМ уровне сигнала на линии SCL. Эту комбинацию сигналов принято также называть стартовым состоянием, или состоянием Старт. Стоп-бит (или состояние Стоп) определяется наличием нарастающего фронта на линии SDA при ВЫСОКОМ уровне на линии SCL. Состояния Старт и Стоп генерируются ведущим устройством.

После формирования стартового бита ведущее устройство выставляет на линию данных SDA 8 бит данных, начиная со старшего бита байта. Каждый бит сопровождается импульсом синхронизации SCL. Затем приемник генерирует бит подтверждения приема ACK, устанавливая линию SDA в НИЗКОЕ состояние на интервале девятого импульса на линии SCL.

Если приемник не распознал принятые данные, то он генерирует бит «неподтверждения» приема NACK, просто не переводя линию SDA в НИЗКОЕ состояние. В этом случае передатчик заканчивает текущий сеанс передачи данных и генерирует стоп-бит. В соответствие с алгоритмом прикладной программы передатчик пробует передать информацию позже. На рисунке ниже показаны временные диаграммы на линиях I2C, которые сопровождают передачу байта 0x26 (передача успешная, бит подтверждения ACK сформирован).

Протокол I2C предусматривает возможность принудительного снижения скорости передачи данных ведомым устройством в процессе самой передачи. Когда линия SCL находится в НИЗКОМ состоянии, ведомое устройство может принудительно продлить это состояние на линии, пока оно не будет готово принять следующий бит данных. Данная функция называется «расширением тактовых импульсов» и применяется в случаях когда ведомое устройство не успевает обработать данные. Для примера можно привести EEPROM, микросхема довольно медленная и на запись данных требует время.

При необходимости взаимодействия нескольких устройств по шине I2C у каждого подключенного к шине устройства должен быть свой адрес. Адрес может быть 7- или 10-битным.

Далее представлена структура кадра обмена по I2C при передаче 7-битного адреса. Кадр состоит из старт-бита, 7 бит адреса, бита направления передачи для следующего кадра R/W, бита подтверждения ACK. Первые 9 бит, включая старт-бит, генерируются ведущим устройством, а бит подтверждения приема — ведомым устройством, чей адрес совпадает с адресом, который выставил на линию ведущий.

Бит R/W (чтение/запись), который передается вслед за 7-битным адресом, определяет, что будет происходить с ведомым устройством: данные в него будут записываться (R/W = 0) или считываться из него (R/W = 1).

Процедура адресации происходит следующим образом:

  1. Ведущее устройство генерирует состояние Старт.
  2. Ведущее устройство выставляет на линию адрес ведомого устройства.
  3. Исходя из значения бита R/W, ведущее устройство посылает данные ведомому устройству (если R/W = 0) или считывает данные, выставленные на линию ведомым устройством (R/W = 1).
  4. Как только все данные посланы, ведущее устройство генерирует на линии состояние Стоп.

В спецификации протокола описан еще один режим, который называется «Повторный старт». В этом режиме ведущее устройство устанавливает еще раз состояние Старт и передает один байт адреса без генерации состояния Стоп. Это бывает полезно для изменения направления передачи данных.

Перейдем к STM32.

HAL библиотека предоставляет следующий набор функций:

1) Инициализация и деинициализация интерфейса

void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c);

Данные функции вызывают __weak функции

void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c);

В них CubeMX помещает функции инициализации портов, включения тактирования и включения/настройки прерываний. Для Deinit, соответственно функции деинициализации.

2) Polling mode - функции приема/передачи данных с обработкой в основном потоке. Т.е. при передаче/приеме данных, процессор будет занят выполнением данных функций с ожиданием соответствующих флагов и событий. Данные методы не рекомендуется применять при работе с большими объемами данных.

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);

3) Interrupt mode - функции используют прерывания для приема/передачи данных. Дают возможность работать другим задачам в основном потоке, но не снижают нагрузки на процессор.

HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);

4) DMA mode - функции используют DMA для приема/передачи данных, при этом не только отдают процессорное время другим задачам, но и значительно снижают нагрузку на процессор, т.к. копированием данных занимается модуль DMA.

HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);

5) Функции обработчики событий (IRQHandler and Callbacks) для режимов Interrupt и DMA.

void HAL_I2C_EV_IRQHandler(I2C_HandleTypeDef *hi2c);
void HAL_I2C_ER_IRQHandler(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode);
void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c);
void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c);

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

Функции HAL_I2C_Master_x применяются когда вы реализуете ведущее устройство на шине, HAL_I2C_Slave_x, соответственно, ведомое.

Функции HAL_I2C_x_Tx/HAL_I2C_x_Rx используются для приема/передачи данных без указания внутреннего адреса устройства с которым мы работаем.

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

Для примера возьмем макетную плату Open107V и макетку с магнитометром HMC6352.


В CubeMX создадим новый проект для МК STM32F107VCT, настроем тактирование на 72 MHz от внешнего источника.

Активируем интерфейс I2C1 с настройками по умолчанию, выбрав Mode - I2C.

Подключем HMC6352 к макетной плате к соответствующим выводам (на обратной стороне HMC6352 выводы подписаны). При использовании других микросхем с интерфейсом I2C, распиновку смотрите в DataSheet'е на свою микросхему.

Генерируем исходный код. Интерфейс подключен и настроен, остается получить показания магнитометра.

Прежде всего идем в DataSheet HMC6352. Нам необходимо найти адрес устройства на шине и метод получения показаний магнитометра.

Адрес найден:

uint8_t devAddress = 0x42;

В таблице команд находим адрес регистра хранения данных.

uint8_t regAddress = 0x41;

Обратите внимание на то, что при чтении данных будет запущен процесс расчета новых данных. Считывать будем два байта, которые сформируют 16-ти разрядное число (от 0 до 359). Показания характеризуют направление на магнитное поле земли в градусах.

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

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
        status = HAL_I2C_Mem_Read(&hi2c1, devAddress, regAddress, 1, buffer, 2, 1000);
        if (status != HAL_OK)
        {
            buffer[0] = 0;
            buffer[1] = 0;
            HAL_Delay(100);
        }
        else 
        {
            magn = ((buffer[0] << 8) + buffer[1]) / 10;
        }
        HAL_Delay(1000);
  }
  /* USER CODE END 3 */

Все готово, компилируем проект и запускаем его в режиме отладки.

Для отслеживания показаний откроем окно (панель) Watch 1, для этого выберем соответствующий пункт в меню:

View -> Watch Windows -> Watch 1

Добавим необходимые переменные для отслеживание показаний и проверим работу магнитометра запустив исполнение программы.

Если ваше устройство не отвечает или работает не стабильно, убедитесь что на шине имеются подтягивающие резисторы к питанию.

Дальнейшая диагностика неисправностей, сводится к исследованию происходящего на шине с использованием осциллографа.

Архив с проектом  - ссылка на скачивания.

Datasheet HMC6352 - ссылка на скачивание.

Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.


© Copyright 2017. Все права защищены.
Яндекс.Метрика