Смотрим на шину I2C через осциллограф

Подразумевается, что читатель уже имеет некоторое представление о шине i2c. Например, такие вещи как подключение устройств к шине, что устройства должны иметь уникальные адреса, что устройств может быть не более 127, что нужны подтягивающие резисторы, что посылка начинается с состояния старт, а заканчивается состоянием стоп. Но немного не хватает практики в части визуального контроля и понимания процессов. В этой статье я постараюсь лишить читателя этого самого практического пробела в знаниях.

Итак, рассмотрим поведение шины I2C при работе с микроконтроллером stm32 с использованием библиотеки HAL. На шине I2C1 подсоединена микросхема EEPROM 24LC21.

Согласно описанию на микросхему ее адрес на шине — 1010ххх (7 бит), где ХХХ — не имеет значения, хоть ноль, хоть единицы. Следом за адресом устройства идет 8й бит R/W̅. Этот бит говорит slave-устройству, что нужно будет делать дальше — принимать или отдавать данные. Если лог 0, то slave-устройство будет принимать данные (write), то есть запись в slave-устройство. Если лог 1, то slave-устройство будет отправлять данные (read), то есть чтение из slave-устройства. Когда slave-устройство видит на шине свой адрес, то на 9 м тактовом импульсе (на линии SCL) оно прижимает линию SDAв ноль, сообщая master-устройству о своем присутствии на линии. Если мастер выдал адрес, но устройства с таким адресом на шине не обнаружилось, то на 9 м тактовом импульсе линия SDAбудет в единице. Это девятый бит называют битом подтверждения, acknowledge bit (А, АСК). Каждый обмен сообщениями обрамляется стартовым и стоповым битом. Стартовый бит это состояние когда при высоком состоянии линии SCL линия SDA прижимается к нулю. Стоповый бит это состояние когда при высоком состоянии линии SCL линия SDA прижимается к единице.

Выписка из описания на микросхему

cb880848403fcf450e66f45681c95676.png

Напишем простую программу чтения из микросхемы трех байт с адреса 5. Для этого нужно сперва записать в микросхему число 5. Это число внутри микросхемы поместится в указатель адреса. А затем считать 3 байта.

В качестве адреса HAL-овские функции просят адрес устройства (взятый из описания) предварительно сдвинуть влево. Об этом указано в описании функций  библиотеки HAL. «User manual. Description of STM32F7 HAL and low-layer drivers. UM1905» https://www.st.com/resource/en/user_manual/DM00189702-.pdf (стр. 493)  «DevAddress: Target device address: The device 7 bits address value in datasheet must be shifted to the left before calling the interface». Поэтому к 7 битному адресу просто добавляем ноль.

txData[0] = 5;											// 1
  while (1)
  {
	HAL_I2C_Master_Transmit(&hi2c1, 0B10100000, txData, 1, 100);
	HAL_I2C_Master_Receive (&hi2c1, 0B10100000, rxData, 3, 100);
	HAL_Delay(1000);
  }

В результате работы получаем такую осциллограмму на шине i2c. Левая часть это работы функции HAL_I2C_Master_Transmit, правая — HAL_I2C_Master_Receive.

Рисунок 1

Рисунок 1

Рассмотрим их поближе.

  • HAL_I2C_Master_Transmit

Рисунок 2

Рисунок 2

Посылка начинается со стартового бита. Затем идут 7 бит адреса (1010000), затем бит r/w (лог 0) (его формирует функция HAL_I2C_Master_Transmit). Затем девятым битом микросхема подтверждает свое присутствие, прижимая SDA к нулю.  Мастер, понимая, что устройство отвечает, продолжает посылку, отправляя в шину число 5 (0b00000101). Снова девятым битом устройство прижимает линию SDA к нулю. Так как в функции мы отправляли всего одно число (4й аргумент), то посылка завершается стоповым битом.

  • HAL_I2C_Master_Receive

Рисунок 3

Рисунок 3

