Top.Mail.Ru

STM32 I2C. Настройка и пример использования шины I2C.

Довольно часто возникает необходимость связать микроконтроллер STM32 с другим микроконтроллером или устройством, например, внешней памятью или датчиком. И тут на помощь приходит шина I2C, о которой до сих пор было не слишком много статей на нашем сайте. Пора исправлять это недоразумение 👍

Теоретическая часть.

Введение в I2C.

И снова для начала кратко обсудим теоретические аспекты. Итак, I2C – последовательная шина данных, разработанная Philips около тридцати лет назад. I2С очень часто применяется в различных блоках для реализации внутренней связи между устройствами. Возможные применения включают, например:

  • Обмен информацией между микроконтроллерами.
  • Доступ к модулям памяти.
  • Обмен данными с модулями АЦП.
  • Обмен данными с ЦАП.

И, естественно, это далеко не полный список. I2С гарантирует неплохую скорость работы при относительной простоте разработки и низкой себестоимости. К слову о скоростях:

  • Обычный режим – до 100КГц
  • Быстрый режим – до 400КГц

I2С использует две линии, которые подтянуты к напряжению питания. Одна линия – линия данных, другая – линия тактирования. Я не буду расписывать тут как работает I2С, потому что это уже много раз описано, без труда можно найти в интернете материал на свой вкус. А мы сразу перейдем к особенностям реализации I2С в STM32.

Основные характеристики шины:

  • Каждое устройство может выступать как в роли Master, так и Slave.
  • В режиме Master реализуется:
    • Генерация тактового сигнала.
    • Генерация старт-сигнала и стоп-сигнала.
  • В режиме Slave реализуется:
    • Механизм подтверждения адреса.
    • Использование двух Slave адресов.
    • Обнаружение стоп-бита, выданного ведущим на линию.
  • Генерация и определение 7/10 битных адресов.
  • Поддержка разных скоростей передачи данных.
  • Наличие множества флагов, сигнализирующих о событиях, а также об ошибках на линии.

Возможна работа в одном из следующих режимов:

  • Slave-приемник
  • Slave-передатчик
  • Master-приемник
  • Master-передатчик

По умолчанию установлен режим Slave, но как только устройство генерирует старт-бит оно сразу же превращается из подчиненного в ведущего. Структурная схема модуля I2С в STM32:

Блох-схема I2C в STM32.

Само собой на каждое событие I2С можно повесить прерывание:

Прерывания I2C.

Для работы с I2С в STM32 выделено 9 регистров. Не будем на этом останавливаться сейчас подробно, но даташитом лучше не пренебрегать даже в том случае, если непосредственно с регистрами в проекте работа и не ведется.

А мы перейдем как водится к практическому примеру использования.

Подключение датчика BMP280.

У меня как раз есть плата на базе STM32F103, на которой помимо всего прочего установлен датчик давления BMP280, который подключен как раз по I2C. Скоро я отдельно расскажу про эту плату и про все возможности BMP280, сегодня же мы задействуем интерфейс I2C для чтения данных из его регистров (и еще один пример использования I2C в STM32 вдогонку - дисплей на базе контроллера SSD1306).

Открываем даташит и, как и во многих других датчиках, первым в списке идет регистр "ID". И традиционно именно этот регистр используется для отладки коммуникации, потому что его значение строго фиксировано. Если считанное значение равно тому, которое указано в документации - связь работает, если нет... в общем, все понятно.

BMP280 I2C регистры.

В данном случае "правильным" для нас будет значение 0x58. То есть план таков:

  • конфигурируем I2C модуль
  • отправляем датчику команду на чтение регистра ID
  • проверяем полученное значение

В итоге мы получим минимально необходимый функционал, который уже можно использовать в своих проектах каким угодно образом. Пара слов по физической коммутации... На моей плате датчик подключен к I2C1:

  • PB8 - I2C SCL
  • PB9 - I2C SDA

По умолчанию для I2C1 в CubeMx активируются пины PB6 и PB7, так что в данном случае нужно будет поменять их на соседние. Да, кстати, микроконтроллер - STM32F103RET6, так что проект будет именно для него. Но, как и в любом проекте, созданном с помощью STM32CubeMx, проделать аналогичные действия для другого контроллера в случае необходимости будет максимально просто. И, конечно же, всегда при работе с I2C не забываем о подтягивающих резисторах, я обычно ставлю 4.7 КОм:

STM32 I2C подтягивающие резисторы.

Кроме того, необходимо определиться с I2C адресом датчика. У BMP280 6 старших битов адреса фиксированы, а младший бит определяется сигналом на выводе SDO:

BMP280 pinout.

Таким образом, адрес имеет следующий вид - 0b111011x, где x = 1, если на SDO высокий уровень сигнала, и x = 0 при логическом нуле. У меня как раз этот вывод подтянут к земле, поэтому получаем адрес - 0b1110110 = 0x76.

Практическая часть.

