Комментарии 17
Если уж Вы называете свой проект библиотекой, то ожидание изменения состояний I2C без контроля таймаута, наподобие
Так же не мешало бы перед инициализацией сбросить I2C путем установки и последующего сброса бита SWRST регистра CR1. В библиотеке HAL это делается в функции HAL_I2C_Init(), а вот в варианте LL — нет, из за чего в свое время никак не мог понять, почему иногда I2C «переклинивает».
Еще у STM32F103 в I2C есть очень неприятный косяк, описанный в документе ES096 (errata) в пункте 2.14.7 (page 26). Ни разу не попадал на такую ситуацию, но в библиотеке внимание на это следовало бы обратить при инициализации GPIO.
while (!(I2C1->SR1 & I2C_SR1_SB)){}
while (!(I2C1->SR1 & I2C_SR1_ADDR)){}
и т.д.
не самое хорошее решение.Так же не мешало бы перед инициализацией сбросить I2C путем установки и последующего сброса бита SWRST регистра CR1. В библиотеке HAL это делается в функции HAL_I2C_Init(), а вот в варианте LL — нет, из за чего в свое время никак не мог понять, почему иногда I2C «переклинивает».
Еще у STM32F103 в I2C есть очень неприятный косяк, описанный в документе ES096 (errata) в пункте 2.14.7 (page 26). Ни разу не попадал на такую ситуацию, но в библиотеке внимание на это следовало бы обратить при инициализации GPIO.
А в чем глубинный смысл помещать внутренности модулей в заголовочный файл, чтобы это все добро торчало наружу? Я про кучу дефайнов в заголовках которые к внешнему интерфейсу модулей не имеют никакого отношения и спокойно могли бы жить внутри С файлов. Оно же жутко мешает понять как интерфейс для работы с модулем выглядит.
Второй момент. У вас код гвоздями прибит к I2C1. А что делать если этот интерфейс занят, но есть свободные другие I2C? По-хорошему, библиотека, да и любой драйвер устройств должен принимать интерфейс, как параметр в Init() функции. Код внутри должен быть максимально абстрагирован от конктретного инстанса интерфейса или шины где датчик живет.
Второй момент. У вас код гвоздями прибит к I2C1. А что делать если этот интерфейс занят, но есть свободные другие I2C? По-хорошему, библиотека, да и любой драйвер устройств должен принимать интерфейс, как параметр в Init() функции. Код внутри должен быть максимально абстрагирован от конктретного инстанса интерфейса или шины где датчик живет.
Меня тоже в интерфейсе что-то смущало, но не мог понять что. Теперь понял. Подскажите, пожалуйста как организовать в Init() выбор интерфейса. Я хотел сделать это на препроцессоре #ifndef ( но в процессе про это забыл ) это не правильно?
Препроцессор для этих целей практически не решает проблемы. А проблем тут как минимум две, которые дизайн драйвера должен решать:
1. Множество шин у контроллера плюс одинаковые датчики на разных шинах.
2. На одной шине может жить несколько одинаковых устройств.
Решение тут по сути простое. Init() должен принять в параметрах структуру с информацией о шине, адресе и остальных параметрах уникальных к экземпляру устройства. Драйвер инициализирует все что надо и возвращает «хэндл». Далее, при всех обращениях к остальным ф-циям драйвера, клиенты должы предоставлять этот хэндл, чтобы драйвер мог внутри понять с каким устройством работать. Т.о. вы получите один код драйвера, который обслуживает любое кол-во шин и устройств на них.
Далее, код драйвера устройства, не должен ничего знать о реализации шины. О шине должен знать код драйвера шины, а драйвер устройства использовать его API. У вас же получается все в кучу, вроде код драйвера сенсора, а внутри прямая работа с регистрами контроллера I2C.
Насчет реализации, тут зависит от окружения. Если stm32 HAL используется, то он скорее всего уже дает драйверы шин и в качестве ID шины можно там взять их ID. Если без HAL, то выносите логику I2C в отдельный драйвер и давайте ему какой-то ID как параметр.
1. Множество шин у контроллера плюс одинаковые датчики на разных шинах.
2. На одной шине может жить несколько одинаковых устройств.
Решение тут по сути простое. Init() должен принять в параметрах структуру с информацией о шине, адресе и остальных параметрах уникальных к экземпляру устройства. Драйвер инициализирует все что надо и возвращает «хэндл». Далее, при всех обращениях к остальным ф-циям драйвера, клиенты должы предоставлять этот хэндл, чтобы драйвер мог внутри понять с каким устройством работать. Т.о. вы получите один код драйвера, который обслуживает любое кол-во шин и устройств на них.
Далее, код драйвера устройства, не должен ничего знать о реализации шины. О шине должен знать код драйвера шины, а драйвер устройства использовать его API. У вас же получается все в кучу, вроде код драйвера сенсора, а внутри прямая работа с регистрами контроллера I2C.
Насчет реализации, тут зависит от окружения. Если stm32 HAL используется, то он скорее всего уже дает драйверы шин и в качестве ID шины можно там взять их ID. Если без HAL, то выносите логику I2C в отдельный драйвер и давайте ему какой-то ID как параметр.
Спасибо большое за дельное замечание. Обязательно доработаю как будет время. Был бы признателен, если бы Вы подсказали как таймауты правильно организовать. Я только начинаю изучать stm32 и микроконтроллеры в целом. Косяк, описанный в errata мне не встречался, зато встречался другой. Если сначала сконфигурировать регистр на альтернативную функцию, а потом настроить на выход, то I2C работать не будет. Но это скорее мой косяк
да как же не будет? По большому счету перед каждым I2C пакетом шину надо проверять на «подтянутость к плюсу» ( во первых, что не занята, во -вторых, что аппаратно с ней все в порядке) — вот как раз и надо лапы переинициализировать сначала как аналоговый вход ну а том под i2c. ну и так каждый раз как пишешь/читаешь.
Для проверки занятости шины есть флаг BUSY в регистре SR2.
До того как вы отправите данные в шину BUSY никак встать не сможет. Поэтому врят ли он после инициализации еще до отправки данных как то вас оповестит о том, что на линии не все в порядке. К тому же это все таки флаг занятости а не ошибки.
Документация (RM0008 part 26.6.7) о флаге BUSY регистра SR2 говорит несколько другое:
Но должен признать, что сам я многомастерные конфигурации шины I2C не использовал, поэтому на практике это не проверял.
Bit 1 BUSY: Bus busy
0: No communication on the bus
1: Communication ongoing on the bus
– Set by hardware on detection of SDA or SCL low
– cleared by hardware on detection of a Stop condition.
It indicates a communication in progress on the bus. This information is still updated when
the interface is disabled (PE=0).
Но должен признать, что сам я многомастерные конфигурации шины I2C не использовал, поэтому на практике это не проверял.
Что другое? Я говорю о том, что до начала коммуникации еще после инициализации не плохо проверять шину вот и все. Пока вы не отправите данные в шину бизя выставится не сможет. Т е вы все равно даже его проверяя не можете на начальном этапе выявить проблемы на шине. Но я лично так просто перестраховываюсь. Много процессорного времени функция проверки не занимает. Поэтому вставляю ее всегда перед началом транзакции.
Пока вы не отправите данные в шину бизя выставится не сможет.Да почему? Более того, в документации сказано, что флаг занятости шины работает даже при неактивном интерфейсе:
This information is still updated when the interface is disabled (PE=0).Если же Вы хотите сказать, что в данном месте документация не соответствует действительности, то так и напишите прямым текстом — эта информация может многим быть весьма полезна.
Я абсолютно с Вами согласен, что лишняя проверка состояний ног не займет много ресурсов, но если она действительно лишняя, то зачем? Тем более, что свободная шина на момент начала коммуникации вовсе не гарантирует от конфликта с другим мастером уже в процессе работы, о чем, насколько я Вас понял, Вы хотели сказать фразой:
Т е вы все равно даже его проверяя не можете на начальном этапе выявить проблемы на шине.Ну так для выявления этих ситуаций служат флаги ошибок, в частности ARLO в регистре SR1, которые тоже надо контролировать.
И в догонку — в библиотеке HAL в начале функций работы с I2C для определения доступности шины присутствует проверка наподобие:
/* Wait until BUSY flag is reset */
if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET,
I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK)
{
return HAL_BUSY;
}
Конкретно этот фрагмент я взял из функции HAL_I2C_Master_Transmit(), но в других тоже присутствует нечто подобное. Я вовсе не считаю HAL безспорным образцом для подражания, но все же.BUSY — это флаг занятости линии, а не периферии I2C. Он выставляется по состоянию линии, а что вызвало занятость линии — отсутствие подтягивающего резистора, внешняя периферия или собственные действия контроллера I2C — не важно.
Нужна ли предложенная проверка с помощью аналоговых входов, если реализовать алгоритм из errata (там дергаем пинами и проверяем состояние в IDR)? Или состояние IDR не отображает реальную ситуацию на пине?
Если сначала сконфигурировать регистр на альтернативную функцию, а потом настроить на выход, то I2C работать не будет.
Конечно не будет, ведь Вы отключаете выводы от контроллера I2C и передаете управление ими контроллеру GPIO :)
Был бы признателен, если бы Вы подсказали как таймауты правильно организовать.Ну хотя бы в исходниках того же HAL посмотрите, как реализованы функции работы с I2C в простейшем варианте (без прерываний и DMA): HAL_I2C_Master_Transmit(), HAL_I2C_Master_Receive() и т.д. Я не призываю Вас обязательно использовать HAL, но заглянуть в его реализацию иногда полезно.
Правда, насколько я понял, у Вас системный таймер используется не для генерации прерываний для обновления программного счетчика системного времени, как это сделано и широко используется в HAL, а для организации задержек. Вообще то это не очень хорошо, потому что некоторый счетчик времени иметь полезно для многих применений, но если хотите именно так, то ничего Вам не мешает так же, как в Ваших функциях задержки установить системный таймер перед началом ожидания флага I2C, а внутри цикла ожидания проверять как ожидаемый флаг I2C, так и флаг системного таймера. Другой вариант — использовать для отсчета таймаута любой свободный таймер TIM1...TIM4.
Преобразование адреса датчика из А5 в В4 описано в спецификации интерфейса. См. на сайте www.nxp.com
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
STM32 и бесконтактный датчик температуры MLX90614. Подключение по I2C