STM32 и I2C. Настройка и использование интерфейса I2C.

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

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

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

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

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

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

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

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

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

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

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

Блок-схема модуля I2С:

Блох-схема

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

Прерывания I2C

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

А мы двинемся дальше и перейдем к практическому использованию интерфейса. Посмотрим, как реализована работа с I2С в Standard Peripheral Library. Как и для другой периферии, все настройки режима, скорости и всего остального находятся в заголовочном файле. Давайте смотреть. Находим там объявление структуры 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;

Вот с этими полями нам и придется работать, чтобы настроить интерфейс I2C так, как мы хотим:

  • uint32_t I2C_ClockSpeed – частота тактового сигнала, максимум – 400 КГц,
  • uint16_t I2C_Mode – тут все понятно, режим работы,
  • uint16_t I2C_DutyCycle – настройки для работы в быстром режиме,
  • uint16_t I2C_OwnAddress – собственный адрес устройства,
  • uint16_t I2C_Ack – включено или нет использование бита подтверждения Ack,
  • uint16_t I2C_AcknowledgedAddress – выбор формата адреса, 7 бит или 10 бит.

С этим вроде бы все понятно 🙂

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

/***************************************************************************************/
GPIO_InitTypeDef gpio;
I2C_InitTypeDef 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;
	// Адрес я тут взял первый пришедший в голову 
	i2c.I2C_OwnAddress1 = 0x15;
	i2c.I2C_Ack = I2C_Ack_Disable;
	i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_Init(I2C1, &i2c);

	// I2C использует две ноги микроконтроллера, их тоже нужно настроить
	gpio.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	gpio.GPIO_Mode = GPIO_Mode_AF; 
	gpio.GPIO_Speed = GPIO_Speed_50MHz;
	gpio.GPIO_OType = GPIO_OType_OD;
	gpio.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOB, &gpio);

	GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1);

	// Ну и включаем, собственно, модуль I2C1
	I2C_Cmd(I2C1, ENABLE);
}


/***************************************************************************************/

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

/***************************************************************************************/
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));
	}
}


/***************************************************************************************/

С этим разобрались, осталось написать главные функции – для приема и передачи данных!

/***************************************************************************************/
void I2C_WriteData(I2C_TypeDef* I2Cx, uint8_t data)
{
	// Просто вызываем готоваую функцию из SPL и ждем, пока данные улетят
	I2C_SendData(I2Cx, data);
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}


/***************************************************************************************/
uint8_t I2C_ReadData(I2C_TypeDef* I2Cx)
{
	uint8_t data;
	
	// Тут картина похожа, как только данные пришли быстренько считываем их и возвращаем
	while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED));
	data = I2C_ReceiveData(I2Cx);
	
	return data;
}


/***************************************************************************************/

Чуть не забыли важную часть! Закончив обмен данными по I2C, необходимо вызвать функцию I2C_GenerateSTOP(I2Cx, ENABLE). Не забываем про этот момент.

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

  • инициализируем модуль I2C, нужные ножки контроллера, ну и все остальное
  • посылаем “старт”
  • шлем данные
  • генерируем “стоп”

Как видите для всех этих пунктов мы уже подготовили специальные функции 🙂 Поэтому на этом и остановимся на сегодня, до скорых встреч на сайте!

Поделиться!

Подписаться
Уведомление о
guest
72 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Александр
Александр
7 лет назад

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

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

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

Александр
Александр
Reply to  Aveal
7 лет назад

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

Nemo
Nemo
7 лет назад

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

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

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

Александр
Александр
Reply to  Nemo
7 лет назад

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

Nemo
Nemo
Reply to  Александр
7 лет назад

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

Александр
Александр
Reply to  Nemo
7 лет назад

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

RiseOfDeath
RiseOfDeath
7 лет назад

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

RiseOfDeath
RiseOfDeath
Reply to  Aveal
7 лет назад

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

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

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

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

Nikolay
Nikolay
6 лет назад

Добрый вечер!
Не знаю что и делать, косяк у меня с 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
6 лет назад

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

Nikolay
Nikolay
6 лет назад

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

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

Nikolay
Nikolay
6 лет назад

Попробую завтра изменить на
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
6 лет назад

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

Nikolay
Nikolay
6 лет назад

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

Nikolay
Nikolay
6 лет назад

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

Nikolay
Nikolay
6 лет назад

отправляю на 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
6 лет назад

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

Nikolay
Nikolay
6 лет назад

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

Aleksey
Aleksey
6 лет назад

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

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

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

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

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

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

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

I2C_SendData(I2C1,0xAA);

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

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

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

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

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

Nemo
Nemo
5 лет назад

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

Nemo
Nemo
5 лет назад

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

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

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

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

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

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

Мне нужно было организовать связь по 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);
}

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

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

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

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

vadim
vadim
5 лет назад

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

vadim
vadim
5 лет назад

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

vadim
vadim
5 лет назад

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

Andrey
Andrey
5 лет назад

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

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

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

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

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

Geraksh
4 лет назад

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

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

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

Petya
3 лет назад

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

Petya
Reply to  Aveal
3 лет назад

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

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

Petya
Reply to  Aveal
3 лет назад

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

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

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

Сергей
1 год назад

Добрый день. пытаюсь реализовать 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 пытаюсь передать один байт. не понимаю где его ловить. и почему он не подтверждает приём адреса ?

Сергей
Reply to  Aveal
1 год назад

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

Сергей
1 год назад

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

Сергей
1 год назад

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

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

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

Сергей
1 год назад

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

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Октябрь 2020
Пн Вт Ср Чт Пт Сб Вс
 1234
567891011
12131415161718
19202122232425
262728293031  

© 2013-2020 MicroTechnics.ru