STM32. Использование I2C.

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

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

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

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

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

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

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

• Каждое устройство может выступать как в роли Master, так и Slave
• В режиме Master реализуется:
o Генерация тактового сигнала
o Генерация старт-сигнала и стоп-сигнала
• В режиме Slave реализуется:
o Механизм подтверждения адреса
o Использование двух Slave адресов
o Обнаружение стоп-бита, выданного ведущим на линию
• Генерация и определение 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)
{
    // Тут картина похожа, как только данные пришли быстренько считываем их и возвращаем
    while( !I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) );
    data = I2C_ReceiveData(I2Cx);
    return data;
}
 
 
 
/*******************************************************************/

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

Вот так вот получилось. При использовании I2C для передачи данных, например, последовательность действий должна быть такой:
1. Инициализируем модуль I2C, нужные ножки контроллера, ну и все остальное
2. Посылаем «старт»
3. Шлем данные
4. Генерируем «стоп»
Как видите для всех этих пунктов мы уже подготовили специальные функции ) Поэтому на этом и остановимся на сегодня, до скорых встреч на сайте!

Понравилась статья? Поделись с друзьями!

STM32. Использование I2C.: 56 комментариев
  1. Прием так же можно организовать таким образом:
    uint8_t I2C_ReadData(I2C_TypeDef* I2Cx)
    {
    while( !I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) );
    return (uint8_t)I2Cx->DR;

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

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

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

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

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

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

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

    • Ага, в этом примере как Master работает, если будет Slave-ом, то последовательность инструкций будет совсем другая

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

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

        • По поводу потери байт — хорошо бы осциллографом проверить все линии. А пример под slave постараюсь организовать )

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

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

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

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

  7. Попробую завтра изменить на
    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 вызывать, а потом включать. Странно, что вообще ничего нет на линии.

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

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

  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;
    }

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

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

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

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

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

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

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

    I2C_SendData(I2C1,0xAA);

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

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

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

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

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

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

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

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

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

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

    • В принципе, есть два варианта только — либо на сайте ST — официальные примеры (там мне кажется такого не найти), либо на форумах каких-нибудь (тоже не факт что есть).
      SPL что для F4, что для F3 (да и для других семейств) в принципе имеет одинаковую структуру, поэтому можно просто пробежаться по коду и если какой-то функции для F3 нет, то скорее всего есть какая-то похожая.

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

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

    • Есть конечно подозрительные моменты, очень уж похожие в статье, но вроде бы в целом там хоть и о том же, но в основном другими словами )))

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *