воскресенье, 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) для вывода получения результатов и некоторые другие команды. Полный текст программы можно скопировать из поля:

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


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

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

Датчик цвета на ESP32-CAM

 Модуль ESP32-CAM (http://alii.pub/69872w) может быть не только обычной дистанционной камерой для просмотра области пространства на которую она направлена но и также выполнять некоторые манипуляции с визуальными данными которые этот модуль получает и также самостоятельно принимать некоторые решения на основе этих данных. Микроконтроллер ESP32 имеет достаточно неплохую производительность (по сравнению с другими микроконтроллерами рассматриваемыми ранее в данном блоге) что позволяет использовать его для обработки изображений и машинного зрения. Ранее, в данном блоге, появилась статья про датчик цвета на основе Raspberry pi 3 и USB камеры - https://electe.blogspot.com/2020/05/usb-raspberry-pi.html. Датчик цвета сделать получилось но работал от крайне медленно. На получение снимка, его обработку, сохранение, конвертирование и всё стальное в сумме уходило более одной секунды что было видно без специальных таймеров замера времени. Если к этой задержке прибавить время более сложной операции, нежели просто усреднение цветов пикселей, то скорость работы наверняка также не порадует. Более быстрой работы устройства можно добиться исключив ненужные действия из процесса. Добиться этого на Raspberry pi c USB камерой будет не просто т.к. придется работать с камерой на более низком программном уровне + добавляются ограничения по скорости передачи данных по USB. Эти проблемы можно было решить применением специальной камеры для Raspberry pi но на тот момент такие камеры стоили гораздо дороже чем USB камеры. Модуль же ESP32-CAM сам по себе не дорогой, имеет небольшие размеры и используя его и среду разработки Arduino IDE можно получить доступ к пикселям изображений делаемых камерой без больших сложностей. Конечно на данный модуль нельзя установить такую тяжёлую библиотеку как opencv но в большинстве случаев её функционал избыточен по тому что не всегда необходимо делать с изображениями все преобразования которые может данная библиотека. Чтобы модуль ESP32-CAM мог показать что он правильно поработал с пикселями и определил цвет, можно дополнить его например RGB светодиодом который будет показывать какой цвет обнаружил модуль. Конечно можно использовать имеющийся в модуле WIFI или bluetooth или даже uart (если совсем лень) но светодиод кажется нагляднее. При этом его не следует направлять в ту сторону куда смотрит камера, иначе модуль будет определять тот цвет который сам же и создает что приведет к положительной обратной связи всей системы и более трудному выводу её из устойчивого состояния. Схема может выглядеть например так:

Рисунок 1 - Схема датчика цвета на ESP32-CAM

Модуль может питаться автономно от пауэрэнка даже без отключения программатора - это может быть удобно т.к. для перепрограммироания нужно делать только одно пересоединение. Скетч не сильно сложный и может быть такой:
В начале, как всегда, подключаются заголовочные файлы. После нужно добавить константы которые копируются из файла с константами примера для камеры. Константы нужно скопировать из блока для используемого модуля, в данном случае для ai thinker. В функции setup() включается последовательный порт для отладки. Далее заполняется структура для настройки и инициализации камеры. После чего имеется необходимая для запуска устройства последовательность строк скопированная из скетча примера. Далее делается инициализация камеры функцией esp_camera_init и проверка этой инициализации. После совершаются настройки. Снижается разрешение кадра чтобы сделать его быстрее и увеличивается насыщенность цвета чтобы датчик лучше различал цвета. Далее пины к которым подключен светодиод настраиваются на выход. В начале основного цикла объявлен указатель на структуру типа camera_fb_t который заполняется функцией esp_camera_fb_get после взятия фотографии от камеры. Если оно прошло неудачно то в последовательный порт передается соответствующее сообщение. Далее обявляются некоторые специальные переменные суть которых отражена в их названиях. Функцией dl_matrix3du_alloc выделяется память для матрицы изображения. Если этого сделать не получилось то оригинальное изображение возвращается функцией esp_camera_fb_return и в последовательный порт передается сообщение о неудаче. Далее заполняются специальные переменные. После чего функцией fmt2rgb888 изображение из камеры преобразуется из формата jpeg в формат rgb888 т.е. в обычный набор пикселей в котором на каждую компоненту цвета выделено по 8 бит. После успешного конвертирования набор пикселей будет доступен через указатель out_buf из которого можно извлекать пиксели так же как из обычного массива (такие особенности языка си) через квадратные скобки. Несмотря на то что в аббревиатуре RGB буква R, обозначающая красный, стоит на первом месте. Извлекать компоненты цвета из out_buf следует в обратном порядке т.е. синяя компонента первого пикселя будет в out_buf[0], зеленая в out_buf[1], красная в out_buf[2], синяя компонента второго пикселя будет в out_buf[3] и т.д. Конец этого хранилища записан в переменной out_len которую можно использовать для завершения цикла перебора пикселей. Для каждой компоненты цвета находиться среднее арифметическое после чего, обычным сравнением, определяется какая компонента преобладает и в соответствии с ней загорается нужный свет RGB светодиода. В конце выделенная память освобождается функцией dl_matrix3du_free. Результат работы датчика можно посмотреть на видео:

суббота, 8 октября 2022 г.

Гаусган

Гаусган (или пушка Гауса, по русски) является интересным девайсом не имеющим большого практического значения ввиду своей низкой эффективности. Данный девайс преобразует электрическую энергию конденсаторов (или других накопителей) в кинетическую энергию снаряда. Существуют также такого тип двайсы (приборы, по русски) которые осуществляют аналогичное преобразование с гораздо большей эффективностью. Например электродвигатели обычно имеют КПД 70 и более процентов. На примере двигателей видно что для преобразователей электрической энергии в кинетическую нет существенных ограничений для достижения высокой эффективности близкой к 100% (такие ограничения например есть для преобразователей тепловой энергии в кинетическую это например второй закон термодинамики). Т.о. можно заключить что  конструкция высокоэффективной электромагнитной пушки является возможной. Изготовить простейший гаусган, в домашних условиях, не трудно. Этому посвящено огромное колличество статей в интернете. Классический одноступенчатый гаусган представляет собой пусковую кутушку на стволе на которую, через тиристор, подается напряжение от конденсаторов которые заряжаются повышающим преобразователем работающим от батареек. Суммируя вышесказанное можно сгенерировать схему пушки гаусса например такую:

Рисунок 1 - Схема пушки Гаусса

Повышающий преобразователь сделан по классической двухтактной схеме на двух транзисторах с трансформатором на тороидальном сердечнике. Такой сердечник может быть толщиной например 3X5мм и диаметром 10мм. Это маленький сердечник который не имеет большой мощности и сильно ограничивает ток но уже при нем данный преобразователь потребляет около 200 мА если питается от двух пальчиковых батареек а это не маленький ток для них. Заряд силовых конденсаторов (или лучше одного с силового конденсатора C4) происходит не быстро. Если использовать например мощные литиевые аккумуляторы (или один мощный литиевый аккумулятор) то магнитопровод можно использовать побольше а транзисторы VT1 и VT2 помощьнее и иметь более быструю скорость заряда силовых (силового) конденсаторов.  Первичная обмотка имеет 6 витков, в одну сторону, с отводом от середины. Вторичная 140 витков. Толщина провода такая чтобы влезть во внутрь кольца. Напр. у вторички 0.1мм а у первички 0.2мм. Диоды VD1...VD4 д.б. высоковольтными и высокочастотными BYT25G для данного случая более чем подойдут. Все резисторы маломощьные напр. по 0.125 вт. В схеме не предусмотрен индикатор заряда конденсатора поэтому можно подключить к ней например вольтметр. После того как силовой конденсатор C4 зарядиться можно будет нажать кнопку SB1 (хотя правда до этого никто тоже не запрещает этого делать) что приведет к запуску снаряда (если он железный и был вставлен в ствол непосредственно перед катушкой до нажатия на кнопку. Если снаряда не было то он соответственно не полетит)



суббота, 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 на компьютер. Полный код программы можно скачать из текстового поля:

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

суббота, 6 августа 2022 г.

Система двухканального радиоуправления без микросхем

 Современные микросхемы и электронные модули значительно упрощают процесс изготовления разнообразных электронных устройств на их основе. Особенно это актуально для устройств работающих с радиоволнами т.к. для высокочастотных устройств имеются определнные требования к качеству разводки и изготовления плат и некоторым другим вещам которые бывает трудно реализовать в неподходящих, для этого, условиях. Однако же электронные системы, работающие с радиоволнами, существовали до появления микросхем а следовательно могут быть реализованы на дискретных элементах. Делать это может быть необходимо по разным причинам. Давайте представим например что сделать такое устройство необходимо например в гипотетической стране которая по каким то причинам не ведет торговлю с другими странами а производить может только дискретные элементы. Удивительная ситуация но допустим например чисто теоретически что она возникла. В одной из предыдущих статей рассматривалась многоканальная система радиоуправления с микросхемой-счетчиком для распределения одного канала импульсов на их большее количество. Одним из возможных вариантов построения многоканальной системы радиоуправления будет - замена двоичного интегрального счетчика на двоичный транзисторный счетчик. Двоичный счетчик может быть построен на Т-триггерах. Т-триггер - это такой триггер который меняет свое состояние на выходе при подаче сигнала на его вход т.о. получается что один такой триггер является одноразрядным двоичным счетчиком. Если соединить выход одного Т триггера со входом другого то получиться двухразрядный двоичный счетчик который можно применить для увеличения количества каналов системы радиоуправления. Однако построение такого счетчика на дискретных элементах - не простая задача т.к. для его реализации требуется большое количество деталей. Если пытаться его уменьшать всевозможными ухищрениями то можно ухудшить его качество. Под "качеством", в данном случае, можно подразумевать такие характеристики как стабильность, скорость работы (что важно для двухканальной системы "реального времени") и возможно некоторые другие. За основу для построения Т триггера, на дискретных элементах, можно взять обычный RS триггер на транзисторах:

Рисунок 1 - RS триггер на транзисторах

Для построения Т триггера на основе данного RS триггера, его нужно дополнить элементами задержки:
Рисунок 2 - RS триггер на транзисторах с элементами задержки

Это нужно для того чтобы триггер мог поменять свое состояние относительно того которое, как бы временно сохранено в таком импровизированном ОЗУ. Иначе триггер бы переключившись, сразу же бы увидел новое состояние и начал бы снова переключаться и т.д. т.е. без элементов задержки сделать Т триггер нельзя. Далее понадобятся два логических элемента "И-НЕ" чтобы просадить на землю одну из половин триггера в зависимости от того какая из них просажена в данный момент и чтобы это происходило только в том случае если на вход подан короткий запускающий импульс. Запускающий импульс должен быть коротким для того чтобы напряжения на конденсаторах элементов задержки не успели сильно измениться и повлиять на правильную работу триггера. Данное поведение не сильно соответствует обычному Т триггеру который может работать с обычными сигналами но это плата за простоту схемы, иначе пришлось бы использовать большое количество транзисторов. Схема с добавленными элементами "И-НЕ" :


Рисунок 3 - RS триггер на транзисторах с элементами задержки и "И-НЕ"

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


Рисунок 4 - Двухразрядный двоичный счетчик на дискретных элементах

Данную схему можно совместить с приемником (рассмотренным в статье http://electe.blogspot.com/2020/04/blog-post.html):
Рисунок 5 - Схема двухканального радиоприемника на дискретных элементах

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

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

воскресенье, 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 выполнилась успешно в обе стороны.
Видео по данной теме: