Показаны сообщения с ярлыком STM32. Показать все сообщения
Показаны сообщения с ярлыком STM32. Показать все сообщения

пятница, 15 марта 2024 г.

Системный таймер STM32

 Микроконтроллеры STM32 содержат некоторое количество таймеров. Таймеры делятся на типы и отличаются друг от друга. Существуют, ранее рассмотренные, таймеры общего назначения (напр. TIM2 и TIM3), продвинутые таймеры (TIM1 почти такие же как таймеры общего назначения но с большим количеством дополнительных функций), сторожевые таймеры и системные таймеры (обычно системный таймер только один). Системный таймер самый простой и может использоваться когда не нужен какой либо сложный функционал. Данный тип таймера имеет некоторое небольшое количество регистров. Например регистр счетчика. В этом регистре находиться число которое показывает до скольки досчитал данный таймер. Считать он может только вниз т.е. только декрементировать (уменьшать на единицу) число в данном регистре. Этот регистр 23х разрядный:


После того как число в этом регистре достигло нуля, происходит запись числа в этот регистр из регистра перезагрузки:

Т.е. можно сказать что в этом регистре находиться число того сколько раз считает таймер прежде чем произойдет его переполнение. После того как произошло переполнение, данного таймера, может быть вызвано прерывание. Чтобы разрешить прерывание нужно установить единицу в бит
1 регистра настройки таймера:

У данного таймера всего один регистр настройки в котором для настройки есть всего три бита. Также есть один флаг. Бит 1 (как было показано выше) отвечает за разблокировку прерывания по переполнению (других прерываний у данного таймера нет). Этот бит д.б. установлен в единицу чтобы прерывание срабатывало. Также есть бит 0 для включения таймера:
И бит 2 для выбора внутреннего источника тактирования:

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



Адрес биткоин кошелька для поддержки канала - bc1qlhrmmkh77x2lzhqe4lt9qwkglswj64tsqt2l5g

суббота, 4 ноября 2023 г.

Правильная настройка таймера на STM32 CMSIS режим импульса

 Таймеры микроконтроллеров STM32 имеют функцию одиночного импульса. Эта функция похожа на обычное использование таймера с циклической выдачей импульсов но только + к этому добавляется автоматическая аппаратная остановка сразу после выдачи первого импульса. Настраивается данная функция очень просто. Для этого просто устанавливается один третий бит регистра CR1 в единицу. Однако при использовании данного режима и обычной настройке (какая была описана в статье -> https://electe.blogspot.com/2021/12/stm32f103c8t6-cmsis_24.html) будет заметна такая проблема как срабатывание прерывания таймера сразу после старта микроконтроллера. Это может быть большой проблемой т.к. некоторые действия будут выполняться в незапланированный промежуток времени. Чтобы этого избежать нужно выполнить определенную последовательность действий при настройке таймера. Рассмотрим код:

Константой DELAY_MS  задается время задержки в миллисекундах. В константу DEL записывается значение для записи в регистр для создания задержки записанной в константе DELAY_MS . Следующий макрос с конструкцией if проверяет константу на не выход за допустимый предел значения. Если предел превышен то компилятор выдаст ошибку и компиляция не произойдет. В этом случае нужно уменьшить задержку. В основной функции производиться настройка пинов. Пин PC13 (со встроенным светодиодом на плате "blue pill") настраивается на выход в режиме push-pull. Пин PB8 настраивается на вход с подтяжкой к плюсу для подключения кнопки между этим пином и землей для запуска таймера по нажатию данной кнопки. При нажатии на кнопку запускается таймер, через некоторое время (заданное константой DELAY_MS ) срабатывает прерывание и светодиод меняет свое состояние вслед за изменением своего состояния пином PC13 микроконтроллера. Правильная последовательность настройки таймера выглядит так:

 1) - записать, что надо, прерывание еще не разрешать,

 2) - установить искусственно флаг UG в регистре EGR - это обновит PSC|ARR новыми значениями.

3) - сбросить установившиеся флаги прерываний

4) - только теперь разрешить прерывания и запустить таймер.

Посмотреть работу платы blue-pill, в таком режиме, можно на видео:



 источник -> https://electronix.ru/forum/index.php?app=forums&module=forums&controller=topic&id=120406


воскресенье, 4 декабря 2022 г.

STM32 CMSIS внешние прерывания и счетчик дребезга контактов

Внешние прерывания могут быть полезны например когда нужно зафиксировать кратковременные импульсы на пине микроконтроллера, по тому что обычный опрос пина не обязательно будет происходить в нужный момент времени. Для демонстрации полезности внешних прерываний, на микроконтроллере STM32, можно изготовить например счетчик дребезга контактов. Ранее, в данный блог, уже выкладывалась статья о внешних прерываниях для ATtiny2313 и настройке их на ассемблере. Такой счетчик показывает что дребезг есть при нажати на кнопку и даже при её отпускании, хотя при отпускании он заметно слабее и даже иногда может отсутствовать, хотя это зависит от конструкции кнопки. Счетчик дребезга контактов также может использоваться как генератор случайных чисел т.к. количество дребезга практически невозможно предсказать. М.б. на него можно, в некоторой степени повлиять например магнитом, но например в тандеме с псевдослучаным программным генератором, счетчик дребезга контактов может быть одновременно и устойчивым к внешним физическим воздействиям воздействиям и генерировать реально случайные числа.  Схема может быть например такой:

Рисунок 1 - Счетчик дребезга контактов кнопки

В микроконтроллере STM32F103 на плате "blue pill" может одновременно применяться не более 16ти датчиков внешних прерываний. Каждое из этих 16ти прерываний может быть переключено между портами к пинам соответствующим номеру прерывания т.е. например первое прерывание может быть на пине PA1 или PB1 (или PC1 если бы он был на данном микроконтроллере (на других микроконтроллерах STM32, с большим количеством пинов, это может быть)). По умолчанию все внешние прерывания работают на порту А поэтому, чтобы меньше возиться с настройками, можно использовать для внешних прерываний только пины порта A это сделает настройку внешних прерываний не сложной. Как обычно, нужно включить тактирование порта А в регистре APB2ENR, сделать это можно командой:

Выбранный пин (пин А2) надо настроить на вход с альтернативной функцией и подтяжкой к +питания, сделать это можно командами:

Установить прерывания по восходящему фронту для пина А2 можно установкой в 1 бита 2 регистра XTI_RTSR:

Для этого в коде можно написать команду:

Для того чтобы аналогично включить прерывания по спаду, можно использовать регистр XTI_FTSR:


Для разрешения внешних прерываний пина 2, бит 2 регистра EXTI_IMR

 устанавливается в 1:

Теперь чтобы прерывание сработало осталось разрешить его в NVIC командой:

NVIC_EnableIRQ (EXTI2_IRQn);

После этого можно написать функцию-обработчик прерывания:

Записью единицы в бит 2 регистра EXTI_PR сбрасывается флаг внешнего прерывания 2.

counter++  -инкремент счетчика дребезга контактов.

