Главная » Микроконтроллеры
Призовой фонд
на июль 2017 г.
1. Осциллограф DSO138
Паяльник
2. Регулируемый паяльник 60 Вт
Паяльник
3. 200 руб.
От пользователей

STM32F030. Не блокирующая реализация I2C

В одном из своих проектов использую микроконтроллеры STM32F030. Недавно возникла необходимость подключения внешней EEPROM памяти по шине I2C. Сначала я хотел взять готовый пример с инета, но в итоге пришлось изобретать свой велосипед писать свой код. В статье рассказываю о типичных граблях при работе с шиной I2C STM32F030, и предлагаю свой велосипед вариант работы с шиной.

Итак, для критики возьму один из примеров кода, взятый с интернета:

/** Описание Записывает байт данных в I2C EEPROM.
* Параметр data: переменная для записи в EEPROM.
* Параметр WriteAddr: Внутренний адрес EEPROM для записи.
* Возвращаемое значение нет
*/
uint32_t EEPROM_I2C_Write(uint8_t data, uint16_t WriteAddr)
{
//uint32_t DataNum = 0;
Address = Address + (WriteAddr / 256);

/* Конфигурирование адреса ведомого; количество байтов, которые будут запрограммированы (переданы); перезагрузки и генерировать старт */
I2C_TransferHandling(I2C1, Address, 1, I2C_Reload_Mode, I2C_Generate_Start_Write);

/* Подождите, пока TXIS флаг не будет установлен */
while(I2C_GetFlagStatus(I2C1, I2C_ISR_TXIS) == RESET);

/* Отправить адрес памяти */
I2C_SendData(I2C1, (uint8_t)WriteAddr);

/* Подождите, пока TCR флаг не будет установлен */
while(I2C_GetFlagStatus(I2C1, I2C_ISR_TCR) == RESET);

/* Обновить CR2: установить Адрес ведомого, установить запрос на запись, генерировать Пуск и заданного конечного режим */
I2C_TransferHandling(I2C1, Address, 1, I2C_AutoEnd_Mode, I2C_No_StartStop);

/* Подождите, пока TXIS флаг не будет установлен */
while(I2C_GetFlagStatus(I2C1, I2C_ISR_TXIS) == RESET);

/* Запись данных в TXDR */
I2C_SendData(I2C1, data);

/* Подождите, пока STOPF флаг не будет установлен */
while(I2C_GetFlagStatus(I2C1, I2C_ISR_STOPF) == RESET);

/* Очистить флаг STOPF */
I2C_ClearFlag(I2C1, I2C_ICR_STOPCF);
}

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

/* Конфигурирование адреса ведомого; количество байтов, которые будут запрограммированы (переданы); перезагрузки и генерировать старт */
I2C_TransferHandling(I2C1, Address, 1, I2C_Reload_Mode, I2C_Generate_Start_Write);

На шину выдан старт, отправлен адрес устройства. Так как мы отключили микросхему памяти, установился флаг NACKF (Not Acknowledge Flag). Смотрим код дальше.

/* Подождите, пока TXIS флаг не будет установлен */
while(I2C_GetFlagStatus(I2C1, I2C_ISR_TXIS) == RESET);

А вот здесь микроконтроллер зависает, так как ждёт запрос на передачу байта (установку флага TXIS, Transmit Interrupt Status). Запрос никогда не поступит, так как ведомое устройство на шине не отвечает. Это первые грабли. Соответственно, пока никаких сбоев на шине нет - наше устройство работает нормально. Как только произошёл малейший сбой - микроконтроллер наглухо виснет. Смотрю код дальше.

/* Отправить адрес памяти */
I2C_SendData(I2C1, (uint8_t)WriteAddr);

/* Подождите, пока TCR флаг не будет установлен */
while(I2C_GetFlagStatus(I2C1, I2C_ISR_TCR) == RESET);

Здесь тоже имеется ошибка. Если микросхема не отвечает, устанавливается флаг NACKF, а флаг TCR (Transfer Complete Reload) никогда не будет возведён. Микроконтроллер зависнет.

Далее ищу новые грабли. Для этого замыкаем одну из ног шины на землю, или просто соединяем обе ноги между собой. Шина должна заметить подвох, и выдать ошибку.

