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
Должны работать в режиме “открытый сток”, схема включения:
Для начала работы необходимо настроить линии ввода-вывода и разрешить тактирование модуля:
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;
Хух, Прочитали один байтик
Запись одного байта проще:
В связи с тем, что при записи нужного адреса ячейки памяти мы уже находимся в режиме запись.
На этом пока все.
Исходный код
comments powered by Disqus