STM32 → I2C

I2C

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

  • память (EEPROM, Flash, FRAM)
  • микросхемы часов точного времени
  • расширители интерфейсов
  • датчики
  • микросхемы управления светодиодами
  • ЦАП/АЦП
  • цифровые потенциометры
  • RFID-сенсоры
  • обмен данными между микроконтроллерами

Простой и нетребовательный к ресурсам, этот интерфейс нашел свое применение во всевозможных случаях обмена МК-периферия, где не нужна высокая скорость. На его основе сделаны SMBUS (диагностическая шина на материнской плате компьютера) и DDC (шина настройки режимов монитора), также он входит в состав интерфейсов UEXT и mikroBus.

Плюсы и минусы

Плюсы:

  • Низкоскоростной, не нужны особые меры по эмс.
  • Топология шина, поэтому для связи (вместе со всей адресацией) достаточно двух проводов SCL и SDA.
  • Встроенная адресация на шине, позволяющая подключать по двум проводам большое количество устройств параллельно (даже однотипных, благодаря шинам выбора адреса).
  • Большой возраст спецификации интерфейса, поэтому очень много устройств поддерживают его.
  • Во многих МК встроен аппаратный модуль.
  • Простота протокола.

Минусы:

  • Нет обнаружения и коррекции ошибок.
  • Обе линии — двунаправлены, что усложняет реализацию гальванической развязки и преобразователей уровня.
  • Низкая скорость передачи. В первой версии 100 кбод, во второй 400 кбод, третью со скоростью 3.4 Мбод используют очень редко.
  • Некоторые микросхемы содержат ошибку и не поддерживают собственные ножки выбора адреса.

Логика работы

Выходные каскады всех устройств сделаны по схеме с открытым коллектором и требуют подтяжки обоих линий к плюсу через резистор в 10 кОм. Так реализуется схема «голосования» за 1: на линии не может появиться 1, пока все устройства не выставят 1 на своих выходах. Так исключается ситуация конфликта передачи, и реализуется контроль потока: более медленные устройства могут замедлять обмен на линии, устанавливая 1 на скл только после того, как станут готовы принимать следующий бит.

Из этого вытекает логика работы хоста: при передаче очередного бита установить 1 на SCL, и только дождавшись реального появления 1 на линии продолжать передачу.

Хост генерирует старт, и начинает передавать биты по очереди, каждый новый бит должен появляться на SDA только во время 0 на SCL. Логика работы SCL: 0 — устанавливаем бит на линии SDA, 1 — бит принимают устройства. Дожидаемся реального 1 на SCL, выжидаем несколько миллисекунд и передаем следующий бит. Количество байт в посылке неограничено, прием каждого байта слейв подтверждает передачей 1. Хост заканчивает передачу сигналом стоп.

Библиотека  stm32_i2c

Я подготовил библиотеку, в которой реализованы функции инициализации, записи и чтения одного байта, записи и чтения нескольких байт.

Очень важный вопрос рассмотрен в статье про отлов ошибок I2C и реакцию на них. Там приведены улучшенные функции, но они пока не внесены в основной репозиторий.

Практика

Давайте рассмотрим работу STM32 с шиной I2C на примере микросхемы памяти 24AA. EEPROM-память — настолько стандартный компонент, что реализация I2C в нём точно будет эталонной, да и очень многие другие устройства общаются по I2C в стиле микросхем памяти. К примеру, цифровой барометр BMP180 предоставляет несколько регистров с калибровочными данными (для чтения), регистр управления (запись определённого числа туда запускает измерение) и три регистра выходных данных (для чтения результата измерения) — работать с ними можно точно так же как с ячейками памяти EEPROM.

Я использую микроконтроллер STM32F107, модуль I2C1 на контактах PB6/PB7.

void I2C_init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
I2C_InitTypeDef I2C_InitStructure;
I2C_StructInit(&I2C_InitStructure);
I2C_InitStructure.I2C_ClockSpeed = 100000;
I2C_InitStructure.I2C_OwnAddress1 = 1;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);
I2C_AcknowledgeConfig(I2C1, ENABLE);
}

void I2C_single_write(uint8_t HW_address, uint8_t addr, uint8_t data)
{
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, HW_address, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_SendData(I2C1, data);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
}

uint8_t I2C_single_read(uint8_t HW_address, uint8_t addr)
{
uint8_t data;
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, HW_address, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, HW_address, I2C_Direction_Receiver);
while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED));
data = I2C_ReceiveData(I2C1);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
I2C_AcknowledgeConfig(I2C1, DISABLE);
I2C_GenerateSTOP(I2C1, ENABLE);
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
return data;
}

void delay()
{
for(volatile uint32_t del = 0; del<250000; del++);
}

int main()
{
uint8_t single_data_read = 0;

I2C_init();
delay();

I2C_single_write(0xA0, 0x00, 0x15);
delay();

single_data_read = I2C_single_read(0xA0, 0x03);

while(1);
}