/* Запись данных в TXDR */
I2C_SendData(I2C1, data);

/* Подождите, пока STOPF флаг не будет установлен */
while(I2C_GetFlagStatus(I2C1, I2C_ISR_STOPF) == RESET);

Последняя строчка ожидает возведения флага STOPF (Stop detection Flag), но мы замкнули ножки и заблокировали обмен данными. Шина замечает подвох, и взлетает флаг ARLO (Arbitration Lost). Флаг STOPF не устанавливается, микроконтроллер зависает. Более того, появляются ещё одни грабли.

/* Конфигурирование адреса ведомого; количество байтов, которые будут запрограммированы (переданы); перезагрузки и генерировать старт */
I2C_TransferHandling(I2C1, Address, 1, I2C_Reload_Mode, I2C_Generate_Start_Write);

Так как возведён флаг ARLO, обмен данными по шине невозможен, микроконтроллер не будет выдавать даже старт на шину.

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

Отправка данных.

/*
Выполняет транзакцию записи Size байт в регистр Register по адресу Adress.
Параметры:
Adress - адрес ведомого устройства
Register - регистр, в который хотим передать данные
Data - указывает откуда брать данные для передачи
Size - сколько байт хотим передать (от 1 до 254)
Возвращает:
1 - если данные успешно переданы
0 - если произошла ошибка
*/
u8 I2C_Write_Transaction (u8 Adress, u8 Register, u8 *Data, u8 Size)
{
	u8 Count=0;	// Счётчик успешно переданных байт
	// Старт
	I2C_Start_Direction_Adress_Size (I2C_Transmitter, Adress, 1+Size);
	// Сейчас либо I2C запросит первый байт для отправки,
	// Либо взлетит NACK-флаг, говорящий о том, что микросхема не отвечает.
	// Если взлетит NACK-флаг, отправку прекращаем.
	while ((((I2C_BUS->ISR & I2C_ISR_TXIS)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY)) {};
	if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR=Register;	// Отправляю адрес регистра
	// Отправляем байты до тех пор, пока не взлетит TC-флаг.
	// Если взлетит NACK-флаг, отправку прекращаем.
	while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY))
	{
		if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR=*(Data+Count++);	// Отправляю данные
	}
	I2C_Stop();
	if (Count == Size) return 1; return 0;
}

После старта шины и отправки адреса микросхемы, есть 3 варианта исхода событий:

  • байт успешно отправлен, если в очереди есть ещё один байт - возводится TXIS, если все байты переданы - возводится TC (Transfer Complete)
  • микросхема не отвечает - возводится NACKF
  • прочие ошибки на шине - возводится ARLO или BERR (Bus Error), опускается BUSY (Bus Busy)

Следует обратить внимание на циклы while - они реализованы с учётом всех вышеописанных вариантов. Идём дальше.

Приём данных.

/*
Выполняет транзакцию чтения Size байт из регистра Register по адресу Adress.
Параметры:
Adress - адрес ведомого устройства
Register - регистр, из которого хотим принять данные
Data - указывает куда складывать принятые данные
Size - сколько байт хотим принять (от 1 до 255)
Возвращает:
1 - если данные успешно приняты
0 - если произошла ошибка
*/
u8 I2C_Read_Transaction (u8 Adress, u8 Register, u8 *Data, u8 Size)
{
	u8 Count=0;	// Счётчик успешно принятых байт
	// Старт
	I2C_Start_Direction_Adress_Size (I2C_Transmitter, Adress, 1);
	// Сейчас либо I2C запросит первый байт для отправки,
	// Либо взлетит NACK-флаг, говорящий о том, что микросхема не отвечает.
	// Если взлетит NACK-флаг, отправку прекращаем.
	while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY))
	{
		if (I2C_BUS->ISR & I2C_ISR_TXIS) I2C_BUS->TXDR = Register;	// Отправляю адрес регистра
	}
	// Повторный старт
	I2C_Start_Direction_Adress_Size (I2C_Receiver, Adress, Size);
	// Принимаем байты до тех пор, пока не взлетит TC-флаг.
	// Если взлетит NACK-флаг, приём прекращаем.
	while ((((I2C_BUS->ISR & I2C_ISR_TC)==0) && ((I2C_BUS->ISR & I2C_ISR_NACKF)==0)) && (I2C_BUS->ISR & I2C_ISR_BUSY))
	{
		if (I2C_BUS->ISR & I2C_ISR_RXNE) *(Data+Count++) = I2C_BUS->RXDR;	// Принимаю данные
	}
	I2C_Stop();
	if (Count == Size) return 1; return 0;
}