Помимо описанных выше команд, также понадобиться работа с UARTом (см. передача по UART - https://electe.blogspot.com/2022/01/uart-stm32f103c8-cmsis.html и прием по UART - https://electe.blogspot.com/2022/01/uart-stm32f103c8-cmsis_7.html) для вывода получения результатов и некоторые другие команды. Полный текст программы можно скопировать из поля:

Результат работы счетчика дребезга контактов можно посмотреть на видео:


КАРТА БЛОГА (содержание)

суббота, 3 сентября 2022 г.

STM32 CMSIS ADC использование нескольких каналов АЦП

 Микроконтроллеры STM32 (например stm32f103c8), как правило, имеют некоторое количество каналов аналого-цифрового преобразователя (АЦП(ADC)) что может быть использовано например для снятия показаний например с некоторого количества потенциометров или аналоговых датчиков. О том как снимать показания с одного потенциометра уже имеется статья в данном блоге. Если взять код от туда и дополнить его 3мя строками инициализации АЦП то можно получить устройство которое снимает показания с двух потенциометров.

 Далее аналогично можно расширить устройство на больше количество каналов. Есть некоторое количество способов использования нескольких каналов но наиболее простой в настройке - это использование прерывистого режима. Микроконтроллер stm32f103c8 (установленный в популярной плате "plue pill") может автоматически поочередно переключать, своим мультиплексором, свой АЦП между разными каналами но если это регулярные каналы то регистр данных у них один и данные в нем будут также автоматически перезаписываться после автоматического переключения, поэтому чтобы "спокойно" забрать результат из регистра данных нужно чтобы после преобразования наступила пауза. Если включить прерывистый режим, установкой бита 11 регистра CR1,

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

Далее нужно регистрами SQR1,SQR2,SQR3 настроить количество и номера каналов на которые будет переключаться АЦП. 


Для настройки количества каналов используются биты 20...23 регистра SQR1, 0b0000 - означает 1 канал, 0b0001 - 2 канала и т.д.



Для того чтобы вторым каналом был второй, по счету, канал нудно в биты 5....9 регистра SQR1 записать единицу т.е. записать единицу в бит 5 (т.к. остальные, по умолчанию, нули).



Если нужно использовать 3 канала то аналогично настраиваются биты 10...15 данного канала (не забыв при этом про настройку количества). Если регистр SQR3 закончился то дальше идет переход на SQR2 а после на SQR1 до битов настройки количества. После добавления данных 3х строк программа уже будет выдавать в последовательный порт числа поочередно с одного и с другого каналов. Для большего удобства и вывода показаний в два столбца можно добавить специальную конструкцию.



Полный код можно скопировать из текстового поля:


Посмотреть результат работы программы можно на видео:


КАРТА БЛОГА (содержание)









среда, 31 августа 2022 г.

АЦП (ADC) на stm32f103, Си и cmsis

 Аналого цифровой преобразователь (далее АЦП) является важным периферийным устройством в микроконтроллере т.к. аналоговые сигналы, наряду с цифровыми, часто используются. Примеров ситуаций при которых используются аналоговые сигналы можно привести много. Это например может быть какой нибудь датчик температуры, напряжения, токовый шунт, джойстик, резистивная кнопка и т.д. АЦП микроконтроллеров плохи тем что напряжения подаваемые на пин микроконтроллера часто небольшие (особенно для STM32 это 3.3В) а также тем что они могут уступать по быстродействию и/или разрядности специализированным микросхемам. Первый недостаток устраняется использованием резистивного делителя напряжения а второй тем что это не всегда имеет большое значение а также большим количеством настроек аппаратного АЦП данного микроконтроллера для управления точностью, скоростью и количеством каналов. Начать изучение АЦП микроконтроллеров STM32 желательно с чего нибудь простого. Например можно подключить к нему какой нибудь простой потенциометр и попытаться определить напряжение на среднем его выводе а результат выводить например по uart на пк и смотреть его в мониторе последовательного порта среды разработки Arduino IDE. Для начала нужно выбрать пин микроконтроллера к которому можно подключить средний вывод потенциометра. Для этого можно глянуть таблицу в даташите:

Если выбрать пин PA0 то дальше настройка потенциометра будет немного проще чем если выбрать другой пин. Т.к, при таком варианте, не нужно будет настраивать регистры SQR. Схема может быть например такой:
Сопротивление резистора может быть другим но главное чтобы оно не было слишком низким чтобы не сделать слишком большую нагрузку на цепи питания. Конденсатор нужен для уменьшения погрешности измерения путем ослабления помех. Как обычно для периферийных устройств для АЦП нужно включить тактирование, но только теперь помимо этого нужно установить делитель частоты т.к. АЦП не может работать на частоте более 14МГц. Чтобы например сделать деление на 8 нужно записать единицы в 14й и 15й биты регистра конфигурации тактирования.
Далее, как обычно (см. предыдущие уроки), настраивается тактирование периферийных устройств (GPIO, АЦП и UART) а также режимы работы GPIO (в данном случае пин PA0 настраивается на альтернативный вход) и UART1 настраивается на передачу.
Далее если бы использовался бы пин не PA0 а другой то тогда бы пришлось настраивать регистры SQR. В данном микроконтроллере этих регистров 3 штуки ADC_SQR1, ADC_SQR2 и ADC_SQR3. В биты 20-23 регистра ADC_SQR1 записывается количество измерений за один проход. АЦП в данном микроконтроллере не один и каналов АЦП больше одного а также АЦП дает погрешность для уменьшения которой может понадобиться усреднение значения с некоторого количества измерений. В остальные биты данных регистров, за исключением битов 24-31 регистра ADC_SQR1, записываются номера каналов на которых будут производиться измерения. Первый из них записывается в биты 0...4 регистра ADC_SQR3 далее записываются остальные каналы. Если регистр ADC_SQR3 заканчивается то далее аналогично продолжается запись в регистр ADC_SQR2. Если и ADC_SQR2 тоже заканчивается то далее заполняется регистр ADC_SQR1 до бита 20 т.к. с этого бита начинается запись количества каналов. Т.к. по умолчанию в этих регистрах нули то это означает что измерение будет проводиться один раз на нулевом канале поэтому в данном случае данные регистры можно не трогать.
Есть ещё регистр настройки времени выборки ADC_SMPR2
Можно пробовать изменять значение в этом регистре чтобы повлиять на точность измерений. Работа АЦП может быть запущена разными способами. Для установки запуска программным способом нужно в регистр в биты 17...19 регистра ADC_CR2 записать единицы. 
Также нужно установить бит 20 данного регистра в единицу для разрешения внешнего запуска.
Для включения АЦП, в единицу устанавливается бит 0 данного регистра.
Для запуска калибровки АЦП нужно установить бит 2 данного регистра. 
По окончании калибровки данный бит сам сброситься. Чтобы преждевременно не начать работу с АЦП можно организовать проверку данного бита в цикле.
Для запуска АЦП, устанавливается бит 22 данного регистра



Об окончании аналого цифрового преобразования свидетельствует бит 1 регистра ADC_SR


Теперь можно забрать результат преобразования из регистра данных ADC1_DR
Чтобы увидеть результат можно его преобразовать в символы и отослать по UART на компьютер. Полный код программы можно скачать из текстового поля:

Данный урок в видеоформате можно посмотреть на видео:
КАРТА БЛОГА (содержание)

воскресенье, 3 июля 2022 г.

Связь STM32 c EEPROM по I2C на Си и CMSIS

 Шина I2C является синхронной, последовательной и полудуплексной. Полудуплексность означает что в один момент времени по шине может происходить либо прием либо передача для какого либо одного устройства. Данная шина также ассиметричная что означает наличие ведомого (мастера который выдает импульсы синхронизации и управляет коммуникацией) и ведущего (слейва) на ней. Но в I2C, в отличии от SPI, мастеров может быть больше одного. Однако обычно мастер только один. В данной шине имеется два сигнальных провода, один нужен для передачи данных и называется SDA а другой для передачи синхроимпульсов и называется SCL. Синхроимпульсы поступают от мастера к слейвам. А данные могут поступать в обе стороны т.е. от мастера к слейву или от слейва к мастеру. Т.к. провод данных только один то пин микроконтроллера к которому подключен провод SDA должен переключаться между режимом входа и выхода в процессе работы. Если пин SDA работает как выход то этот выход должен быть с открытым стоком т.к. возможна ситуация при которой у двух и более разных устройств будут выходы на данном проводе одновременно и с разными уровнями напряжений. Тоже самое верно и для пина SCL т.к. мастеров м.б. больше одного. Данные пины, соответственно, подтягиваются к плюсу питания резисторами сопротивления которых не следует делать слишком большими т.к. чем больше сопротивление резистора тем длиннее фронт сигнала т.к. при меньшем токе медленнее заряжается паразитная емкость шины. Данная емкость также ограничивает максимальное количество устройств на шине. Однако, тем не менее, оно всё таки может быть достаточно велико и скорее всего больше чем устройств на SPI т.к. колличество слейвов на SPI ограничено количеством свободных пинов GPIO мастера. И хотя этот недостаток можно преодолеть использованием например десятичных счетчиков или сдвиговых регистров. I2C все таки более предпочтительная шина для связи большого количества устройств из за меньшего количества проводов, дополнительных деталей и большей безопасности выходов с открытым стоком по сравнению с полумостовыми выходами. Изучение данной шины лучше начать с организации связи микроконтроллера с какой нибудь не сложной спецаиализированной микросхемой, например с микросхемой EEPROM памяти. Микроконтроллеры STM32 обычно не имеют встроенной EEPROM памяти как например микроконтроллеры AVR или PIC. Но в этом нет большой необходимости т.к. EEPROM может быть заменена FLASH памятью. Внешняя EEPROM память обладает некоторыми преимуществами перед внутренней FLASH памятью. Например обычно EEPROM память обладает большим количеством циклов записи, в этом можно убедиться посмотрев даташит например на микроконтроллер ATtiny2313 где указано:

 2K Bytes of In-System Self Programmable Flash Endurance 10,000 Write/Erase Cycles – 

128 Bytes In-System Programmable EEPROM Endurance: 100,000 Write/Erase Cycles

Видна разница в 10 раз. Недостатком EEPROM памяти является то что срок хранения в ней информации ограничен. И хотя у современных микросхем он высок, он всё же конечен. Вынеся устройство постоянного хранения информации за пределы микроконтроллера, появляется возможность ремонта устройства с истекшим сроком хранения информации или количеством циклов записи, путем замены микросхемы памяти что дешевле и проще замены микроконтроллера. Ещё т.о. не занимая FLASH память, которая также используется для хранения инструкций выполняемых микроконтроллером, можно избежать некоторых возможных багов связанных с заимствованием FLASH памяти для кода программы и естественно так для кода программы места будет больше. Поэтому связь микроконтроллера по I2C с микросхемой EEPROM памяти имеет (помимо образовательной) также и некоторую практическую ценность. В микроконтроллере stm32f103c8 есть 2 интерфейса I2C. Для постоянного хранения информации можно использовать например микросхему EEPROM памяти AT24C64 в которой можно хранить до 64 кБ данных. Данная микросхема может хранить данные до 100 лет а количество циклов записи у неё 1 миллион раз. Она имеет пины I2C, 3 пина установки адреса (которые внутренне подтянуты к земле), пин зашиты записи (WP который внутренне подтянут к уровню отключающему данную опцию). Соединить данную микросхему с отладочной платой с микроконтроллером stm32f103c8 можно например так так на схеме:

Рисунок 1 - Схема соединения платы "blue pill" с микросхемой AT24C64 по I2C

Резисторы R3 и R4 делают подтяжку к + питания открытых стоков, чтобы помимо логических нулей могли быть и логические единицы. Они могут быть сопротивлением например 10к но для укорачивания фронтов выбраны сопротивления поменьше. Резисторы R1 и R2 нужны для ограничения тока на случай неправильного конфигурирования пинов микроконтроллера. При подключении данной микросхемы следует обратить внимание на расположение первого пина. Если на микросхеме отсутствует привычная точка для его нахождения (а такое бывает) то определить первый пин можно по скосу с одной стороны микросхемы.
Рисунок 2 - Стороны корпуса микросхемы AT24C64

В даташите, почему то, не уточнили что на нижнем рисунке показан передний конец а не задний. Если микросхему перевернуть и подать напряжение наоборот (на GND + а на Vcc -) то данная микросхема начнет потреблять нехарактерно большой для неё ток. В даташите также есть диаграммы которые помогут реализовать связь с данной микросхемой по I2C.
Рисунок 3 - Диаграмма записи одного байта в ячейку памяти микросхемы EEPROM памяти AT24C64

Чтобы записать один байт данных в какую либо ячейку памяти микросхемы AT24C64, нужно сначала передать ей, по I2C, старт сигнал. Микроконтроллер сам правильно формирует этот сигнал при выполнении соответствующей команды. После чего надо послать лог. 1 если микроконтроллер будет работать в режиме мастер и лог.0 если микроконтроллер будет слейвом. Слейвом, в данном случае, микроконтроллер точно не будет т.к. EEPROM работает только в режиме слейва и не знает к каким ячейкам её памяти необходимо обращаться в какие моменты времени. Далее, именно для данной микросхемы, передаются 3 фиксированных бита адреса, после чего 3 настраиваемых. Эти 3 настраиваемых бита можно выставить меняя логические уровни напряжений на пинах A0...A2 микросхемы AT24C64. Если оставить данные пины никуда не подключенными то на них будут нули и следовательно 3 настраиваемых бита адреса тоже будут нулями. Если нужно увеличить память то можно подключить некоторое количество (до 8 шт. (больше адресное пространство не позволяет)) таких микросхем и использовать возможность установки адреса. Следующий бит указывает будет ли осуществляться запись байта данных в текущую ячейку EEPROM памяти (лог.0) или чтение (лог.1) его из этой ячейки. После того как слейв получил все эти 8 бит он формирует ответ просаживая линию SDA на землю или формирует отсутствие ответа не просаживая линию SDA на землю т.е. оставляя лог.1 на ней. Для чтения данного ответа или его отсутствия в микроконтроллере есть специальный флаг. После получения ответа микроконтроллер должен отослать слейву два байта адреса ячейки к которой требуется обратиться для чтения или записи в неё. Т.к. памяти в данной микросхеме 64кб то нужно именно два байта т.к. в один не поместятся все адреса а если байтов будет больше двух то как минимум один будет лишним. После передачи одного байта адреса, слейв также формирует ответ, после которого нужно отправить второй байт адреса. Если передача адреса прошла успешно то в микросхеме EEPROM памяти произойдет переключение на ячейку памяти по переданному адресу и к этой ячейке можно будет обращаться. EEPROM подтверждает данный успех выдавая ответ просаживанием линии SDA на 0 питания. Если последний бит байта адреса слейва указывал запись то после того как EEPROM переключился на какую то ячейку памяти, передачей в данный EEPROM ещё одного байта, запуститься запись этого байта в текущую ячейку памяти. После чего от этой микросхемы придет ответ и если больше ничего записывать не надо то слейву выставляется сигнал "стоп". Если нужно записать больше одного байта то они просто передаются по очереди а текущий адрес ячейки инкрементируется сам. Если байт нужно не прочитать а записать то сначала нужно перейти к требуемой ячейке памяти. Сделать это так как это делалось бы для случая записи. После перехода к требуемой ячейке памяти, передается сигнал "старт" ещё раз. После чего передается адрес слейва но последний бит теперь должен указывать на чтение а не на запись т.е. он д.б. лог.1. После успешного перехода в режим чтения, слейв выдает ответ и матер может подать синхроимпульс на SCL и прочитать бит данных по SDA после чего подать ещё один синхроимпульс и прочитать ещё один бит данных и так до тех пор пока не прочтется один байт, после этого мастер либо подает ответ слейву (так как ранше делал только слейв для мастера) и прочитать ещё один байт данных либо не передать ответ. После не передачи ответа, мастер передает сигнал "стоп" и соединение заканчивается.
Рисунок 4 - Диаграмма чтения одного байта данных из микросхемы AT24C64

В начале кода, как всегда, будет подключение заголовочного файла библиотеки CMSIS. После две глобальные переменные (хотя их можно сделать локальными) для чтения и для записи. Также будет вспомогательная функция для отправки символа по UART. В функции main() нужно включить тактирование всей необходимой периферии (UART1, I2C2, GPIO порт A и GPIO порт B). Пины SCL и SDA нужно настроить на альтернативный выход с открытым стоком.

В регистр I2C2->CR2 записывается частота шины от которой тактируется I2C2. По умолчанию это 36 МГц. В регистр I2C2->CСR записывается число на которое надо поделить частоту шины APB1 чтобы получилась удвоенная частота работы I2C. Т.к. I2C это синхронная шина то частота м.б. любой но обычно используются две, 100 кГц - обычный режим и 400 кГц - ускоренный. Чтобы настроить I2C2 на обычный режим нужно в регистр I2C2->CСR записать число 180 т.к. 100 000 Гц * 2 = 36 000 000 Гц / 180. В регистр I2C2->TRISE нужно записать число на единицу большее чем в регистре I2C2->CR2 т.е. чем частота шины APB1. 
Чтобы принимать и отправлять байты для записи и чтения в EEPROM нужно также настроить UART, об этом есть отдельные статьи в данном блоге (передача по UART -> https://electe.blogspot.com/2022/01/uart-stm32f103c8-cmsis.html, прием по UART -> https://electe.blogspot.com/2022/01/uart-stm32f103c8-cmsis_7.html). Чтобы записать какой нибудь, напечатанный в мониторе последовательного порта, символ в EEPROM, надо сначала получить его по UART. Дождавшись, перед этим, его прихода.
Получить байт данных для записи в EEPROM лучше в начале цикла, после чего прочитать байт данных из ячейки в переменную для этого а потом записать байт для записи т.к. иначе новое значение в ячейке перезапишет старое и протестировать двустороннюю связь микроконтроллера с микросхемой AT24C64 по I2C не получиться.
Чтобы разрешить работу I2C2 нужно установить бит 0 регистра I2C2->CR1:
Чтобы разрешить работу чтобы подать сигнал "старт" установить бит 8 этого регистра I2C2->CR1:
Прежде чем послать байт адреса устройства нужно дождаться окончания формирования сигнала "старт", для проверки этого есть бит 0 регистра I2C2->SR1:
После того как данный сигнал сформирован нужно прочитать регистр I2C2->SR1:
Теперь можно передать адрес слейва для установки связи с ним. В документации на микросхему показано как формируется её адрес:
После того как произведен запуск отправки адреса слейва чтобы переходить к следующей части коммуникации микроконтроллера с AT24C64 нужно дождаться окончания отправки этого адреса и приема ответа от слейва. Об этом сигнализирует бит 1 регистра I2C2->SR1:

После того как данный бит установится в единицу нужно будет прочитать регистры I2C2->SR1 и I2C2->SR2.
После этого можно перейти к переходу к нужной ячейке памяти в AT24C64. Для этого нужно передать сначала старшую часть адреса ячейки памяти а потом младшую. Передаются части адресов ячеек также т.е. записью их в регистр данных I2C2->DR. После того как произведен запуск отправки старшей части адреса ячейки памяти, нужно дождаться окончания отправки и передать младшую часть. Часть адреса ячейки памяти, в отличии от адреса слейва, является данными, как и те данные которые хранятся, записываются и читаются в эти ячейки. Поэтому для проверки окончания записи части адреса есть флаг окончания передачи данных по I2C который является битом 2 регистра I2C2->SR1:
Далее для того чтобы прочитать байт данных из текущей ячейки (адрес которой был передан) нужно снова послать сигнал "старт" после чего адрес устройства но теперь с битом указывающим чтение а не запись.
Потом также дождаться окончания передачи адреса и прочитать статусные регистры. После чего, если прием будет только одного байта данных то, просто дождаться прихода байта и забрать его из регистра данных. Если планируется забрать более одного байта из ячеек следующих после текущей то надо заранее выставить бит 10 первого регистра управления т.к. этот бит отвечает за автоматическй ответ мастера слейву после успешного приема байта данных. Об окончании приема байта данных свидетельствует бит 6 регистра I2C2->SR1:


Если принимается не один байт то перед приемом последнего надо убрать бит автоматической выдачи ответа. После успешного приема данных нужно сделать сигнал "стоп" установкой бита 9 регистра I2C2->CR1:
После того как байт данных получен его можно вывести на экране компьютера передав по uart через usb-uart переходник. Для этого можно воспользоваться функцией sendSumbol() (написанной над функцией main()). Записать байт данных принятый по uart проще. Для этого проделываются все те же действия как и для чтения до момента передачи сигнала "старт" второй раз. Вместо которого передается ещё один байт который и запишется в текущую ячейку памяти.
Полный код программы можно скопировать из текстового поля:

Проверить программу можно загрузив её в микроконтроллер, подключив через USB-UART переходник к компьютеру на котором надо запустить например "монитор последовательного порта" (или монитор просто порта (или штуки аналогичного назначения но с другим названием)) среды Arduino IDE (или другой программы с аналогичным функционалом). В этом мониторе надо установить скорость 9600 (если такая скорость уже заранее там не установлена (обычно для установки скорости в Arduino IDE есть выпадающий список справа в низу)). Если отправить один символ по UART (вписав его в верхнюю строку и нажав кнопку "send" или кнопку с другим названием но аналогичной функцией) то он запишется в ячейку памяти EEPROM а в мониторе порта выведется символ который был в этой ячейке до этого. Если, после этого, перезагрузить микроконтроллер (нажав соответствующую кнопку на плате "blue pill" (если конечно используется именно такая плата)) или отключить питание (монитор порта, перед этим (а также перед перезагрузкой), следует выключить) после чего его снова подать (после подачи питания можно включить монитор порта) и послать символ по UART то можно увидеть что в мониторе порта появился символ отосланный ранее. Это свидетельствует о том что он был сохранен в энергонезависимой памяти EEPROM и связь по I2C выполнилась успешно в обе стороны.
Видео по данной теме: