STM32L-Интерфейс I2C

14 Декабря 2011 К комментариям

Краткий обзор и простой пример работы с интерфейсом 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

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

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


Исходный код

2011-12-14-STM32L-I2C-LCD-Demo



comments powered by Disqus