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

Введение Описание протокола на русском языке с красивыми диаграммами работы можно посмотреть на сайте Easyelectronics. Возможности и описание модуля представлено в руководстве пользователя:

Пример Сам протокол не сложный, однако множество вариантов состояний (ошибок) требуют “запутанной” схемы их обработки, реализовать которые сходу у меня не получится, а уж об использовании ПДП (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
Должны работать в режиме “открытый сток”, схема включения:

Для начала работы необходимо настроить линии ввода-вывода и разрешить тактирование модуля:
RCC->AHBENR |= RCC_AHBENR_GPIOBEN; RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; PIN_CONFIGURATION(PIN_I2C_SCL); PIN_CONFIGURATION(PIN_I2C_SDA);
Далее необходимо выбрать частоту тактирования модуля.
Сам модуль подключен к выходу шины APB1 (PCLK1), через два предварительных делителя (обвел синим):

Контроллер поддерживает два режима обмена: стандартный (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 памяти:

Как видим необходимо:
- сформировать сигнал “Старт”
- передать адрес ведомого устройства
- передать адрес ячейки памяти
- сформировать сигнал “Повторный Старт”
- передать адрес ведомого устройства
- считать данные
- послать сигнал “Стоп”
Можно выделить два режима работы.
Первый режим — запись данных (от “Старт” до “Повторный Старт”).
Второй режим — чтение данных (от “Повторный Старт” до “Стоп”).
Диаграмма работы в ведущего в режиме — запись данных:

У микросхемы 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)) { }
Диаграмма работы в ведущего в режиме – чтение данных:

Действия аналогичны предыдущему режиму, поэтому сразу код:
// формирование сигнала "Повторный Старт" 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;
Хух, Прочитали один байтик

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

В связи с тем, что при записи нужного адреса ячейки памяти мы уже находимся в режиме запись.
На этом пока все.
Исходный код
Скачать
- +3
- 14 декабря 2011, 12:21
- ZiB
С использованием DMA получается ощутимо проще. Но I2C периферия в STM32 на удивление косая. В качестве примера можете посмотреть как сделано в www.chibios.org/dokuwiki/doku.php
Во первых потому что пропустил двойку :(
2,097 МГц / 2 / 100 кГц = 10;
а во вторых, нужно было объяснить откуда она появляется :(
Дело в том, что в зависимости от режима работы (стандартный/скоростной) длительности положительного и отрицательного уровня сигнала отличаются. В стандартном режиме отношение их длительностей один к одному, отсюда и получается 1+1=2. В скоростном режиме уже другой расклад, это либо 1:2, либо 9:16. Этот момент нужно учитывать при расчете. См. документацию для более подробного понимания процесса.
2,097 МГц / 2 / 100 кГц = 10;
а во вторых, нужно было объяснить откуда она появляется :(
Дело в том, что в зависимости от режима работы (стандартный/скоростной) длительности положительного и отрицательного уровня сигнала отличаются. В стандартном режиме отношение их длительностей один к одному, отсюда и получается 1+1=2. В скоростном режиме уже другой расклад, это либо 1:2, либо 9:16. Этот момент нужно учитывать при расчете. См. документацию для более подробного понимания процесса.
А почему в статье нет про бит ACK регистра CR1? Стоит о нем упомянуть — при приеме нескольких байт — если он установлен — мастер отсылает ACK, если нет — не отсылает
- SlavikMIPT
- 14 марта 2012, 06:11
- ↓
Наверное потому, что пример простой и не использует проверку подтверждений.
После публикации я им не занимался и мне сейчас тяжело ответить «правильно»,
нужно опять читать доку.
Но вы можете расширить и опудликовать свои мысли.
После публикации я им не занимался и мне сейчас тяжело ответить «правильно»,
нужно опять читать доку.
Но вы можете расширить и опудликовать свои мысли.
тут не проверка подтверждения — мастер после приема байта отсылает слейву бит подтверждения если установлен флаг ACK — в вашем примере принимается один байт — тут подтверждение приема от мастера не требуется, но как правило одним байтом передача не ограничивается — поэтому после каждого не последнего байта мастер должен слать ACK — иначе слейв не получив подтверждение не выдаст следующий байт
- SlavikMIPT
- 14 марта 2012, 13:57
- ↑
- ↓
чтобы несколько байт отсылать нужно делать перед приемом I2C1->CR1 |= I2C_CR1_ACK; тогда другой вопрос — если байт уже примется а мы не успели выставить ACK — тогда передача сорвется, но байт начинает приниматься сразу после того, как мы рестартнули и отправили адрес с параметром R — то есть мы либо бит должны выставлять перед отправкой адреса, либо как то задержать начало приема, до выставления этого бита, кроме того перед приемом последнего байта — его нужно снять, то есть по сути между тактами успеть — возникает вопрос — как можно приостановить начало приема байта?
- SlavikMIPT
- 14 марта 2012, 14:03
- ↑
- ↓
Устанавливать ACK постоянно нет необходимости. Он не сбрасывается сам. А вот со сбросом какраз таки и возникают проблемы. По этому в даташите (и еррате) приводятся 3 разных алгоритма для 1 байта, 2 байт и >2 байт. И связанная с этим свистопляска с POS. Но всё равно решение не надёжное и по этому надо ещё и прерывания запрещать с определённых местах.
так я постоянно и не ставлю — один раз до отправки адреса, тут проблем нет — а вот про снятие и спрашивал, посмотрю, что там с POS в дш
- SlavikMIPT
- 14 марта 2012, 16:40
- ↑
- ↓
… на 8-ми битных легко реализуется программно. Да и нужен то он — сохранить конфигурацию как правило, а для больших объемов SD_card намного удобнее и быстрее (… и дешевле). ST в свойственной им манере «нахреначить всего — чтоб было» не сильно заморачиваются по многим «пустякам» :)
- ChipKiller
- 14 марта 2012, 13:04
- ↑
- ↓
Комментарии (32)
RSS свернуть / развернуть