Stm32 интерфейс i2c описание продолжение. STM32, последовательный интерфейс I2С. Регистр управления IICC2

В статье приведено описание последовательного интерфейса I2С 32-разрядных ARM-микроконтроллеров серии STM32 от компании STMicroelectronics. Рассмотрены архитектура, состав и назначение регистров конфигурирования интерфейса, а также приведены примеры программ его использования.

Введение

Интерфейс I2С, или IIC получил свою аббревиатуру от английских слов Inter-Integrated Circuit и представляет собой последовательную шину, состоящую из двух двунаправленных линий связи с названием SDA и SCL, как сокращение от слов Serial Data Address и Serial Clock. Он обеспечивает обмен данными между микроконтроллером и различными периферийными устройствами, такими как АЦП, ЦАП, микросхемы памяти, другие микроконтроллеры и микросхемы. Схема подключения устройств по интерфейсу I2C показана на рисунке 1.

Рис. 1. Схема подключения устройств по интерфейсу I 2 C

Стандарт на интерфейс I2С был разработан фирмой Philips в начале 1980-х годов. Согласно этому стандарту, интерфейс имел 7-разрядный адрес. Он позволял обращаться к 127 устройствам на скорости до 100 кбит/с. В дальнейшем интерфейс получил своё развитие и стал 10-разрядным, позволяющим обращаться к 1023 устройствам на скорости до 400 кбит/с. Максимальное
допустимое количество микросхем, подсоединённых к одной шине, ограничивается максимальной ёмкостью шины в 400 пФ. Версия стандарта 2.0, выпущенная в 1998 году, представила
высокоскоростной режим работы со скоростью до 3,4 Мбит/с с пониженным энергопотреблением. Версия 2.1 2001 года включает в себя лишь незначительные доработки.

Описание интерфейса I 2 C

Микроконтроллер STM32 включает в свой состав интерфейс I2С, который отличается своей развитостью. Он допускает несколько ведущих устройств на шине и поддерживает высокоскоростной режим. Кроме того, в микроконтроллере STM32 интерфейс I2C можно использовать для широкого спектра приложений, включая генерацию и верификацию контрольной суммы. С ним также можно работать по протоколам SMBus (System Management Bus) и PMBus (Power Management Bus). Большинство моделей STM32 включают в свой состав два интерфейса I2С с именами I2С1 и I2С2. Интерфейс может работать в одном из следующих четырёх режимов:

  • Slave transmitter (ведомый передатчик);
  • Slave receiver (ведомый приёмник);
  • Master transmitter (ведущий передатчик);
  • Master receiver (ведущий приёмник).

По умолчанию интерфейс работает в режиме «Ведомый» и автоматически переключается на «Ведущий» после генерирования старт-условия. Переключение с «Ведущего» на «Ведомый» происходит при потере арбитража или после генерирования стоп-условия, что позволяет работать нескольким «Ведущим» микроконтроллерам в одной системе поочередно. В режиме «Ведущий» I2C инициирует обмен данными и генерирует тактовый сигнал. Передаче последовательных данных всегда предшествует старт-условие, а завершается обмен всегда стоп-условием. Оба этих условия генерируются в режиме «Ведущий» программно. В режиме «Ведомый» I2C способен распознать свой собственный адрес (7 или 10 бит) и адрес общего вызова. Определение наличия адреса общего вызова можно включить или отключить программно. Адрес и данные передаются 8-битными посылками, старшим битом вперёд. Первый байт, следующий за стартусловием, содержит адрес (один байт в 7-битном режиме и два байта в 10-битном режиме). Адрес всегда передаётся в режиме «Ведущий».

За 8 тактами передачи байта данных следует 9-й такт, в течение которого приёмник должен послать бит уведомления ACK, получивший своё название от слова ACKnowledge. На рисунке 2 приведена временна′я диаграмма одной посылки интерфейса I2C. Наличие уведомления в ответе можно программно включить или отключить. Размерность адреса интерфейса I2C (7 бит или 10 бит и адрес
общего вызова) можно выбрать программно.


Рис. 2. Временная диаграмма одной посылки интерфейса I

Архитектура блока интерфейса I 2 С

Функциональная схема блока интерфейса I2C для микроконтроллера STM32 приведена на рисунке 3.


Рис. 3. Функциональная схема блока интерфейса I 2 C

Регистр сдвига на этой схеме представляет собой основной регистр, через который передаются и принимаются данные. Передаваемые данные предварительно записываются в регистр данных, после чего через регистр сдвига последовательно транслируются в линию связи SDA. Принимаемые по этой же линии связи данные накапливаются в регистре сдвига, а затем перемещаются в регистр данных. Таким образом, интерфейс может передавать и принимать данные только поочередно. Кроме того, регистр сдвига аппаратно подключён к компаратору, который позволяет сравнивать принятый адрес
с адресными регистрами и, таким образом, определять, для кого предназначен очередной блок данных. Узел управления частотой позволяет формировать сигнал синхронизации SCL в роли ведущего и синхронизироваться от этого сигнала в качестве ведомого устройства. Регистр CCR обеспечивает программную настройку данного узла. Блок интерфейса подключён к выходу PCLK1 шины APB1 через
два предварительных делителя. Микроконтроллер поддерживает два режима обмена: стандартный (Standard Speed) – до 100 кГц, и быстрый (Fast Speed) – до 400 кГц. В зависимости от режима обмена частота тактирования модуля должна быть не менее 2 МГц в стандартном режиме и не менее 4 МГц в быстром режиме. Блок вычисления позволяет аппаратно вычислять контрольную сумму блока данных и сохранять её в регистре PEC. Управление блоком интерфейса I2C, а также формирование флагов событий и прерываний выполняется узлом логики управления. Он же позволяет обслуживать запросы ПДП и формировать сигнал ACK. Связь этого блока с микроконтроллером осуществляется программно с помощью регистров управления CR1, CR2 и регистров состояния SR1, SR2.

Прерывания от I 2 C

Интерфейс I2C имеет аппаратную организацию, способную формировать запросы на прерывание в зависимости от режима работы и текущих событий. В таблице 1 приведены запросы на прерывание от интерфейса I2C.

Таблица 1. Запросы на прерывание от интерфейса I 2 C

Описание регистров

Для работы с интерфейсом I2C в микроконтроллере STM32 имеются специальные регистры. Карта этих регистров с названием входящих в них разрядов представлена в таблице 2. Рассмотрим регистры, необходимые для работы интерфейса I2С. К ним относятся:

  • I 2 C_CR1 – управляющий регистр 1;
  • I 2 C_CR2 – управляющий регистр 2;
  • I 2 C_OAR1 – регистр собственного адреса 1;
  • I 2 C_OAR2 – регистр собственного адреса 2;
  • I 2 C_DR – регистр данных;
  • I 2 C_SR1 – статусный регистр 1;
  • I 2 C_SR2 – статусный регистр 2;
  • I 2 C_CCR – регистр управления тактовым сигналом;
  • I 2 C_TRISE – регистр параметра TRISE.

Некоторые разряды этих регистров используются для работы в режиме SMBus. Регистр I2C_CR1 является первым управляющим регистром интерфейса I2C. Он имеет следующие управляющие разряды:

  • разряд 15 SWRST – обеспечивает программный сброс шины I 2 C;
  • разряд 14 – зарезервирован;
  • разряд 13 SMBus – формирует сигнал тревоги в режиме SMBus;
  • разряд 12 PEC – служит для функции проверки ошибки пакета (Packet Error Checking);
  • разряд 11 POS – служит для анализа сигналов ACK или PEC при приёме;
  • разряд 10 ACK – возвращает бит уведомления ACK после приёма корректного байта адреса или данных;
  • разряд 9 STOP – служит для формирования и анализа стоп-условия;
  • разряд 8 START – служит для формирования и анализа старт-условия;
  • разряд 7 NOSCTETCH – отключает растяжку такта в режиме ведомого;
  • разряд 6 ENGC – разрешает общий вызов;
  • разряд 5 ENPEC – разрешает сигнал PEC;
  • разряд 4 ENARP – разрешает сигнал ARP;
  • разряд 3 SMBTYPE – назначает тип интерфейса в качестве ведущего или ведомого для режима SMBus;
  • разряд 2 – зарезервирован;
  • разряд 1 SMBUS – переключает режимы I 2 C и SMBus;
  • разряд 0 PE – разрешает работу интерфейса.

Регистр I2C_CR2 является вторым управляющим регистром интерфейса I2C и имеет следующие управляющие разряды:

  • разряды 15…13 – зарезервированы;
  • разряд 12 LAST – используется в режиме ведущего приёмника, чтобы позволить генерацию сигнала NACK по последнему принятому байту;
  • разряд 11 DMAEN – разрешает запрос DMA;
  • разряд 10 ITBUFEN – разрешает прерывания от буфера;
  • разряд 9 ITEVTEN – разрешает прерывания от события;
  • разряд 8 ITERREN – разрешает прерывания от ошибки;
  • разряды 7 и 6 – зарезервированы;
  • разряды 5…0 FREQ – задают частоту работы шины.

Регистр I2C_OAR1 – первый регистр собственного адреса, включает в себя следующие разряды:

  • разряд 15 ADDMODE – задаёт 7- или 10-разрядный режим адресации в качестве ведомого;
  • разряды 14…10 – зарезервированы;
  • разряды 9 и 8 ADD – назначают 9 и 8 биты адреса при 10-битной адресации интерфейса;
  • разряды 7…1 ADD – назначают 7…1 биты адреса;
  • разряд 0 ADD0 – назначает бит 0 адреса при 10-битной адресации интерфейса.

Регистр I2C_OAR2 – второй регистр собственного адреса, включает в себя следующие разряды:

  • разряды 15…8 – зарезервированы;
  • разряды 7…1 ADD – назначают 7…1 биты адреса в режиме двойной адресации;
  • разряд 0 ENDUAL – разрешает режим двойной адресации.

Регистр данных I2C_DR имеет 8 разрядов DR для приёма и передачи данных на шину I2C. В этот регистр данные записываются для передачи и читаются из него при приёме. Разряды 15…9 – зарезервированы. Регистр I2C_SR1 – первый статусный регистр, и включает в себя следующие разряды:

  • разряд 15 SMBALERT – сигнализирует о тревоге шины SMBus;
  • разряд 13 – зарезервирован;
  • разряд 14 TIMEOUT – оповещает об ошибке превышения времени для сигнала SCL;
  • разряд 12 PECERR – свидетельствует об ошибке PEC при приёме;
  • разряд 11 OVR – формируется при ошибке переполнения данных;
  • разряд 10 AF – возникает в случае ошибки уведомления;
  • разряд 9 ARLO – указывает на ошибку потери прав на шину;
  • разряд 8 BERR – устанавливается при ошибке шины;
  • разряд 7 TxE – оповещает, что регистр данных пуст;
  • разряд 5 – зарезервирован;
  • разряд 6 RxNE – информирует, что регистр данных не пуст;
  • разряд 4 STOPF – детектирует стоп-условие в режиме ведомого;
  • разряд 3 ADD10 – устанавливается, когда ведущий послал первый байт адреса при 10-битной адресации;
  • разряд 2 BTF – оповещает о завершении передачи байта;
  • разряд 1 ADDR – устанавливается если послан адрес в режиме ведущего или принят адрес в режиме ведомого;
  • разряд 0 SB – устанавливается при генерации старт-условия в режиме ведущего.

Регистр I2C_SR2 – второй статусный регистр, включает в себя следующие разряды:

  • разряды 15…8 PEC – содержат контрольную сумму кадра;
  • разряд 7 DUALF – является флагом двойной адресации в режиме ведо-мого;
  • разряд 6 SMBHOST – устанавливается, когда принят заголовок SMBus Host в режиме ведомого;
  • разряд 5 SMBDEFAULT – возникает, если принят адрес по умолчанию для SMBus-устройства в режиме ведомого;­
  • разряд 4 GENCALL – указывает, что принятадрес общего вызова в режиме ведомого;
  • разряд 3 – зарезервирован;
  • разряд 2 TRA – оповещает о режиме передачи/приёма;
  • разряд 1 BUSY – информирует, что шина занята;
  • разряд 0 MSL – детектирует режим «Ведущий»/«Ведомый».

Регистр I2C_CCR – регистр управления тактовым сигналом, который включает в себя разряды:

  • разряд 15 F/S – задаёт стандартную или быструю скорость для режима ведущего;
  • разряд 14 DUTY – назначает скважность 2 или 16/9 в быстром режиме;
  • разряды 13 и 12 – зарезервированы;
  • разряды 11…0 CCR – управляют тактовым сигналом для быстрой и стандартной скорости в режиме ведущего.

Регистр I2C_TRISE – регистр параметра TRISE, который включает в себя:

  • разряды 15…6 – зарезервированы;
  • разряды 5…0 TRISE – определяют максимальное время фронта для быстрой и стандартной скорости в режиме ведущего. Данный пара-метр задаёт момент времени, по которому производятся выборка состояния линий.

Более подробное описание назначения всех регистров I2C и их разрядов можно найти на сайте www.st.com .

П рограммирование интерфейса I 2 С

Рассмотрим практическую реализацию по использованию интерфейса I2С. Для этого можно воспользоваться стандартной библиотекой периферии микроконтроллера STM32. Для интерфейса I2C настройки режима, скорости и всего остального находятся в заголовочном файле и объявлены в виде структуры:

I2C_InitTypeDef:typedef struct{ uint32_t I2C_ClockSpeed; uint16_t I2C_Mode; uint16_t I2C_DutyCycle; uint16_t I2C_OwnAddress1; uint16_t I2C_Ack; uint16_t I2C_ AcknowledgedAddress; }I2C_InitTypeDef;

В этой структуре её элементы имеют следующее назначение:

  • uint32_t I 2 C_ClockSpeed – частота тактового сигнала, максимум – 400 КГц;
  • uint16_t I 2 C_Mode – режим работы;
  • uint16_t I 2 C_DutyCycle – настройки для работы в быстром режиме;
  • uint16_t I 2 C_OwnAddress – собственный адрес устройства;
  • uint16_t I 2 C_Ack – включено или нет использование бита подтверждения ACK;
  • uint16_t I 2 C_AcknowledgedAddress – выбор формата адреса: 7 бит или 10 бит.

Рассмотрим процедуры инициализации и работы с интерфейсом I2C. Для настройки интерфейса I2C в качестве ведущего устройства и передачи данных через него необходимо выполнить следующие действия:

  1. разрешить тактирование портов;
  2. инициализировать I 2 C, задав его скорость, адрес и формат адреса;
  3. назначить выводы микроконтроллера;
  4. разрешить работу интерфейса;
  5. сформировать стартовое условие;
  6. послать адрес адресуемого устройства и данные;
  7. сформировать стоповое условие.

Для облегчения процесса программирования желательно создать набор основных функций для работы с I2C. В листинге 1 приведена функция инициализации интерфейса I2C в соответствии с описанным выше алго­ритмом.

Листинг 1 GPIO_InitTypeDef gpio; // Создание структуры для портов ввода-вывода I2C_InitTypeDef i2c; // Создание структуры для интерфейса I2C void init_I2C1(void) { // Включить тактирование RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); // Инициализировать I2C i2c.I2C_ClockSpeed = 100000; i2c.I2C_Mode = I2C_Mode_I2C; i2c.I2C_DutyCycle = I2C_DutyCycle_2; // Задать адрес=0x12 i2c.I2C_OwnAddress1 = 0x12; i2c.I2C_Ack = I2C_Ack_Disable; i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &i2c); // Назначить выводы интерфейса gpio.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;

Теперь рассмотрим функцию для общения по I2C. Для расширения возможностей эта функция имеет три параметра: номер используемого блока I2C, направление передачи данных и адрес подчинённого устройства. Код данной функции приведён в лис­тинге 2.

Листинг 2 void I2C_StartTransmission(I2C_TypeDef* I2Cx, uint8_t transmissionDirection, uint8_t slaveAddress) { // Ждать освобождения шины while (I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)); // Сформировать старт-условие I2C_GenerateSTART(I2Cx, ENABLE); // Ждать установки бита while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)); // Отправить адрес ведомому устройству I2C_Send7bitAddress(I2Cx, slaveAddress, transmissionDirection); // Если передача данных if(transmissionDirection== I2C_Direction_Transmitter) {while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));} // Если прием данных if(transmissionDirection== I2C_Direction_Receiver) {while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));} }

Приведённая функция использует простые функции передачи и приёма данных, приведённые в листинге 3.

Листинг 3 // Функция передачи данных void I2C_WriteData(I2C_TypeDef* I2Cx, uint8_t data) { // Вызвать библиотечную функцию передачи данных I2C_SendData(I2Cx, data); // Ждать окончания передачи данных while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); } // Функция приема данных uint8_t I2C_ReadData(I2C_TypeDef* I2Cx) { // Ждать поступления данных while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)); data = I2C_ReceiveData(I2Cx); // Считать данные из регистра return data; // Возвратить данные в вызывающую функцию

Закончив обмен данными по I2C, необходимо вызвать функцию формирования стоп-условия
I2C_Generate STOP(I2Cx, ENABLE).
На основе приведённых функций можно создавать программы для работы с множеством разнообразных периферийных устройств.

Заключение

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

Первые шаги с STM32 и компилятором mikroC для ARM архитектуры - Часть 4 - I2C, pcf8574 и подключение LCD на базе HD4478

Следующую статью я хочу посвятить работе с распространенным интерфейсом i2c, достаточно часто используемом в разнообразных микросхемах, подключаемых к микроконтроллеру.

I2C представляет собой шину работающую по двум физическим соединениям (помимо общего провода). Достаточно много о ней расписано в Интернете, неплохие статьи есть в Википедии . Кроме того алгоритм работы шины очень понятно описан . В вкратце, шина представят собой двухпроводную синхронную шину. На шине может одновременно находится до 127 устройств (адрес устройства 7-битный, к этому вернемся далее). Ниже приведена типичная схема подключения устройств к i2c шине, с МК в качестве ведущего устройства.


Для i2c все устройства (как мастер так и слейвы) используют open-drain выходы. Проще говоря они могут притягивать шину ТОЛЬКО К ЗЕМЛЕ. Высокий уровень ша шине обеспечивается подтягивающими резисторами. Номинал этих резисторов обычно выбирается в диапазоне от 4,7 до 10 кОм. i2c достаточна чувствительна к физическим линиям, соединяющим устройства, поэто если используется соединение с большой емкостью (например длинный тонкий или экранированный кабель), влияние этой емкости может «размыть» фронты сигналов и помешать нормальной работе шины. Чем меньше подтягивающий резистор, тем меньше влияет эта емкость на характеристику фронтов сигнала, но ТЕМ БОЛЬШЕ НАГРУЗКА на выходные транзисторы на интерфейсах i2c. Значение этих резисторов подбирается для каждой конкретной реализации, но они не должны быть меньше 2,2 кОмов, иначе можно просто спалить выходные транзисторы в устройствах, работающих с шиной.

Шина состоит из двух линий: SDA (линии данных) и SCL (тактирующего сигнала). Тактирует шину Мастер устройство , обычно наш МК. Когда на SCL высокий уровень информация считывается с шины данных. Изменять состояние SDA можно только при низком уровне тактирующего сигнала . При высоком уровне SCL сигнал на SDAизменяется при формировании сигналов START (при высоком уровне SCL сигнал на SDA изменяется высокого на низкий) и STOP - при высоком уровне SCL сигнал на SDA изменяется с низкого на высокий).

Отдельно следует сказать, что в i2c адрес задается 7-битным числом. 8 - младший бит указывает направление передачи данных 0 - означает что слейв будет передавать данные, 1 - принимать. . Вкратце алгоритм работы с i2c такой:

  • Высокий уроень на SDA и SCL - шина свободна, можно начинать работу
  • Мастер поднимает SCL в 1, и изменяет состояние SDA c 1 на 0 - притягивает его к земле - формируется сигнал START
  • Мастер передает 7-битный адрес слейва с битом направления (данные на SDA выставляются когда SCL притянут к земле, и читаются слейвом когда он отпущен). Если слейв не успевает «схавать» предыдущий бит, он притягивает SCL к земле, давая понять мастеру что состаяние шинны данных не нужно менять: «еще читаю предыдущий». После того как мастер отпустил шину он проверяет, отпустил ли ее слейв .
  • После передачи 8 бит адреса мастер генерирует 9-й такт и отпускает шину данных. Если слейв услышал и свой адрес и принял его то он прижмет SDA к земле . Так формируется сигнал ASK - принял, все ОК. Если слейв ничего не понял, или его просто там нет то некому будет прижать шину. мастер подождет таймаут и поймет что его не поняли.
  • После передачи адреса, если у нас выставлено направление от мастера к слейву (8 бит адреса равен 1), то мастер передает данные в слейв, не забывая после передачи каждого байта проверять наличие ASK от слейва, ожидая обработки поступившей информации ведомым устройством.
  • При приеме мастером данных от слейва, мастер сам формирует сигнал ASK после приема каждого байта, а слейв контролирует его наличие. Мастер может специально не послать ASK перед отправкой команды STOP , обычно, так давая понять ведомому, что больше предавать данные не нужно.
  • Если после отправки данных мастером (режим записи) необходимо прочитать данные со слейва, то мастер формирует снова сигнал START , отправляя адрес слейва с флагом чтения. (еcли перед командой START не был передан STOP то формируется команда RESTART ). Это используется для смены направления общения мастре-слейв. Например мы передаем слейву адрес регистра, а потом читаем из него данные.)
  • По окончанию работы со слейвом мастер формирует сигнал STOP - при высоком уровне тактирующего сигнала формирует переход шины данных с 0 в 1.
В STM 32 есть аппаратно реализованные приемопередатчики i2c шины. Таких модулей в МК может быть 2 или 3. Для их конфигурации используются специальные регистры, описанные в референсе к используемому МК.

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