Посылка начитается со стартового бита. Затем идут 7 бит адреса (1010000), затем бит R/W (лог 1) (его формирует функция HAL_I2C_Master_Receive). Затем девятым битом микросхема подтверждает свое присутствие, прижимая SDA к нулю.  Мастер, понимая, что устройство отвечает, продолжает генерировать тактовые импульсы на линии SCL. Но теперь линией SDA будет управлять slave-устройство, то есть в нашем случае микросхема EEPROM. Именно она формирует данные (в нашем случае нули, так как в микросхему действительно записаны нули). Теперь master их считывает и выставляет девятый бит в ноль или единицу. Так как в функции HAL_I2C_Master_Receive мы указали считать три байта (4й аргумент функции), то после получения третьего байта, master выставляет 9й бит в лог 1, тем самым сообщая slave-устройству что больше от него не требуется выдавать данные, и заканчивает посылку выставляя стоповый бит. Таким образом, функция Receive (прием) сперва фактически делает обращение (запись) к устройству, а затем уже чтение из устройства.

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

	HAL_I2C_Master_Transmit(&hi2c1, 0B10010000, txData, 1, 100);
	HAL_I2C_Master_Receive (&hi2c1, 0B10110000, rxData, 3, 100);

Рисунок 4. Нет такого адреса на шине

Рисунок 4. Нет такого адреса на шине

Рассмотрим поближе

  • HAL_I2C_Master_Transmit

Рисунок 5

Рисунок 5

  • HAL_I2C_Master_Receive

Рисунок 6

Рисунок 6

Как видим в обоих случаях указан неверный адрес и slave-устройство не откликнулось, не прижало линию SDA к нулю на 9 м бите. И в обоих случаях текущая посылка прерывается мастером выдачей стопового бита.

Теперь давайте считаем какие-нибудь данные отличные от нуля.Но для этого их надо туда записать.

Для записи данных пишем такой код.

void erase_eeprom (){
	uint8_t txData[9] ={0};

	for (uint8_t j=0; j<8; j++){
		for (uint8_t i=0; i<8; i++) {txData[i+1] = txData[0] + i;}
		HAL_I2C_Master_Transmit(&hi2c1, 0xA0, txData, 9, 100);
		HAL_Delay(11);
		txData[0] += 8;
	}
}

Данный код запишет в ячейки памяти число равное адресу этой ячейки. Пишем постранично, режим 4.2.PageWrite (см. описание на микросхему), по 8 байт за один раз. После того как отправили 8 байт данных в микросхему, они там размещаются во внутреннем буфере и после стопового бита происходит процесс записи непосредственно в ячейки памяти. Процесс записи это «долгий» процесс. Занимает 10 мс (см. описание на микросхему), поэтому делаем паузу между записями. Отправили 8 байт, подождали 11 мс. Отправили еще 8 байт, снова ждем 11 мс. И так 8 раз.

Рисунок 7

Рисунок 7

На картинке 9й «импульс» это уже чтение из первой части статьи (см Рисунок 1).
Раскроем покрупнее первую посылку записи.

Рисунок 8. Запись 8 байт в микросхему eeprom начиная с адреса 0

Рисунок 8. Запись 8 байт в микросхему eeprom начиная с адреса 0

1 — обращение к slave-устройству с адресом 1010000 в режиме записи (бит R/W̅ в лог. 0).
2 — установка адреса 0 (с адреса 0 будет далее писать 8 байт)
3 — запись в текущий адрес числа 0
… после занесения числа внутренний указатель адреса увеличивается на единицу, поэтому следующая запись будет по адресу 1
4 — запись в текущий адрес (1) числа 1

10 — запись в текущий адрес (7) числа 7

Раскроем покрупнее запись числа 5 (шаг 8 на рисунке 8)

Рисунок 9

Рисунок 9

Посмотрим на запись числа 20 в ячейку 20 — (третий «импульс» на рисунке 7)

0c15e70ad7ef0543e8f7fe578a5de4e8.png43cfd6ef15f6e570a2fc48e19114de92.pngbbfa2e0c642db2f97cf4d649cbfd7253.png

1 — обращение к slave-устройству с адресом 1010000 в режиме записи (бит R/W̅ в лог. 0).
2 — установка адреса 16 (с адреса 16 будет далее писать 8 байт)
7 — запись в текущий адрес (20) числа 20

Теперь в EEPROM сохранили данные. Самое время их считать.

eb537ef4a6193a99191b1763a26ecf90.png

На осциллограмме результат работы функции  HAL_I2C_Master_Receive (&hi2c1, 0B10100000, rxData, 3, 100);

Видим как устанавливается адрес slave-устройства 1010000, бит R/W̅ устанавливается в лог.1. И затем идут три числа считанные из EEPROM — 5, 6, 7.

На этом пока всё. Также планирую изучить (и написать статью) работу с шиной i2c в режиме прерываний и DMA.

Буду рад критике.

© Habrahabr.ru