Конфигурация модуля I2C STM32.

Итак, пора переходить к созданию проекта. Здесь все проходит как обычно (создание проекта и настройки тактирования). Только для разнообразия давайте задействуем в этом проекте внутренний источник тактирования вместо внешнего:

Настройки тактирования.

Включаем модуль I2C1 и настраиваем те выводы, которые у нас используются, то есть PB8 и PB9. Также выбираем Serial Wire в окне Debug для отладки:

Настройка SWD.
STM32 I2C pinout.

На этом, в общем-то и все, настройки I2C пока менять нет никакой необходимости, оставляем все по умолчанию:

STM32 I2C STM32CubeMx settings.

Обмен данными с датчиком BMP280.

Генерируем проект и переходим к его редактированию, сразу же определяем адрес датчика:

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define I2C_ADDRESS                                              0x76

/* USER CODE END PD */

И вот тут есть один небольшой, но важный нюанс. Многие проблемы с работой I2C связаны именно с этим. При отправке данных BMP280 первый из отправляемых байтов является не просто адресом устройства, а адресом (который у нас состоит из 7 битов), дополненным еще одним битом. Этот бит RW отвечает за направление передачи данных, то есть определяет, выполняем мы чтение или запись:

Команда на чтение по I2C.

И поскольку бит RW является младшим, то 7 битов адреса нам нужно будет сдвигать на одну позицию влево при передаче в функцию I2C HAL драйвера. Сам же бит RW библиотека HAL добавит к адресу автоматически, без нашего участия. Сейчас на примере со всем этим разберемся.

Итак, нам нужно прочитать значение регистра ID, адрес которого - 0xD0. Снова смотрим на схему - получается, что нам нужно отправить BMP280 команду с адресом самого датчика и адресом регистра, значение которого мы хотим получить. После этого мы снова выдаем на линию адрес и встаем на чтение. В HAL весь комплекс этих операций мы сможем осуществить всего двумя функциями:

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)

Аргументы функций идентичны:

  • hi2c - указатель на структуру, в которой хранится информация о модуле I2C. Эту структуру STM32CubeMx уже сгенерировал, она используется при настройке модуля.
  • DevAddress - адрес устройства, не забываем сдвинуть на 1 бит влево.
  • pData - указатель на буфер данных. При записи - данные из этого буфера будут передаваться по интерфейсу I2C. При чтении, соответственно, в этот буфер будут сохраняться полученные значения.
  • Size - количество байтов данных для чтения/записи.
  • Timeout - величина таймаута, по истечении которого функция выдаст ошибку и прекратит выполнение. Этот механизм необходим для того, чтобы при каком-либо сбое в коммуникации программа не зависала намертво на ожидании ответа.

С этим разобрались, идем дальше. Зададим упомянутый таймаут равным 10 мс и определим дефайном:

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define I2C_ADDRESS                                              0x76
#define I2C_ID_ADDRESS                                           0xD0
#define I2C_TIMEOUT                                              10

/* USER CODE END PD */

Кроме того, мы определили адрес регистра, с которым будем экспериментировать. Добавим еще парочку переменных:

/* USER CODE BEGIN PV */
uint8_t regData = 0;
uint8_t regAddress = I2C_ID_ADDRESS;

/* USER CODE END PV */

И, наконец-то, перейдем к отправке данных:

/* USER CODE BEGIN 2 */
  HAL_I2C_Master_Transmit(&hi2c1, (I2C_ADDRESS << 1), &regAddress, 1,  I2C_TIMEOUT);
  HAL_I2C_Master_Receive(&hi2c1, (I2C_ADDRESS << 1), &regData, 1,  I2C_TIMEOUT);
  /* USER CODE END 2 */

В результате в переменной regData у нас окажется верное значение регистра ID:

Значение регистра ID.

Связь STM32 с датчиком по I2C налажена 👍

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

  • 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 предлагает нам использовать ряд callback-функций. Остановимся на двух из них:

  • void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
  • void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)

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

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

Реализуем в проекте, но для начала включим прерывания I2C в STM32CubeMx:

Прерывания I2C в STM32CubeMx.

Теперь можно переходить к коду. В функции main() выполняем первый из этих этапов:

HAL_I2C_Master_Transmit_IT(&hi2c1, (I2C_ADDRESS << 1), &regAddress, 1);

Теперь на очереди callback-функции:

/* USER CODE BEGIN 4 */
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  HAL_I2C_Master_Receive_IT(&hi2c1, (I2C_ADDRESS << 1), &regData, 1);
}

void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  // I2C data ready!
}
/* USER CODE END 4 */

Если мы поставим брейкпоинт в функции HAL_I2C_MasterRxCpltCallback(), то можем под отладчиком увидеть, что в переменной regData, как и при работе в Polling режиме, находится абсолютно верное значение. И на этом я на сегодня заканчиваю с модулем STM32 I2C, спасибо всем за внимание 🤝

Выкладываю полный проект к этой статье - ссылка.

Подписаться
Уведомить о
guest

75 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Александр
Александр
11 лет назад

Огромное спасибо! Давно ждал примера использования шины I2C.

Александр
Александр
11 лет назад

Прием так же можно организовать таким образом:
uint8_t I2C_ReadData(I2C_TypeDef* I2Cx)
{
while( !I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) );
return (uint8_t)I2Cx->DR;

Александр
Александр
Ответ на комментарий  Aveal
11 лет назад

Благодаря вашей статье, запустил, наконец_то, дисплей TIC32. Чуть позже, если кому пригодится, выложу архив с проектом, правда в CooCox CoIDE/

Nemo
Nemo
11 лет назад

Думаю для проектов с РТОС или прерываниями работать будет плохо. Всё из за ожидания:
while( !I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) );

1. Например и2с глюкнул и мы никогда не сможем дождаться приема байта.
2. Если при ожидании байта произойдет прерывание, что случиться?

Лучше делать на конечном автомате, но с и2с сложновато будет.....

Александр
Александр
Ответ на комментарий  Nemo
11 лет назад

Можно ввести тайм аут. Внутри цикла while инкрементировать счетчик, и при значении больше определенного прекратить ожидание.

Nemo
Nemo
Ответ на комментарий  Александр
11 лет назад

так мы будем спасены от зависания мк, но а если будет прерывания, и процес будет прерван и мы потеряем информацию

Александр
Александр
Ответ на комментарий  Nemo
11 лет назад

Насколько мне известно то после выполнения прерывания программа продолжится с того же места на котором была прервана. То есть, если во время ожидания флага придет прерывание, то мы ничего не потеряем, ровно так же как в процессе передачи или приема с шины I2C. Вот что мы потеряем в данном случае - так это скорость исполнения кода, так как придется ждать выставления флагов относительно медленного интерфейса I2C (здесь я имею ввиду медленного по отношению к тактовой 168МГц)

RiseOfDeath
RiseOfDeath
11 лет назад

Я правильно понимаю, что наш МК работает в режиме Master? Как сильно изменится код, если он будет работать в качестве Slave?

RiseOfDeath
RiseOfDeath
Ответ на комментарий  Aveal
11 лет назад

Как это ни забавно, в интернете я нашел всего 2 примера STM32 в качестве слейва, и оба без CMSIS, напрямую с регистрами.

К стати, Вы, случайно, не знаете, по какой причине при передаче иногда теряются байты. (в случайном месте)?

Андрей
Андрей
10 лет назад

Спасибо автору!Но не подскажете случаем как оптимизировать код под stm32f3 и почитать регистры в акселерометре?

Nikolay
Nikolay
10 лет назад

Добрый вечер!
Не знаю что и делать, косяк у меня с i2c.
На Ардуино работает нет вопросов, код простейший.
На отладках stm32 на 100RB & 429i не хочет запускаться. Может я настраиваю не правильно?

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
GPIO_InitData.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //SDA & SCL
GPIO_InitData.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitData.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &GPIO_InitData);

I2C_Param.I2C_ClockSpeed = 100000;
I2C_Param.I2C_Mode = I2C_Mode_I2C;
I2C_Param.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_Param.I2C_OwnAddress1 = 0x00;
I2C_Param.I2C_Ack = I2C_Ack_Enable;
I2C_Param.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C2, &I2C_Param);
I2C_Cmd(I2C2, ENABLE);

Nikolay
Nikolay
10 лет назад

Конечно. Менял сопротивления с 10 кОм на 4,7кОм не помогло.

Nikolay
Nikolay
10 лет назад

На осциллографе вообще тишина, такое ощущение что шина не включена или не тактируется.

Разница в последовательности команд Cmd и Init имеет значение? И я не прописывал I2C_AcknowledgeConfig(I2C_EE, ENABLE);
т.к. ACK не включал. В остальном все по примеру, ничего не менял. Подключаю к микросхеме TDA8425.

Nikolay
Nikolay
10 лет назад

Попробую завтра изменить на
I2C_DeInit(I2C2);
I2C_Param.I2C_ClockSpeed = 100000;
I2C_Param.I2C_Mode = I2C_Mode_I2C;
I2C_Param.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_Param.I2C_OwnAddress1 = 0×00;
I2C_Param.I2C_Ack = I2C_Ack_Enable;
I2C_Param.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

I2C_Cmd(I2C2, ENABLE);
I2C_Init(I2C2, &I2C_Param);
I2C_AcknowledgeConfig(I2C2, ENABLE);
Вдруг что измениться. И какой из режимов для настройки скорости выбирать _2 или _16_9. Может микруха не успевает

Nikolay
Nikolay
10 лет назад

Все перепробовал, ощущение что i2c вообще не включается, вешаю осцил на обе ножки, на камень 100rb(m3), 429(m4), и на ардуину 2560, и на stm полная тишина, на ардуино все ок! И на ней естественно все работает. В чем прикол не пойму. Могу выложить полностью main.c В чем прикол может быть?!?!

Nikolay
Nikolay
10 лет назад

Погонял код в отладке. Зависает при проверки флагов, а именно по порядку, проверка освобождение интерфейса (FLAG_BUSY), за ним зависает на подтверждении захвата шины (EVENT_MASTER_MODE_SELECT)

Nikolay
Nikolay
10 лет назад

При подключенной микросхемы (TDA8425) виснет на I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED

Nikolay
Nikolay
10 лет назад

отправляю на 0x41, но в отладке на DR идет 0x40 и так вне зависимости от адреса, то есть запись 0x(**-1). И устанавливается бит ADD0, но судя по исходникам, он должен устанавливаться только в случае приема, но не передачи.
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction)
{
/* Check the parameters */
assert_param(IS_I2C_ALL_PERIPH(I2Cx));
assert_param(IS_I2C_DIRECTION(I2C_Direction));
/* Test on the direction to set/reset the read/write bit */
if (I2C_Direction != I2C_Direction_Transmitter)
{
/* Set the address bit0 for read */
Address |= OAR1_ADD0_Set;
}
else
{
/* Reset the address bit0 for write */
Address &= OAR1_ADD0_Reset;
}
/* Send the address */
I2Cx->DR = Address;
}

Nikolay
Nikolay
10 лет назад

Разумеется да! Попробую Если ставлю на прием, то DR выставляется как положено, 0x41. При установке на прием минусуется. И зависание происходит непосредственно на EV5 (проверка флага). Смотрел разные библиотеки, везде одно и то же. Хочу попробовать еще на 429i с библиотеками HAL, посмотрю что там. Но если за комментировать опросы флагов то осцил. показывает.

Nikolay
Nikolay
10 лет назад

Убив кучу времени, но все же разобрался. Выкинув функцию I2C_Send7bitAddress которая не корректно выставляет DR, и вбив все ручками, обходя этот кусок либы, все заработало. Не правильно выставлялся адрес DR и регистр ADD0 который не сбрасывался, и висел как на прием, вместо передачи

Aleksey
Aleksey
10 лет назад

функция I2C_Send7bitAddress работает корректно только если в нее передавать адрес сдвинутый на 1 бит влево, т.к. 0 бит адреса устанавливается или опускается в зависимости от направления передачи (а для того, чтобы он, так сказать, был "доступен" и нужен сдвиг влево).

пример вот:
I2C_Send7bitAddress(I2C1, 0x1D<<1, I2C_Direction_Transmitter); (адрес устройства 0x1D)

Странно, почему авторы либы не сделали свдиг влево внутри самой функции...

Дмитрий
Дмитрий
10 лет назад

постоянно ошибки при сборке проекта выскакивают.Кто нибудь может выложить рабочий проект для I2C в CooCox?

Дмитрий
Дмитрий
10 лет назад

Здравствуйте помогите пожалуйста кодом для STM32F303. Гугл не помог. Для это микроконтроллера примеров маловато.
i2c1_init();
I2C_GenerateSTART(I2C1, ENABLE);

I2C_SendData(I2C1,0xAA);

I2C_GenerateSTOP(I2C1, ENABLE);
Из всего этого на осциллографе только старт все остальные функции не работают если у кого то есть опыт прошу помочь.

Тихон
Тихон
10 лет назад

Доброго времени суток, я новичок, помогите, пожалуйста, или направте в правильное место. Не могу найти как подключить библиотеку Standart Periph Lib в CooCox CoIDE 1.7.6

Рамис
Рамис
10 лет назад

1) Скидываешь в папку с проектом стандартную библиотеку.
2) В CooCox в Workspace где отображено дерево проекта создаешь папку Library (или как удобней), а в ней еще две папки inc и src и в каждую папку закидываешь соответствующие файлы из стандартной библиотеки.

Nemo
Nemo
9 лет назад

I2C в режиме мастера полно. Сейчас разбираюсь как сделать с STM32F4 Discovery I2C slave,намного сложнее...И рабочых примеров в инт. почти нет...

Nemo
Nemo
9 лет назад

Наверное уже не актуально... Можно добалять проще, заходим в
View - Repository - PERIPHERIAL.ST - галочками выбираем нужные билиотеки. - и они автоматически добавляються в проект(если забрать галочку, библиотеки удаляються с проекта) Репозиторий доступен при создание проекта, так и в уже собраном.

Михаил
Михаил
9 лет назад

Александр, выложите, пожалуйста, проект для TIC32.

Александр
Александр
9 лет назад

Михаил проверьте почту. Я вам скинул проект.

Толик
Толик
9 лет назад

Мне нужно было организовать связь по I2C между двумя stm32f407. Поэтому пришлось написать код для мастера и для маргариты) . Все функции полностью рабочие, я проверял.

I2C master stm32f4
Для МАСТЕРА!!!!!!!!!!

/*** Функции передачи и чтения информации по I2C.
****
**** WRITE_I2C( (1<<1), 0x20, 0xAA); //записываем в устройство с адресом 0х01 в регистр с адресом 0х20 байт 0хАА.
**** WRITE_MULTI_I2C( (2<<1), 0x70, 5); //записываем в устройство с адресом 0х02 в регистры с адресом 0х70...0х74 байты, записанные в массиве TxBuffer_I2C.
**** Data_I2C = READ_I2C( (1<<1), 0x26);//прочитаем из устройства с адресом 0х01 регистр с адресом 0х26.
**** READ_MULTI_I2C( (2< 10));
if (Timeout 10));
if (Timeout max_data_Tx) ) return 0; //если записали отправить 0 байт или больше максимального, то выход.

//ждем пока шина освободится.
Timeout = Timeout_for_I2C1;
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) && (Timeout-- > 10));
if (Timeout<=10) {return 0; }///ошибка связи

//генерируем старт-бит.
I2C_GenerateSTART(I2C1, ENABLE);
//ждем флага "Start condition generated". (EV5).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_SB))
{ if(Timeout!=0) Timeout--; else {return 0;} }

//посылаем адрес устройства подчиненному.
I2C_Send7bitAddress(I2C1, Slave_addr, I2C_Direction_Transmitter);
//ждем флага "Address sent". (EV6).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR))
{ if(Timeout != 0) Timeout--; else {I2C_GenerateSTOP(I2C1, ENABLE); return 0;} }

I2C_ReadRegister(I2C1,I2C_Register_SR1); //убираем флаг для этого читаем SR1 и SR2. (cleared by software sequence).
I2C_ReadRegister(I2C1,I2C_Register_SR2);

//ждем флага "Data register empty". (EV8_1).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE))
{ if(Timeout != 0) Timeout--; else {return 0;} }

//посылаем адрес регистра подчиненному.
I2C_SendData(I2C1, Reg_addr);
//ждем флага "Data register empty". (EV8).
Timeout = Timeout_for_I2C1;
while ((!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE)) || (!I2C_GetFlagStatus(I2C1, I2C_FLAG_BTF)))
{ if(Timeout != 0) Timeout--; else {return 0;} }

//посылаем новые данные подчиненному.
for(cnt_i2c = 0; cnt_i2c max_data_Rx) ) return 0; //если записали прочитать 0 байт или больше максимального, то выход.

//ждем пока шина освободится.
Timeout = Timeout_for_I2C1;
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)&& (Timeout-- > 10));
if (Timeout<=10) {return 0;}///ошибка связи

//генерируем старт-бит.
I2C_GenerateSTART(I2C1, ENABLE);
//ждем флага "Start condition generated". (EV5).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_SB))
{ if(Timeout != 0) Timeout--; else {return 0;} }

I2C_AcknowledgeConfig(I2C1, ENABLE); //ACK выкл.

//посылаем адрес устройства подчиненному.
I2C_Send7bitAddress(I2C1, Slave_addr, I2C_Direction_Transmitter);
//ждем флага "Address sent". (EV6).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR))
{ if(Timeout != 0) Timeout--; else {I2C_GenerateSTOP(I2C1, ENABLE); I2C_AcknowledgeConfig(I2C1, ENABLE); return 0;} }

I2C_ReadRegister(I2C1,I2C_Register_SR1); //убираем флаг для этого читаем SR1 и SR2. (cleared by software sequence).
I2C_ReadRegister(I2C1,I2C_Register_SR2);

//ждем флага "Data register empty". (EV8_1).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE))
{ if(Timeout != 0) Timeout--; else {return 0;} }

//посылаем адрес регистра подчиненному.
I2C_SendData(I2C1, Reg_addr);
//ждем флага "Data register empty". (EV8).
Timeout = Timeout_for_I2C1;
while ((!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE)) || (!I2C_GetFlagStatus(I2C1, I2C_FLAG_BTF)))
{ if(Timeout != 0) Timeout--; else {return 0;} }

//генерируем старт-бит второй раз.
I2C_GenerateSTART(I2C1, ENABLE);
//ждем флага "Start condition generated". (EV5).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_SB))
{ if(Timeout != 0) Timeout--; else {return 0;} }

//посылаем адрес устройства подчиненному.
I2C_Send7bitAddress(I2C1, Slave_addr, I2C_Direction_Receiver);
//ждем флага "Address sent". (EV6).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR))
{ if(Timeout != 0) Timeout--; else {return 0;} }

I2C_ReadRegister(I2C1,I2C_Register_SR1); //убираем флаг для этого читаем SR1 и SR2. (cleared by software sequence).
I2C_ReadRegister(I2C1,I2C_Register_SR2);

for(cnt_i2c = 0; cnt_i2c = Cnt)
{
// ACK disable
I2C_AcknowledgeConfig(I2C1, DISABLE);
//генерируем стоп-бит.
I2C_GenerateSTOP(I2C1, ENABLE);
}
//ждем флага "Data register not empty". (EV7).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE))
{ if(Timeout != 0) Timeout--;
else {return 0;} }
//чтение данных.
RxBuffer_I2C[cnt_i2c] = I2C_ReceiveData(I2C1);
}

//ждем флага "Data register not empty". (EV7).
Timeout = Timeout_for_I2C1;
while (!I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE))
{ if(Timeout != 0) Timeout--; else {return 0;} }

//генерируем стоп-бит.
//I2C_GenerateSTOP(I2C1, ENABLE);

I2C_AcknowledgeConfig(I2C1, ENABLE); //ACK вкл.

return tmp;
}

I2C slave stm32f4
Для СЛЕЙВА!!!!!!!!!!

/*** Функции передачи и чтения информации по I2C.
**** все действия происходят по прерываниям.
****
*/

