Разработка аппаратной платформы для соревнований по Embedded программированию

от автора

Предыстория

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

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

В статье я немного расскажу про этапы соревнований, про идею и реализацию моего устройства для этапа «программирование», а также про путь проектирования и сборки печатной платы, моделирования и печати корпуса, написания тестовой прошивки и разработки заданий для участников. Повествование будет в хронологическом порядке. Потратил на весь путь около года ради нескольких часов соревнований. Но это лишь мое хобби.

«Long story short…»

Немного про соревнования

Само соревнование в компетенции «Электроника» проходит в индивидуальном зачете в течение нескольких дней и делится на несколько этапов:

  • Схемотехника. На данном этапе участникам необходимо решить схемотехнические задачи, провести моделирование и привести доказательства решений. Обычно 2-3 часа на 5 заданий. В качестве исходных данных – заготовки в предложенной среде моделирования, документация на применяемые компоненты и список допустимых к использованию компонентов.

  • Измерения. На данном этапе необходимо провести измерения контрольных значений в соответствии с заданием и оформить отчет. Обычно 1-2 часа на 3-5 заданий. В качестве исходных данных – схема и технические описания на компоненты печатной платы. Инструменты – осциллограф, мультиметр, генератор.

  • Трассировка печатной платы. На данном этапе необходимо за отведенное время, обычно 3-4 часа, развести печатную плату в соответствии с заданием. В качестве исходных данных – электрическая схема в электронном варианте и библиотеки элементов. Часть схемы в электронном варианте может отсутствовать, её необходимо восстановить по бумажной копии. Библиотеки элементов могут содержать ошибки, не соответствовать посадочные места и типоразмер корпусов. Печатная плата обычно на 150-250 «дорожек» и около 100 компонентов.

  • Пайка и наладка печатной платы. На данном этапе необходимо за отведенное время, обычно 3-4 часа, установить компоненты на печатную плату. Иногда в задании применяется частичная установка SMD компонентов на паяльную пасту (используются трафарет и ракель), но большинство компонентов монтируются вручную паяльником или феном. Оцениваются качество пайки, чистота после отмывки и работоспособность.

  • Ремонт. На данном этапе частникам выдаются печатные платы с одинаковыми неисправностями. Обычно около 5 неисправностей на 3 часа. В качестве исходных данных участникам выдаются: документация на применяемые компоненты и электрическая схема. Иногда электрическая схема не выдается и её нужно восстанавливать самостоятельно. Доказательство ремонта необходимо зафиксировать в отчете с приложением измерений. На первых этапах соревнований часто выдают «донора», как магазин компонентов — для замены вышедших из строя. Иногда замену необходимо «заказывать»: Выпаиваешь компонент, заполняешь лист запроса замены. Твой компонент забирают, выдают запрашиваемый.

  • Программирование. Этап программирования проводится на микроконтроллерах STM32. На этот этап отводится 3-5 часов. Делится на два подэтапа: разработка библиотек и непосредственно, алгоритмы. Разработка библиотек сводится к реализации низкоуровневых функций конфигурации или общения с периферией, алгоритмы – непосредственно программирование для восстановления «утраченного» функционала устройства.

Аппаратная часть для задания

Я участвовал несколько раз в соревнованиях и был очень вдохновлен разносторонним подходом к разработке заданий. К некоторым этапам соревнований невозможно целенаправленно подготовиться, они, на мой взгляд, проверяют наличие практического опыта.

Прошло несколько лет с моего последнего участия, и я решил попробоваться в роли разработчика задания на этап «Программирование».

Можно было пойти более простым путем: использовать аппаратную студенческую платформу, так называемый ПЛК – плата расширения для платы Nucleo от STM32. На данной плате содержатся: семисегментные индикаторы подключенные по схеме с динамической индикацией, матрица клавиатуры примерно на 20 кнопок, различные датчики, светодиоды и звуковой излучатель. На этих ПЛК иногда проводят отборочные этапы соревнований, но мне показалось, что это слишком просто и решил собрать своё устройство.

Начало разработки. Планы

Главными критериями были:

  • Встроенный отладчик на плате

  • Графический экран

  • Клавиатура

Сначала хотел собрать что-то похожее на калькулятор, но что-то пошло не так.

Чем дольше идея лежала в ящике, тем больше она обрастала функционалом, но в рамках умеренного, на мой взгляд, бюджета.

На первой версии печатной платы решил установить следующие компоненты, сразу приобретая отладочные платы для тестов:

  • USB-C разъем для питания и подключения к ПК

  • Основной микроконтроллер – STM32G431CBU6

  • Встроенный отладчик, чтобы устройство было одно, без дополнительных внешних девайсов

  • Графический экран 320×170 точек, контроллер ST7789, подключение SPI

  • USB-хаб, чтобы можно было использовать и отладчик, и USB основного микроконтроллера

Готового хаба с GL852G в QFN корпусе, к сожалению, не нашел. Ох и наделает эта микросхема мне проблем в будущем

  • Контроллер заряда Li-Ion аккумулятора BQ25896 + Li-Ion аккумулятор. Отладочную плату удалось найти только на BQ25895, для тестов пойдет

  • Клавиатура. Мне как раз понравились мягкие бесшумные SMD-кнопки

  • Подсветка клавиатуры реализована на адресных светодиодах. Я использовал такие в ранее описанном проекте клавиатуры

  • MicroSD карта памяти

  • RFID. К этому моменту «калькулятор» превратился в «кассовый аппарат»

  • Гироскоп/Акселерометр на базе микросхемы MPU6050

  • Приемопередатчик 433 МГц на базе микросхемы TI CC1101

  • УНЧ на базе микросхемы PAM8403 для вывода звука с ЦАП на динамики

Ну и разошелся я с компонентами, но руки уже чесались использовать все.

После проверки работоспособности всех устройств в отдельности, я сел разрабатывать первую печатную плату.

Все это мне напоминает старую шутку про утку: «Просто, потому что смог».

Разработка первой версии печатной платы

Результатом идеи стала первая версия печатной платы. Изначально я начал качать требуемые мне STEP-модели корпусов из открытых источников, чтобы при разработке корпуса нигде не допустить ошибку. Но так получилось, что цветовые параметры корпусов разных авторов сильно отличались. И я не нашел ничего лучшего, чем сделать все STEP модели вручную.

Так печатная плата выглядела в рендере:

Пришлось придумывать расположение экрана, чтобы он не занимал полезную площадь печатной платы. В дальнейшем будет 3D печатная рамка.

Каких-то 25-30 дней ожидания и ко мне приехали первые печатные платы

Далее расскажу о схемотехнических решениях подробнее.

Основной микроконтроллер

В первой версии платы раскидал подключение всех модулей по следующей схеме:

Встроенный отладчик на плате

Я нашел самую простую схему отладчика ST-link на базе микроконтроллера STM32F103, только использовал микроконтроллер в безвыводном корпусе STM32F103CBU6

На плате поставил резистор R39 как контактные площадки для первоначальной прошивки отладчика по SWD. В случае, если что-то пойдет не так, оставил возможность включить встроенный загрузчик, добавив R41 для подключения BOOT к питанию.

Графический экран

Не смог найти за приемлемую цену такой экран с тач-скрином, потому схема вышла без него:

Яркость подсветки регулируется с помощью ШИМ, также есть возможность полудуплексного обмена данными с экраном по одной линии MOSI. Общение реализовано в 16 битном режиме. 8-битные команды дополнены командой NOP (0x00) в начале посылки. В таком режиме при использовании DMA, получается кратно увеличить скорость заполнения экрана, а особенно – его очистку. Теперь для заполнения экрана одним цветом с использованием DMA не нужно заполнять буфер для передачи, а достаточно заполнить 2 байта (16 бит) цвета (так как экран настроен на режим цветности rgb565) и включить передачу, просто отключив инкремент адреса источника данных.

Формат цвета rgb565 представляет собой 16-битное беззнаковое число uint16_t, где с 15 по 11 бита – красный цвет, с 10 по 5 бит – зеленый цвет, с 4 по 0 бит – синий цвет.

Пример кодирования формата цвета rgb565

USB-хаб

Как видно по схеме, к USB-хабу подключены: отладчик ST-link и целевая микросхема STM32G431

Контроллер питания

Мне кажется, что BQ25896 — это одна из самых интересных микросхем в этом проекте.

Я всегда раньше использовал следующую связку микросхем: зарядка TP4056, котроллер заряда DW01 с транзисторной сборкой и MT3608 в режиме SEPIC (повышающий-понижающий преобразователь). Питанием управлял через ENABLE DC-DC преобразователя. На фото часть схемы одного из старых проектов:

Схема рабочая, кочевала со мной из проекта в проект. Принцип работы прост:

Аккумулятор заряжается через микросхему TP4056, индикаторы – VD2 и VD3. Микросхема DW01 и транзисторная сборка FS8205A дополнительно защищают аккумулятор от перезаряда/переразряда. DC-DC MT3608 в SEPIC режиме, вне зависимости от входного напряжения Li-Ion аккумулятора (3.2 – 4.2 (В)) на выходе выдает 4 (В). SEPIC преобразователь довольно шумный, потому на выходе стоят LDO на 3.3 (В).

По умолчанию, схема находится в отключенном состоянии, вход «EN» микросхемы MT3608 стянут к сигнальной земле.

При нажатии кнопки SW3 «ENTER», по линии «DC-DC EN» поступает напряжение батареи, минус падение на диоде VD6, и запускается работа преобразователя. Также с появлением питания запускается целевая микросхема и подает на линию «POWER EN» высокий уровень. Этот высокий уровень, проходя через диод VD5 поступает на линию «DC-DC EN» и наступает момент самоподдержания рабочего режима. Помимо всего, есть независимая реакция на кнопку «ENTER» (линия «ENTER» развязана от управляющих каналов преобразователя DC-DC).

Для отключения устройства достаточно подать низкий уровень на линию «POWER EN»и схема отключится. В отключенном режиме потребление минимальное. У меня брелок с аккумулятором на 400мАч лежит уже несколько лет без подзарядки.

Схема рабочая, но с кучей недостатков:

  • Сложно осуществить замер напряжения батареи – нужно ставить делитель напряжения, а он всегда будет разряжать аккумулятор

  • Схема громоздкая

  • Ток заряда настраивается резистором R8 и не может быть изменена без внесения изменений в плату

  • Чтобы получить напряжение питания 5 (В) на плате, необходимо ставить дополнительный повышающий преобразователь

И тут я нашел дешевое и удобное, как мне кажется, решение в виде микросхемы BQ25896. Я использовал следующую схему включения:

Вот некоторые его преимущества:

  • Данный преобразователь управляется по шине I2C

  • Может заряжать Li-Ion аккумулятор с настраиваемым током заряда

  • Может подавать на целевое устройство напряжение с аккумулятора (шина «V_SYS»), а в случае, если напряжение аккумулятора ниже определенного, заданного порога, повышать его на некоторую величину. Т.е. если напряжение на аккумуляторе дошло до 3.4 (В), можно поднять V_SYS, например, на 0.2 (В), обеспечив запас для падения на LDO

  • Может замерять ток, напряжение VUSB и напряжение аккумулятора. Замер грубый – 7 бит со смещением, но мне этого хватает

  • Помимо вышеуказанных преимуществ, может работать в режиме OTG как повышающий преобразователь, выдавая напряжение 5 (В) на линии VBUS. Напряжение можно немного настраивать. Это напряжение понадобится для питания адресных светодиодов и микросхемы УНЧ

  • Все DC-DC преобразования выполняются благодаря одной! катушке на плате. Ну не чудо ли

  • Также есть возможность полностью отключать питание целевого устройства по I2C. Возобновление питания происходит с помощью удержания входа QON в низком уровне более 3 секунд

  • Если что-то пошло не так, вы не вывели сброс основного микроконтроллера, программа зависла, вы не можете выключить/включить питание, то не беда – удержали QON в низком уровне более 18 секунд и контроллер перезапустил питание

  • Индикаторы в виде светодиодов тоже присутствуют, но появилась возможность включать/отключать заряд аккумулятора

Кнопки, светодиоды и MicroSD-карта памяти

Под каждой кнопкой присутствует светодиод подсветки, а также 4 светодиода на подсветке зоны RFID. Схема включения представлена на фото

На плате установлено 20 кнопок, которые подключены частично в режиме матрицы. Для подключения использовал микросхему – расширитель портов PCF8574 в корпусе QFN. Каждый ее вывод может работать как выход с открытым коллектором (притянуть линию к сигнальной земле) или «отпустить» линию. В момент отпускания линии, можно считать состояние линии – логическая единица или логический ноль. С помощью этой микросхемы реализовал матрицу 4х4. Но этого недостаточно для 20 кнопок. Одна кнопка у нас «аппаратная» для BQ25896 (но она подключена и к целевому микроконтроллеру), остается еще 3. Ставить еще одну PCF8574 расточительство – просто подключил данную строку к микроконтроллеру. Итого имеем 20 кнопок, 4 сигнальных линии (SCL, SDA, ROW 0, QON) для опроса всех кнопок. Причем шина I2C подключена «транзитом» через микросхему MPU6050 (у нее есть дополнительные выходы, которые можно настроить соответствующим образом). Так было удобней для трассировки.

Уровень сигнальная линия адресных светодиодов согласован через полевой транзистор D18, сигнал инвертирован. Источником сигнала является таймер с DMA.

Карта памяти подключена на отдельный контроллер SPI.

RFID

Здесь я сильно не вникал в схемотехнику, а уж тем более не изучал и не делал расчет контура ближнего поля. Скопировал схему и скопировал топологию.

Акселерометр и гироскоп

Тоже остались без изменений

Приемопередатчик 433 МГц

Схему и топологию приемопередатчика тоже перенес с модуля, может только дополнительно сшил с помощью VIA сигнальную землю

УНЧ

Усилитель низкой частоты, как и писал ранее, запитал от напряжения 5 (В). Входы подключил к DAC основной микросхемы через делители, которых оказалось мало и УНЧ мог уйти в защиту.

На схеме есть катушка L9. Хотел сделать фильтр питания, подобрав номинал индуктивности, но пришлось заменить катушку на диод шотки, так как контроллер питания BQ25896 во время большого потребления УНЧ и паразитных влияний катушки, начинал «думать», что его заряжают и отключал режим OTG.

К сожалению, у данного дешевого УНЧ нет возможности регулировать уровень громкости. Во второй версии платы пришлось добавить внешние цифровые резисторы.

Паяем, отлаживаем

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

Установить контроллер питания BQ25896, USB-хаб GL852G, разъем USB-C, микросхему STM32F103 и прошить её.

На трафарет я не раскошелился, пришлось паять вручную. Почти все резисторы и конденсаторы 0402.

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

Но не буду о плохом. Все установил, подпаялся программатором, прошил ST-link и плата заработала

Это была очень большая эмоциональная подпитка, двигаемся дальше

Ставлю основной микроконтроллер и экран, светодиоды, пишу библиотеки, и оно оживает:

Подключение экрана

Пишу библиотеку для контроллера питания, проверяю все светодиоды

Провожу небольшие доработки «по месту» со входами УНЧ

И вывод звука заработал.

Где-то на этом этапе я начинаю задумываться: А зачем я вообще этим начал заниматься?

Но это был риторический вопрос у меня в голове и продолжаю дальше.

Разрабатываю и печатаю первую версию корпуса из ABS

Пробую печатать кнопки из полупрозрачного PETg

Замечаю особенности лишней засветки, учту на второй версии платы и корпуса.

