STM32L-Интерфейс I2C (кратенько)

Краткий обзор и простой пример работы с интерфейсом I2C, на примере – CAT24C02 (EEPROM память, объем 256 Байт).
Введение Описание протокола на русском языке с красивыми диаграммами работы можно посмотреть на сайте Easyelectronics. Возможности и описание модуля представлено в руководстве пользователя: RM0038: STM32L151xx and STM32L152xx advanced ARM-based 32-bit MCUs (Руководство пользователя).
Пример Сам протокол не сложный, однако множество вариантов состояний (ошибок) требуют “запутанной” схемы их обработки, реализовать которые сходу у меня не получится, а уж об использовании ПДП (DMA) вообще можно не думать. Микросхема EEPROM памяти CAT24C02 в контексте интерфейса I2C является ведомым (Slave) устройством, следовательно модуль I2C необходимо настроить в режим ведущего (Master). В данном режиме микроконтроллер (для простоты описания) сам генерирует тактовые сигналы и инициирует передачу данных. В установленном на плате STM32L-Discovery микроконтроллере имеется два модуля I2C1 и I2C2, я выбрал первый, так как сигнальные линии второго “попадают” под ЖКИ индикатор, а мне хотелось бы его оставить для вывода отладочной информации. Правда, при этом пришлось отказаться от светодиодов, так как они оба подключены к линиям первого модуля, но паять (выпаивать) на плате ни чего не нужно. Определение линий:
#define PIN_I2C_SCL		B, 6, HIGH, MODE_AF_OPEN_DRAIN_PULL_UP, SPEED_400KHZ, AF4
#define PIN_I2C_SDA		B, 7, HIGH, MODE_AF_OPEN_DRAIN_PULL_UP, SPEED_400KHZ, AF4


Должны работать в режиме “открытый сток”, схема включения:

image

Для начала работы необходимо настроить линии ввода-вывода и разрешить тактирование модуля:

RCC->AHBENR |= RCC_AHBENR_GPIOBEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;

PIN_CONFIGURATION(PIN_I2C_SCL);
PIN_CONFIGURATION(PIN_I2C_SDA);


Далее необходимо выбрать частоту тактирования модуля.

Сам модуль подключен к выходу шины APB1 (PCLK1), через два предварительных делителя (обвел синим):

image

Контроллер поддерживает два режима обмена: стандартный (Standard Speed — up to 100 kHz) и быстрый (Fast Speed (up to 400 kHz).

В зависимости от режима обмена частота тактирования модуля должна быть не ниже:

  • 2 MHz в стандартном режиме
  • 4 MHz в быстром режиме

После сброса микроконтроллера значение обоих делителей равно одному, а в качестве системной тактовой частоты используется внутренний MSI генератор, с частотой по умолчанию 2,097 МГц. Поэтому я выбрал режим “стандартной” скорости обмена (Standard Speed (up to 100 kHz)).

Текущее значение частоты PCLK1 необходимо прописать в регистр (I2C_CR2):

// текущее значение частоты PCLK1 (может быть от 2 до 32, включительно)
I2C1->CR2 &= ~I2C_CR2_FREQ;
I2C1->CR2 |= 2;


Не совсем понял для чего это нужно. Возможно, что бы было меньше “телодвижений” при смене тактовой частоты.

В зависимости от указанной частоты необходимо установить предварительный делитель в соответствии с выбранной скоростью обмена. Выберем по максимуму, для стандартного режима – 100 кГц:

2,097 МГц / 100 кГц = 10;

I2C1->CCR &= ~I2C_CCR_CCR;
I2C1->CCR |= 10;


Так как линия данных и тактирования работают в режиме “открытый сток”, то время нарастания сигнала (переход от низкого уровня к высокому) будет довольно большое (зависит частоты обмена и от емкости линий связи).

Для правильной работы необходимо задать в “тактах” частоты тактирования модуля максимально возможное время установления, по спецификации для стандартного режима максимальное время равно 1000 нс.

Период тактовой частоты равен (1 / 2,097 МГц = 476 нс), следовательно время максимальное нарастания:

1000 нс / 476 нс = 2 + 1 (плюс единица – небольшой запас) = 3

I2C1->TRISE = 3;


Как я понял данный параметр задает момент времени по которому производятся выборка состояния линий.

Всё, базовая настройка произведена.




Углубляемся в протокол обмена

Диаграмма работы в режиме произвольного чтения данных из EEPROM памяти:

image

Как видим необходимо:

  • сформировать сигнал “Старт”
  • передать адрес ведомого устройства
  • передать адрес ячейки памяти
  • сформировать сигнал “Повторный Старт”
  • передать адрес ведомого устройства
  • считать данные
  • послать сигнал “Стоп”

Можно выделить два режима работы.

Первый режим — запись данных (от “Старт” до “Повторный Старт”).

Второй режим — чтение данных (от “Повторный Старт” до “Стоп”).

Диаграмма работы в ведущего в режиме — запись данных:

image

У микросхемы EEPROM памяти ширина адреса 7 бит (верхняя диаграмма).

“Пройдемся” по данному режиму параллельно с программным кодом.

Формирование сигнала “Старт”:

// разрешаем работу
I2C1->CR1 |= I2C_CR1_PE;
	
// формирование сигнала старт
I2C1->CR1 |= I2C_CR1_START;


После формирования дожидаемся возникновения события EV5 (см. выше), т.е. установки бита:

// ждем окончания формирования сигнала "Старт"
while (!(I2C1->SR1 & I2C_SR1_SB))
{
}


Пока я не рассматриваю (не обрабатываю) исключительные ситуации (ошибки, коллизии) возникающие на линии.

Адрес ведомого состоит из:

  • бит 0 – говорит о направлении передачи последующих данных (0- от ведущего к ведомогу / 1 – от ведомого к ведущему)
  • биты 1-7 – задают адрес ведомого (для EEPROM 0x50)

Передаем адрес ведомого:

(void) I2C1->SR1;

// передаем адрес ведомого
I2C1->DR = 0xA0;


Перед передачей необходимо прочитать регистр SR1, для сброса бита SB.

Ожидаем окончания передачи адреса (событие EV6) и сбрасываем бит ADDR (чтением SR1 и SR2):

// ожидаем окончания передачи адреса
while (!(I2C1->SR1 & I2C_SR1_ADDR))
{
}

(void) I2C1->SR1;
(void) I2C1->SR2;



Для примера считаем значение десятой ячейки EEPROM памяти:

// передаем адрес десятой ячейки
I2C1->DR = 10;


Ожидаем окончания передачи:

// ожидаем окончания передачи адреса
while (!(I2C1->SR1 & I2C_SR1_BTF))
{
}


Диаграмма работы в ведущего в режиме – чтение данных:

image

Действия аналогичны предыдущему режиму, поэтому сразу код:

// формирование сигнала "Повторный Старт"
	I2C1->CR1 |= I2C_CR1_START;

	// ждем окончания формирования сигнала "Повторный Старт"
	while (!(I2C1->SR1 & I2C_SR1_SB))
	{
	}
	(void) I2C1->SR1;

	// передаем адрес ведомого + чтение
	I2C1->DR = 0xA1;

	// ожидаем окончания передачи адреса
	while (!(I2C1->SR1 & I2C_SR1_ADDR))
	{
	}
	(void) I2C1->SR1;
	(void) I2C1->SR2;

	// ожидаем окончания приема данных
	while (!(I2C1->SR1 & I2C_SR1_RXNE))
	{
	}

	// cчитываем приянтое значение
	eeprom_data = I2C1->DR;


После формируем сигнал “Стоп”

// формирование сигнала "Стоп"
I2C1->CR1 |= I2C_CR1_STOP;


Хух, Прочитали один байтик Улыбка




Запись одного байта проще:

image

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

На этом пока все.




Исходный код

Скачать
  • +3
  • 14 декабря 2011, 12:21
  • ZiB

Комментарии (31)

RSS свернуть / развернуть
Да уж, кратенько… :)
+1
оценивал не по объёму, а по содержанию :)
0
С использованием DMA получается ощутимо проще. Но I2C периферия в STM32 на удивление косая. В качестве примера можете посмотреть как сделано в www.chibios.org/dokuwiki/doku.php
0
для моей задачи в ПДП смысла нет, но для интереса попробую.
мне не нравиться сам протокол, стараюсь использовать очень редко.
0
Чтобы сохранить светодиоды попробуйте глянуть ремаппинг. Хотя бы один интерфейс должен ремапиться…
0
Я же написал, что доступность линий проверил в первую очередь, к сожалению все занято.
0
сорри, значит я не правильно понял…
0
нормально, это побудило меня ещё раз проверить себя.
0
Спасибо за кратенький обзорчик :) Я правда так и не пойму как считать ACK/NACK от slave, как обработать(полингом или в прерывании неважно) ACK/NACK не подскажете?
0
Если slave не скажет ACK после передачи адреса в шину — взведется флаг ошибки AF в регистре I2C_SR1. А если разрешены и настроены прерывания — то прилетит и прерывание.
0
Спасибо.
0
Спасибо, пригодилось :)
Жаль, плюсануть немогу :(
0
2,097 МГц / 100 кГц = 10;
Почему 10, а не 20?
0
Во первых потому что пропустил двойку :(
2,097 МГц / 2 / 100 кГц = 10;
а во вторых, нужно было объяснить откуда она появляется :(
Дело в том, что в зависимости от режима работы (стандартный/скоростной) длительности положительного и отрицательного уровня сигнала отличаются. В стандартном режиме отношение их длительностей один к одному, отсюда и получается 1+1=2. В скоростном режиме уже другой расклад, это либо 1:2, либо 9:16. Этот момент нужно учитывать при расчете. См. документацию для более подробного понимания процесса.
0
Выпала часть мысли…
В указном регистре, фактически задается длительность базы, а не частота тактовых сигналов.
0
Спасибо. Надо повнимательней почитать ДШ.
0
А почему в статье нет про бит ACK регистра CR1? Стоит о нем упомянуть — при приеме нескольких байт — если он установлен — мастер отсылает ACK, если нет — не отсылает
0
тьфу — точнее POS бит
0
хотя все же ACK
0
Наверное потому, что пример простой и не использует проверку подтверждений.
После публикации я им не занимался и мне сейчас тяжело ответить «правильно»,
нужно опять читать доку.
Но вы можете расширить и опудликовать свои мысли.
0
тут не проверка подтверждения — мастер после приема байта отсылает слейву бит подтверждения если установлен флаг ACK — в вашем примере принимается один байт — тут подтверждение приема от мастера не требуется, но как правило одним байтом передача не ограничивается — поэтому после каждого не последнего байта мастер должен слать ACK — иначе слейв не получив подтверждение не выдаст следующий байт
0
чтобы несколько байт отсылать нужно делать перед приемом I2C1->CR1 |= I2C_CR1_ACK; тогда другой вопрос — если байт уже примется а мы не успели выставить ACK — тогда передача сорвется, но байт начинает приниматься сразу после того, как мы рестартнули и отправили адрес с параметром R — то есть мы либо бит должны выставлять перед отправкой адреса, либо как то задержать начало приема, до выставления этого бита, кроме того перед приемом последнего байта — его нужно снять, то есть по сути между тактами успеть — возникает вопрос — как можно приостановить начало приема байта?
0
Устанавливать ACK постоянно нет необходимости. Он не сбрасывается сам. А вот со сбросом какраз таки и возникают проблемы. По этому в даташите (и еррате) приводятся 3 разных алгоритма для 1 байта, 2 байт и >2 байт. И связанная с этим свистопляска с POS. Но всё равно решение не надёжное и по этому надо ещё и прерывания запрещать с определённых местах.
0
так я постоянно и не ставлю — один раз до отправки адреса, тут проблем нет — а вот про снятие и спрашивал, посмотрю, что там с POS в дш
0
а POS это писец какой-то
0
Да и сам I2C как протокол, та ещё головная боль :)
0
Ну софтовая реализация для еепромок (причем разных) на PICе просто прекрасно выходит :)
Но вот с хардварей стм, да. какой-то ужас кромешный.
0
Я в целом про протокол, а не про реализацию СТМ :)
0
… на 8-ми битных легко реализуется программно. Да и нужен то он — сохранить конфигурацию как правило, а для больших объемов SD_card намного удобнее и быстрее (… и дешевле). ST в свойственной им манере «нахреначить всего — чтоб было» не сильно заморачиваются по многим «пустякам» :)
+1
Это манера не только ст, многие так делают.
0
«Железный» I2C кажись, только у его «родителя» (филипса) нормальный, что неудивительно. :) Пользовал их 51, там в прерывании I2C вычитывается регистр статуса (один!), дальше таблица переходов по его содержимому. А вот Silabs уже мозги выносит, ST ещё сильнее…
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.