#include "stm32f4xx.h"
#include "stm32f4xx_i2c.h"
#include "I2C_slave.h"

GPIO_InitTypeDef GPIO_Init_I2C_Slave;
I2C_InitTypeDef I2C_Init_Slave;
NVIC_InitTypeDef NVIC_Init_I2C;

extern uint8_t GA_Adress; //адрес нашего устройства.

extern uint8_t TxBuffer[10]; //массив для передачи данных мастеру.
extern uint8_t Tx_Idx;
extern uint8_t RxBuffer[10]; //массив для приема данных от мастера.
extern uint8_t Rx_Idx;

//============================================================================
// Инициализация I2C в режиме Слейва.
//============================================================================
//I2C с кросс платой.
#define PORT_I2C_OUT GPIOB
#define I2C_SCL_OUT GPIO_Pin_10
#define I2C_SDA_OUT GPIO_Pin_11 //I2C интерфейс с кросс-платой.
#define I2C_READY GPIO_Pin_12 //флаг готовности I2C с буфера.
void Init_I2C_Slave(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

//ЦИФРОВЫЕ ВХОДЫ:
GPIO_Init_I2C_Slave.GPIO_Mode = GPIO_Mode_IN; //IN -вход цифровой //OUT -выход цифровой //AN-аналоговый //AF-альтернативные функции
GPIO_Init_I2C_Slave.GPIO_OType = GPIO_OType_PP ; //PP-с полным размахом //OD -открытый коллектор
GPIO_Init_I2C_Slave.GPIO_Speed = GPIO_Speed_2MHz; //100MHz 50MHz 25MHz 2MHz
GPIO_Init_I2C_Slave.GPIO_PuPd = GPIO_PuPd_NOPULL; //NOPULL-подтяжки нет // UP-подтяжка к плюсу //DOWN-подтяжка к земле
GPIO_Init_I2C_Slave.GPIO_Pin = I2C_READY;
GPIO_Init(PORT_I2C_OUT, &GPIO_Init_I2C_Slave);

//конфигурация выводов PB10-SCL, PB11-SDA.
GPIO_Init_I2C_Slave.GPIO_OType = GPIO_OType_OD;
GPIO_Init_I2C_Slave.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init_I2C_Slave.GPIO_Mode = GPIO_Mode_AF;
GPIO_Init_I2C_Slave.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init_I2C_Slave.GPIO_Pin = I2C_SCL_OUT | I2C_SDA_OUT;
GPIO_Init(PORT_I2C_OUT, &GPIO_Init_I2C_Slave);

GPIO_PinAFConfig(PORT_I2C_OUT, GPIO_PinSource10, GPIO_AF_I2C2); //указываем альтернативные функции для пинов.
GPIO_PinAFConfig(PORT_I2C_OUT, GPIO_PinSource11, GPIO_AF_I2C2);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); // затактировали I2C2

//I2C_InitTypeDef I2C_InitStructure;
I2C_Init_Slave.I2C_ClockSpeed = 50000; //скорость 100 КГц.
I2C_Init_Slave.I2C_Mode = I2C_Mode_I2C;
I2C_Init_Slave.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_Init_Slave.I2C_OwnAddress1 = (GA_Adress << 1); //адрес нашей платы (задаем географическую адресацию).
I2C_Init_Slave.I2C_Ack = I2C_Ack_Enable;
I2C_Init_Slave.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C2, &I2C_Init_Slave);

//Configure the I2C event priority//
NVIC_Init_I2C.NVIC_IRQChannel = I2C2_EV_IRQn;
NVIC_Init_I2C.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_Init_I2C.NVIC_IRQChannelSubPriority = 0;
NVIC_Init_I2C.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_Init_I2C);

//Configure I2C error interrupt to have the higher priority.
NVIC_Init_I2C.NVIC_IRQChannel = I2C2_ER_IRQn;
NVIC_Init(&NVIC_Init_I2C);