В целом, результат меня уже радует, но на данный момент:

  • не знаю, как нанести обозначения на кнопки

  • нужно решить проблему с УНЧ (поставить цифровые резисторы)

  • мне не нравятся некоторые контуры корпуса, нужно тоже учесть на второй версии платы

Также проверил RFID

Реализовал чтение 8-битного WAV файла с MicroSD. Воспроизведение реализовал через кольцевой буфер. Буфер выдает данные через DMA, DMA «подпинывает» таймер. А когда прошли половину буфера, заполняем пройденную половину новыми данными. Так песни играют без искажений. Анимация, конечно, не передает звук, но при срабатывании карточки, воспроизводится звук аськи.

Музыка играет достаточно приятно для 8 битного WAV и двух 3W динамиков. В рамках тестов записывал несколько композиций, чтобы можно было разные послушать.

Приемопередатчик CC1101 я проверил с моими старыми наработками, тут показать пока нечего.

Разработка второй версии платы

Настала пора разрабатывать вторую версию платы и закупать компоненты на 15 устройств.

Изменений во второй версии внесено не много, структурная схема получилась следующей:

А конфигурация выводов микроконтроллера следующей:

USB-хаб

Не стал использовать встроенный LDO в GL852G, чтобы она меньше грелась, взял питание 3.3 (В) от ST-Link и поменял USB порты

Но как писал ранее, из-за массового брака или подделок микросхем, реальное подключение стало таким:

Основной микроконтроллер

Немного изменилось подключение основной микросхемы к периферийным устройствам, для удобства вынес на схему адреса и подключения периферии. Это оказалось очень удобно для отладки.

Не писал ранее, но уже на первой версии платы были добавлены кнопки сброса и подтяжки ножки BOOT основной микросхемы к питанию.

Звук

Изменилась схема подключения УНЧ, а также добавился микрофон с усилителем. На схеме снова присутствует фильтрующий дроссель L9, вместо него устанавливается диод шотки SS54.

Печатная плата

Получилась такая топология печатной палаты:

Сейчас на фотографиях она для меня смотрится достаточно простой, но вспоминая потраченное время…

Придумал новый логотип:

По задумке: котик роняет со стола паяльник, но получилось — что получилось.

Заказываю печатные платы. А в это же время приобретаю новую паяльную станцию под жала C115 (слева на фото), до этого все паял станцией с жалом C245 (справа на фото)

Заказал 10 печатных плат, подумал, что потом дозакажу еще. А мне приехало, как раз 15 плат, необходимых для соревнований. Я был несказанно рад. Небольшая экономия, хотя в рамках потраченного бюджета, это около 1%.

Сравнение печатных плат:

Интерактивный BOM

Пока изготавливались и шли платы, я узнал про интересный плагин от BoasTools, и с ним пайка пошла намного быстрей, сами посмотрите на интерактив:

Выбираешь номинал и паяешь все, что отмечено. Удобно и в формате HTML.

Пайка

Нашел более удобное жало для паяльника и заказал его

Стало намного удобней пропаивать QFN корпуса. Так как на вторую версию платы я тоже не заказывал трафарет, то паяю вручную. Безвыводные корпуса пролуживаю паяльником, также пролуживаю посадочные места, затем устанавливаю микросхему паяльным феном и прохожу паяльником. Получается следующий результат, который меня полностью устраивает:

Заканчиваю пайку платы. Пока еще компоненты в пути, всё снимаю с синей платы. Плата не отмыта до конца, но другой фотографии не осталось. Заложил одно крепежное отверстие под экраном для его фиксации.

В будущем заменю большой микрофон на более компактный.

Фото с подключенным экраном.

Так как паяю все вручную, а у QFN корпусов центральный ПАД необходимо тоже припаивать (у STM32G431CBU6 это вообще единственный вывод сигнальной земли GND), то под каждой микросхемой сделал по большому переходному отверстию и, после установки микросхемы, пропаиваю центральный вывод паяльником. Одно переходное отверстие не очень эффективно отводит тепло, но надеюсь, мне этого хватит.

Проверяю работоспособность. Параллельно дописываю библиотеки и реализую будущий функционал.

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

Корпус

Проектирую и печатаю корпус.

Во всех пунктах меню сделал подсветку только активных функциональных клавиш.

Проверяю и корректирую зазоры кнопок и функциональных элементов

Получился такой корпус

Крышка отсека для батареек подпружинена и закрывается с защелкиванием.

При подключении к ПК или зарядному устройству, зарядка не осуществляется сразу, ее необходимо включить. Сделал так, чтобы не было резкого потребления тока, пока не отладил весь процесс работы устройства.

Возле USB разъема расположил две группы светодиодов, которые видны через светопрозрачный пластик – рамку вокруг USB-C разъема.

Сверху два светодиода – индикаторы контроллера заряда, снизу два светодиода – индикаторы внутрисхемного отладчика.

Небольшая демонстрация работы. Процесс включения, активация режима OTG (работы повышающего преобразователя), настройка часов реального времени, реакция на RFID карту, воспроизведение музыки и управление громкостью, график с демонстрацией данных микрофона, работа гироскопа, чтение картинки с карты памяти, управление яркостью экрана и подсветки, работа контроллера питания и отключение питания.

Приемопередатчик CC1101

Я использовал приемопередатчик только в режиме амплитудной модуляции, без специализированных протоколов, на которую он рассчитан аппаратно.

Вся приемопередача настроена на режим On-Off Keying, что это значит:

Передатчик либо генерирует несущую (генерирует в эфир сигнал нужной частоты) на частоте 433.92 МГц, либо молчит и ничего не передает. Соответственно, управляя временем – сколько передаем и сколько молчим, можем реализовать что-то похожее на протокол передачи, а обрабатывая данные из эфира, можем определять – наш это протокол или нет.

Как написал выше, я сконфигурировал микросхему приемопердетчика таким образом, чтобы она преобразовывала низкочастотный сигнал в сигнал высокой частоты. Такой режим у нее есть и используется вывод GDO0. Если в режиме передачи на этот вход подать логический «0», то передатчик будет молчать, если подать логическую «1», то передатчик будет излучать в эфир сигнал нашей высокой частоты. Соответственно, в режиме приемника будет происходить обратное преобразование: несущей нет – на GDO0 – логический «0», несущая есть – на GDO0 – логическая «1». Звучит легко, но эфир у нас всегда наполнен множеством шума. В микросхеме CC1101 есть множество различных настроек, в том числе настройки порога срабатывания, но понять все я так и не смог. Наиболее простым решением может оказаться взять инициализацию из чьего-нибудь проекта.

Я уже давно вожусь с этим модулем и собирал пульт для шлагбаума (многие старые шлагбаумы работают на OOK и имеют статический код для управления). Именно в этом устройстве я последний раз применял старую схему управления питанием.

Буду использовать это устройство для отладки приемопередачи.

Написал небольшую программу на ПК, которая передает системное время с ПК по USB (программный USB-UART в STM32), микроконтроллер получает эти данные и передает время по радиоканалу.

Структура пакета данных радиопередачи:

Принимаемый пакет данных точного времени состоит из преамбулы и данных.

  • Преамбула – 5 чередующих переключений наличия и отсутствия несущей через время 1000 мкс.

  • Логическая «1» — отсутствие несущей в течение 600 мкс и наличие несущей в течение 300мкс.

  • Логический «0» — отсутствие несущей в течение 300 мкс и наличие несущей в течение 600мкс.

Допустимое временное отклонение — 20%.

Данные – 24 бита информации точного времени. Данные идут старшим битом вперед (MSB). Представляют собой три 8-битных числа в формате uint8_t.

  • Первые (старшие) 8 бит данных – принимаемое количество часов.

  • Вторые (средние) 8 бит данных – принимаемое количество минут.

  • Последние (младшие) 8 бит данных – принимаемое количество секунд.

Демонстрация работы всей связки устройств:

Прием данных двумя устройствами

Печать кнопок

Прототип меня почти полностью устраивает, кроме кнопок. Нужно что-то решать. И на помощь мне пришел коллега, у которого есть SLA принтер.

Проектирую кнопки под печать из смолы с последующей ручной заливкой надписей.

Ну и хороша же SLA печать

Изначально смола была прозрачной, но в результате нескольких итераций подбора смеси из прозрачной и белой смолы, получился подходящий молочный цвет. Для черной надписи – углубление на 1,5мм, которое заливалось вручную черной смолой.

SLA печать тоже не идеальна из-за некоторых физических «ловушек» раствора, пришлось каждую кнопочку дорабатывать дремелем. Это касается обратной части, которая устанавливается на SMD-кнопку.

Устройство приобрело законченный вид:

До хорошего дизайна, конечно, мне далеко, но, по моему мнению, получилась не плохая функциональная коробочка.

RFID и картинки

Для демонстрации работы использовал карточки Mifare объемом 1K, их структура данных выглядит следующим образом (фото из интернета):

Каждый сектор состоит из 4х блоков, последний блок в секторе служебный, его не получится использовать для хранения пользовательских данных, еще нулевой блок в нулевом секторе содержит идентификатор карточки, этот блок тоже не получится использовать. Итого остается 15х3+2 = 47 блоков по 16 байт, т.е. 752 байта для записи пользовательских данных. Долго думал, чтобы в них записать и решил сохранить пользовательские монохромные картинки. Но не просто черно-белые, а буду хранить в 0-м секторе в 1-м блоке ИМЯ, а во 2-м секторе ЦВЕТ картинки.

На саму картинку остается 15х3х16х8 = 5760 бит. Получилось использовать картинки размером 80×72 пикселя.

Нашел в интернете аватарки 80×80

Провел некоторые преобразования и получил черно-белые картинки

Реализовал программу сохранения картинок с MicroSD карты памяти на карточки Mifare с помощью этого же устройства и получил следующий результат:

Сборка всех устройств

Первый прототип работает, осталось дело за малым – спаять и собрать 15 устройств.

К этому моменту, два уже собрано.

Ручная пайка, конечно, заняла много времени. В один из дней я утром сел за рабочий стол, включил паяльную станцию и решил: сегодня установлю все конденсаторы 0402 номиналом 100нФ. Открыл вышеописанный плагин от BoasTools, выбрал интересующие меня конденсаторы – 47 конденсаторов на одну плату. На 13 плат – 611 конденсаторов. Конденсаторы, со всеми перерывами, паялись около 7 часов. Да, нужно было брать трафарет.

Каких-то 3 недели пайки по вечерам и 15 плат готовы, параллельно печатались корпуса

Как раз мне допечатали все кнопки и недостающий конструктив из смолы.

Прошло время и устройства собраны в полном объеме:

Функционал

Начал с описание устройства и его возможностей, которые будут реализованы на соревнованиях:

  • Вывод данных на экран

  • Реакция на нажатие кнопок, переход по пунктам меню

  • Настройка часов реального времени и отображение их на экране

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

  • Считывание и отображение данных из RFID карт

  • Формирование звуковых сигналов

  • Оцифровка и вывод данных состояния микрофона

  • Считывание и отображение действительных значений углов ориентации устройства и температуры из датчика MPU6050

  • Чтение данных из MicroSD карты

  • Считывание и отображение действительных значений напряжений питания и аккумуляторной батареи, тока заряда батареи

  • Обмен данными через UART с использованием встроенного конвертора в ST-Link

Назначение основных органов управления:

В контроллере предусмотрено 20 функциональных кнопок и 2 сервисные кнопки.

Функциональные кнопки размещены в 5 рядов по 4 столбца, их наименования представлены в таблице

Сервисные кнопки находятся с правой стороны от экрана. На верхней стороне платы – кнопка «RESET», на нижней стороне платы – кнопка «BOOT».

При питании от аккумуляторной батареи включение устройства производится удержанием клавиши «POWER» более чем на 3 секунды, 5В от встроенного в контроллер питания повышающего преобразователя активируются отдельно через функциональные кнопки.

В контроллере предусмотрена подсветка функциональных клавиш адресными светодиодами. Подсветка зоны считывателя RFID карт состоит из четырех адресных светодиодов, расположенных по углам.

Разработка задания

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

Задание по модулю «Программирование встраиваемых систем» было разделено на два отдельных этапа:

  • Аппаратное программирование

  • Алгоритмическое программирование

Общее время выполнения конкурсного задания составляет 4,5 часа. Между этапами предусмотрены остановки времени для сбора результатов работы участников и выдачи необходимых данных для следующих этапов. Время выполнения части 1 составляет 1,5 часа, части 2 – 3 часа.

На каждом из этапов участнику соревнований выдается эталонный HEX-файл для демонстрации работоспособности устройства.

При разработке заданий столкнулся с проблемой, что мне необходимо привести фотографии или скрины экрана для описания. Фотографии отмел сразу, надо делать скрины. У контроллера ST7789 есть возможность считать буфер кадра по SPI. Отлично, реализовал считывание данных и запись дампа на флешку в виде BMP файла.

К заданиям приведу небольшие описания и мои решения.

Аппаратное программирование. Задание

На данном этапе в контроллере кассового аппарата реализован следующий функционал:

1.Вывод отладочной информации на экран:

  • Текстовый заголовок «DRIVERS»

  • Тестовый вывод 10 цифр из шрифта «FontClock»

  • Отображение действующих напряжений питания: линии VBUS, аккумуляторной батареи и выходного напряжения Vsys.

  • Графическая пиктограмма состояния проигрывания WAV-файла

  • Графическое отображение установленного уровня громкости.

2.Тестовое переключение адресных светодиодов – подсветки функциональных клавиш и зоны считывателя RFID карт. Смена подсветки происходит последовательно в режиме бегущего огня: КРАСНЫЙ, ЗЕЛЕНЫЙ, СИНИЙ, БЕЛЫЙ.

3.Функциональные клавиши:

  • Клавиша «NUM_1» запускает воспроизведение музыкального файла

  • Клавиша «NUM_0» останавливает воспроизведение музыкального файла

  • Клавиша «PLUS» увеличивает громкость

  • Клавиша «MINUS» уменьшает громкость

  • Клавиша «DOT» разрешает/запрещает зарядку аккумулятора

  • Клавиша «RETURN» разрешает/запрещает работу повышающего преобразователя

  • Клавиша «POWER» подготавливает контроллер к отключению питания. При отсутствии питания VBUS, контроллер выключится

За отведенное время необходимо исправить или реализовать следующие части кода, входящие в библиотеку периферии:

1.Файл fontClock.c содержит битовое отображение цифрового шрифта FontClock. В выданном цифровом шрифте отображение цифры 5 содержит ошибки.

2.Файл ws2812.c содержит функцию, которую необходимо реализовать

Примечание: параметр яркость применяется к каждому компоненту преобразованного цвета

3.Файл bq25896.c содержит функции, которые необходимо реализовать:

Примечание: если было получено минимальное значение напряжения из доступного диапазона, функция должна вернуть ноль.

4.Файл ad5245.c содержит функции, которые необходимо реализовать:

К оценке принимаются файлы fontClock.c, ws2812.c, bq25896.c и ad5245.c

Для оценки результатов работы мы размещали вышеуказанные файлы, предоставленные участником, в эталонном проекте. После чего проводилась компиляция проекта и его загрузка в эталонное устройство. Оценка результатов работы производилась по функциональности эталонной платы. Заранее были обговорены критерии оценок.

Аппаратное программирование. Решения

1.Файл fontClock.c и цифра 5.

Описание

Шрифт хранится в битовой маске, для примера, цифра «4» представлена в виде