I2Cn_Init_Advanced(unsigned long: I2C_ClockSpeed, const Module_Struct *module);

  • n - номер используемого модуля, например I2C1 или I2C2 .
  • I2C_ClockSpeed - скорость работы шины, 100000 (100 kbs, стандартный режим) или 400000 (400 kbs, быстрый режим). Второй в 4 раза быстрее, но его поддерживают не все устройства
  • *module - указатель на периферийный модуль, например &_GPIO_MODULE_I2C1_PB67 , здесь не забываем что Code Assistant (ctrl-пробел ) очень помогает.
Для начала проверим свободность шины, для этого существует функция I2Cn_Is_Idle(); возвращающая 1 если шина свободна, и 0 если по ней идет обмен.

I2Cn_Start();
где n - номер используемого модуля i2c нашего микроконтроллера. Функция вернет 0 если на шине возникла ошибка и 1 если все ОК.

Для того чтоб передать данные слейву используем функцию:

I2Cn_Write(unsigned char slave_address, unsigned char *buf, unsigned long count, unsigned long END_mode);

  • n - номер используемого модуля
  • slave_address - 7-битный адрес слейва.
  • *buf - указатель на наши данные - байт или массив байтов.
  • count - количество передаваемых байт данных.
  • END_mode - что делать после передачи данных слейву, END_MODE_STOP - передать сигнал STOP , либо END_MODE_RESTART снова отправить START , сформировав сигнал RESTART и дав понять ведомству, что сеанс работы с ним не окончен и с него сейчас будут читать данные.
Для чтения данных со слейва используется функция:

I2Cn_Read(char slave_address, char *ptrdata, unsigned long count, unsigned long END_mode);

  • n - номер используемого модуля
  • slave_address - 7-битный адрес слейва.
  • *buf - указатель на переменную или массив в который мы принимаем данные, тип char или short int
  • count - количество принимаемых байт данных.
  • END_mode - что делать после приема данных от слейва - END_MODE_STOP - передать сигнал STOP , либо END_MODE_RESTART отправить сигнал RESTART .
Давайте попробуем что-то подключить к нашему МК. Для начала: распостраненную микросхему PCF8574(A) представляющего собой расширитель портов ввода вывода с управлением по шине i2c. Данная микросхема содержит всего один внутренний регистр, являющийся ее физическим портом ввода-вывода. Тоесть если ей передать байт, он тут-же выставится на ее выводы. Если считать с нее байт (Передать START адрес с флагом чтения, сигнал RESTERT, прочитать данные и в конце сформировать сигнал STOP ) то он отразит логические состояния на ее выводах. Подключим нашу микросхему в соответствии с даташитом:


Адрес микросхемы формируется из состояния выводов A0, А1, А2 . Для микросхемы PCF8574 адрес будет: 0100A0A1A2 . (Например у нас A0, А1, А2 имеют высокий уровень, соответственно адрес нашей микросхемы будет 0b0100111 = 0x27). Для PCF8574A - 0111A0A1A2 , что с нашей схемой подключения даст адрес 0b0111111 = 0x3F . Если, допустим A2 соединить с землей, то адрес для PCF8574A будет 0x3B . Итого на одну шину i2c можно одновременно повесить 16 микросхем, по 8 PCF8574A и PCF8574.

Давайте попробуем что-то передать иннициализировать i2c шину и что-то передать нашей PCF8574.

#define PCF8574A_ADDR 0x3F //Адреc нашей PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) { I2C1_Start(); // Формируем сигнал START I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); // Передаем 1 байт данных и формируем сигнал STOP } char PCF8574A_reg; // переменная которую мы пишем в PCF8574 void main () { I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Запускаем I2C delay_ms(25); // Немного подождем PCF8574A_reg.b0 = 0; //зажжем первый светодиод PCF8574A_reg.b1 = 1; // погасим второй светодиод while (1) { delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; //инвертируем состояние светодиодов I2C_PCF8574_WriteReg (PCF8574A_reg); //передадим нашей PCF8574 данные } }
Компилируем и запускаем нашу программу и видим что наши светодиоды попеременно моргают.
Я не просто так подключил светодиоды катодом к нашей PCF8574. Все дело в том, что микросхема при подачи на выход логического 0 честно притягивает свой вывод к земле, а вот при подаче логической 1 подключает его к + питания через источник тока в 100 мкА. Тоесть «честной» логической 1 на выходе не получить. И светодиод от 100 мкА не зажечь. Сделано это для того, чтобы без дополнительных регистров настраивать вывод PCF8574 на вход. Мы просто пишем в выходной регистр 1 (фактически устанавливаем состояния ножки в Vdd) и можем просто коротить его на землю. Источник тока не даст «сгореть» выходному каскаду нашего расширителя ввода/вывода. Если ножка притянута к земле, то на ней потенциал земли, и читается логический 0. Если ножка притянута к +, то читается логическая 1. С одной стороны просто, но с другой, про это всегда нужно помнить, работая с данными микросхемами.


Давайте попробуем прочитать состояние выводов нашей микросхемы-расширителя.

#define PCF8574A_ADDR 0x3F //Адреc нашей PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) { I2C1_Start(); // Формируем сигнал START I2C1_Write(PCF8574A_ADDR, &wData, 1, END_MODE_STOP); // Передаем 1 байт данных и формируем сигнал STOP } void I2C_PCF8574_ReadReg(unsigned char rData) { I2C1_Start(); // Формируем сигнал START I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); // Читаем 1 байт данных и формируем сигнал STOP } char PCF8574A_reg; //переменная которую мы пишем в PCF8574 char PCF8574A_out; // переменная в которую мы читаем и PCF8574 char lad_state; //включен либо выключен наш светодиод void main () { I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Запускаем I2C delay_ms(25); // Немного подождем PCF8574A_reg.b0 = 0; // зажжем первый светодиод PCF8574A_reg.b1 = 1; // погасим второй светодиод PCF8574A_reg.b6 = 1; // Притяним выводы 6 и 7 к питанию. PCF8574A_reg.b7 = 1; while (1) { delay_ms(100); I2C_PCF8574_WriteReg (PCF8574A_reg); // пишем данные в РCF8574 I2C_PCF8574_ReadReg (PCF8574A_out); // читаем из РCF8574 if (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; // Если нажата 1 кнопка (6 бит прочитанного байта из РCF8574 равен 0, то включим/выключим наш светодиод) if (~PCF8574A_out.b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // аналогично для 2 кнопки и 2 светодиода } }
Теперь нажимая на кнопочки мы включаем или отключаем наш светодиод. У микросхемы есть еще вывод INT . На нем формируется импульс каждый раз, когда меняется состояние выводов нашего расширителя ввода/вывода. Подключив его в входу внешнего прерывания нашего МК (как настроить внешние прерывания и как с ними работать я расскажу в одной из следующих статей).

Давайте используя наш расширитель портов подключим через него символьный дисплей. Таких существует великое множество, но практически все они построены на базе чипа-контроллера HD44780 и его клонов. Например я использовал дисплей LCD2004.


Даташит на него и контроллер HD44780 можно с легкостью найти в Интернете. Подключим наш дисплей к РCF8574, а ее, соответственно к нашему STM32.

HD44780 использует параллельный стробируемый интерфейс. Данные передаются по 8 (за один такт) либо 4 (за 2 такта) стробирующего импульса на выводе E . (читаются контроллером дисплея по нисходящему фронту, переходу с 1 в 0). Вывод RS указывает передаем ли мы нашему дисплею данные (RS = 1 ) (символы которые он должен отобразить, фактически из ASCII коды) либо команды (RS = 0 ). RW указывает направление передачи данных, запись либо чтение. Обычно мы пишем данные в дисплей, поэтому (RW = 0 ). Резистор R6 управляет контрастностью дисплея. Просто подключать вход регулировке контрастности к земле или питанию нельзя, иначе ничего не увидите. . VT1 служит для включения и выключения подсветки дисплея по командам МК. В MicroC есть библиотека для работе с такими дисплеями по параллельному интерфейсу, но обычно, тратить на дисплей 8 ног накладно, поэтому я практически всегда использую РCF8574 для работы с такими экранчиками. (Если кому-то будет интересно, то напишу статью про работу с дисплеями на базе HD44780 встроенными в MicroC по параллельному интерфейсу.) Протокол обмена не особо сложный (мы будем использовать 4 линии данных и передавать информацию за 2 такта), его наглядно показывает следующая временная диаграмма:


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

  • 0x28 - связь с индикатором по 4 линиям
  • 0x0C - включаем вывод изображения, отключаем отображение курсора
  • 0x0E - включаем вывод изображения, включаем отображение курсора
  • 0x01 - очищаем индикатор
  • 0x08 - отключаем вывод изображения
  • 0x06 - после вывода символа курсор сдвигается на 1 знакоместо
Так как нам будет нужно достаточно часто работать с данным индикатором то создадим подключаемую библиотеку «i2c_lcd.h» . Для этого в Project Maneger Header Files и выберем Add New File . Создадим наш заголовочный файл.

#define PCF8574A_ADDR 0x3F //Адреc нашей PCF8574 #define DB4 b4 // Соответствие выводов PCF8574 и индикатора #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define RW b2 #define RS b1 #define BL b0 //управление подсветкой #define displenth 20 // количество символов в строке нашего дисплея static unsigned char BL_status; // переменная хранящая состояние подсветки (вкл/выкл) void lcd_I2C_Init(void); // Функция иннициализации дисплея и PCF8574 void lcd_I2C_txt(char *pnt); // Выводит на экран строку текста, параметр - указатель на эту строку void lcd_I2C_int(int pnt); // Выводит на экран значение целочисленной переменной, параметр - выводимое значение void lcd_I2C_Goto(unsigned short row, unsigned short col); // перемещает курсор на указанную позицию, параметры row - строка (от 1 до 2 или 4 в зависимости от дисплея) и col - (от 1 до displenth)) void lcd_I2C_cls(); // Очищает экран void lcd_I2C_backlight (unsigned short int state); // Включает (при передаче 1 и отключает - при передаче 0 подсветку дисплея)
Теперь опишем наши фунции, снова идем в Project Maneger клацнем правой кнопкой по папке Sources и выберем Add New File . Создаем файл «i2c_lcd.с» .

#include "i2c_lcd.h" //инклудим наш хедер-файл char lcd_reg; //регистр временного хранения данных отправляемых в PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) //функция отпарвки данных по i2c в чип PCF8574 { I2C1_Start(); I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); } void LCD_COMMAND (char com) //функция отправки команды нашему дисплею { lcd_reg = 0; //пишем 0 во временный регистр lcd_reg.BL = BL_status.b0; //пин подсветки выставляем в соответстви со значением переменной, хранящей состояние подсветки lcd_reg.DB4 = com.b4; //выставляем на шину данных индикатора 4 старших бита нащей команды lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; lcd_reg.EN = 1; //ставим строб. вывод в 1 I2C_PCF8574_WriteReg (lcd_reg); //пишем в регистр PCF8574, фактически отправив данные на индикатор delay_us (300); //ждем тайммаут lcd_reg.EN = 0; //сбрасываем строб импульс в 0, индикатор читает данные I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.DB4 = com.b0; //то же самое для 4 младших бит lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.EN = 1; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); } void LCD_CHAR (unsigned char com) //отправка индикатору данных (ASCII кода символа) { lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; //отправка символа отличается от отправки команды установкой в 1 бита RS lcd_reg.DB4 = com.b4; //выставляем на входах 4 старших бита lcd_reg.DB5 = com.b5; lcd_reg.DB6 = com.b6; lcd_reg.DB7 = com.b7; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg.EN = 0; //сбрасываем строб. импульс в 0, индикатор читает данные I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; lcd_reg.DB4 = com.b0; //выставляем на входах 4 младших бита lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg); delay_us (300); } void lcd_I2C_Init(void) { I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); //иннициализируем наш I2c модуль у МК delay_ms(200); lcd_Command(0x28); // Дисплей в режиме 4 бита за такт delay_ms (5); lcd_Command(0x08); //Отключаем вывод данных на дисплей delay_ms (5); lcd_Command(0x01); //Очищаем дисплей delay_ms (5); lcd_Command(0x06); //Включаем автоматический сдвиг курсора после вывода символа delay_ms (5); lcd_Command(0x0C); //Включаем отображение информации без отображения курсора delay_ms (25); } void lcd_I2C_txt(char *pnt) //Вывод строки символов на дисплей { unsigned short int i; //временная переменная индекса масисва символов char tmp_str; //временный массив символов, длиной на 1 больше длинны строки дисплея, так как строку нужно закончить сиv символом NULL ASCII 0x00 strncpy(tmp_str, pnt, displenth); //копируем в нашу временную строку не более displenth символов исходной строки for (i=0; i Теперь подключим только что созданную библиотеку у файлу с нашей главной функцией:

#include "i2c_lcd.h" //инклудим наш хедер-файл unsigned int i; //временная переменная счетчик void main() { lcd_I2C_Init(); //иннициализируем дисплей lcd_I2C_backlight (1); //включим подсветку lcd_I2C_txt ("Hellow habrahabr"); //выведем на дисплей стрроку while (1) { delay_ms(1000); lcd_I2C_Goto (2,1); //перейдем к 1 символу 2 строки lcd_i2c_int (i); //выведем значение на дисплей i++; // инкриментируем счетчик } }

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

В следующей статье мы продолжем разбиратся с i2c протоколом и устройствами работающем с ним. Рассмотрим работу с EEPROM 24XX памятью и акселерометром/гироскопом MPU6050.

Опубліковано 26.10.2016

В предыдущей статье мы рассмотрели работу STM32 с шиной I 2 C в качестве Мастера. То есть, он был ведущий и опрашивал датчик. Теперь сделаем так, чтобы STM32 был Slave-ом и отвечал на запросы, то есть сам работал как датчик. Мы выделим 255 байт памяти под регистры с адресами от 0 до 0xFF, и позволим Мастеру в них писать/читать. А чтобы пример был не таким простым, сделаем из нашего STM32, еще и аналого-цифровой преобразователь с интерфейсом I 2 C. ADC будет обрабатывать 8 каналов. Результаты преобразований контроллер будет отдавать Мастеру при чтении из регистров. Поскольку результат преобразования ADC занимает 12 бит, нам потребуется 2 регистра (2 байта) на каждый канал ADC.

i2c_slave.h содержит настройки:

I2CSLAVE_ADDR – адрес нашего устройства;

ADC_ADDR_START – начальный адрес регистров, которые отвечают за результаты преобразований ADC.

В файле i2c_slave.c нас больше всего интересуют функции get_i2c1_ram и set_i2c1_ram . Функция get_i2c1_ram отвечает за считывание данных из регистров. Она возвращает данные с указанного адреса, которые отдаются Мастеру. В нашем случае данные считываются из массива i2c1_ram , но, если Мастер спрашивает адреса регистров из диапазона отведенного для результатов ADC, то отправляются данные преобразований ADC.