I2C_ITConfig(I2C2, I2C_IT_ERR, ENABLE);
I2C_ITConfig(I2C2, I2C_IT_EVT, ENABLE);
I2C_ITConfig(I2C2, I2C_IT_BUF, ENABLE);

I2C_Cmd(I2C2, ENABLE); // включили I2C2
I2C_AcknowledgeConfig(I2C2, ENABLE);
}

//===============================================================================
//===============================================================================
// ПРЕРЫВАНИЯ
//===============================================================================
//===============================================================================

//=============================================================================
// Прерывания по событию I2C2
//=============================================================================
//процедура записи у мастера: старт - посылаем адрес устройства - посылаем адрес регистра - посылаем новые данные - стоп.
//процедура чтения у мастера: старт - посылаем адрес устройства - посылаем адрес регистра - второй старт- посылаем адрес устройства - читаем данные - стоп.

//=========================== Slave Events ================================
void I2C2_EV_IRQHandler(void)
{
uint32_t event = 0;
event = I2C_GetLastEvent(I2C2); //возвращаем переменной оба статус регистра.

switch (event)
{
//EV1: ADDR=1, cleared by reading SR1 followed by reading SR2
//EV3_1: TxE=1, shift register empty,data register empty, write Data1 in DR.
case I2C_EVENT_SLAVE_TRANSMITTER_ADDRESS_MATCHED: //совпадение адреса.
Tx_Idx = 0;
I2C_ReadRegister(I2C2,I2C_Register_SR1); //убираем флаг ADDR для этого читаем SR1 и SR2. (cleared by software sequence).
I2C_ReadRegister(I2C2,I2C_Register_SR2);
I2C_SendData(I2C2, TxBuffer[Tx_Idx]); //передаем данные мастеру.
break;

//EV3: TxE=1, shift register not empty,data register empty, cleared by writing DR.
case I2C_EVENT_SLAVE_BYTE_TRANSMITTED:
Tx_Idx++;
I2C_SendData(I2C2, TxBuffer[Tx_Idx]);
//сделать ограничение на передачу: выдавать ошибку если мастер просит передачи, а байты уже закончились.
break;

//EV1: ADDR=1, cleared by reading SR1 followed by reading SR2.
case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED : //совпадение адреса.
Rx_Idx = 0x00;
I2C_ReadRegister(I2C2,I2C_Register_SR1); //убираем флаг ADDR для этого читаем SR1 и SR2. (cleared by software sequence).
I2C_ReadRegister(I2C2,I2C_Register_SR2);
break;

//EV2: RxNE=1, cleared by reading DR register.
case I2C_EVENT_SLAVE_BYTE_RECEIVED: //читаем данные от мастера.
RxBuffer[Rx_Idx++] = I2C_ReceiveData(I2C2);
break;

//EV4: STOPF=1, cleared by reading SR1 register followed by writing to the CR1 register.
case I2C_EVENT_SLAVE_STOP_DETECTED:
I2C_GetFlagStatus(I2C2, I2C_FLAG_STOPF); //STOPF (STOP detection) is cleared by software sequence: a read operation to I2C_SR1 register (I2C_GetFlagStatus())
I2C_Cmd(I2C2, ENABLE); //followed by a write operation to I2C_CR1 register (I2C_Cmd() to re-enable the I2C peripheral).
break;

default:
break;
}
}

//=========================== Slave Erors =================================
//Отслеживаем NACK.
void I2C2_ER_IRQHandler(void)
{
//EV3_2: AF=1, AF is cleared by writing '0' in AF bit of SR1 register.
if (I2C_GetITStatus(I2C2, I2C_IT_AF)) I2C_ClearITPendingBit(I2C2, I2C_IT_AF);
}

Толик
Толик
9 лет назад

Все функции не влезают в комментарий, плохо что тут нельзя прикрепить файлы. или можно?

Игорь
Игорь
9 лет назад

Огромное спасибо за slave, ценно для понимания протокола!
К сожалению, форумный движок попортил код, кое-что стало неясно.
Не могли бы вы где-то выложить код в чистом виде?

vadim
vadim
9 лет назад

А можно выложить код связи по I2C между двумя stm32?

vadim
vadim
9 лет назад

Aveal, для stm32f3 может есть где примеры для связи по i2c двух мконтроллеров? Или ссылки подскажете?

vadim
vadim
9 лет назад

Перелопатил весь интернет, ну нет связи 2мк по интерфейсу i2c для stm32f3. Для F4 и F3 сильно отличается. Как мне к примеру заменить для F4 функцию I2C_Send7bitAddress(I2Cx, slaveAddress, transmissionDirection); на функцию для F3?
Вообщем, для stm32f3 может есть где примеры для связи по i2c двух мконтроллеров?

Andrey
Andrey
9 лет назад

А мажет у кого есть пример без паразитных while а на прерываниях, а в ожидании прерываний можно чем то полезным заняться например поспать!

Z-z-z
Z-z-z
9 лет назад

А еще лучше - с ПДП!