0x00, 0x00, 0x00, //0x00, 0x00, 0x00, //0x00, 0x00, 0x00, //0x20, 0x04, 0x00, //  #          #0x70, 0x0E, 0x00, // ###        ###0x70, 0x0E, 0x00, // ###        ###0x70, 0x0E, 0x00, // ###        ###0x70, 0x0E, 0x00, // ###        ###0x70, 0x0E, 0x00, // ###        ###0x70, 0x0E, 0x00, // ###        ###0x70, 0x0E, 0x00, // ###        ###0x27, 0xE4, 0x00, //  #  ######  #0x0F, 0xF0, 0x00, //    ########0x07, 0xE4, 0x00, //     ######  #0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x04, 0x00, //             #                  0x00, 0x00, 0x00, //0x00, 0x00, 0x00, //

А цифра «5» пустая:

0x00, 0x00, 0x00, //                  0x00, 0x00, 0x00, //     ######0x00, 0x00, 0x00, //    ########0x00, 0x00, 0x00, //  #  ######0x00, 0x00, 0x00, // ###0x00, 0x00, 0x00, // ###0x00, 0x00, 0x00, // ###0x00, 0x00, 0x00, // ###0x00, 0x00, 0x00, // ###0x00, 0x00, 0x00, // ###0x00, 0x00, 0x00, // ###0x00, 0x00, 0x00, //  #  ######0x00, 0x00, 0x00, //    ########0x00, 0x00, 0x00, //     ######  #0x00, 0x00, 0x00, //            ###0x00, 0x00, 0x00, //            ###0x00, 0x00, 0x00, //            ###0x00, 0x00, 0x00, //            ###0x00, 0x00, 0x00, //            ###0x00, 0x00, 0x00, //            ###0x00, 0x00, 0x00, //            ###0x00, 0x00, 0x00, //     ######  #0x00, 0x00, 0x00, //    ########0x00, 0x00, 0x00, //     ######
Решение

Самое простое решение – скопировать нужные битовые строки от других цифр, тем более сама цифра «5» уже прорисована в комментариях

0x00, 0x00, 0x00, //                  0x07, 0xE0, 0x00, //     ######0x0F, 0xF0, 0x00, //    ########0x27, 0xE0, 0x00, //  #  ######0x70, 0x00, 0x00, // ###0x70, 0x00, 0x00, // ###0x70, 0x00, 0x00, // ###0x70, 0x00, 0x00, // ###0x70, 0x00, 0x00, // ###0x70, 0x00, 0x00, // ###0x70, 0x00, 0x00, // ###0x27, 0xE0, 0x00, //  #  ######0x0F, 0xF0, 0x00, //    ########0x07, 0xE4, 0x00, //     ######  #0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x00, 0x0E, 0x00, //            ###0x07, 0xE4, 0x00, //     ######  #0x0F, 0xF0, 0x00, //    ########0x07, 0xE0, 0x00, //     ######

2.Файл ws2812.c и функция WS2812_LedConvert.

Описание

Дан прототип функции

void WS2812_LedConvert(volatile uint8_t * pos, uint16_t color, uint8_t bright){//bright 0..100//testfor(int i=0; i<24; i++) pos[i] = WS_ZERO; //WS_ONE//test_end}

И выдана документация на адресные светодиоды WS2812, в которой расписан протокол передачи данных

Решение

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

Функция преобразует 16-битный цвет rgb565 в 24-битный цвет, только учитывая коэффициент яркости. Если не знать последовательность цветов RGB или BGR, можно просто проверить на примере

void WS2812_LedConvert(volatile uint8_t * pos, uint16_t color, uint8_t bright){uint16_t color_r = ((color >> 11) & 0x1F) * bright / 100;uint16_t color_g = ((color >>  5) & 0x3F) * bright / 100;uint16_t color_b = ((color >>  0) & 0x1F) * bright / 100;for(int i=0; i<8; i++){if(color_g & (uint16_t)1<<(7-i)) pos[i] = WS_ONE;else pos[i] = WS_ZERO;}for(int i=0; i<8; i++){if(color_r & (uint16_t)1<<(7-i)) pos[8+i] = WS_ONE;else pos[8+i] = WS_ZERO;}for(int i=0; i<8; i++){if(color_b & (uint16_t)1<<(7-i)) pos[16+i] = WS_ONE;else pos[16+i] = WS_ZERO;}}

Тут необходимо поработать с битовыми операциями.

3.Файл bq25896.c b и функции BQ25896_GetBatteryVoltage, BQ25896_GetVsysVoltage, BQ25896_GetVBUSVoltage.

Описание

Три функции имеют одинаковое решение. У участников есть документация на микросхему BQ25896, в ней есть описания соответствующих регистров регистров

Решение

Думаю, что, поняв решение для одной функции, можно применить его для всех трех. Останется только разобраться, как читать регистры по I2C, но рядом есть другие функции, в которых уже реализовано чтение данных из этой микросхемы. Нужно только разобраться со смещением, маской и битовыми операциями, вот мой вариант решения:

uint16_t BQ25896_GetBatteryVoltage(void){ //mVbq25896_reg0E_t reg0E;HAL_I2C_Mem_Read(&hi2c_BQ25896, BQ25896_ADDR, 0x0E, 1, &reg0E.reg, 1, BQ25896_I2C_TIMEOUT);if(reg0E.bit.BATV == 0) return 0;else                    return ((uint16_t)reg0E.bit.BATV * 20) + 2304;}uint16_t BQ25896_GetVsysVoltage(void){ //mVbq25896_reg0F_t reg0F;HAL_I2C_Mem_Read(&hi2c_BQ25896, BQ25896_ADDR, 0x0F, 1, &reg0F.reg, 1, BQ25896_I2C_TIMEOUT);if(reg0F.bit.VSYS == 0) return 0;else                    return ((uint16_t)reg0F.bit.VSYS * 20) + 2304;}uint16_t BQ25896_GetVBUSVoltage(void){ //mVbq25896_reg11_t reg11;HAL_I2C_Mem_Read(&hi2c_BQ25896, BQ25896_ADDR, 0x11, 1, &reg11.reg, 1, BQ25896_I2C_TIMEOUT);if(reg11.bit.VBUSV == 0) return 0;else                     return ((uint16_t)reg11.bit.VBUSV * 100) + 2600;}Структура типов уже определена в файле bq25896.h:typedef union{struct{uint8_t BATV               :7;uint8_t THERM_STAT         :1;}bit;uint8_t reg;}bq25896_reg0E_t;typedef union{struct{uint8_t VSYS               :7;uint8_t RESERVE            :1;}bit;uint8_t reg;}bq25896_reg0F_t;typedef union{struct{uint8_t TSPCT              :7;uint8_t RESERVE            :1;}bit;uint8_t reg;}bq25896_reg10_t;

4.Файл ad5245.c и функции AD5245_SetVolume и AD5245_GetVolume

Описание

В данном задании необходимо тоже смотреть документацию на микросхемы. Из отличия от предыдущего задания – общение происходит с двумя разными устройствами на шине I2C. Информация о протоколе общения есть:

Решение
void AD5245_SetVolume(uint16_t Channel, uint8_t volume){uint8_t data[2] = {0, volume};HAL_I2C_Master_Transmit(&hi2c_AD5245, Channel, data, 2, I2C_TIMEOUT);}uint8_t AD5245_GetVolume(uint16_t Channel){uint8_t data;HAL_I2C_Master_Receive(&hi2c_AD5245, Channel, &data, 1, I2C_TIMEOUT);return data;}

Для упрощения, передаваемая переменная Channel – аудиоканал с принимаемыми значениям: AD5245_LEFT_CHANNEL и AD5245_RIGHT_CHANNEL. А эти значения объявлены в ad5245.h как соответствующие адреса микросхем на шине I2C

Алгоритмическое программирование. Задание

В контроллере кассового аппарата реализовано сервисное меню, состоящее из 8 основных пунктов. Переключение между пунктами меню происходит с помощью клавиш «F1» (вверх) и «F2» (вниз).

При перемещении по пунктам меню, подсвечиваются только активные клавиши.

1.«CLOCK» — отображение времени и даты.

Клавиша «RETURN» активирует настойку времени и даты

В режиме настройки времени и даты:

  • Настраиваемый сегмент на экране подсвечивается с помощью инверсии цвета.

  • Активные клавиши NUM_0 — NUM_9 задают числовое значение сегмента.

  • Клавиша «CLR» – оставляет текущий сегмент без изменений.

  • Настройка заканчивается после установки последнего сегмента или по нажатию клавиши «ENTER».

2.«RADIO» — синхронизация точного времени по радиоканалу

Клавиша «POWER» запускает и прекращает синхронизацию.

На экране отображаются последние принятые данные.

В режиме синхронизации на экране появляется надпись «WAIT» и производится непрерывная обработка принятого радиосигнала. Принятая временная последовательность для отладки выводится по UART на скорости 115200 бод. В случае успешного декодирования принятой посылки, на экране отобразится принятое точное время.

3.«RFID» — чтение данных с RFID-карт

Активных клавиш нет

В режиме ожидания на экране отображается надпись «WAIT», либо последние принятые данные. При успешном считывании данных с RFID карты, на экране отображаются актуальные считанные данные, проигрывается звуковой файл «icq.wav» с карты памяти, а также подсвечивается область индикации беспроводного модуля.

4.«MUSIC» — формирование звуковых сигналов. Чтение WAV файла с SD-карты

Клавиша «NUM_1» запускает воспроизведение файла

Клавиша «NUM_0» останавливает воспроизведение файла

Клавиша «PLUS» увеличивает громкость

Клавиша «MINUS» уменьшает громкость

При запуске воспроизведения файла, начинает проигрываться звуковой файл «music1.wav» с карты памяти

5.«MICROPHONE» — оцифровка и вывод данных состояния микрофона на экран в виде графика

Активных клавиш нет

График отображает считанное значение сигнала с микрофона встроенным АЦП в режиме реального времени.

6.«MPU» — отображение действительных значений углов ориентации устройства и температуры на экран

Активных клавиш нет

В данном меню отображаются состояния ускорений по осям, считанные с акселерометра, а также значение его температуры.

В границах зеленого квадрата находится красный шарик, который перемещается пропорционально значениям Ax и Ay.

7.«COLOR» — настройка яркости экрана и подсветки клавиш. Чтение с SD карты и вывод на экран графического изображения

Клавиша «PLUS» увеличивает яркость

Клавиша «MINUS» уменьшает яркость

8.«POWER» — отображение действующих значений тока заряда, напряжений питания и аккумуляторной батареи на экране

Клавиша «DOT» разрешает/запрещает зарядку аккумулятора

Клавиша «RETURN» разрешает/запрещает работу повышающего преобразователя

Клавиша «POWER» подготавливает контроллер к отключению питания. При отсутствии питания VBUS, контроллер выключится.

На втором этапе участнику выдавался полный проект, в котором нарушены или отсутствуют алгоритмические связи. Время выполнения задания составляло 3 часа. Участник должен был доработать участки кода до обеспечения полной функциональности и логики работы устройства в соответствии с Конкурсным заданием. Все библиотеки драйверов, выданные с проектом, полностью функциональны и не требуют изменений. Доработки требует только основная логика программы:

  1. Реализовать ручную настройку часов реального времени с помощью кнопок

  2. Реализовать декодер синхронизации точного времени по радиоканалу

  3. Добавить пункт меню «RFID»

  4. Реализовать вывод графика состояния входа микрофона

  5. Отобразить красный шарик и реализовать его движение с ускорениями, пропорциональными наклону устройства

К оценке принимались файлы: папка с исправленным проектом и HEX-файл прошивки

Для оценки результатов работы загружали HEX файл в эталонное устройство, а при его отсутствии была выполнена компиляция проекта и его загрузка в эталонное устройство. (наличие HEX файла давало дополнительные баллы)

Оценка результатов работы производилась по функциональности эталонной платы.

Алгоритмическое программирование. Решения

1.Реализовать ручную настройку часов реального времени с помощью кнопок

Описание

Мне кажется, что это задание одно из самых трудоемких по решению, нужно написать кучу обработчиков кнопок, подсветить нужные кнопки, настроить часы. В качестве входных данных есть место в коде с комментарием:

if(Buttons_IsPressed(BUTTON_RETURN) == SET){LED_Set(LED_F1, COLOR_BLACK, 0);LED_Set(LED_F2, COLOR_BLACK, 0);LED_Set(LED_NUM_0, COLOR_GREEN, light);LED_Set(LED_NUM_1, COLOR_GREEN, light);LED_Set(LED_NUM_2, COLOR_GREEN, light);LED_Set(LED_NUM_3, COLOR_GREEN, light);LED_Set(LED_NUM_4, COLOR_GREEN, light);LED_Set(LED_NUM_5, COLOR_GREEN, light);LED_Set(LED_NUM_6, COLOR_GREEN, light);LED_Set(LED_NUM_7, COLOR_GREEN, light);LED_Set(LED_NUM_8, COLOR_GREEN, light);LED_Set(LED_NUM_9, COLOR_GREEN, light);LED_Set(LED_CLR,  COLOR_PURPLE, light);do Buttons_UpdateState(); while (Buttons_IsPressed(BUTTON_RETURN) == SET);//==============================================//начало блока настройки часов реального времениdo{Buttons_UpdateState();}while (Buttons_IsPressed(BUTTON_RETURN) == RESET);HAL_RTC_SetTime(&hrtc, &RTC_Time, RTC_FORMAT_BIN);HAL_RTC_SetDate(&hrtc, &RTC_Date, RTC_FORMAT_BIN);// конец блока настройки часов реального времени//==============================================for(int i = 3; i <= 18; i++) LED_Set((leds_e)i, COLOR_BLACK, 0);LED_Set(LED_F1, COLOR_BLUE, light);LED_Set(LED_F2, COLOR_BLUE, light);do Buttons_UpdateState(); while (Buttons_IsPressed(BUTTON_RETURN) == SET);}
Решение

Я для удобства отладки и быстрого написания, сделал следующим образом:

//==============================================//начало блока настройки часов реального времениuint8_t clock_time_pos = 0;do{Buttons_UpdateState();GUI_change_clock(&RTC_Time, &RTC_Date, &clock_time_pos);HAL_Delay(250);}while ( (Buttons_IsPressed(BUTTON_RETURN) == RESET) & (clock_time_pos < 12) );HAL_RTC_SetTime(&hrtc, &RTC_Time, RTC_FORMAT_BIN);HAL_RTC_SetDate(&hrtc, &RTC_Date, RTC_FORMAT_BIN);// конец блока настройки часов реального времени//==============================================А сама функция GUI_change_clock написана далеко не оптимально:void GUI_change_clock(RTC_TimeTypeDef* RTC_Time, RTC_DateTypeDef* RTC_Date, uint8_t* pos){FlagStatus flag = SET;uint16_t tmp_number;uint16_t x_pos;uint16_t y_pos;if(*pos < 6) y_pos = 55;else y_pos = 105;switch (*pos){case  0:x_pos = 104 + (*pos) * 17;tmp_number = RTC_Time->Hours / 10;tmp_number %= 10;break;case  1:x_pos = 104 + (*pos) * 17;tmp_number = RTC_Time->Hours % 10;break;case  2:x_pos = 104 + (*pos + 1) * 17;tmp_number = RTC_Time->Minutes / 10;break;case  3:x_pos = 104 + (*pos + 1) * 17;tmp_number = RTC_Time->Minutes % 10;break;case  4:x_pos = 104 + (*pos + 2) * 17;tmp_number = RTC_Time->Seconds / 10;break;case  5:x_pos = 104 + (*pos + 2) * 17;tmp_number = RTC_Time->Seconds % 10;break;case  6:x_pos = 87 + (*pos - 6) * 17;tmp_number = RTC_Date->Date / 10;break;case  7:x_pos = 87 + (*pos - 6) * 17;tmp_number = RTC_Date->Date % 10;break;case  8:x_pos = 87 + (*pos - 6 + 1) * 17;tmp_number = RTC_Date->Month / 10;break;case  9:x_pos = 87 + (*pos - 6 + 1) * 17;tmp_number = RTC_Date->Month % 10;break;case 10:x_pos = 87 + (*pos - 6 + 4) * 17;tmp_number = RTC_Date->Year / 10;break;case 11:x_pos = 87 + (*pos - 6 + 4) * 17;tmp_number = RTC_Date->Year % 10;break;}LCD_PrintfClock(ALIGNMENT_LEFT, x_pos,  y_pos,  COLOR_BLACK, COLOR_GREEN, "%1d", tmp_number);do{Buttons_UpdateState();if(Buttons_IsPressed(BUTTON_NUM_0) == SET) { tmp_number = 0; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_1) == SET) { tmp_number = 1; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_2) == SET) { tmp_number = 2; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_3) == SET) { tmp_number = 3; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_4) == SET) { tmp_number = 4; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_5) == SET) { tmp_number = 5; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_6) == SET) { tmp_number = 6; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_7) == SET) { tmp_number = 7; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_8) == SET) { tmp_number = 8; flag = RESET; }if(Buttons_IsPressed(BUTTON_NUM_9) == SET) { tmp_number = 9; flag = RESET; }}while ((Buttons_IsPressed(BUTTON_RETURN) == RESET) & (Buttons_IsPressed(BUTTON_CLR) == RESET) & (flag == SET));switch (*pos){case  0:RTC_Time->Hours = RTC_Time->Hours % 10 + tmp_number * 10;break;case  1:RTC_Time->Hours = RTC_Time->Hours - RTC_Time->Hours % 10 + tmp_number;break;case  2:RTC_Time->Minutes = RTC_Time->Minutes % 10 + tmp_number * 10;break;case  3:RTC_Time->Minutes = RTC_Time->Minutes - RTC_Time->Minutes % 10 + tmp_number;break;case  4:RTC_Time->Seconds = RTC_Time->Seconds % 10 + tmp_number * 10;break;case  5:RTC_Time->Seconds = RTC_Time->Seconds - RTC_Time->Seconds % 10 + tmp_number;break;case  6:RTC_Date->Date = RTC_Date->Date % 10 + tmp_number * 10;break;case  7:RTC_Date->Date = RTC_Date->Date - RTC_Date->Date % 10 + tmp_number;break;case  8:RTC_Date->Month = RTC_Date->Month % 10 + tmp_number * 10;break;case  9:RTC_Date->Month = RTC_Date->Month - RTC_Date->Month % 10 + tmp_number;break;case 10:RTC_Date->Year = RTC_Date->Year % 10 + tmp_number * 10;break;case 11:RTC_Date->Year = RTC_Date->Year - RTC_Date->Year % 10 + tmp_number;break;}LCD_PrintfClock(ALIGNMENT_LEFT, x_pos,  y_pos,  COLOR_GREEN, COLOR_BLACK, "%1d", tmp_number);if(Buttons_IsPressed(BUTTON_RETURN) == SET) *pos = 12;else *pos +=1;}

Считаю задание самым трудоемким

2.Реализовать декодер синхронизации точного времени по радиоканалу

Описание

В центре помещения я разместил ноутбук с передатчиком, который раз в 2 секунды передает сигнал точного времени. Полученный сигнал у каждого устройства, в исходном варианте, транслируется по UART в виде массива временных данных.

В коде есть функция

decoder_time_t Decode(uint16_t * buf, uint16_t size){decoder_time_t decoded;//флаг успешного декодирования принятой посылкиdecoded.state = ERROR;//=====================================================//начало блока декодера принятой посылки по радиоканалу// конец блока декодера принятой посылки по радиоканалу//=====================================================return decoded;}

Описание протокола, про который я писал ранее, участникам выдано в качестве приложения к заданию

Решение

Для решения нужно делать проверку принятых данных и искать подходящий сигнал с отклонением по времени в 20%

Моё решение не оптимальное, но я пытался специально реализовать его за минимально потраченное время:

typedef struct{ErrorStatus state;uint8_t h;uint8_t m;uint8_t s;}decoder_time_t;decoder_time_t Decode(uint16_t * buf, uint16_t size){decoder_time_t decoded;//флаг успешного декодирования принятой посылкиdecoded.state = ERROR;//=====================================================//начало блока декодера принятой посылки по радиоканалуfor(int pos = 0; pos < size-54; pos++){uint8_t h = 0, m = 0, s = 0;FlagStatus flag = SET;for(int i=0; i<5;i++){if( (buf[pos + i] < 1000-200) || (buf[pos + i] > 1000+200) ) flag = RESET;}if(flag != RESET){for(int i=0; i<8;i++){if( (buf[pos + 5 + 2*i] < 300 + 60) && (buf[pos + 5 + 2*i] > 300 - 60) ){if( (buf[pos + 5 + 2*i + 1] < 600 + 120) && (buf[pos + 5 + 2*i + 1] > 600 - 120) ){}else{flag = RESET;}}else if( (buf[pos + 5 + 2*i] < 600 + 120) && (buf[pos + 5 + 2*i] > 600 - 120) ){if( (buf[pos + 5 + 2*i + 1] < 300 + 60) && (buf[pos + 5 + 2*i + 1] > 300 - 60) ){h |= 1<<(7-i);}else{flag = RESET;}}else{flag = RESET;}}}if(flag != RESET){for(int i=0; i<8;i++){if( (buf[pos + 5 + 16 + 2*i] < 300 + 60) && (buf[pos + 5 + 16 + 2*i] > 300 - 60) ){if( (buf[pos + 5 + 16 + 2*i + 1] < 600 + 120) && (buf[pos + 5 + 16 + 2*i + 1] > 600 - 120) ){}else{flag = RESET;}}else if( (buf[pos + 5 + 16 + 2*i] < 600 + 120) && (buf[pos + 5 + 16 + 2*i] > 600 - 120) ){if( (buf[pos + 5 + 16 + 2*i + 1] < 300 + 60) && (buf[pos + 5 + 16 + 2*i + 1] > 300 - 60) ){m |= 1<<(7-i);}else{flag = RESET;}}else{flag = RESET;}}}if(flag != RESET){for(int i=0; i<8;i++){if( (buf[pos + 5 + 32 + 2*i] < 300 + 60) && (buf[pos + 5 + 32 + 2*i] > 300 - 60) ){if( (buf[pos + 5 + 32 + 2*i + 1] < 600 + 120) && (buf[pos + 5 + 32 + 2*i + 1] > 600 - 120) ){}else{flag = RESET;}}else if( (buf[pos + 5 + 32 + 2*i] < 600 + 120) && (buf[pos + 5 + 32 + 2*i] > 600 - 120) ){if( (buf[pos + 5 + 32 + 2*i + 1] < 300 + 60) && (buf[pos + 5 + 32 + 2*i + 1] > 300 - 60) ){s |= 1<<(7-i);}else{flag = RESET;}}else{flag = RESET;}}}if(flag != RESET){decoded.h = h;decoded.m = m;decoded.s = s;decoded.state = SUCCESS;return decoded;}}// конец блока декодера принятой посылки по радиоканалу//=====================================================return decoded;}

3.Добавить пункт меню «RFID»

Описание

Самое простое задание, на мой взгляд. Просто в списке отсутствует пункт меню. Нужно разобраться как вызывается меню, так как функция этого меню уже реализована.

Есть массив

menu_node_t menu_node[] = {{"TEST CLOCK",Menu_clock_func},{"RADIO",     Menu_radio_func},{"MUSIC",     Menu_music_func},{"MICROPHONE",Menu_micro_func},{"MPU",       Menu_mpu_func  },{"COLOR",     Menu_color_func},{"POWER",     Menu_power_func}};

Где каждый элемент массива – структура, которая содержит имя и указатель на функцию (имя функции)

typedef struct{char name[10];void (*menu_func)(void);}menu_node_t;
Решение

В списке не хватает одной функции, а все функции выделены в файле огромными комментариями (было удобно, пока сам писал задание)

//==============================================================================================//|                                                                                            |//|                                       2.MENU_RFID                                          |//|                                                                                            |//==============================================================================================uint8_t rfid_buf[RFID_BUF_SIZE] = {0};void Menu_rfid_func(void){LED_Set(LED_F1, COLOR_BLUE, light);LED_Set(LED_F2, COLOR_BLUE, light);. . .. . .

И необходимо просто добавить эту функцию в массив:

menu_node_t menu_node[] = {{"TEST CLOCK",     Menu_clock_func},{"RADIO",     Menu_radio_func},{"RFID",      Menu_rfid_func },{"MUSIC",     Menu_music_func},{"MICROPHONE",Menu_micro_func},{"MPU",       Menu_mpu_func  },{"COLOR",     Menu_color_func},{"POWER",     Menu_power_func}};

4.Реализовать вывод графика состояния входа микрофона

Описание

В основном пункте меню вызывается функция

//функция добавления точки на графикGUI_micro_put_dot(HAL_ADC_GetValue(&hadc1));А в файле gui.c есть заготовка:#define MICRO_LEFT_FRAME   39#define MICRO_UP_FRAME     39#define MICRO_RIGHT_FRAME 311#define MICRO_DOWN_FRAME  161#define MICRO_X_SIZE      MICRO_RIGHT_FRAME - MICRO_LEFT_FRAME - 1#define MICRO_Y_SIZE      MICRO_DOWN_FRAME  - MICRO_UP_FRAME - 1uint16_t micro_array[MICRO_X_SIZE] = {0};uint16_t micro_pos = 0;uint16_t dot_pos_y = 0;void GUI_micro_put_dot(uint16_t volume){dot_pos_y = ( MICRO_Y_SIZE - ((volume/4096.f) * (MICRO_Y_SIZE)) ) + MICRO_UP_FRAME;//=======================================//начало блока добавления точки на график// конец блока добавления точки на график//=======================================}
Решение

Здесь в решении я провел небольшую оптимизацию с круговым буфером, но в задании нигде не было требований по скорости

void GUI_micro_put_dot(uint16_t volume){ dot_pos_y = ( MICRO_Y_SIZE - ((volume/4096.f) * (MICRO_Y_SIZE)) ) + MICRO_UP_FRAME;//=======================================//начало блока добавления точки на графикmicro_pos++;if(micro_pos >= MICRO_X_SIZE) micro_pos = 0;micro_array[micro_pos] = dot_pos_y;uint16_t lcd_pos = MICRO_LEFT_FRAME + 1;for(int i = micro_pos + 1; i < MICRO_X_SIZE; i++){if(lcd_pos == MICRO_LEFT_FRAME + 1){LCD_VerticalLine(lcd_pos, MICRO_UP_FRAME+1, MICRO_DOWN_FRAME-1, COLOR_BLACK);}else{LCD_Pixel(lcd_pos, micro_array[i-1], COLOR_BLACK);}if(micro_array[i] > MICRO_UP_FRAME) LCD_Pixel(lcd_pos, micro_array[i], COLOR_ORANGE);lcd_pos++;}for(int i = 0; i <= micro_pos; i++){if(lcd_pos == MICRO_LEFT_FRAME + 1){LCD_VerticalLine(lcd_pos, MICRO_UP_FRAME+1, MICRO_DOWN_FRAME-1, COLOR_BLACK);}else{if(i == 0){LCD_Pixel(lcd_pos, micro_array[MICRO_X_SIZE-1], COLOR_BLACK);}else{LCD_Pixel(lcd_pos, micro_array[i-1], COLOR_BLACK);}}if(micro_array[i] > MICRO_UP_FRAME) LCD_Pixel(lcd_pos, micro_array[i], COLOR_ORANGE);lcd_pos++;}// конец блока добавления точки на график//=======================================}

5.Отобразить красный шарик и реализовать его движение с ускорениями, пропорциональными наклону устройства

Описание

В файле программы есть заготовка

float x_pos = MPU_LEFT_FRAME + (MPU_RIGHT_FRAME - MPU_LEFT_FRAME)/2;float y_pos = MPU_UP_FRAME   + (MPU_DOWN_FRAME  - MPU_UP_FRAME  )/2;float x_last_pos = x_pos;float y_last_pos = y_pos;do{Buttons_UpdateState();MPU6050_ReadAll(&mpu6050);LCD_Printf(&Font16, ALIGNMENT_LEFT, MPU_RIGHT_FRAME + 35 + 45, MPU_UP_FRAME + 16, COLOR_GRAY_200, COLOR_BLACK, (mpu6050.Ax < 0) ? "%1.2f" : " %1.2f", (float)mpu6050.Ax);LCD_Printf(&Font16, ALIGNMENT_LEFT, MPU_RIGHT_FRAME + 35 + 45, MPU_UP_FRAME + 36, COLOR_GRAY_200, COLOR_BLACK, (mpu6050.Ay < 0) ? "%1.2f" : " %1.2f", (float)mpu6050.Ay);LCD_Printf(&Font16, ALIGNMENT_LEFT, MPU_RIGHT_FRAME + 35 + 45, MPU_UP_FRAME + 56, COLOR_GRAY_200, COLOR_BLACK, (mpu6050.Az < 0) ? "%1.2f" : " %1.2f", (float)mpu6050.Az);LCD_Printf(&Font16, ALIGNMENT_LEFT, MPU_RIGHT_FRAME + 35 + 45, MPU_UP_FRAME + 76, COLOR_GRAY_200, COLOR_BLACK, (mpu6050.Temperature < 0) ? "%2.1f" : " %2.1f", (float)mpu6050.Temperature);//========================================//начало блока отображения красного шарикаGUI_draw_ball(x_last_pos, y_last_pos, COLOR_BLACK);GUI_draw_ball(x_pos, y_pos, COLOR_RED);// конец блока отображения красного шарика//========================================}while ( (Buttons_IsPressed(BUTTON_F1) == RESET) && (Buttons_IsPressed(BUTTON_F2) == RESET) );

И уже есть функция отрисовки шарика

Решение
//========================================//начало блока отображения красного шарикаif( (x_pos - 5 > MPU_LEFT_FRAME)  && (mpu6050.Ax > 0) ) x_pos -= mpu6050.Ax;if( (x_pos + 4 < MPU_RIGHT_FRAME) && (mpu6050.Ax < 0) ) x_pos -= mpu6050.Ax;if( (y_pos - 5 > MPU_UP_FRAME)    && (mpu6050.Ay < 0) ) y_pos += mpu6050.Ay;if( (y_pos + 4 < MPU_DOWN_FRAME)  && (mpu6050.Ay > 0) ) y_pos += mpu6050.Ay;if( ((uint16_t) x_pos != (uint16_t) x_last_pos) || ((uint16_t) y_pos != (uint16_t) y_last_pos)){GUI_draw_ball(x_last_pos, y_last_pos, COLOR_BLACK);x_last_pos = x_pos;y_last_pos = y_pos;}GUI_draw_ball(x_pos, y_pos, COLOR_RED);// конец блока отображения красного шарика//========================================

Задания готовы, устройства собраны, а я поехал за тысячу километров и повез задание для участников.

А если вы дочитали до этого места, то можете оценить: выполнимые были задания или нет.

Спасибо за внимание.

ссылка на оригинал статьи https://habr.com/ru/articles/1037350/