Довольно часто возникает необходимость связать микроконтроллер 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:
Само собой на каждое событие I2С можно повесить прерывание:
Для работы с I2С в STM32 выделено 9 регистров. Не будем на этом останавливаться сейчас подробно, но даташитом лучше не пренебрегать даже в том случае, если непосредственно с регистрами в проекте работа и не ведется.
А мы перейдем как водится к практическому примеру использования.
Подключение датчика BMP280.
У меня как раз есть плата на базе STM32F103, на которой помимо всего прочего установлен датчик давления BMP280, который подключен как раз по I2C. Скоро я отдельно расскажу про эту плату и про все возможности BMP280, сегодня же мы задействуем интерфейс I2C для чтения данных из его регистров (и еще один пример использования I2C в STM32 вдогонку - дисплей на базе контроллера SSD1306).
Открываем даташит и, как и во многих других датчиках, первым в списке идет регистр "ID". И традиционно именно этот регистр используется для отладки коммуникации, потому что его значение строго фиксировано. Если считанное значение равно тому, которое указано в документации - связь работает, если нет... в общем, все понятно.
В данном случае "правильным" для нас будет значение 0x58. То есть план таков:
- конфигурируем I2C модуль
- отправляем датчику команду на чтение регистра ID
- проверяем полученное значение
В итоге мы получим минимально необходимый функционал, который уже можно использовать в своих проектах каким угодно образом. Пара слов по физической коммутации... На моей плате датчик подключен к I2C1:
- PB8 - I2C SCL
- PB9 - I2C SDA
По умолчанию для I2C1 в CubeMx активируются пины PB6 и PB7, так что в данном случае нужно будет поменять их на соседние. Да, кстати, микроконтроллер - STM32F103RET6, так что проект будет именно для него. Но, как и в любом проекте, созданном с помощью STM32CubeMx, проделать аналогичные действия для другого контроллера в случае необходимости будет максимально просто. И, конечно же, всегда при работе с I2C не забываем о подтягивающих резисторах, я обычно ставлю 4.7 КОм:
Кроме того, необходимо определиться с I2C адресом датчика. У BMP280 6 старших битов адреса фиксированы, а младший бит определяется сигналом на выводе SDO:
Таким образом, адрес имеет следующий вид - 0b111011x, где x = 1, если на SDO высокий уровень сигнала, и x = 0 при логическом нуле. У меня как раз этот вывод подтянут к земле, поэтому получаем адрес - 0b1110110 = 0x76.
Практическая часть.
Конфигурация модуля I2C STM32.
Итак, пора переходить к созданию проекта. Здесь все проходит как обычно (создание проекта и настройки тактирования). Только для разнообразия давайте задействуем в этом проекте внутренний источник тактирования вместо внешнего:
Включаем модуль I2C1 и настраиваем те выводы, которые у нас используются, то есть PB8 и PB9. Также выбираем Serial Wire в окне Debug для отладки:
На этом, в общем-то и все, настройки I2C пока менять нет никакой необходимости, оставляем все по умолчанию:
Обмен данными с датчиком BMP280.
Генерируем проект и переходим к его редактированию, сразу же определяем адрес датчика:
/* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define I2C_ADDRESS 0x76 /* USER CODE END PD */
И вот тут есть один небольшой, но важный нюанс. Многие проблемы с работой I2C связаны именно с этим. При отправке данных BMP280 первый из отправляемых байтов является не просто адресом устройства, а адресом (который у нас состоит из 7 битов), дополненным еще одним битом. Этот бит RW отвечает за направление передачи данных, то есть определяет, выполняем мы чтение или запись:
И поскольку бит 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), ®Address, 1, I2C_TIMEOUT); HAL_I2C_Master_Receive(&hi2c1, (I2C_ADDRESS << 1), ®Data, 1, I2C_TIMEOUT); /* USER CODE END 2 */
В результате в переменной regData
у нас окажется верное значение регистра 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:
Теперь можно переходить к коду. В функции main()
выполняем первый из этих этапов:
HAL_I2C_Master_Transmit_IT(&hi2c1, (I2C_ADDRESS << 1), ®Address, 1);
Теперь на очереди callback-функции:
/* USER CODE BEGIN 4 */ void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) { HAL_I2C_Master_Receive_IT(&hi2c1, (I2C_ADDRESS << 1), ®Data, 1); } void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) { // I2C data ready! } /* USER CODE END 4 */
Если мы поставим брейкпоинт в функции HAL_I2C_MasterRxCpltCallback()
, то можем под отладчиком увидеть, что в переменной regData
, как и при работе в Polling режиме, находится абсолютно верное значение. И на этом я на сегодня заканчиваю с модулем STM32 I2C, спасибо всем за внимание 🤝
Выкладываю полный проект к этой статье - ссылка.
Огромное спасибо! Давно ждал примера использования шины I2C.
Прием так же можно организовать таким образом:
uint8_t I2C_ReadData(I2C_TypeDef* I2Cx)
{
while( !I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) );
return (uint8_t)I2Cx->DR;
Ага, но разницы в принципе особой нету)
Благодаря вашей статье, запустил, наконец_то, дисплей TIC32. Чуть позже, если кому пригодится, выложу архив с проектом, правда в CooCox CoIDE/
Отлично!
Думаю для проектов с РТОС или прерываниями работать будет плохо. Всё из за ожидания:
while( !I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) );
1. Например и2с глюкнул и мы никогда не сможем дождаться приема байта.
2. Если при ожидании байта произойдет прерывание, что случиться?
Лучше делать на конечном автомате, но с и2с сложновато будет.....
Можно ввести тайм аут. Внутри цикла while инкрементировать счетчик, и при значении больше определенного прекратить ожидание.
так мы будем спасены от зависания мк, но а если будет прерывания, и процес будет прерван и мы потеряем информацию
Насколько мне известно то после выполнения прерывания программа продолжится с того же места на котором была прервана. То есть, если во время ожидания флага придет прерывание, то мы ничего не потеряем, ровно так же как в процессе передачи или приема с шины I2C. Вот что мы потеряем в данном случае - так это скорость исполнения кода, так как придется ждать выставления флагов относительно медленного интерфейса I2C (здесь я имею ввиду медленного по отношению к тактовой 168МГц)
Я правильно понимаю, что наш МК работает в режиме Master? Как сильно изменится код, если он будет работать в качестве Slave?
Ага, в этом примере как Master работает, если будет Slave-ом, то последовательность инструкций будет совсем другая
Как это ни забавно, в интернете я нашел всего 2 примера STM32 в качестве слейва, и оба без CMSIS, напрямую с регистрами.
К стати, Вы, случайно, не знаете, по какой причине при передаче иногда теряются байты. (в случайном месте)?
По поводу потери байт - хорошо бы осциллографом проверить все линии. А пример под slave постараюсь организовать )
Спасибо автору!Но не подскажете случаем как оптимизировать код под stm32f3 и почитать регистры в акселерометре?
Надо посмотреть, что там в SPL для F3, я думаю код не сильно будет отличаться
Добрый вечер!
Не знаю что и делать, косяк у меня с 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);
А линии подтянуты к питанию?
Конечно. Менял сопротивления с 10 кОм на 4,7кОм не помогло.
А на каком этапе обмена данными зависает?
На осциллографе вообще тишина, такое ощущение что шина не включена или не тактируется.
Разница в последовательности команд Cmd и Init имеет значение? И я не прописывал I2C_AcknowledgeConfig(I2C_EE, ENABLE);
т.к. ACK не включал. В остальном все по примеру, ничего не менял. Подключаю к микросхеме TDA8425.
Попробую завтра изменить на
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. Может микруха не успевает
Вообще сначала надо Init вызывать, а потом включать. Странно, что вообще ничего нет на линии.
Все перепробовал, ощущение что i2c вообще не включается, вешаю осцил на обе ножки, на камень 100rb(m3), 429(m4), и на ардуину 2560, и на stm полная тишина, на ардуино все ок! И на ней естественно все работает. В чем прикол не пойму. Могу выложить полностью main.c В чем прикол может быть?!?!
Погонял код в отладке. Зависает при проверки флагов, а именно по порядку, проверка освобождение интерфейса (FLAG_BUSY), за ним зависает на подтверждении захвата шины (EVENT_MASTER_MODE_SELECT)
При подключенной микросхемы (TDA8425) виснет на I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED
Адрес точно правильный?
отправляю на 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;
}
I2C_StartTransmission() с параметром I2C_Direction_Transmitter) вызывается?
Разумеется да! Попробую Если ставлю на прием, то DR выставляется как положено, 0x41. При установке на прием минусуется. И зависание происходит непосредственно на EV5 (проверка флага). Смотрел разные библиотеки, везде одно и то же. Хочу попробовать еще на 429i с библиотеками HAL, посмотрю что там. Но если за комментировать опросы флагов то осцил. показывает.
Убив кучу времени, но все же разобрался. Выкинув функцию I2C_Send7bitAddress которая не корректно выставляет DR, и вбив все ручками, обходя этот кусок либы, все заработало. Не правильно выставлялся адрес DR и регистр ADD0 который не сбрасывался, и висел как на прием, вместо передачи
функция I2C_Send7bitAddress работает корректно только если в нее передавать адрес сдвинутый на 1 бит влево, т.к. 0 бит адреса устанавливается или опускается в зависимости от направления передачи (а для того, чтобы он, так сказать, был "доступен" и нужен сдвиг влево).
пример вот:
I2C_Send7bitAddress(I2C1, 0x1D<<1, I2C_Direction_Transmitter); (адрес устройства 0x1D)
Странно, почему авторы либы не сделали свдиг влево внутри самой функции...
постоянно ошибки при сборке проекта выскакивают.Кто нибудь может выложить рабочий проект для I2C в CooCox?
Здравствуйте помогите пожалуйста кодом для STM32F303. Гугл не помог. Для это микроконтроллера примеров маловато.
i2c1_init();
I2C_GenerateSTART(I2C1, ENABLE);
I2C_SendData(I2C1,0xAA);
I2C_GenerateSTOP(I2C1, ENABLE);
Из всего этого на осциллографе только старт все остальные функции не работают если у кого то есть опыт прошу помочь.
Доброго времени суток, я новичок, помогите, пожалуйста, или направте в правильное место. Не могу найти как подключить библиотеку Standart Periph Lib в CooCox CoIDE 1.7.6
Честно говоря в coocox никогда не работал, даже не представляю как ide выглядит )
1) Скидываешь в папку с проектом стандартную библиотеку.
2) В CooCox в Workspace где отображено дерево проекта создаешь папку Library (или как удобней), а в ней еще две папки inc и src и в каждую папку закидываешь соответствующие файлы из стандартной библиотеки.
I2C в режиме мастера полно. Сейчас разбираюсь как сделать с STM32F4 Discovery I2C slave,намного сложнее...И рабочых примеров в инт. почти нет...
Наверное уже не актуально... Можно добалять проще, заходим в
View - Repository - PERIPHERIAL.ST - галочками выбираем нужные билиотеки. - и они автоматически добавляються в проект(если забрать галочку, библиотеки удаляються с проекта) Репозиторий доступен при создание проекта, так и в уже собраном.
Александр, выложите, пожалуйста, проект для TIC32.
Михаил проверьте почту. Я вам скинул проект.
Мне нужно было организовать связь по 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);
}
Все функции не влезают в комментарий, плохо что тут нельзя прикрепить файлы. или можно?
Нельзя прикрепить, к сожалению
Огромное спасибо за slave, ценно для понимания протокола!
К сожалению, форумный движок попортил код, кое-что стало неясно.
Не могли бы вы где-то выложить код в чистом виде?
А можно выложить код связи по I2C между двумя stm32?
Вот сверху в одном из предыдущих комментариев выкладывали код для связи двух STM32F4.
Aveal, для stm32f3 может есть где примеры для связи по i2c двух мконтроллеров? Или ссылки подскажете?
Перелопатил весь интернет, ну нет связи 2мк по интерфейсу i2c для stm32f3. Для F4 и F3 сильно отличается. Как мне к примеру заменить для F4 функцию I2C_Send7bitAddress(I2Cx, slaveAddress, transmissionDirection); на функцию для F3?
Вообщем, для stm32f3 может есть где примеры для связи по i2c двух мконтроллеров?
В принципе, есть два варианта только - либо на сайте ST - официальные примеры (там мне кажется такого не найти), либо на форумах каких-нибудь (тоже не факт что есть).
SPL что для F4, что для F3 (да и для других семейств) в принципе имеет одинаковую структуру, поэтому можно просто пробежаться по коду и если какой-то функции для F3 нет, то скорее всего есть какая-то похожая.
А мажет у кого есть пример без паразитных while а на прерываниях, а в ожидании прерываний можно чем то полезным заняться например поспать!
А еще лучше - с ПДП!
))) Ваше пост (практически без изменений) опублиован в журнале Современная электроника №1 за 2015г.
Есть конечно подозрительные моменты, очень уж похожие в статье, но вроде бы в целом там хоть и о том же, но в основном другими словами )))
Здравствуйте, а можно пример использование i2c с dma?
Приветствую! Прочитал, спасибо, и хорошо бы тот же пример для F303 дискавери на Кубе сгенерировать и проверить. Если автор выдаст такую статью, я буду готов его благодарить сколько попросит.
Будет пример для Cube и I2C, но через некоторое время )
Добрый день. Попробовал настроить i2c по вашему гайду. В моменте while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); в функции startTransmission программа зацикливается и дальше ни в какую. В чем может быть проблема?
Похоже на проблему с подключением Slave. Резисторы стоят подтягивающие? Адрес устройства верный?
У меня есть датчик давления MS5803-14BA в корпусе, внутри плата залита герметиком, но я так думаю что на плате резисторы стоят. на выходе из корпуса у меня есть 4 провода: питание, земля, SDA и SCL. По поводу адреса, мне сказали (изготовитель платы и корпуса), что в конкретно моем случае он должен работать по адресу 0х77, а вообще там в зависимости от того CSB замкнута или нет, на сколько я знаю. Вообще я и пробовал и 0x76 и 0x77, тот же эффект. На arduino с готовой библиотекой все работает исправно также по i2c.
ps. У вас в функции readData нет объявления переменной data, это опечатка?
Да, опечатка, спасибо )
я почитал в комментариях и нашел похожую проблему "Nikolay говорит 03.03.2014 в 23:43". Но я не до конца понял, что он изменил. А в следующем комментарии говорят, что в функции I2C_Send7bitAddress надо сдвинуть адрес на один бит влево. Может быть в этом проблема?
Обычно к 7битному адресу добавляется младший бит, который отвечает за направление передачи. В этой функции почему-то сделали так, что к аргументу функции (адресу, который мы передаем) сразу же прибавляется младший бит, без сдвига самого адреса. Поэтому нужно сдвинуть перед вызовом функции. В HAL библиотеках, насколько я помню, исправили эту проблему.
Добрый день! Реализовал по вашему алгоритму общение с датчиком. Писать в него отлично получается, а вот читать не выходит. Дебаггер показывает что застрял на while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)); Крутится внутри, пишет ошибка статуса. Что это может быть? Пробовал и с включенным и выключенным подтверждением, одинаково.
Может датчик не отвечает? Есть возможность сигнал на линии осциллографом проанализировать?
А вообще рекомендую перейти на HAL Driver. Там по части I2C все намного лучше, чем в SPL. Да и, в целом, SPL не поддерживается больше ST, они рекомендуют переходить на HAL и STM32Cube.
Добрый день. пытаюсь реализовать 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 пытаюсь передать один байт. не понимаю где его ловить. и почему он не подтверждает приём адреса ?
Добрый день!
Надо посмотреть в официальных примерах от ST, как они в режиме Slave работают. Вполне возможно, что там обнаружится ключ к решению.
Совет хороший 🙂
но не помог он мне.
А можете мне проект прислать на почту? Я завтра посмотрю обязательно.
в общем я вот что нашел.
контроллер F030F6P6
при приёме адреса срабатывает прерывание и надо подтвердить совпадение адреса
I2C1->ICR |= I2C_ICR_ADDRCF;
добавил это в обработчик ардесс стал подтверждаться и данные пошли.
ещё надо чтоб
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_ENABLE;
если его выключать подтверждение сдвигается до переднего фронта клока и срабатывает не всегда. раз на третий. в общем 30% посылок теряется.
ещё почемуто во всех примерах переводят I2c в ListenMode. я так понял что это вообще не надо. после генерации кода в кубе без всяких запусков дополнительных I2C включено и готово принимать. добавляем подтверждение и понеслось 🙂
Отлично! 🙂
Что-то не работает в 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);
Надо под отладчиком посмотреть, на каком этапе проблемы возникают, какие ошибки при обмене по I2C, если они есть.
Спасибо за ответ.
Все заработало. Напутал с адресом слэйва, т к. слэйв у меня другой МК.
И ещё переменную, куда записывается значение параметра *pData по указателю из буфера данных, сделал массивом:
uint8_t regData[n];
Почему то если regData без размера массив указать, то по юарту приходит непонятного стандартна код-кракозябра, хотя в отладчике переменная обновляется/подсвечивается корректно.
В общем на HAL достаточно одной функции(MASTER_RX) чтобы считывать данные отсылаемые другим контролёром(соответственно в режиме SLAVE_TX).