Эдик
Эдик
9 лет назад

))) Ваше пост (практически без изменений) опублиован в журнале Современная электроника №1 за 2015г.

Geraksh
8 лет назад

Здравствуйте, а можно пример использование i2c с dma?

Евгений
Евгений
7 лет назад

Приветствую! Прочитал, спасибо, и хорошо бы тот же пример для F303 дискавери на Кубе сгенерировать и проверить. Если автор выдаст такую статью, я буду готов его благодарить сколько попросит.

Petya
7 лет назад

Добрый день. Попробовал настроить i2c по вашему гайду. В моменте while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); в функции startTransmission программа зацикливается и дальше ни в какую. В чем может быть проблема?

Petya
Ответ на комментарий  Aveal
7 лет назад

У меня есть датчик давления MS5803-14BA в корпусе, внутри плата залита герметиком, но я так думаю что на плате резисторы стоят. на выходе из корпуса у меня есть 4 провода: питание, земля, SDA и SCL. По поводу адреса, мне сказали (изготовитель платы и корпуса), что в конкретно моем случае он должен работать по адресу 0х77, а вообще там в зависимости от того CSB замкнута или нет, на сколько я знаю. Вообще я и пробовал и 0x76 и 0x77, тот же эффект. На arduino с готовой библиотекой все работает исправно также по i2c.

ps. У вас в функции readData нет объявления переменной data, это опечатка?

Petya
Ответ на комментарий  Aveal
7 лет назад

я почитал в комментариях и нашел похожую проблему "Nikolay говорит 03.03.2014 в 23:43". Но я не до конца понял, что он изменил. А в следующем комментарии говорят, что в функции I2C_Send7bitAddress надо сдвинуть адрес на один бит влево. Может быть в этом проблема?

Андрей
7 лет назад

Добрый день! Реализовал по вашему алгоритму общение с датчиком. Писать в него отлично получается, а вот читать не выходит. Дебаггер показывает что застрял на while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)); Крутится внутри, пишет ошибка статуса. Что это может быть? Пробовал и с включенным и выключенным подтверждением, одинаково.

Сергей
5 лет назад

Добрый день. пытаюсь реализовать Slave I2c. с кубом.
и чёт херня какаято.
генерю в кубе код.
в мэйне вставляю:
HAL_I2C_EnableListen_IT(&hi2c1);
прерывания включены. после приёма адреса срабатывает прерывание выполняется процедура void I2C1_IRQHandler(void)
срабатывает вызов калбэка
void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)

вроде как адресс он понял. но он не подтверждает на шине ACK не формирует.
и чёт не могу понять что дальше то делать 🙂 ардресс устройства 0x10 пытаюсь передать один байт. не понимаю где его ловить. и почему он не подтверждает приём адреса ?

Сергей
Ответ на комментарий  Aveal
5 лет назад

Совет хороший 🙂

Сергей
5 лет назад

но не помог он мне.

Сергей
5 лет назад

в общем я вот что нашел.
контроллер F030F6P6
при приёме адреса срабатывает прерывание и надо подтвердить совпадение адреса
I2C1->ICR |= I2C_ICR_ADDRCF;
добавил это в обработчик ардесс стал подтверждаться и данные пошли.

ещё надо чтоб
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_ENABLE;

если его выключать подтверждение сдвигается до переднего фронта клока и срабатывает не всегда. раз на третий. в общем 30% посылок теряется.

Сергей
5 лет назад

ещё почемуто во всех примерах переводят I2c в ListenMode. я так понял что это вообще не надо. после генерации кода в кубе без всяких запусков дополнительных I2C включено и готово принимать. добавляем подтверждение и понеслось 🙂

Anx99
Anx99
1 год назад

Что-то не работает в Polling режиме, не пойму что.
Сделал все как написано:
две функции
HAL_I2C_Master_Transmit(...);
HAL_I2C_Master_Receive(...);

Адрес датчика должен быть 0x58, вроде как. (Использую Ардуино вместо датчика, в режиме slave sender, там все правильно).

Пытаюсь читать данные из буфера и отправлять по юарту. Ничего не шлёт.
Читаю буфер так:

uint8_t buf[12]
char trans_str[63] = {0,};

snprintf(trans_str, 63, "... %d\r\n", buf);

Anx99
Anx99
Ответ на комментарий  Aveal
1 год назад

Спасибо за ответ.
Все заработало. Напутал с адресом слэйва, т к. слэйв у меня другой МК.

И ещё переменную, куда записывается значение параметра *pData по указателю из буфера данных, сделал массивом:
uint8_t regData[n];
Почему то если regData без размера массив указать, то по юарту приходит непонятного стандартна код-кракозябра, хотя в отладчике переменная обновляется/подсвечивается корректно.

В общем на HAL достаточно одной функции(MASTER_RX) чтобы считывать данные отсылаемые другим контролёром(соответственно в режиме SLAVE_TX).

75
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x