В одном из своих проектов использую микроконтроллеры 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. Также очень важно проверить наличие ошибок, и сбросить флаги ошибок, если они есть.
Мой велосипед код написан с учётом всех найденных граблей, и работает стабильно при любых извращениях с шиной. Если вы заметили ошибку в коде, или просто важное уточнение - пишите в комментарии.
Прикрепленные файлы:
- STM32F030_I2C_Zlodey_v2.zip (3 Кб)
Комментарии (15) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
[Автор]
Если адрес микросхемы 10-битный, нужно поправить инициализацию шины - установить 10-bit addressing mode (см. референс мануал), затем поправить функции под адреса 10 бит.
Если имеется ввиду 16-битные адреса регистров, то достаточно поправить функции приёма/передачи (разбить адрес на 2 байта)
[Автор]
Приношу свои извинения. Этот код работает. Это мои контроллеры не все работают
Eeprom M24C16-WBN6 (16kbit).
Atollic TrueSTUDIO for ARM 5.4.0. Работает.
Я так понял под все события у STM32 есть свои прерывания
[Автор]
Если сравнить функции отправки байта и приемки, то в самом начале наблюдаются различия в первом цикле while. Опрос флага TC и TXIS. Хотя, передача идет просто адреса.
В чем может быть проблема?
.....
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; // Отправляю адрес регистра
}
.....