В статье "Часть 12. STM32 и C++. I2C в Cortex-M4" я описал, как работать с I2C на ядрах Cortex-M4. Продолжая работать с I2C на других чипах, я столкнулся с тем что данный способ общения с I2C корректен не для всех ядер. Описанный способ точно подходит для STM32F405xx и STM32F407xx, вполне возможно что подходит также и для STM32F415, STM32F417 и STM32F43x. Проверить это утверждение я не могу, так как нет необходимых МК. Что точно могу сказать, для серии STM32F411xx этот метод точно не походит, но к этому МК вернёмся чуть позже.
Сейчас рассмотрим STM32F030x4/x6/x8/xC и STM32F070x6/xB (про серию STM32F0x1/STM32F0x2/STM32F0x8 пока сказать ничего не могу, позже проверю и в комментариях отпишу). У него, да и у многих других МК, в регистре CR2 есть два интересных поля: NBYTES[7:0]
- в который пишется количество байт/слов которое мы хотим передать и SADD[9:0]
- в который пишется адрес устройства, к которому мы хотим обратиться. А так как не нужны пляски с бубном, как у F407, здесь код получается попроще. Я не буду приводить длиннющие куски кода, просто опишу алгоритм, а как это оформлено можно посмотреть в библиотеке.
Алгоритм чтения.
- Ждём флаг BUSY
- В регистре CR2:
- Сбрасываем:
- SADD
- NBYTES
- RELOAD
- AUTOEND
- RD_WRN
- START
- STOP
- Устанавливаем:
- SADD
- NBYTES
- AUTOEND или RELOAD
- READ
- START
- Сбрасываем:
- Ждём флаг RXNE
- В цикле из регистра RXDR считываем данные
- Ждём флаг STOP
- В регистре статуса ICR сбрасываем флаг STOPCF
- В регистре CR2 сбрасываем:
- SADD
- HEAD10R
- NBYTES
- RELOAD
- RD_WRN
- Выходим
Вроде всё просто, но есть нюансы.
Шаг 2 - я написал флаги и регистры по порядку, но не всё так просто. Есть МК, у которых эти команды можно выполнить пошагово, на F030CCx я столкнулся с тем, что необходимо считать регистр целиком, сбросить необходимые биты, установить необходимые биты и записать это всё обратно. Как только запишем получившуюся команду, I2C выплюнет на линию адрес с запросом на чтение и будет ждать ответа. Ещё один нюанс, на обычных I2C устройствах этот шаг не критичен, а при работе с микросхемами памяти - критичен. Если при работе с памятью мы не выполним это условие, то зависнем на третьем шаге.
Шаг 3 - интереса не представляет.
Шаг 4 - в шаге 2.2.3 указано два флага, а не один. Эти флаги влияют на количество принимаемых байт. Регистр NBYTES
имеет длину 8 бит. Соответственно, мы можем передать только 255 байт/слов, а мы хотим принять 100500. Для приёма всей информации нам нужно разбить её на блоки по 255 байт. Мы записываем в NBYTES
число 255, устанавливаем указатель на начало буфера и во втором шаге поднимаем флаг RELOAD
. Интерфейс сгенерирует все необходимые сигналы и примет первую порцию байт/слов. И так нужно делать, пока в буфере не отстанется меньше 255 байт/слов. В этом случае необходимо установить флаг AUTOEND
и интерфейс примет оставшуюся информацию и сгенерирует STOP.
Дальнейшие шаги интереса не представляют.
Здесь есть небольшой момент. Мы стираем даже те флаги, которые в процессе работы этого алгоритма могут и не подняться, но нужно учитывать, что мы в эту функцию можем прийти из другой, где эти флаги могут оказаться задействованными, поэтому лучше подстраховаться.
Алгоритм записи
Практически ничем не отличается от алгоритма чтения за исключением:
- Шаг 2.2.4: нужно поднять флаг WRITE
- Шаг 2.2.5: исключаем
- Шаг 3: ждём флаг TXIS
- Шаг 4: кидаем данные в регистр TXDR
Все остальное точно также.
Алгоритм чтения EEPROM, например AT24C02.
- Ждём флаг BUSY
- В регистре CR2:
- Сбрасываем:
- SADD
- NBYTES
- RELOAD
- AUTOEND
- RD_WRN
- START
- STOP
- Устанавливаем:
- SADD
- NBYTES
- WRITE
- START
- Сбрасываем:
- Ждём флаг TXIS
- Если адрес
- 8 бит
- Кидаем его в TXDR
- 16 бит
- Кидаем младший байт в TXDR
- Ждём флаг TXIS
- Кидаем старший байт в TXDR
- 8 бит
- Ждём флаг TC
- Далее как в алгоритме чтения данных
Алгоритм записи EEPROM.
- Ждём флаг BUSY
- В регистре CR2:
- Сбрасываем:
- SADD
- NBYTES
- RELOAD
- AUTOEND
- RD_WRN
- START
- STOP
- Устанавливаем:
- SADD
- NBYTES
- WRITE
- RELOAD
- START
- Сбрасываем:
- Ждём флаг TXIS
- Если адрес
- 8 бит
- Кидаем его в TXDR
- 16 бит
- Кидаем младший байт в TXDR
- Ждём флаг TXIS
- Кидаем старший байт в TXDR
- 8 бит
- Ждём флаг TCR
- Далее как в записи обычных данных за некоторыми исключениями.
- Шаг 2.2.4: нужно поднять флаг WRITE
- Шаг 2.2.5: исключаем
- Шаг 3: ждём флаг TXIS
- Шаг 4: кидаем данные в регистр TXDR
Под конец пару слов о том хитром регистре CR2
, и почему так с ним нужно управляться. На картинке показаны сигналы, в случае последовательной записи в этот регистр:
А на этой - при правильной записи в регистр:
Как видно, после того, как адрес ячейки EEPROM выкинули на шину, идёт команда "Рестарт", в случае же последовательной инициализации регистра, на этом месте "Стоп" и "Старт", что сносит крышу у EEPROM.
На этом пока всё.
Примечание: сделайте или приобретите логический анализатор, без него, как без рук. Такие нюансы я бы долго искал.
Огромное спасибо за статью, очень помогла. Не является ли ошибкой сброс флага в шаге 3 записи TXIS, так бы мы делали если работали через прерывание, поидее надо вот этот TXE: Transmit data register empty (transmitters), пока не проверил.
Пробуйте. Потом отпишитесь.
Про шаги не помню уже. Пытаюсь охватить как можно больше тем. Может где то и ошибся. Но у меня работало.
Но вместе мы решим эти проблемы, если возникнут.
Я ошибся, у вас все верно. Провозился долго с шиной, перебрал все регистры, а все потому что на самом датчике не правильно был выставлен режим с помощью порта IO.
Хоть здесь всё хорошо 🙂
Извините. Я уже не помню. Когда это пишешь, кажется логичным. А сейчас и сам вспомнить не могу. Библиотеку написал и пользуюсь, а что я думал когда писал, покрыто тайной.
Просмотрел статью. Ещё бы сам код посмотреть.
Выполняются шаги описанные в Алгоритме чтения EEPROM, а потом идут шаги Чтения.
Там разница чтения просто с I2C и чтения с EEPROM в основном только в том, что нужно сделать некоторые шаги, что бы EEPROM подготовила данные для чтения.
На разных МК это делается немного по разному.
Разобрался. Вообще делал на камне stm32g431, после передачи адреса ячейки не нужно ждать BUSY, а сразу читать или писать. Иначе виснет на BUSY. А вообще спасибо за внятное объяснение в статье!
Там проблема в том, что даже в пределах одной серии, но на разных камнях работать с I2C нужно по разному. Я обычно на HAL генерю проект и разбираюсь в каком порядке что делать. STM32F4xx самая геморойная серия по работе с I2C