Здесь всё почти так же, как и при передаче. Подробно описывать не буду, идём дальше.

Остановка шины.

/*
Это служебная функция, использовать её не нужно.
Выдаёт стоп на шину.
Очищает флаги.
Проверяет наличие ошибок, очищает флаги ошибок.
*/
void I2C_Stop (void)
{
	I2C_BUS->CR2 |= I2C_CR2_STOP;				// Выдать стоп на шину
	while (I2C_BUS->ISR & I2C_ISR_BUSY) {};		// Ожидать выдачу стопа
	// Очищаю флаги - необходимо для дальнейшей работы шины
	I2C_BUS->ICR |= I2C_ICR_STOPCF;		// STOP флаг
	I2C_BUS->ICR |= I2C_ICR_NACKCF;		// NACK флаг
	// Если есть ошибки на шине - очищаю флаги
	if (I2C_BUS->ISR & (I2C_ISR_ARLO | I2C_ISR_BERR))
	{
		I2C_BUS->ICR |= I2C_ICR_ARLOCF;
		I2C_BUS->ICR |= I2C_ICR_BERRCF;
	}
}

Здесь важно контролировать остановку шины по опусканию флага BUSY, а не по возведению флага STOPF. Также очень важно проверить наличие ошибок, и сбросить флаги ошибок, если они есть.

Мой велосипед код написан с учётом всех найденных граблей, и работает стабильно при любых извращениях с шиной. Если вы заметили ошибку в коде, или просто важное уточнение - пишите в комментарии.

Прикрепленные файлы:

Теги:

Опубликована: 0 1
Я собрал 0 0
x

Оценить статью

  • Техническая грамотность
  • Актуальность материала
  • Изложение материала
  • Полезность устройства
  • Повторяемость устройства
  • Орфография
0

Средний балл статьи: 0 Проголосовало: 0 чел.

Комментарии (7) | Я собрал (0) | Подписаться

0
Oleg #
Здравствуйте! Есть вопрос по коду (I2C), если подключить 24С32 (размер 4 Кб), а у вас адрес 8 бит, как указать 16-битный адрес?
Ответить
0

[Автор]
Zlodey #
Здравствуйте.
Если адрес микросхемы 10-битный, нужно поправить инициализацию шины - установить 10-bit addressing mode (см. референс мануал), затем поправить функции под адреса 10 бит.
Если имеется ввиду 16-битные адреса регистров, то достаточно поправить функции приёма/передачи (разбить адрес на 2 байта)
Ответить
0
Владимир #
На контроллере STM32F030F4P6 пины для инициализации I2C - PA9 и PA10 вместо PB6 и PB7.
Ответить
0

[Автор]
Zlodey #
В этой статье описывается неблокирующая реализация, а не разбираются подробности инициализации. Писалось всё под STM32F030K6T6, но никто не мешает поправить инит под свой камень. Главное - аппаратный блок I2C у всей серии F030 одинаковый.
Отредактирован 12.06.2015 21:02
Ответить
0
Влад #
Спасибо за отличную статью, заработала Ваша либа в Кокосе.
Ответить
0
Александр #
Весьма досадно, что эта штука не работает и даже прекрасно блокируется и зависает.
Приношу свои извинения. Этот код работает. Это мои контроллеры не все работают
Ответить
0
JaRcom #
На stm32f030c8 поменял выводы на PB8,9.
Eeprom M24C16-WBN6 (16kbit).
Atollic TrueSTUDIO for ARM 5.4.0. Работает.
Ответить
Добавить комментарий
Имя:
E-mail:
не публикуется
Текст:
Защита от спама:
В чем измеряется напряжение?
Файлы:
 
Для выбора нескольких файлов использйте CTRL

AVR-программатор USB ASP
AVR-программатор USB ASP
DC-DC регулируемый преобразователь 1.5-37В 2А с индикатором Набор для сборки - LED лампа
вверх