get_i2c1_ram :

Uint8_t get_i2c1_ram(uint8_t adr) { //ADC data if ((ADC_ADDR_START <= adr) & (adr < ADC_ADDR_START + ADC_CHANNELS*2)) { return ADCBuffer; } else { // Other addresses return i2c1_ram; } }

Функция set_i2c1_ram – записывает данные принятые от Мастера в регистры с указанным адресом. В нашем случае данные просто записываются в массив i2c1_ram . Но это не обязательно. Вы можете, например, добавить проверку, и, когда на определенный адрес приходит определенное число, выполнить какие-то действия. Таким образом, Вы сможете подавать микроконтроллеру разные команды.

set_i2c1_ram :

Void set_i2c1_ram(uint8_t adr, uint8_t val) { i2c1_ram = val; return; }

Инициализация достаточно проста:

Int main(void) { SetSysClockTo72(); ADC_DMA_init(); I2C1_Slave_init(); while(1) { } }

Сначала мы устанавливаем максимальную частоту работы контроллера. Максимальная скорость необходима, когда нужно избежать любых задержек на шине I 2 C. Затем запускаем работу ADC с использованием DMA. О . О . И, наконец, выполняем инициализацию шины I 2 C как Slave . Как видите, ничего сложного.

Теперь подключим наш модуль STM32 к Raspberry Pi. К каналам ADC подключим потенциометры. И будем считывать с нашего контроллера показатели ADC. Не забываем, что для работы шины I 2 C нужно на каждую линию шины установить подтягивающие резисторы.

В консоли Raspberry проверим видно ли наше устройство на шине I 2 C (о том, ):

I2cdetect -y 1

Как видите, адрес устройства 0x27 , хотя мы указали 0x4E. Когда будет время, подумайте – почему так произошло.

Для считывания из регистров I 2 C-Slave устройства выполняем команду:

I2cget -y 1 0x27 0x00

Где:
0x27 – адрес устройства,
0x00 – адрес регистра (0x00…0xFF).

Для записи в регистры I 2 C-Slave устройства выполняем команду:

I2cset -y 1 0x27 0xA0 0xDD

Де:
0x27 – адрес устройства,
0xA0 – адрес регистра
0xDD -8-bit данные (0x00…0xFF)

Предыдущая команда записала число 0xDD в регистр 0xA0 (писать в первые 16 регистров можно, и смысла нет, по они отведены под ADC). Теперь прочитаем:

I2cget -y 1 0x27 0xA0

Чтобы упростить процесс считывания данных ADC-каналов я написал скрипт:

#!/usr/bin/env python import smbus import time bus = smbus.SMBus(1) address = 0x27 while (1): ADC = {}; for i in range(0, 8): LBS = bus.read_byte_data(address, 0x00+i*2) MBS = bus.read_byte_data(address, 0x00+i*2+1) ADC[i] = MBS*256 + LBS print ADC time.sleep(0.2)

Он опрашивает и выводит в консоль результаты всех 8-ми ADC-каналов.

Аналогичным образом можно объединить несколько микроконтроллеров. Один из них должен быть Master (), другие Slave.

Желаю успехов!

Кто-то любит пирожки, а кто-то - нет.

Интерфейс i2c широко распространён и используется. В stm32f4 модулей, реализующих данный протокол, аж целых три штуки.
Естественно, с полной поддержкой всего этого дела.

Работа с модулем, в целом, такая же, как и в других контроллерах: даёшь ему команды, он их выполняет и отчитывается о результате:
Я> Шли START.
S> Ок, послал.
Я> Круто, шли адрес теперь. Вот такой: 0xXX.
S> Ок, послал. Мне сказали, что ACK. Давай дальше.
Я> Жив ещё, хорошо. Вот тебе номер регистра: 0xYY, - шли.
S> Послал, получил ACK.
Я> Шли ему теперь данные, вот тебе байт: 0xZZ.
S> Послал, он согласен на большее: ACK.
Я> Фиг ему, а не ещё. Шли STOP.
S> Okay.

И всё примерно в таком духе.

В данном контроллере выводы i2c раскиданы по портам таким образом:
PB6: I2C1_SCL
PB7: I2C1_SDA

PB8: I2C1_SCL
PB9: I2C1_SDA

PB10: I2C2_SCL
PB11: I2C2_SDA

PA8: I2C3_SCL
PC9: I2C3_SDA
Вообще, распиновку периферии удобно смотреть в на 59 странице.

Что удивительно, но для работы с i2c нужны все его регистры, благо их немного:
I2C_CR1 - команды модулю для отправки команд/состояний и выбор режимов работы;
I2C_CR2 - настройка DMA и указание рабочей частоты модуля (2-42 МГц);
I2C_OAR1 - настройка адреса устройства (для slave), размер адреса (7 или 10 бит);
I2C_OAR2 - настройка адреса устройства (если адресов два);
I2C_DR - регистр данных;
I2C_SR1 - регистр состояния модуля;
I2C_SR2 - регистр статуса (slave, должен читаться, если установлен флаги ADDR или STOPF в SR1);
I2C_CCR - настройка скорости интерфейса;
I2C_TRISE - настройка таймингов фронтов.

Впрочем, половина из них типа «записать и забыть».

На плате STM32F4-Discovery уже есть I2C устройство, с коим можно попрактиковаться: CS43L22 , аудиоЦАП. Он подключён к выводам PB6/PB9. Главное, не забыть подать высокий уровень на вывод PD4 (там сидит ~RESET), иначе ЦАП не станет отвечать.

Порядок настройки примерно таков:
1 . Разрешить тактирование портов и самого модуля.
Нам нужны выводы PB6/PB9, потому надо установить бит 1 (GPIOBEN) в регистре RCC_AHB1ENR, чтоб порт завёлся.
И установить бит 21 (I2C1EN) в регистре RCC_APB1ENR, чтоб включить модуль I2C. Для второго и третьего модуля номера битов 22 и 23 соответственно.
2 . Дальше настраиваются выводы: выход Oped Drain (GPIO->OTYPER), режим альтернативной функции (GPIO->MODER), и номер альтренативной функции (GPIO->AFR).
По желанию можно настроить подтяжку (GPIO->PUPDR), если её нет на плате (а подтяжка к питанию обеих линий необходима в любом виде). Номер для I2C всегда один и тот же: 4. Приятно, что для каждого типа периферии заведён отдельный номер.
3 . Указывается текущая частота тактирования периферии Fpclk1 (выраженная в МГц) в регистре CR2. Я так понял, это нужно для расчёта разных таймингов протокола.
Кстати, она должна быть не менее двух для обычного режима и не менее четырёх для быстрого. А если нужна полная скорость в 400 кГц, то она ещё и должна делиться на 10 (10, 20, 30, 40 МГц).
Максимально разрешённая частота тактирования: 42 МГц.
4 . Настраивается скорость интерфейса в регистре CCR, выбирается режим (обычный/быстрый).
Cмысл таков: Tsck = CCR * 2 * Tpckl1, т.е. период SCK пропорционален CCR (для быстрого режима всё несколько хитрее, но в RM расписано).
5 . Настраивается максимальное время нарастания фронта в регистре TRISE. Для стандартного режима это время 1 мкс. В регистр надо записать количество тактов шины, укладывающихся в это время, плюс один:
если такт Tpclk1 длится 125 нс, то записываем (1000 нс / 125 нс) + 1 = 8 + 1 = 9.
6 . По желанию разрешается генерация сигналов прерывания (ошибки, состояние и данных);
7 . Модуль включается: флаг PE в регистре CR1 переводится в 1.

Дальше модуль работает уже как надо. Надо только реализовать правильный порядок команд и проверки результатов. Например, запись регистра:
1 . Сначала нужно отправить START, установив флаг с таким именем в регистре CR1. Если всё ок, то спустя некоторое время выставится флаг SB в регистре SR1.
Хочу заметить один момент, - если нет подтяжки на линии (и они в 0), то этот флаг можно не дождаться вовсе.
2 . Если флаг-таки дождались, то отправляем адрес. Для семибитного адреса просто записываем его в DR прям в таком виде, как он будет на линии (7 бит адреса + бит направления). Для десятибитного более сложный алгоритм.
Если устройство ответит на адрес ACK"ом, то в регистре SR1 появится флаг ADDR. Если нет, то флаг AF (Acknowledge failure).
Если ADDR появился, надо прочитать регистр SR2. Можно ничего там и не смотреть, просто последовательное чтение SR1 и SR2 сбрасывает этот флаг. А пока флаг установлен, SCL удерживается мастером в низком состоянии, что полезно, если надо попросить удалённое устройство подождать с отправкой данных.
Если всё ок, то дальше модуль перейдёт в режим приёма или передачи данных в зависимости от младшего бита отправленного адреса. Для записи он должен быть нулём, для чтения - единицей.
но мы рассматриваем запись, потому примем, что там был ноль.
3 . Дальше отправляем адрес регистра, который нас интересует. Точно так же, записав его в DR. После передачи выставится флаг TXE (буфер передачи пуст) и BTF (передача завершена).
4 . Дальше идут данные, которые можно отправлять, пока устройство отвечает ACK. Если ответом будет NACK, то эти флаги не установятся.
5 . По завершении передачи (или в случае непредвиденного состояния) отправляем STOP: устанавливается одноимённый флаг в регистре CR1.

При чтении всё то же самое. Меняется только после записи адреса регистра.
Вместо записи данных идёт повторная отправка START (повторный старт) и отправка адреса с установленным младшим битом (признак чтения).
Модуль будет ждать данных от устройства. Чтобы поощрать его к отправке следующих байт, надо перед приёмом установить флаг ACK в CR1 (чтобы после приёма модуль посылал этот самый ACK).
Как надоест, флаг снимаем, устройство увидит NACK и замолчит. После чего шлём STOP обычным порядком и радуемся принятым данным.

Вот то же самое в виде кода:
// Инициализация модуля void i2c_Init(void) { uint32_t Clock = 16000000UL; // Частота тактирования модуля (system_stm32f4xx.c не используется) uint32_t Speed = 100000UL; // 100 кГц // Включить тактирование порта GPIOB RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; // Настроим выводы PB6, PB9 // Open drain! GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_9; // Подтяжка внешняя, потому тут не настраивается! // если надо, см. регистр GPIOB->PUPDR // Номер альтернативной функции GPIOB->AFR &= ~(0x0FUL << (6 * 4)); // 6 очистим GPIOB->AFR |= (0x04UL << (6 * 4)); // В 6 запишем 4 GPIOB->AFR &= ~(0x0FUL << ((9 - 8) * 4)); // 9 очистим GPIOB->AFR |= (0x04UL << ((9 - 8) * 4)); // В 9 запишем 4 // Режим: альтернативная функция GPIOB->MODER &= ~((0x03UL << (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим GPIOB->MODER |= ((0x02UL << (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2 // Включить тактирование модуля I2C1 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // На данный момент I2C должен быть выключен // Сбросим всё (SWRST == 1, сброс) I2C1->CR1 = I2C_CR1_SWRST; // PE == 0, это главное I2C1->CR1 = 0; // Считаем, что запущены от RC (16 МГц) // Предделителей в системе тактирования нет (все 1) // По-хорошему, надо бы вычислять это вс из // реальной частоты тактирования модуля I2C1->CR2 = Clock / 1000000UL; // 16 МГц // Настраиваем частоту { // Tclk = (1 / Fperiph); // Thigh = Tclk * CCR; // Tlow = Thigh; // Fi2c = 1 / CCR * 2; // CCR = Fperiph / (Fi2c * 2); uint16_t Value = (uint16_t)(Clock / (Speed * 2)); // Минимальное значение: 4 if(Value < 4) Value = 4; I2C1->CCR = Value; } // Задаём предельное время фронта // В стандартном режиме это время 1000 нс // Просто прибавляем к частоте, выраженной в МГц единицу (см. RM стр. 604). I2C1->TRISE = (Clock / 1000000UL) + 1; // Включим модуль I2C1->CR1 |= (I2C_CR1_PE); // Теперь можно что-нибудь делать } // Отправить байт bool i2c_SendByte(uint8_t Address, uint8_t Register, uint8_t Data) { if(!i2c_SendStart()) return false; // Адрес микросхемы if(!i2c_SendAddress(Address)) return i2c_SendStop(); // Адрес регистра if(!i2c_SendData(Register)) return i2c_SendStop(); // Данные if(!i2c_SendData(Data)) return i2c_SendStop(); // Стоп! i2c_SendStop(); return true; } // Получить байт bool i2c_ReceiveByte(uint8_t Address, uint8_t Register, uint8_t * Data) { if(!i2c_SendStart()) return false; // Адрес микросхемы if(!i2c_SendAddress(Address)) return i2c_SendStop(); // Адрес регистра if(!i2c_SendData(Register)) return i2c_SendStop(); // Повторный старт if(!i2c_SendStart()) return false; // Адрес микросхемы (чтение) if(!i2c_SendAddress(Address | 1)) return i2c_SendStop(); // Получим байт if(!i2c_ReceiveData(Data)) return i2c_SendStop(); // Стоп! i2c_SendStop(); return true; } Использование: { uint8_t ID = 0; i2c_Init(); // Считаем, что PD4 выставлен в высокий уровень и ЦАП работает (это надо сделать как-нибудь) // Отправка байта в устройство с адресом 0x94, в регистр 0x00 со значением 0x00. i2c_SendByte(0x94, 0x00, 0x00); // Приём байта из устройства с адресом 0x94 из регистра 0x01 (ID) в переменную buffer i2c_ReceiveByte(0x94, 0x01, &ID); }
Конечно, кроме как в учебном примере так делать нельзя. Ожидание окончания действия слишком уж долгое для такого быстрого контроллера.

(Руководство разработчика по микроконтроллерам семейства HCS08)

Для управления модулем I2C используются 6 регистров специальных функций:

  • IICC — первый регистр управления модуля I2C;
  • IICC2 — второй регистр управления модуля I2C;
  • IICS — регистр состояния модуля I2C;
  • IICF — регистр скорости обмена модуля I2C;
  • IICA — регистр адреса модуля I2C;
  • IICD — регистр данных модуля I2C.

В МК серии QE присутствуют 2 модуля I2C и, соответственно, по два регистра управления каждого типа. Например, первый регистр состояния IIC1S и второй регистр состояния IIC2S.

11.2.8.1. Регистр управления IICC

Для МК серий AC. AW, Dx, EL, GB, GT, JM, LC, QE. QG. SG, SH, SL
Регистр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICC Чтение IICEN IICIE MST TX TXAK 0 0 0
Запись RSTA
Сброс 0 0 0 0 0 0 0 0
Описание битов:
Имя бита Описание Символ в языке С
IICEN Бит разрешения работы модуля I2C:
0 — контроллер I2C выключен;
1 — контроллер I2C включен.
bIICEN
IICIE Бит разрешения прерывания модуля от I2C:
0 — прерывания по запросу I2C запрещены;
1 — прерывания по запросу I2C разрешены.
bHCIE
MST Бит выбора режима работы контроллера I2C:
0 — контроллер I2C работает в режиме ведомого (Slave);
1 — контроллер I2C работает в режиме ведущего (Master).
Когда этот бит изменяется с 0 на 1, генерируется состояние Старт. Наоборот, когда бит изменяется с 1 на 0, генерируется состояние Стоп.
bMST
TX Бит выбора направления передачи по линии данных SDA:
0 — линия работает на ввод;
1 — линия работает на вывод.
bTX
TXAK Бит подтверждения в режиме приема:
0 — генерируется бит подтверждения после приема байта;
1 — генерируется бит неподтверждения приема байта.
Этот бит управляет генерацией бита подтверждения после приема байта данных, независимо от того, ведомый это или ведущий.
bTXAK
RSTA Если модуль I2C работает в режиме ведущего, то запись 1 в этот бит вызывает повторную генерацию состояния Старт — «Повторный старт». bRSTA

11.2.8.2. Регистр состояния IICS

Для МК серий AC, AW, Dx, EL, GB, GT, JM, LC, QE, QG, SG, SH, SL
Регистр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICS Чтение TCF IAAS BUSY ARBL 0 SRW IICIF RXAK
Запись
Сброс 0 0 0 0 0 0 0 0
Описание битов:
Имя бита Описание Символ в языке С
TCF Бит завершения обмена. Устанавливается после завершения обмена одним байтом:
0 — обмен не завершен;
1 — обмен завершен.
Флаг TCF сбрасывается в 0, когда читают регистр данных IICD (в режиме приема) или когда записывают в регистр данных IICD (в режиме передачи).
bTCF
IAAS Флаг адреса ведомого. Устанавливается, если устройство работает в режиме ведомого и переданный в сообщении ведущего адрес равен адресу ведомого, который хранится в регистре адреса IICA.
Флаг сбрасывается при записи в регистр IICC.
bIAAS
BUSY Флаг занятой линии. Этот флаг устанавливается, если модуль I2C распознал старт-бит на линии. Флаг сбрасывается, когда модуль распознает стоп-бит на линии.
0 — шина I2C свободна;
1 — шина I2C занята.
bBUSY
ARBL Флаг потери арбитража:
0 — нет нарушений в работе шины I2C;
1 — есть потеря арбитража. Модуль I2C должен некоторое время подождать, а потом начать операцию передачи снова.
bARBL
SRW Бит направления передачи ведомого. Этот бит показывает состояние бита R/W в поле адреса:
0 — ведомый принимает. Ведущий передает ведомому;
1 — ведомый передает. Ведущий принимает от ведомого.
bSRW
IICIF Флаг необслуженных запросов на прерывание модуля I2C.Устанавливается в 1, если установлен один из флагов: TCF, IAAS или ARBL.
0 — нет необслуженных прерываний;
1 — есть необслуженные прерывания.
Флаг сбрасывается посредством записи в него 1.
bIICIF
RXAK Бит подтверждения приема ведущего:
0 — ведомый подтвердил прием данных;
1 — ведомый не подтвердил прием данных.
Этот бит отражает состояние поля ASK на временной диаграмме обмена.
bRXAK

11.2.8.3. Регистр адреса IICA

Регистр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICA Чтение ADDR
Запись
Сброс 0 0 0 0 0 0 0 0

В этом регистре хранится 7-битный адрес ведомого устройства, который разработчик присвоил данному устройству при разработке системы. Этот адрес автоматически сравнивается с кодом адреса, который ведомый получил в поле адреса по шине I2C. Если адреса совпали, то устанавливается бит IAAS в регистре состояния IICS.

11.2.8.4. Регистр скорости обмена IICF

Для МК серий AC, AW, Dx, EL,GB, GT, JM, LC, QE, QG, SG, SH, SL
Регистр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICF Чтение MULT ICR
Запись
Сброс 0 0 0 0 0 0 0 0
Описание битов:

Значения коэффициентов SCL_DIV и SDA_HV

ISR SCL_DIV SDA_HV ISR SCL_DIV SDA_HV
0x00 20 7 0x20 160 17
0x01 22 7 0x21 192 17
0x02 24 8 0x22 224 33
0x03 26 8 0x23 256 33
0x04 28 9 0x24 288 49
0x05 30 9 0x25 320 49
0x06 34 10 0x26 384 65
0x07 40 10 0x27 480 65
0x08 28 7 0x28 320 33
0x09 32 7 0x29 384 33
0x0A 36 9 0x2A 448 65
0x0B 40 9 0x2B 512 65
0x0C 44 11 0x2C 576 97
0x0D 48 11 0x2D 640 97
0x0E 56 13 0x2E 768 129
0x0F 68 13 0x2F 960 129
0x10 48 9 0x30 640 65
0x11 56 9 0x31 768 65
0x12 64 13 0x32 896 129
0x13 72 13 0x33 1024 129
0x14 80 17 0x34 1152 193
0x15 88 17 0x35 1280 193
0x16 104 21 0x36 1536 257
0x17 128 21 0x37 1920 257
0x18 80 9 0x38 1280 129
0x19 96 9 0x39 1536 129
0x1A 112 17 0x3A 1792 257
0x1B 128 17 0x3B 2048 257
0x1C 144 25 0x3C 2304 385
0x1D 160 25 0x3D 2560 385
0x1E 192 33 0x3E 3072 513
0x1F 240 33 0x3F 3840 513

В этом регистре хранятся два битовых поля, которые определяют скорость и параметры временной диаграммы обмена по I2C. Частота сигналов синхронизации определяется по формуле:

Время установления данных SDA_hold_time на шине I2C — это временной интервал между моментом установления в 0 сигнала SCL и изменением данных на линии SDA. Назначается параметром SDA_HV (SDA_Hold_Value) из таблицы для фактора ICR регистра скорости обмена:

.

11.2.8.5. Регистр данных IICD

Для МК серий AC, AW, Dx, EL,GB, GT, JM, LC, QE, QG, SG, SH, SL
Регистр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICD Чтение I2C DATA
Запись
Сброс 0 0 0 0 0 0 0 0

Если модуль I2C работает в режиме ведущего, то операция записи в этот регистр инициирует обмен по I2C (но только если бит направления обмена в регистре управления IICC выставлен правильно, т.е. TX = 1). Первый байт после состояния Старт, который программа записывает в регистр данных, интерпретируется ведомыми как адрес устройства. Поэтому программа должна правильно сформировать содержимое первого байта. Операция чтения регистра возвращает последний принятый байт по I2C. Операция чтения регистра также инициирует начало приема следующего байта, но только если бит направления обмена в регистре управления IICC выставлен правильно, т.е. при TX = 0! При TX = 1 операция чтения регистра не вызовет прием нового байта по I2C от ведомого.

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

11.2.8.6. Регистр управления IICC2

Для МК серий AC, Dx, EL, JM, QE, SG, SH, SL
Регистр Режим D7 D6 D5 D4 D3 D2 D1 D0
IICC Чтение GCAEN ADEXT 0 0 0 AD10 AD9 AD8
Запись
Сброс 0 0 0 0 0 0 0 0