Русский
Русский
English
Статистика
Реклама

Микроконтроллеры

Перевод Программирование голосом. Передовые рубежи разработки ПО

06.04.2021 12:11:10 | Автор: admin

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


Работа в Serenade

Технологическая основа


Мы все больше и больше взаимодействуем с гаджетами через речь. Наши старые виртуальные помощники вроде Alexa и Siri теперь объединяются с автомобильными ассистентами, например Apple CarPlay и Android Auto, а также с приложениями, работающими с голосовой биометрикой и командами. Но что, если саму технологию можно было бы создавать при помощи голоса?

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

Однако программирование голосом оказывается не столь простым и кроет в себе несколько слоев сложной технологии. К примеру, в Serenade используется движок преобразования речи в текст, созданный специально для работы с кодом, в отличие от аналогичного API Speech-to-text, спроектированного Google для разговорной речи. После того, как инженер проговаривает код, движок Serenade передает его в слой обработки естественного языка, в котором специально обученные модели машинного обучения (МО) распознают и переводят стандартные программные конструкции в синтаксически верный код.

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




Технология против офисного синдрома


Компания Serenade, собравшая $2.1 миллиона на этапе начального финансирования в 2020 году, родилась в силу необходимости, после того, как одному из ее основателей, Мэтту Уитхоффу, в 2019 году бы поставлен диагноз хроническая травма от повторяющегося напряжения. Я бросил работу разработчиком ПО в Quora, потому что просто перестал с ней справляться, говорит Мэтт. В качестве альтернативы можно было заняться деятельностью, не требующей такого количества набора текста, либо искать другой выход.

Тем же путем в 2017 году пошел Райан Хайлман, оставив свое постоянное место работы программистом, после того как годом ранее у него возникли сильные боли в руке. Тогда Тайлман занялся разработкой Talon, платформы для написания кода без использования рук. Он говорил: Задача Talon полностью заменить клавиатуру и мышь для любого человека.

С этой целью в ней реализовано три программных компонента: распознавание речи, отслеживание глаз и распознавание звука. Движок распознавания речи основан на разработанной Facebook автоматической системе Wav2letter, которую Тайлман расширил командами для программирования. При этом функция отслеживания глаз и распознавания звука симулирует управление с помощью мыши. Курсор перемещается по экрану вслед за перемещением взгляда, а клики совершаются в ответ на издаваемый ртом звук, напоминающий откупоривание шампанского. Как говорит Райан: Издать такой звук легко. Он не требует особых усилий и распознается очень быстро, что представляет более быстрый невербальный способ реализации щелчков мыши, не вызывающий голосового напряжения.

Написание кода с помощью Talon звучит подобно инопланетному языку, как это продемонстрировала Эмили Ши, инженер и практик данной техники, выступавшая на конференции в 2019 году. Предложенное ей видео наполнено командами типа slap (Ввод), undo, spring 3 (перейти к третьей строке файла) и phrase name op equals snake extract word paren mad (дает такую строку: name = extract_word(m)).

С другой стороны, при написании кода с помощью Serenade, команды проговариваются более естественным образом. Можно сказать delete import, чтобы удалить инструкцию импорта в начале файла или build, чтобы выполнить пользовательскую команду сборки. Также можно сказать add function factorial для создания функции, вычисляющей факториал, например, в JavaScript. В этом случае приложение полностью возьмет на себя синтаксис, включая ключевое слово function, скобки и фигурные скобки, избавив вас от необходимости явно проговаривать каждый элемент.

Для программирования голосом требуется хороший микрофон, особенно если вы хотите устранить фоновый шум. Тем не менее модели Serenade обучены на аудио, получаемом с микрофонов ноутбуков. Если же вы хотите использовать Talon с функцией отслеживания движения глаз, то также потребуется соответствующее оборудование. Хотя Talon отлично работает и без него. Открытые платформы для написания кода голосом, такие как Aenea и Caster, хоть и бесплатны, но обе опираются на движок распознавания речи Dragon, который пользователь должен приобретать отдельно. При этом Caster предлагает поддержку Kaldi, открытого набора инструментов для распознавания речи, а также Windows Speech Recognition, поставляемого с ОС Windows по умолчанию.

По словам сооснователя Serenade Labs, Томми МакУилльям, результаты говорят сами за себя: Описать желаемое действие становится намного проще. Ведь куда легче просто сказать move these three lines down или duplicate this method вместо того, чтобы печатать все эти инстуркции или даже использовать горячие клавиши.

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

Написание кода с помощью речи, помимо прочего, снижает барьер входа в разработку ПО. МакУилльям по этому поводу говорит так: Если человек сможет представить код, который он хочет написать, логически и структурированно, то остальное можно будет поручить алгоритму МО, который переведет мысли разработчика в синтаксически правильный код.

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

Подробнее..

Начинаем писать под stm8, выбираем среды разработки и стартуем

28.04.2021 12:07:29 | Автор: admin
image

На пути в программировании stm8 есть развилка, о ней сегодня и поговорим.

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

Первый установка ST Visual Develop и выбор в качестве компилятора COSMIC Бывший платный, а ныне бесплатный, но со своими заморочками; регистрация, получение ключа, и прочие танцы с бубном.

Второй же вариант, более простой VS Code + PlatformIO и компилятор SDCC полностью свободный. И опять же не все так просто. Sdcc не умеет исключать не используемые функции. Я решил этот вопрос хоть и успешно, но не без дополнительных действий при написании кода.

Первая среда, для любителей всё делать правильно


Для начала нам нужен ST Visual Develop. Устанавливаем и ставим запуск ярлыка всегда от администратора. В придачу к нему нам дают ST Visual Programmer, полезный инструмент, особенно когда стоит защита от записи и надо разблокировать микроконтроллер, а ведь китайские blue pill всегда приходят заблокированными. Китайцы бояться что мы украдём их круто оптимизированный Blink.

Вот так выглядит STVD
image
Я так понял её создали, когда в моде были 16 битные цвета...

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

После пройденного лабиринта из форм и запросов, когда всё уже установлено, начнём
image
image
При первом запуске нужно указать расположение ключа, его лучше поместить в директорию компилятора. После создания нажимаем F7, ошибок быть не должно. Если писать на чистых регистрах, то всё готово, но я такой хардкор не люблю, поэтому продолжим и добавим SPL библиотеку.

Распакуем куда-нибудь и заходим в папку STM8S_StdPeriph_Lib\Project\STM8S_StdPeriph_Template. Тут у нас шаблон проекта stm8s_conf.h это конфигурационный файл библиотеки, через него выбирается контроллер. Зайдём в main тут сразу с первых строк #include "stm8s.h" это ссылка на основную библиотеку, а так же кусок кода отладки который начинается с #ifdef USE_FULL_ASSERT, без отладочного кода будут сыпаться ошибки.

Теперь когда мы прошлись по верхам давайте пробовать запускать библиотеку. Добавляем в проект из шаблона main и конфигурационный файл в. В include files добавляем всё из STM8S_StdPeriph_Lib\Libraries\STM8S_StdPeriph_Driver\inc.
image
Теперь всё должно собраться.

Добавим stm8s_gpio.c и соберём простецкую мигалку. У меня один из светодиодов висит на D3, конфигурация ноги на выход выглядит так:

GPIO_DeInit(GPIOD);GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_OUT_PP_LOW_SLOW);

Её вписываем в main до бесконечного цикла.

А вот функция смены состояния. GPIO_WriteReverse(GPIOD, GPIO_PIN_3); вписываем её в бесконечный цикл.

Но вот незадача, в SPL нету функций задержки, скопипастим из примера GPIO в библиотеке. Там она выглядит следующим образом.

void Delay(uint16_t nCount){  /* Decrement nCount value */  while (nCount != 0)  {    nCount--;  }}

Впишем её в конец перед #ifdef USE_FULL_ASSERT. Так же впишем её прототип в начало, где под это выделено место в шаблонном main.

/* Private function prototypes -----------------------------------------------*/void Delay (uint16_t nCount);

Ну и наконец впишем функцию со значением в бесконечный цикл после функции смены состояния: Delay(0xFFFF);

Подключаем ST-Link и прошиваем, для этого нажимаем Start Debugging и Run. Светодиод моргает значит всё хорошо.

У кого не получилось вот полный main.c
/**  ******************************************************************************  * @file    Project/main.c   * @author  MCD Application Team  * @version V2.3.0  * @date    16-June-2017  * @brief   Main program body   ******************************************************************************  * @attention  *  * <h2><center> COPYRIGHT 2014 STMicroelectronics</center></h2>  *  * Licensed under MCD-ST Liberty SW License Agreement V2, (the "License");  * You may not use this file except in compliance with the License.  * You may obtain a copy of the License at:  *  *        http://www.st.com/software_license_agreement_liberty_v2  *  * Unless required by applicable law or agreed to in writing, software   * distributed under the License is distributed on an "AS IS" BASIS,   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  *  ******************************************************************************  */ /* Includes ------------------------------------------------------------------*/#include "stm8s.h"/* Private defines -----------------------------------------------------------*//* Private function prototypes -----------------------------------------------*/void Delay (uint16_t nCount);/* Private functions ---------------------------------------------------------*/void main(void){GPIO_DeInit(GPIOD);GPIO_Init(GPIOD, GPIO_PIN_3, GPIO_MODE_OUT_PP_LOW_SLOW);  /* Infinite loop */  while (1)  {GPIO_WriteReverse(GPIOD, GPIO_PIN_3);    Delay(0xFFFF);  }  }void Delay(uint16_t nCount){  /* Decrement nCount value */  while (nCount != 0)  {    nCount--;  }}#ifdef USE_FULL_ASSERT/**  * @brief  Reports the name of the source file and the source line number  *   where the assert_param error has occurred.  * @param file: pointer to the source file name  * @param line: assert_param error line source number  * @retval : None  */void assert_failed(u8* file, u32 line){   /* User can add his own implementation to report the file name and line number,     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */  /* Infinite loop */  while (1)  {  }}#endif/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/


Теперь посмотрим со стороны на эту среду.

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

Вторая среда, для тех кто не любит заморачиваться.


Второй подход это свободный и обновляемый компилятор SDCC, а так же среда PlatformIO.

Для начала установим VS Code и станем рабами Microsoft, далее найдём расширение PlatformIO.

image

Ждём пока миллион ползунков пройдёт до конца и перезапускаем программу. Открываем расширение (может быть и само откроется). Создаём новый проект и выбираем ближайшую плату с stm8s (микроконтроллер можно будет изменить в конфигурационном файле). В качестве фреймворка выбираем SPL мы же не ардуинщики, нас интересует хардкор.

Не удивляйтесь, проект будет создаваться прилично долго, потому как он на ходу будет подгружать компилятор, библиотеки и тд. И вот перед нами возник девственно голый проект, закрываем его он нам больше не нужен. В меню где мы создавали проект, открываем пример spl-blink, суть в том что blink генерирует неплохой шаблон в котором можно сразу писать. Правим пример под себя и прошиваем, всё моргает.

Разберем поподробнее среду разработки. Во первых весь проект должен лежать в src, иначе среда ведёт себя неадекватно. Во вторых открываем stm8s_conf.h и видим что все библиотеки кроме GPIO закомментированы, если этого не сделать то у мк не хватит памяти что бы поместить весь SPL в микроконтроллер (помните в начале я говорил что он загружает все функции что видит в код?).

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

/* Private define ------------------------------------------------------------*/#define _GPIO_DeInit#define _GPIO_Init#define _GPIO_WriteReverse#define _CLK_DeInit#define _CLK_SYSCLKConfig#define _TIM4_DeInit#define _TIM4_ITConfig#define _TIM4_ClearITPendingBit#define _TIM4_Cmd#define _TIM4_TimeBaseInit#define _TIM4_ClearFlag#define _HAL_GPIO_WritePin#define _GPIO_WriteLow#define _GPIO_WriteHigh//#define STM8S003/* Includes ------------------------------------------------------------------*/#include "stm8s.h"/* Uncomment the line below to enable peripheral header file inclusion */#if defined(STM8S105) || defined(STM8S005) || defined(STM8S103) || defined(STM8S003) ||\    defined(STM8S001) || defined(STM8S903) || defined (STM8AF626x) || defined (STM8AF622x)// #include "stm8s_adc1.h" #endif /* (STM8S105) ||(STM8S103) || (STM8S001) || (STM8S903) || (STM8AF626x) */#if defined(STM8S208) || defined(STM8S207) || defined(STM8S007) || defined (STM8AF52Ax) ||\    defined (STM8AF62Ax)// #include "stm8s_adc2.h"


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

Пройдёмся по преимуществам: работает почти из коробки, полностью бесплатно без попрошайничества, редакции языка обновляются и не придётся учить и переписывать код под си 80-90г. Сам интерфейс настраиваемый и намного приятнее. Для тех кто не любит win есть linux версия.

По минусам: нету полноценной отладки (над ней активно работают, но пока ничего приближённого к cosmic я не видел), в проекте небольшой хаос, раскидать по папкам пока нельзя.

Послевкусие.


Ну и на последок есть ещё среды, варианты и компиляторы, но либо это тот же SDCC вкрученный силой в Eclipse или ещё куда и работающий хуже чем в VS Code, либо это платные варианты IAR, Raisonance. Я лично пользуюсь и тем и тем, но чаще VS Code. Рекомендовать ничего не буду каждому своё, увидимся в комментариях)

Подробнее..

Перевод Чиптюн-музыка на ATtiny4 и трехцентовом Padauk

10.05.2021 16:07:24 | Автор: admin

Когда я услышал Bitshift Variations in С Minor Роба Майлза 16-минутный фрагмент 4-голосого полифонического аудио произведения мне очень захотелось воплотить такое аппаратно. Реализовать это на любом микроконтроллере слишком уж просто, поэтому я решил взять самый мелкий, какой смог найти ATtiny4. Чуть позже я портировал эту программу на небезызвестный трехцентовый микроконтроллер Padauk PMS150С.

Ах да при этом он полностью уместился в RCA-штекер и автоматически обнаруживает подключение.


Плата ATtiny4, внутренняя сборка и готовое устройство.


Как он работает


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

Генерация музыки


ATtiny имеет довольно мощный таймер/счетчик и выполняет двойную работу, генерируя как ШИМ-сигнал для аудио выхода, так и прерывания для генерации очередного PCM-сэмпла.
Говоря точнее, мы устанавливаем его в 8-битный не инвертированный ШИМ-режим без предварительного делителя и включаем прерывание переполнения. Это означает, что таймер отсчитывает от нуля до 255, используя ту же тактовую частоту 4МГц, что и ядро ЦПУ, ШИМ-выход поднимается от нуля до заданного коэффициента заполнения, и при достижении значения TOP сбрасывается к нулю, вызывая прерывание по переполнению.

Немного быстрых расчетов: самая высокая частота, на которой МК может работать, будучи запитанным от 3В таблетки, равна 4МГц. Эти 4МГц, поделенные на 256 шагов, дают нам базовую частоту ШИМ в 15.625КГц. Слегка откалибровав внутренний генератор, мы можем добиться ее округления до 16КГц. Поскольку частота дискретизации исходного тона равна 8КГц, то новый сэмпл нужно генерировать только раз в два переполнения/прерывания. Это оказывается весьма удобно, так как генерация сэмплов в итоге занимает немногим более 400 циклов.



На этом графике я отразил счетчик, его значение для сопоставления, итоговый выход ШИМ, а также прерывание по переполнению и коэффициент заполнения/выполнение подпрограммы sample, запускаемой этим прерыванием.

Ниже вы видите коэффициент заполнения и выход ШИМ на реальной микросхеме. Здесь отчетливо отражено, что в течение одного прерывания генерируется два сэмпла. (И этот вывод коэффициента заполнения/отладки позднее тоже пригодился, так как позволил откалибровать генератор через отслеживание частоты на осциллографе Rigol).


Канал 1 (нижний) показывает выход отладки, канал 2 (верхний) показывает сигнал ШИМ. Обратите также внимание на отображение частоты в верхнем правом углу.

Обнаружение подключения


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

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

На практике же все сложнее: типичное сопротивление линейного аудиовхода составляет 20-100кОм, что намного выше, чем у внутренних подтягивающих резисторов ATtiny, хотя это легко решается внешним подтягиванием. Кроме того, функциональность генерации прерывания при смене контактов недоступна на контакте выхода ШИМ, но и это легко обойти, просто закоротив их.

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

Фильтрация выхода


При использовании базовой частоты ШИМ 16кГц есть один недостаток: она слышима. Изначально я собирался просто это игнорировать, но один друг справедливо убедил меня добавить RC-фильтр нижних частот.

Быстрый и грубый спектральный анализ, сделанный с телефона, подтвердил, что эта частота действительно шумит. Границу среза и кривую отклика я выбрал довольно произвольно (глядя на коробку под SMD-компоненты) около 8кГц, тем не менее с задачей она справляется отлично, так что при заказе итогового списка материалов я придерживался именно ее.


Соотношение сигнал-шум после добавления фильтра существенно улучшилось.

Программное обеспечение


Разобравшись с теорией, осталось только прописать код. Я решил вручную воспроизвести Си программу Роба на ассемблере AVR, отчасти ради забавы, отчасти в качестве (поспешной?) стратегии по оптимизации. У ATtiny нет аппаратного mul/div/mod, и мне потребовалось лишь немного правосторонних множителей/делителей, для чего я написал несколько специальных оптимизированных вариантов.

Начал я с нетронутой программы Си и сначала просто ее упростил, после чего заменил каждую операцию на макрос Си, реализующий соответствующую инструкцию машинного кода. После каждого малейшего изменения я генерировал PCM-поток и сравнивал его с заведомо корректным образцом, чтобы избежать ошибок. Каждое изменение автоматически отправлялось в репозиторий, в результате чего получилось 136 коммитов под именем new version. Только затем я добавил код инициализации и произвел запуск на реальном микроконтроллере.

На этом этапе, сам того не ведая, я допустил ошибку при написании одного из псевдо-ASM макросов: я инвертировал условие ветвления в mod3, в результате чего оно переключалось, когда не должно было, и наоборот. Это привело к невозможности распознавания голосов 3 и 4 на микроконтроллере. Причину ошибки мне удалось обнаружить только год спустя, когда я вновь вернулся к проекту после того, как в simavr, наконец-таки, появилась элементарная поддержка семейства ATtiny 10. Когда я запустил gdb(1), проблема тут же стала очевидной, и для патча потребовалась всего одна инструкция машинного кода.

Гибкие печатные платы



Гибкие печатные платы, которые можно обернуть вокруг батарейки

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

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

И все же нужно было еще проложить два провода, идущих от платы к точкам пайки на внутренней стороне RCA-штекера. Задача оказалась непростой, и я не сразу додумался, как скрутить две части корпуса штекера, не прокручивая провода. Решением стало максимально глубоко задвинуть плату в корпус и слегка провернуть провода против часовой стрелки, чтобы при закрытии корпуса они, наоборот, развернулись.


Стек слоев в KiCad. PDF-схема.

Портирование на Padauk


При использовании ATtiny меня не покидало ощущение, что я мухлюю: он снабжен сравнительно богатой периферией и содержит много очень гибких регистров (16), которыми можно управлять напрямую. К тому же у меня завалялся самодельный программатор для микроконтроллеров Padauk, который подогнал мне один из участников форумов EEVBlog, а также около 500 штук PMS150C.

Эти микроконтроллеры прославились своей невысокой стоимостью около 3 американских центов за экземпляр при приобретении в сравнительно небольших количествах. За свою цену они неплохо оснащены: ПЗУ на 1024 слова (программируемых один раз), 64 байта статического ОЗУ, 8-битный таймер с ШИМ, (несколько странный) 16-битный таймер, внутренний компаратор и источник опорного напряжения. По мнению некоторых, набор инструкций Padauk во многом следует более старой модели PIC, и большинство его операций происходят в одном накапливающем регистре.

Недостаток же в том, что производитель поскупился на документацию: спецификация не описывает, как мнемоника ассемблера сопоставляется с реальными единицами и нулями, то есть вы должны использовать их проприетарные (и доступные только для Windows) IDE, программатор и внутрисхемный эмулятор. Эта IDE даже не имеет компилятора Си, а использует странный язык, который они зовут мини Си: по сути, это ассемблер с позаимствованным из Си синтаксическим сахаром.

Талантливая группа любителей во главе с js_12345678_55AA, tim_ (cpldcpu) и spth (pkk), невзирая на отсутствие вышестоящей поддержки, создала впечатляющую и полностью открытую цепочку инструментов Си, включая компилятор, ассемблер, компоновщик, дизассемблер, симулятор, программное и аппаратное обеспечение программатора, а также низкоуровневую документацию.


Внутренняя сборка Padauk-версии

Для портирования чиптюнов на PMS150С потребовалось полностью перевести исходный Си-код в ассемблер, чтобы наилучшим образом использовать сжатые требования к циклам (в рамках которых я оставался с трудом: в худшем случае использовалось 507 из 512 доступных циклов). После того, как в процессе поиска правильного способа инициализации периферии я сжег тестовыми программами 5 схем, потребовалось еще всего 2, чтобы добиться полноценной отладки программы.

Итого семь микросхем, но при этом гораздо больше попыток: на деле можно программировать одноразовую память несколько раз при условии изменения только единиц на нули. Так что я оставил немного пространства под векторами сброса и прерывания, подставил новую версию кода и исправил инструкции GOTO, заменив их на NOP и добавив новый переход сразу же после. Скажу честно, я не столь скуп, но на неоднократную возню с ZIF-разъемом ушло бы больше времени, чем на этот обходной вариант.


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

Версия Padauk имеет небольшие отличия в сравнении с версией ATtiny: во-первых, здесь я задействую оба таймера, что позволяет использовать более высокую базовую частоту ШИМ (64кГц) и обойтись без ФНЧ. Во-вторых, внутреннее подтягивание Padauk достаточно высокое, и внешнее уже не требуется. Это означает, что мне удалось добиться полного отсутствия внешних компонентов.

И все же без сложностей не обошлось: t1sn M.n (тест старшего бита в статической ОЗУ и пропуск следующей инструкции) и set1 M.n (установка бита в области статической ОЗУ) работают только для первых 16 адресов; по данному поводу в спецификации толком ничего не сказано (заметил я это лишь потому, что в документации по реконструированным наборам инструкций присутствовало 4-битное адресное поле). В симуляторе микроконтроллера ucism было несколько ошибок, связанных с этими (и аналогичными) инструкциями, что слегка сбило меня с пути (патчи я отправил в список рассылки).

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


Обратите внимание на повышенную частоту ШИМ и более интенсивное использование ЦПУ в сравнении с версией ATtiny.

Я не стал озадачиваться созданием новых печатных плат; у меня оставались варианты без ФНЧ, и я просто оставил внешний подтягивающий резистор неподключенным.

Живое демо



Видео одного полного проигрывания музыки. Начало несколько затяжное, но с 1:35 становится интереснее.

Примечания


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

Я собрал себе макетную плату из платы-переходника, поскольку шаг ее контактных площадок в точности соответствует одной стороне форм фактора SOT23-6. Вторую сторону я после подключил проводами. В другом, более раннем, варианте макетной платы использовалась миниатюрная адаптерная ATtiny, которую я приклеил на общую панель плат-переходников, чтобы ее расширить. Последнюю из них я представил на 35C3.




Макетные платы

Гибкие платы я заказал с OSHPark.com, и обошлись они примерно по доллару за штуку. Заказ был обработан довольно быстро, правда некоторые из них пришли с дефектами травления.

Подробнее..

Универсальные платы для умного дома на базе микроконтроллера ATmega128 (ATmega2561)

24.02.2021 22:18:59 | Автор: admin

Недавно я написал первый пост о том, как начал переделывать обычные светодиодные светильники в диммируемые. Многим не понравилось что свой диммер я делаю на базе микроконтроллера ATmega128. Поэтому хочу объяснить, почему используется именно этот микроконтроллер, и почему в наше время разрабатывая что-то ДЛЯ СЕБЯ, не стоит стремиться всё делать на самом слабеньком микроконтроллере, способном протянуть только лишь функционал разрабатываемого вами устройства.

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

Почему был выбран именно этот микроконтроллер? Да всё просто, потому что по цене ATmega128 всего на 20 центов дороже чем чем ATtiny2313. А ATtiny2313 стоит столько же, сколько и ATmega8. То есть уже про ATtiny2313 можно забыть как страшный сон.
Привожу пару картинок с ценами на AliExpress (а именно там я покупаю детальки) и идём дальше.

ATtiny2313:

Стоимость ATtiny2313Стоимость ATtiny2313

ATmega8:

Стоимость ATmega8Стоимость ATmega8

ATmega128:

Стоимость ATmega128Стоимость ATmega128

ATmega2561:

Стоимость ATmega2561Стоимость ATmega2561

Думаю комментарии излишни, сейчас даже для мигалки обычным светодиодиком куда выгоднее и рациональнее брать сразу ATmega128 чем 8 мегу, про тиньку и вообще молчу, забудьте про её существование как страшный сон. Да даже штук 5 транзисторов и резисторов для мигалки, уже будут стоить больше чем ATmega128. Так что забудьте про все микроконтроллеры слабее 128 меги, их использование в домашних проектах просто невыгодно и нерационально со всех сторон как ни посмотри. Да-да друзья мои, хочется вам или нет, но таковы реалии современного мира.

Следующий аргумент можно заметить если сравнить внимательно распиновку ножек ATmega128 и ATmega2561.

Сравнение между собой ATmega128 и ATmega2561Сравнение между собой ATmega128 и ATmega2561

Видим что распиновка ножек очень похожа, выводы SPI для программирования МК совпадают, так же совпадают и выводы питания, в общем почти всё совпадает, там буквально пару ножек различается которые ни на что не влияют, к чему я это веду, да к тому, что разработав плату для ATmega128, вы спокойно можете при необходимости купить и впаять в неё более производительный ATmega2561, а тут и памяти под программу больше в 2 раза и "оперативки". Например, мой главный модуль умного дома будет построен именно на ATmega2561, а остальные на ATmega128. Как итог, мне не нужно будет самому изготавливать плату для ATmega2561. Не знаю как кому, а лично мне, изготавливать в домашних условиях платы для smd микросхем тот ещё геморой. Ну не люблю я разводить такую мелюзгу, особенно ЛУТ-том (другой технологии я пока не освоил). Заказывать в Китае 10 плат ради одного модуля тоже не выгодно. А так мы разводим универсальную плату на базе ATmega128, и в одну из плат впаиваем ATmega2561 для главного модуля умного дома. Как итог, все наши платы для микроконтроллеров изготовлены на заводе в Китае, а в заводские платы даже впаивать smd микроконтроллеры проще, чем в платы собственного изготовления, во всяком случае для меня.

Ну и собственно к самой теме поста.
Схема моих универсальных плат для умного дома:

Схема платыСхема платы

Вот такие платы пришли из Китая:

Лицевая сторона платЛицевая сторона платЗадняя сторона платЗадняя сторона плат

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

Лицевая сторона плат после впаивания компонентовЛицевая сторона плат после впаивания компонентовЗадняя сторона плат после впаивания компонентовЗадняя сторона плат после впаивания компонентов

Плата с модулем ADM488 для связывания всех модулей умного дома в единую сеть:

Плата с модулем ADM488Плата с модулем ADM488Плата с модулем ADM488Плата с модулем ADM488

Плата с модулем беспроводной связи nRF24L01+:

Плата с модулем nRF24L01+Плата с модулем nRF24L01+Плата с модулем nRF24L01+Плата с модулем nRF24L01+

Как видите, на универсальной плате есть 2 специализированных разъёма, для модуля ADM488 и для модуля nRF24L01+, вся остальная периферия подключаемая к таким универсальным платам будет подключаться шлейфами к выведенным штырькам.

Вот собственно и всё. Может кто-то подчерпнёт какие-нибудь полезные идеи и для себя.

Подробнее..

Через тернии к звёздам или LILYGO TTGO T-Internet-POE ESP32 LAN8270A

15.03.2021 10:06:02 | Автор: admin
image
Попалась мне на глаза плата LILYGO TTGO T-Internet-POE ESP32 LAN8270A и конечно я не мог пройти мимо такой интересной новинки: ESP32, LAN8270A, POE, SD карта, Wi-Fi+Ethernet Было интересно пощупать это произведение сумрачного китайского гения своими руками и протестировать в реальной работе, ведь TTX платы сулили очень интересные перспективы для использования в IoT, DIY и вообще в области Wi-Fi+Ethernet и на что фантазии хватит.

Но, как всегда, путь от рекламных проспектов на сайтах продавцов и производителя до запуска платы и проведения на ней тестов оказался очень непростым и тернистым. Далее предлагаю вашему вниманию отчёт о моих увлекательных изысканиях по этой теме.

Камень в огород LILYGO


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

Хорошим примером тут может служить LILYGO TTGO T-Internet-POE ESP32 LAN8270A (далее для краткости будем называть эту плату T-Internet-POE). Производитель сделал интересную плату, но не сделал больше ничего:

  • нет нормальной распиновки контроллера
  • нет принципиальной электрической схемы
  • нет вменяемого описания применения платы и типовых паттернов её использования
  • нет технических пояснений по работе отдельных составляющих платы
  • нет примеров кода (есть 1 (!) скетч на отвали, мальчик, не мешай работать)
  • нет сайта с документацией
  • нет форума с грамотными и мотивированными модераторами
  • нет популярных и мотивирующих статей для тех, кто интересуется этим контроллером, но не знает (не понимает) где он мог бы его применить для своих целей
  • и нет ещё множества вещей, которые должны были бы быть

Короче, нет вообще ничего и каждый, кто рискнёт купить T-Internet-POE, должен быть безупречным воином DIY, иначе у него нет ни одного шанса выстоять в этой битве с LILYGO. Много ли среди нас таких?

И как при таком подходе к делу они вообще умудряются что-то продавать? И насколько выросли бы их продажи, если бы они на минутку отложили в сторону паяльник и вспомнили о своих покупателях?

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

В чём фишка?


Говоря простыми словами, на этой плате удалось более-менее удачно объединить в одном устройстве ESP32 (Wi-Fi), Ethernet, POE и ещё добавить к этому торту вишенку в виде microSD картридера. Из одного только сочетания этих составляющих сразу вытекает множество интересных вариантов применения этой платы:

  • работа по Wi-Fi с резервом в виде Ethernet канала
  • работа по Ethernet с резервом в виде Wi-Fi подключения
  • обслуживание одновременно и Wi-Fi и Ethernet линков
  • роутер между Wi-Fi и Ethernet в обе стороны
  • веб-сервер на два интерфейса
  • разные веб-сервера на разных интерфейсах
  • питание (удалённого) контроллера по POE

И множество других интересных вариантов (кстати, не нужно забывать, что кроме непосредственно сетевой составляющей, плата имеет GPIO выводы и может использоваться по своему прямому назначению, то есть в качестве IoT контроллера).

Как вы видите, сфера применения этой платы в IoT и DIY ограничена только вашей фантазией и вашими потребностями и в целом T-Internet-POE как устройство смотрится очень многообещающе.

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

Технические характеристики


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

  • ESP32-WROOM (4 МБ)
  • LAN8720A (Ethernet PHY)
  • POE 802.3af
  • microSD картридер
  • 12 GPIO пинов для внешних подключений

О чём навскидку нам говорит такая конфигурация? О том, что при использовании этой платы в качестве веб-сервера, файлы можно хранить как на microSD карте памяти, так и во внутренней памяти модуля ESP32 (или и там и там сразу).

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

Варианты контроллера


При ближайшем рассмотрении оказывается, что под одним названием существуют два разных контроллера один на ESP32-WROOM, а второй на ESP32-WROVER-B, что сходу и не разглядишь на вид платы практически одинаковые и можно играть в игру найди 10 отличий.

image

Мне достался контролер на ESP32-WROOM, поэтому дальнейшее повествование будет относится к нему.

Программатор


Инженеры LILYGO так далеко оторвались от своих пользователей, что их решения не всегда можно понять. К таким решениям относится создание ими отдельной платы программатора на чипе CP2104 для контроллера T-Internet-POE.

Зачем? Зачем нужен отдельный программатор, когда этот узел можно было интегрировать на саму плату контроллера или попросту использовать стандартные USB-TTL переходники (как делают все остальные производители железа)? Ответ, видимо, знают только разработчики LILYGO (но простим им этот маленький креатив).

image

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

  • во-первых они применили горячо любимые народом пины с шагом 2,0 мм
  • во-вторых они предусмотрели установку разъёма на обратную (!) сторону платы

(Разработчикам такого решения я бы порекомендовал иногда отвлекаться от работы и уделять время отдыху.)

В результате получился какой-то странный монстр. И всё бы ничего, если бы проблема ограничивалась только эстетической составляющей, но тут вылезают более серьёзные проблемы:

  • если устанавливать и крепить плату в нормальном положении, то пины программатора оказываются снизу платы и к ним невозможно подключиться без демонтажа контроллера;
  • если работать с платой без крепления то разъёмы с шагом 2,0 не обеспечивают должной жёсткости и вся конструкция грозит развалиться в любой момент и всё вокруг позамыкать.

В качестве нивелирования этого косяка разработчиков, можно порекомендовать припаивать линейку пинов с верхней стороны платы и сделать (или купить, если он продаётся) 6-пиновый 2,0 мм гибкий переходник.

Распиновка


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

image

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

Всего на ESP32 имеется 40 пинов (D0D39) из них 14 пинов

D6D11, D20, D24, D28-D31, D37, D38

мы исключаем из рассмотрения как практически неиспользуемые (по разным причинам, подробный разбор назначения этих пинов выходит за рамки этой статьи). Остаются:

Пины подключения Ethernet чипа LAN8720A


D18 ETH_MDIO_PIN
D19 ETH_TX_D0
D21 ETH_TX_EN
D22 ETH_TX_D1
D23 ETH_MDC_PIN
D25 ETH_RX_D0
D26 ETH_RX_D1
D27 ETH_RX_CRS

причём, D18 и D23 устанавливаются в скетче, а остальные 6 пинов чипа LAN8720A являются стандартными и задаются в библиотеке.

Поскольку производитель постеснялся предоставить принципиальную схему контроллера, то я здесь могу только привести аналогичную типовую схему подключения физики Ethernet на LAN8720A.

image

К LAN8720A также относится пин тактирования, который на плате T-Internet-POE подключён к D17 (тоже выбирается в скетче):

D17 ETH_CLOCK

и пин сброса

D5 NRST

microSD картридер


microSD картридер подключён на HSPI:

D2 SD_MISO
D12 SD_CS
D14 SD_SCLK
D15 SD_MOSI

и в случае своего использования забирает свободные для внешних подключений и выведенные на плату пины D2, D14, D15. Вопрос что выгоднее использовать картридер и потерять 3 из 12-и свободных пинов или сохранить 3 лишних GPIO и отказаться от картридера сродни вопросу что лучше: слон или конь? и вам каждый раз придётся отвечать на него при использовании платы T-Internet-POE.

Прочие пины


У нас остаются пины D0 (ETH_CLOCK, не задействован в этом качестве) и D1 (TX0) и D3 (RX0).

Свободные пины


Теперь переходим к самому интересному описанию свободных пинов, выведенных на плату контроллера.

Первой идёт группа D34, D35, D36, D39, работающая только на вход. Лучше конечно на вход, чем вообще ничего, но при таком дефиците GPIO было бы гораздо лучше, если бы эти четыре пина были полноценными GPIO.

И затем 8 полноценных GPIO, которые вы можете использовать в своих проектах. Тут нужно помнить, что хоть эти пины и являются полноценными GPIO, но некоторые из них работают весьма своеобразно (например меняют потенциал на старте контроллера и т.п.). Поэтому прежде, чем к ним что-то подключать, нужно специально выяснять и уточнять эти моменты.

D2 (SD_MISO)
D4
D12
D14 (SD_SCLK)
D15 (SD_MOSI)
D16
D32
D33

Как говорится, вот тебе, мой юный друг программирования и микроконтроллеров, 8 GPIO и ни в чём себе не отказывай.

POE


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

Здесь реализована полноценная поддержка стандарта POE 802.3af с развязкой и управлением питанием на чипе SI3404.

Меня дистанционная запитка контроллера не очень интересует, поэтому этот аспект работоспособности T-Internet-POE я не тестировал, но, судя по всему, с POE здесь проблем нет.

Программная поддержка


Как вы сами понимаете, работать с T-Internet-POE можно при помощи любых программных сред, имеющих представление об этом железе, в том числе в нативном SDK (и вероятно это наиболее правильный вариант), но мы попытаемся выяснить, что можно выжать из этой железки при помощи Arduino IDE.

В качестве программной среды для экспериментов использовались Arduino IDE версии 1.8.5 и ESP32-Arduino версии 1.0.5 (последняя сборка на момент написания статьи).

Сам процесс установки поддержки ESP32 в Arduino IDE я описывать не буду потому, что этому вопросу посвящено огромное количество материалов в интернете, во всех нюансах описывающих этот процесс.

Упомяну здесь только один момент: плюс ко всему, чего не имеет этот контроллер, он ещё не имеет и нативной поддержки в ESP32-Arduino версии 1.0.5. Поэтому в качестве контроллера в менеджере плат выбирался ESP32 DEV Module с настройками:

Flash Mode: DIO
Flash Size: 4MB (32 Mb)
Partition Scheme: 4MB (1,2MB/1,5MB)

Стандартный скетч


Ниже приведён скетч, которым нас порадовал производитель платы. Там особенно комментировать нечего, он просто инициализирует Ethernet интерфейс и посылает запросы к серверу в интернете.

Полный код скетча от производителя платы
/*    This sketch shows how to configure different external or internal clock sources for the Ethernet PHY*/#include <ETH.h>#include <SPI.h>#include <SD.h>#define SD_MISO         2#define SD_MOSI         15#define SD_SCLK         14#define SD_CS           13/*   * ETH_CLOCK_GPIO0_IN   - default: external clock from crystal oscillator   * ETH_CLOCK_GPIO0_OUT  - 50MHz clock from internal APLL output on GPIO0 - possibly an inverter is needed for LAN8720   * ETH_CLOCK_GPIO16_OUT - 50MHz clock from internal APLL output on GPIO16 - possibly an inverter is needed for LAN8720   * ETH_CLOCK_GPIO17_OUT - 50MHz clock from internal APLL inverted output on GPIO17 - tested with LAN8720*/// #define ETH_CLK_MODE    ETH_CLOCK_GPIO0_OUT          // Version with PSRAM#define ETH_CLK_MODE    ETH_CLOCK_GPIO17_OUT            // Version with not PSRAM// Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)#define ETH_POWER_PIN   -1// Type of the Ethernet PHY (LAN8720 or TLK110)#define ETH_TYPE        ETH_PHY_LAN8720// IC-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)#define ETH_ADDR        0// Pin# of the IC clock signal for the Ethernet PHY#define ETH_MDC_PIN     23// Pin# of the IC IO signal for the Ethernet PHY#define ETH_MDIO_PIN    18#define NRST            5static bool eth_connected = false;void WiFiEvent(WiFiEvent_t event){    switch (event) {    case SYSTEM_EVENT_ETH_START:        Serial.println("ETH Started");        //set eth hostname here        ETH.setHostname("esp32-ethernet");        break;    case SYSTEM_EVENT_ETH_CONNECTED:        Serial.println("ETH Connected");        break;    case SYSTEM_EVENT_ETH_GOT_IP:        Serial.print("ETH MAC: ");        Serial.print(ETH.macAddress());        Serial.print(", IPv4: ");        Serial.print(ETH.localIP());        if (ETH.fullDuplex()) {            Serial.print(", FULL_DUPLEX");        }        Serial.print(", ");        Serial.print(ETH.linkSpeed());        Serial.println("Mbps");        eth_connected = true;        break;    case SYSTEM_EVENT_ETH_DISCONNECTED:        Serial.println("ETH Disconnected");        eth_connected = false;        break;    case SYSTEM_EVENT_ETH_STOP:        Serial.println("ETH Stopped");        eth_connected = false;        break;    default:        break;    }}void testClient(const char *host, uint16_t port){    Serial.print("\nconnecting to ");    Serial.println(host);    WiFiClient client;    if (!client.connect(host, port)) {        Serial.println("connection failed");        return;    }    client.printf("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", host);    while (client.connected() && !client.available());    while (client.available()) {        Serial.write(client.read());    }    Serial.println("closing connection\n");    client.stop();}void setup(){    Serial.begin(115200);    WiFi.onEvent(WiFiEvent);    SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);    if (!SD.begin(SD_CS)) {        Serial.println("SDCard MOUNT FAIL");    } else {        uint32_t cardSize = SD.cardSize() / (1024 * 1024);        String str = "SDCard Size: " + String(cardSize) + "MB";        Serial.println(str);    }    pinMode(NRST, OUTPUT);    digitalWrite(NRST, 0);    delay(200);    digitalWrite(NRST, 1);    delay(200);    digitalWrite(NRST, 0);    delay(200);    digitalWrite(NRST, 1);    ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE);}void loop(){    if (eth_connected) {        testClient("baidu.com", 80);    }    delay(10000);}


Когда я в первый раз увидел этот скетч, то задался вопросом: что этим хотел сказать производитель?. Единственное назначение этого скетча показать, что эта технология в принципе работает. Это конечно хорошо, но что дальше? Что со всем этим делать не имея ни документации, ни примеров, ни вменяемой ответной реакции от производителя?

Походу получается, что ответ от LILYGO изучать программирование и создавать ПО самостоятельно (или искать готовые прошивки, хотя это и не спортивно).

Интерфейсы и пинг


Для сетевых профессионалов (и примкнувших к ним) скажу пару слов о скорости работы по интерфейсам Wi-Fi и Ethernet и их отзывчивости. Тестирование проводилось в ненагруженной гигабитной сети, зашумлённость Wi-Fi диапазона специально не контролировалась.

Первый скриншот это пинг контроллера по Wi-Fi интерфейсу. Минимум 24 мс, максимум 105 мс, среднее 67 мс.

image

Второй пинг контроллера по Ethernet интерфейсу. Минимум 0 мс, максимум 9 мс, среднее 2 мс.

image

Как вы видите, пинг по проводному Ethernet кардинально меньше, чем по Wi-Fi (что ожидаемо). Насколько хороши или плохи эти цифры предоставляю судить читателям самостоятельно, меня они вполне устраивают для моих целей.

Тестирование


Тестировать такую систему, как T-Internet-POE на скетчах, подобных предложенному производителем это несерьёзно, поэтому для тестирования контроллера применялась специализированная версия AMS, адаптированная специально для этой платы. Учитывая, что это сервер, который использует полноценные HTML, CSS, Javascript, Ajax, графические файлы и библиотеки, то успешная работа такого сервера на T-Internet-POE будет свидетельствовать о правильно спроектированном железе и возможности его использования в реальных проектах.

Примечание: тестирование производилось на внутренней, не публичной версии AMS для T-Internet-POE. Публикация и распространение этой версии не планируется, возможно это будет сделано позже, после соответствующих доработок.

Тест 1. Запускаем AMS сервер на T-Internet-POE


Запустить AMS на новом контроллере с новым чипом и сетевым интерфейсом это весьма нетривиальная задача, но тем не менее, при правильном подходе и понимании того, что ты делаешь, всё становится возможным.

Косяк номер 1


В процессе этой работы стали вылезать косяки самого контроллера T-Internet-POE и первое, что было выявлено это то, что контроллер отказывается прошиваться при вставленной microSD карте памяти. Не помогает ничего ни замена USB порта, ни питание от отдельного блока, ни нажимание кнопок, ни замена карты контроллер упорно не желает прошиваться при вставленной карте памяти.

Глюк это конкретного экземпляра или родовой дефект всех контроллеров T-Internet-POE сказать трудно (имея в своём распоряжении один экземпляр), можно только констатировать 100-процентную повторяемость и воспроизводимость проблемы.

Что это значит для нас? В практическом плане это значит, что на контроллере T-Internet-POE фактически нет картридера картридер, который блокирует прошивку контроллера это не картридер, а баг.

Что же делать? Остаётся только использовать 1,5 МБ SPIFFS, имеющийся на модуле ESP32. Да, это не очень много, но в принципе 1,5 МБ памяти для IoT устройства это более-менее приемлемо в большинстве случаев.

Косяк номер 2


Ок, от картридера мы отказались, теперь нам нужно подружиться с SPIFFS. Вроде бы задача несложная и даже привычная, но и тут нас ждёт засада: по какой-то причине утилита ESP32FS отказывается нормально работать на этом контроллере (в этой конфигурации). Перенос файлов в память модуля ESP32 приводит к последующей ошибке монтирования SPIFFS диска.

М-да В случае невозможности нормально перенести файлы (сервера) на SPIFFS диск, остаётся только один способ инициализация интерфейса через Serial соединение, и последующий перенос файлов на SPIFFS диск через веб-интерфейс. Способ конечно не очень удобный, но никак не влияющий на конечный результат файлы сервера были успешно перенесены на SPIFFS диск.

Описание самого процесса адаптации кода под новый контроллер я опускаю, поскольку это потребовало бы составления антологии наподобие полного собрания сочинений В. И. Ленина и сразу перехожу к демонстрации факта успешной работы AMS сервера на T-Internet-POE (а значит и работоспособности самой платы T-Internet-POE).

Загрузка страницы по Wi-Fi интерфейсу.

image

Загрузка страницы по Ethernet интерфейсу.

image

Выигрыш по скорости примерно в 4 раза, естественно, в пользу Ethernet. Здесь нужно ещё иметь в виду, что речь идёт о неоптимизированном коде и после проведения соответствующих работ результаты должны значительно улучшиться.

image

Работа сервера по Ethernet интерфейсу на LILYGO TTGO T-Internet-POE ESP32 LAN8270A.

Тест 2. Работа на двух интерфейсах


Тут мне придётся немного поработать разрушителем легенд. В интернете ходят упорные слухи, что одновременная работа Wi-Fi и Ethernet на связке ESP32 и LAN8270A невозможна. Это не так AMS сервер прекрасно работает на двух интерфейсах одновременно и отлично обслуживает запросы по Wi-Fi и Ethernet. Никаких проблем с зависаниями или перезагрузками ESP32 нет.

image

Вот это уже интересный результат, который открывает заманчивые перспективы: поскольку мы имеем собственный сервер, то можем как угодно управлять обслуживанием интерфейсов, например, по Wi-Fi отдавать одни сайты с одним контентом, а по Ethernet другие сайты с другим контентом. Образно говоря, бабушке по Ethernet отдавать сайт с кулинарными рецептами, а внуку по Wi-Fi сайт с избранными статьями из БСЭ.

Тест 3. Бан по одному из интерфейсов


Теперь давайте попробуем на практике реализовать идею с различным обслуживанием интерфейсов веб-сервером. В качестве примера попробуем реализовать отказ в обслуживании для подключений по одному из интерфейсов (Ethernet).

image

Клиент, подключённый к нашему контроллеру по Ethernet интерфейсу получил отказ в обслуживании.

Резервирование интерфейсов


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

Сетевой роутинг


Имея в своём распоряжении два рабочих сетевых интерфейса можно как угодно маршрутизировать пакеты в сети. Никто также не мешает в схему маршрутизации по Wi-Fi и Ethernet добавить маршрутизацию данных по nRF24 или LoRa или по любой другой беспроводной сети. Таким образом можно сделать любой роутер для вашей IoT системы.

Ну, и как было отмечено выше, существует ещё множество интересных вариантов использования контроллера с двумя сетевыми интерфейсами.

Итоги


Теперь давайте подведём итоги этого небольшого исследования: в общем, несмотря на некоторые косяки и детские болезни, контроллер LILYGO TTGO T-Internet-POE ESP32 LAN8270A мне понравился это отличный инструмент для построения IoT систем, особенно если вы обладаете соответствующей квалификацией и не лишены фантазии и креативного подхода к своему творчеству.

Плюсы и минусы LILYGO TTGO T-Internet-POE ESP32 LAN8270A.

Плюсы:
  • Это работает!
  • Законченное интегрированное решение Wi-Fi + Ethernet + POE + GPIO
  • Хорошая работа без зависаний и перезагрузок (проблем при тестировании выявлено не было)
  • Возможность одновременной работы по двум интерфейсам

Минусы:
  • Тотальное отсутствие документации, примеров и пояснений
  • Относительно высокий порог входа
  • Детские болезни и мелкие косяки в реализации
Подробнее..

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

06.04.2021 10:06:21 | Автор: admin

В данной статье я опишу решение поставленной мне задачи по снижению шума и повышению плавности движения механизма, движимого шаговым двигателем (далее просто ШД) при ускорении. Данная задача довольна специфична. Описанное мною решение будет полезно при разработке модулей управления ШД .

Далее опишу алгоритм управления ШД с помощью микроконтроллера.

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

Рассмотрим необходимый минимум основ управления ШД , необходимый для понимания принципа управления, описанного ниже. А именно:

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

  • Изменение полярности обмотки с помощью Н-моста.

  • Принципиальная электрическая схема, включающая в себя 4 драйвера полумоста и 2 Н-моста.

  • Способ модуляции синусоидального сигнала с помощью широтно-импульсной модуляции (далее просто ШИМ).

  • Способы подключения обмоток.

ШД имеют 4,5,6 или 8 выводов обмоток. При подключении к модулю управления ШД в каждом конкретном случае необходимо задействовать определенные выводы. В четырех выводном ШД задействуются все четыре вывода. Пяти выводной ШД не подходит для метода управления, описываемого в этой статье. В шести выводном не задействуются выводы нейтральных точек. В восьми выводном ШД обмотки соединены последовательно (см. рис. 1). Конечно можно задействовать все выводы обмоток, но эти методы управления выходят за рамки статьи.

Рис.1Рис.1

Описание полношагового режима управления.

Для управления ШД необходимо подавать определенную последовательность сигналов на обмотки ШД . По сути создавая статором бегущую волну, заставляющую синхронно вращаться поле статора и ротора ШД . На рис. 2 над диаграммой схематично показан статор с обмотками. Цвет обмоток указывает на их полярность. В центре находится ротор в виде магнита. Если обратить внимание на диаграмму, то будет видно, что в середине каждого шага идет поочередное изменение полярности обмоток. В след за изменяющимся полем статора, вращается ротор по часовой стрелке. Для изменения вращения двигателя можно поменять местами выводы одной обмотки, и ротор начнет вращаться в противоположную сторону. Но зачем менять подключение обмоток, если можно программным путем поменять местами подаваемые сигналы, что и сделано с А+ и А- на рис.2. При этом ротор также начнет вращаться в противоположную сторону. Для большей наглядности сигнал на диаграмме имеет прямоугольную форму. Далее в статье прямоугольный сигнал будет заменен на сигнал ШИМ, модулирующий синусоиду.

Рис.2Рис.2

Изменить полярность обмотки можно с помощью Н-транзисторного моста (см. рис. 3). В первом варианте из 4 транзисторов открыты Т1,Т4. Ток, соответственно, течет через них. Поменять полярность можно, открыв транзисторы Т2 ,Т3 и при этом закрыв Т1 И Т4. Таким образом, ток через обмотку потечет в противоположную сторону.

Рис.3Рис.3

Для управления ШД используют два Н моста чаще всего на основе восьми N-канальных MOSFET транзисторов. Для управления транзисторами используются мостовые или полумостовые драйверы. Напряжение на затворе зачастую должно быть выше напряжения истока транзистора на 5-15В. Для MOSFET транзисторовэто делают драйверы.

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

Механизм встроенного временного промежутка Dead-Time обеспечивает гарантированное закрытие одного силового ключа до момента начала открытия ключа в противоположном плече. Гарантией надежного закрытия противоположного транзистора является встроенная схема, контролирующая состояние ключей и наличие схемы задержки, формирующей промежуток времени, в течение которого закрыты оба транзистора в плечах полумоста.

На рис.4 приведена принципиальная электрическая схема управления ШД, включающая в себя 4 драйвера полумоста IR2104S и 2 Н-моста на основе транзисторов IRF7836. При подаче 0 В на вход SD драйвера IR2104s драйвер переходит в неактивное состояние.

На приведенной схеме сигнал с диаграмм можно подавать на 4 входа. Так на ШИМ1 А+, на ШИМ2 А-, на ШИМ3 В+, на ШИМ4 В- соответственно (Рис.4).

Рис.4Рис.4

Ниже кратко опишу принцип работы ШИМ, расчет и модулирование синусоиды.

ШИМ или PWM (широтно-импульсная модуляция или pulse-width modulation) это способ изменения мощности, подаваемой на нагрузку. Управление заключается в изменении ширины импульса постоянной амплитуды, следующих через равные интервалы времени.

На рис.5 видно, как регулируется мощность. Так 20%, 40%, 80%, 100% это время, когда транзистор открыт относительно времени периода. Соответственно среднее напряжение будет приблизительно равно 20%, 40%, 80%, 100% от максимального.

Рис.5Рис.5

Меняя ширину импульса, можно модулировать различную форму сигнала. Так, по синусоидальному закону, при котором ширина импульсов изменяется следующим образом (рис. 6), максимальна в середине шага, а к началу и концу шага уменьшается. Синусоиду можно увидеть на осциллографе, пропустив ШИМ-сигнал с контроллера через RC фильтр. Для RC фильтра использовал конденсатор 2.2 нФ и резистор 1.5кОм.

На рис. 6 показан пример модуляции одного синусоидального шага, состоящего всего из 4 уровней напряжения. Напряжение питания 12В.

Рис.6Рис.6

Для формирования модулированного сигнала необходимо рассчитать уровни ШИМ и их количество на один шаг. На рис.7 приведено два примера одного синусоидального шага с разным количеством ШИМ уровней, где ШИМ можно изменять в пределах от 0 до 255, что соответствует напряжению от 0 до 100%. Как видно на рис.7, чем больше уровней ШИМ, тем больше форма модулированного сигнала будет повторять синусоиду. Каждое изменение ШИМ происходит по прерыванию таймера в микроконтроллере. Меняя время срабатывания прерывания, можно регулировать время одного шага соответственно и скорость двигателя.

Рис.7Рис.7

Формулу для расчета ШИМ уровней можно получить из формулы мгновенных значений синусоидальных функции. U = Umsin(*t + )

Рис.8Рис.8

Um - амплитудное значение - угловая частота - начальный фаза, значение фазы в начальный момент t=0 аргумент *t + называют фазой синусоидальной функции

Для расчета = 0

Синусоида от 0 до Т/4 повторяет форму половины шага, а от T/4 до Т/2 зеркально отображает вторую половину шага. Поэтому для расчета достаточно взять участок от 0 до Т/4 или от 0 до 90. t для удобства можно заменить на угол = 0 до 90. Um заменим на максимальное значение ШИМ_max в примере 255. U заменим на ШИМ_N. Получается ШИМ_N = ШИМ_maxSIN(). Количество вычислений уровней ШИМ зависит от величины точности к примеру 16 с шагом угла 90/16 = 5,625

Пример:

ШИМ_0 = 255*SIN(0)= 0

ШИМ_1 = 252*(5,6251)= 24,99 25

ШИМ_2 = 252*(5,6252)=49,7450

. . .

ШИМ_16 = 252*(5,625*16)=255

Значения ШИМ необходимо округлять, так как оно может быть только целым.

Вычислив уровни ШИМ, можно модулировать синусоиду. Для этого я занес значения уровней в массив по прерыванию от таймера, поочередно подставляя значения от ШИМ_0 до ШИМ_16 в ШИМ контроллера. Так модулируется половина шага. Для модуляции второй половины нужно наоборот подставлять значения от ШИМ_16 до ШИМ_0.

Так модулируется один шаг. Меняя время срабатывания таймера можно менять время шага. Теперь такие синусоидальные шаги можно подавать, заменив прямоугольные шаги на диаграммах (рис.2).

Для удобства я написал небольшую программу для расчета значений ШИМ . Где PWM_MAX присваивается максимальное значение ШИМ, которое настраивается в микроконтроллере, а sampling присваивается количество уровней ШИМ на половину шага. Так как большинство программируют МК на языке С, данную программу я решил написать на этом же языке.

#include <stdio.h> #include <math.h>#define PI 3.14159265 #define PWM_MAX 255 //максимальное значение ШИМ.  #define sampling 16 // количество уровней напряжения на половину шага.int i; int main()    {for(i=0;i<=sampling;i++)             {printf("%d\n",(int)(PWM_MAXsin((double)90/sampling * PIi/ 180) + 0.5));};             return 0;    } 

Выходные данные

0 25 50 74 98 120 142 162 180 197 212 225 236 244 250 254 255

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

Задача: Двигатель должен вращать массу около 7кг за 0,7 - 1 секунду на 90. Угол поворота отслеживает 10 разрядный или 1024 битный абсолютный энкодер AS5040 закрепленный на валу механизма. При достижении 90 необходимо включить режим удержание ШД. При этом необходимо учитывать воздействие руки человека, толкающей механизм по направлению движения или против, либо удерживающей вплоть до остановки. При этом напряжение питания блока управления ограничено двенадцатью вольтами, а габариты двигателя ограничены по ширине 100мм, а по длине 110мм, включая длину вала. В наличии было три двигателя подходящих габаритов.

Осовные характеристики двигателей:

  • двигатель 1 Рабочий ток 3,1 А. Активное сопротивление обмотки 1,5 Ом Индуктивность обмотки 3,5 мГн. Момент удержания 0,3 кг*м Величина полного шага 1,8 (200 шагов на один оборот ротора).

  • двигатель 2 Рабочий ток 4А. Активное сопротивление обмотки 0,8 Ом. Индуктивность обмотки 3,1 мГн. Момент удержания 0,45 кг*м. Величина полного шага 1,8 (200 шагов на один оборот ротора).

  • двигатель 3 не соответствовал характеристикам, заявленным производителем. Производитель утверждал, что он является аналогом двигателя 2. При замере активного сопротивления обмоток выяснилось, что сопротивление не соответствовало заявленным 0,8 Ом, а составляло всего 0,2 Ом. Следовательно, рабочий ток и индуктивность тоже отличались.

Изначально решить поставленную задачу я планировал с помощью готового блока управления ШД. Было испытано около пяти готовых блоков управления ШД разной ценовой категории и разных производителей. У всех были приблизительно одни и те же результаты, которые будут описаны ниже.

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

Проблема 1. Свист двигателя. При уменьшении тока на блоках управления шум становился меньше, двигатель более плавно вращает ротор, и всё-таки шум и плавность вращения оставляли желать лучшего. При увеличении дробления шага на блоках управления до 1/16 шум уменьшился, и плавность хода увеличилась, но эти улучшения были незначительны. Дальнейшее повышение дробления вплоть до 1/512 не привело к улучшению показателей.

Решение проблемы 1. Решил сделать свой драйвер управления ШД на основе микроконтроллера PIC18F2331. Он заточен под управление двигателями. Было задействовано 4 канала широтно-импульсной модуляции для управления 4 драйверами полумоста.

Я начал эксперименты с формой сигнала, модулируемого с помощью ШИМ. Подавал треугольный, прямоугольный, трапециевидный сигналы на обмотки двигателя. Результаты: неутешительные показатели по шуму, вибрации и плавности хода были значительно хуже по сравнению с готовыми блоками управления. Подавая на обмотки двигателя синусоидальный сигнал, плавность хода, начиная от 5 об/мин и выше, стала практически незаметной на глаз. Детали механизма не гремели, но если скорость была ниже 5 об/мин, детали гремели, механизм передвигался рывками, ток потребляемый двигателем рос и выходил за пределы рабочего тока двигателя. Что касается шума, даже при скорости выше 5 об/мин был неприятный шум. Свист похожий на звук, издаваемый зарядными устройствами для телефонов ноутбуков, только гораздо сильнее. Такой же шум я слышал от асинхронных двигателей, управляемых частотными преобразователями. Изучив работу частотных преобразователей, стало ясно, что свист зависит от частоты ШИМ. У меня частота ШИМ составляла 4кГц. При изменении частоты свист менялся, и когда я поднял частоту до 20кГц, шум полностью пропал. Слух человека в большинстве случаев не воспринимает частоту выше 20кГц.Так была решена проблема 1.

Проблема 2. Двигатель ниже 5 об/мин двигается рывками, а ток сильно возрастает, так как у драйвера нет обратной связи по току, соответственно и контроля по перегрузке тоже нет.

Решение проблемы 2. Когда напряжение питания драйвера с 12V снизил до 9V, рывки при передвижении механизма тоже стали меньше, но всё равно недостаточно плавно. Тогда напряжение выставил 12V, а амплитуду ШИМ синусоиды начал уменьшать, и при 20% амплитуды от максимума движение было плавным и тихим.

Проблема 3. Если запустить двигатель сразу на большой скорости без разгона, то момент на валу двигателя будет недостаточным для вращения механизма. Начнется пропуск шагов, сопровождаемый сильным шумом, который распространяется по всему корпусу механизма. Звук напоминал ведро с болтами, которое трясут изо всех сил. Так как необходимо было провернуть механизм на 90 за 0,7 - 1 сек., я решил, что до 45 буду линейно ускоряться, а с 45 до 90 линейно замедляться. Достигнуть даже приближённо времени в 1сек не удалось. Еще было замечено при старте, что первый шаг периодически сопровождался рывком ротора. Это связанно с тем, что зубцы ротора могут находиться не на оси включенной обмотки статора. В следствие чего зубцы ротора стремительно притянутся к обмотке, произойдёт фиксация ротора, сопровождающаяся звуком, похожим на удар. Все последующие шаги будут плавными.

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

Для примера покажу график Рис.9. Взятый из даташита.

На двигатель FL86STH156-6204 ток фазы 6,0 А. Максимальный постоянный ток потребления от источника питания 3,8 А для блока SMD-9.0 Блоки управления SMD-9.0 (напряжение 72В) и SMD-82 (напряжение 220В) Дробление шага , источник питания 72 В, 300 Вт.

Рис.9Рис.9

Чем больше напряжение подается на обмотки ШД, тем быстрее идет нарастание тока в них. Соответственно зависимость момента от скорости улучшается.

В моем случае питание 12В и диапазон допустимых скоростей невысок, так как момент стремительно падает с увеличением скорости.

Так как при равномерном ускорении не удавалось разогнать механизм. Я решил разделить ускорение на три диапазона. Первый при скорости ниже 5 об/мин ускорение максимально, так как момент максимально высокий. Второй диапазон от 5 до 7 об/мин ускорение уже в три раза меньше и последний третий диапазон от 7 об/мин и выше, ускорение в десять раз меньше относительно первого диапазона. Таким образом двигатель набирал максимально быстро скорость в начале пути и укладывался в 1с. Нелинейно разгоняя двигатель, можно значительно сократить время разгона ШД. Замедление механизма проходило в обратном порядке. Так как сохраненной осциллограммы сигнала, подаваемого на драйверы полумостов, у меня не осталось, я нарисовал, как, примерно, должна выглядеть осциллограмма.

Рис.10Рис.10

При старте, первый шаг периодически сопровождался рывком ротора. Удар можно убрать, путем снижения амплитуды первой половины шага (в моем случае 20% от максимума) и значительно увеличив время первой половины шага по сравнению с другими шагами. Как видно на рис.10, первые половины шагов более вытянуты, чем правые. Это связанно с ускорением. Форма синусоиды искажается, но при этом двигатель работает достаточно плавно и тихо. Как видно на рис.10, ускорение максимально, это заметно по сужению шагов в начале движения. Когда момент максимален, а по мере приближения к максимальной скорости ускорение значительно меньше от изначального. Все диапазоны ускорений подбирались экспериментальным путем. Как регулировать амплитуду и ускорение я описал ранее в этой статье.

Проблема 4. Учет воздействия руки на механизм, если не учитывать воздействие руки, то можно было обойтись парой индуктивных датчиков или оптических датчиков для отслеживания крайних положений и направления вращения. А если учитывать воздействие руки, то без датчика угла поворота (абсолютного энкодера) не обойтись. Если механизм находится в движении, а рука хватает его и удерживает на месте, начинается пропуск шагов и сильный шум. По датчику будет видно, что вал колеблется на месте, и двигатель необходимо отключить и попытаться стартовать через заданный промежуток времени. Эта задача легко решаема. Если рука потянула механизм против направления, по датчику можно легко понять, что механизм вращается в противоположную сторону, и тоже отключить двигатель. И совсем другое дело, воздействие на механизм с увеличением скорости перемещения больше заданной изначально или наоборот притормаживание. Логично было измерять скорость перемещения и подстраиваться, чтобы скорость двигателя совпадала с движением руки, но оказалось, что 10 разрядный датчик as5040 не позволял этого сделать. Предположим, что нужно отслеживать перемещение каждые 1,05 .Так как 10 разрядов это 1024 бита, то вычисляем точность 360/1024 = 0,35, количество градусов на один бит возьмём 0,353 = 1,05. Зная время, за которое двигатель проходит 1,05, можно вычислить скорость. Но не все так просто. У датчика есть погрешность - биение младшего бита, то есть погрешность в один бит, а при измерении скорости нужно сделать два замера пройденного пути. В начале и в конце соответственно погрешность удваивается и составляет 0,352=0,7. Измерить скорость каждые 1,05 невозможно. Логично измерять, например, каждые 0,35*15=5,25. Уже можно оценить скорость, но 5,25 это слишком большой угол, двигатель успеет пройти почти 3 шага или более точно 5,25/1,8= 2,92.

Решение проблемы 4. Целесообразно для измерения скорости использовать 12 разрядный датчик это 4096 бит. Точность измерения увеличится в четыре раза, что позволит точно подбирать скорость движения механизма под скорость движения руки.

Так была решена последняя проблема.

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

Подробнее..

Как я сделал девайс для Korn, Limp Bizkit, Drowning Pool и других рокеров, собрав все продуктовые ошибки

10.04.2021 20:17:22 | Автор: admin
CJ Pierce (Drowning Pool), Wes Borland (Limp Bizkit), Jame Shaffer (Korn), ну и я с педалькамиCJ Pierce (Drowning Pool), Wes Borland (Limp Bizkit), Jame Shaffer (Korn), ну и я с педальками

Привет, Хабра!

Сегодня одолела ностальгия и хочу рассказать, как делал устройства для музыкантов педальки-контроллеры, наделяющие музыкальный процессор Digitech Whammy новыми возможностями. Устройства мало кому нужны, но кому нужны то позарез)

Этот проект мне очень дорог, потому что с него началась настоящая страсть к созданию чего-то нового. И хоть я давно уже не брался за паяльник, а основное время посвящаю развитию в управлении продуктом, всё ещё не оставляю фантазии о фееричном возвращении в music hardware, которое сделает немного шума в чахнущем царстве рока.

Да и перед именитыми ребятами (на фото, например, CJ Pierce из Drowning Pool, James "Munky" Shaffer из Korn и Wesley Borland из Limp Bizkit, ну и я с девайсами), признаться, стыдно, что пропал на целых несколько лет ни слуха от меня, ни духа о новых устройствах.

Удачно сложилось, что сегодня же есть 3,5 часа в самолёте, так что настало время офигительных историй расскажу, как появились эти железки реально из мусора. Курьёзы и неудачи, какие устройства хотел и что получилось, как они оказались у топ-музыкантов мира и почему я отложил такие крутые штуки в долгий ящик.

Возможно это смотивирует кого-то вернуться и доделать свои идеи, а кому-то просто поднимет настроение. Итак, вперёд! Точнее назад, в 2010 год...

Педаль с Марио

Год 2010 был жутко насыщенным (как, впрочем, и все остальные), потому что я работал параллельно:
а) Продавцом в музыкальном магазине,
б) Администратором в концертном агентстве,
в) Был владельцем двух репетиционных баз и совладельцем магазина игрушек,
д) Играл рок с бандой за какие-то деньги и так далее.

В один из выходных, проснувшись в Музторге, мой коллега Кузьмич показал видео на YouTube диковинной и взрывающей голову педальки от Molten Voltage. Это был midi-контроллер для Digitech Whammy, который превращал питч-шифтер в арпеджиатор.

Стоила педаль 100$ (3 000 по тем временам) и платить такие бешеные деньги за девайс было большим расточительством. А так как я увлекался программированием микроконтроллеров (и даже сделал подсветку днища для друзей, звёздное небо для клуба "Шоколад" и другие кринжовые штуки из 2007-ого), то сходу предложил за трёху сделать педаль сильно лучше.

Мне до сих пор плохо верится, что это сработало. Зацените стойки для платы, напиленные из шариковой ручки. Крутилка от выброшенного осциллографа, покрашенная цапон-лаком и отполированная пастой ГОИ. Ну и так далееМне до сих пор плохо верится, что это сработало. Зацените стойки для платы, напиленные из шариковой ручки. Крутилка от выброшенного осциллографа, покрашенная цапон-лаком и отполированная пастой ГОИ. Ну и так далее

Папа у меня инженер и с детства дома были кучи радиодеталей и плат, а я паял всякие нехитрые приспособления. Из Музторга была позаимствована Digitech Whammy, в радиомагазине куплен PIC16F628, собрана схема чисто из советских деталей, написана программа на ассемблере и готово. На всё про всё пара месяцев работы по вечерам.

Получилась такая штука. Это уже мелкосерийный образец ручка справа заводскаяПолучилась такая штука. Это уже мелкосерийный образец ручка справа заводская

Всё делалось дома вручную. Сложнее всего было напилить квадратные отверстия для разъёма питания и окошко для цифры. Окошко с внутренней стороны было закрыто приклеенным куском прозрачной шайбы, которая всегда была на шпинделе компакт-дисков, помните такую?

Программная часть вообще без каких-то новшеств. Пресеты в EEPROM, на старте читаем активный, загружаем в RAM. Раз в несколько миллисекунд опрашиваем кнопки и рисуем на экране чиселку. Конечный автомат прямо в прерывании таймера за микросекунды делает всю работу + считает темп для функции Tap Tempo (которая, конечно, подвирала).

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

Сразу было сделано 3 таких педали двум друзьям и себе. Мне девайс не понравился не считал темп в bpm, плохие возможности программирования, мало программ, работал только с одним типом Whammy. Тем не менее, я снял видео и тоже залил в YouTube типа зацени, Молтон Вольтейдж, пацаны с лебедевочки тоже могут!

На видео добавляет стиля миди-шнурок, свитый из трёх отдельных проводов, советские аудио-разъёмы DIN-5, демо-версия видео-редактора и порванный носок. Да, тогда YouTube многое прощал.

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

Здесь уже фирмища платы заводские, с защитной зелёнкой. Работали, как автомат КалашниковаЗдесь уже фирмища платы заводские, с защитной зелёнкой. Работали, как автомат Калашникова

Но само устройство мне всё ещё не нравилось. Слишком много инженерных и конструктивных недостатков. И функциональности маловато хотелось больше, ярче, веселее.

Тем не менее до 2013-ого года они продавались именно в таком виде. Начали подтягиваться ребята с именами одна ушла Паше Додонову в Дельфин. Одну подарил Стиву Ваю, когда он приезжал в Новосибирск, но он ей скорее всего максимум ножку стола подпирает. Зато поиграли с ним на гитарах, он и его менеджер Франко Пеона похвалили мой Гибсон Лес Пол, ух хороший вечерок был. Но не об этом.

Это Рома на концерте, но видна уже новая педалька чёрнаяЭто Рома на концерте, но видна уже новая педалька чёрная

Ещё одна ушла Роме Хомутскому в 7Расу. А я стал задумываться, что пора бы сделать следующую версию на порядок круче и для этого нужно подтянуть технологии и инструментарий. И в следующий раз, когда 7Раса поехала в тур в Сибирь, купил в Москве на Авито макбук, чтобы учиться на нём работать и впитывать магию Эпол. Попросил ребят привезти мне его заодно в Новосиб типа, всё равно ж сюда едете. Вот такая звёздная доставка получилась.

Главные ошибки

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

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

Мне безумно приятно было получать сообщения, как слева. А справа типичный вид педальки первой версии, которые отправлялись к клиентамМне безумно приятно было получать сообщения, как слева. А справа типичный вид педальки первой версии, которые отправлялись к клиентам

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

Итак, краткий список принципиальных улучшений был намечен такой:
сделать точнейшую поддержку темпа в bpm. Мы же говорим про арпеджио, значит надо чтобы эффект точно попадали в контекст трека;
поддержать все типы актульных тогда (да и сейчас) Digitech Whammy, а их 4 штуки;
сделать овер-дофига пресетов и чтобы их было легко программировать, как на самой педальке, так и на компьютере;
сделать полную поддержку MIDI, чтобы педаль встраивалась в цепочки других устройств, синхронизировалась по темпу, могла сама управлять темпом других устройств и т.д.
работать устройство должно от любого блока питания и от батарейки и ещё и контролировать её разряд, ну и так далее.

Было ещё много других хотелок, но даже эти уже слабо ассоциировалось с резисторами млт и стойками из шариковых ручек. Ещё тогда вышел iPhone 4 и мне, конечно же, хотелось делать продукты в стиле Apple чтобы они были офигенные и на голову опережали конкурентов по желанию ими обладать. В общем, наметился переход на более продвинутую технологию, как в hardware, так и в software.

Оказалось, что делать такие платки в домашних условиях нет большой сложностиОказалось, что делать такие платки в домашних условиях нет большой сложности

С железом определился легко. Это точно должна быть 2-сторонняя плата и smd монтаж, чтобы получить максимум от домашнего прототипирования плат утюгом. Дальше купил паяльную станцию с феном, сделал несколько плат и обнаружил, что 0805 это раз плюнуть, довольно легко запаиваю 0603, а вот 0402 уже тяжко.

С процессором чуть сложнее. Я остался на микроконтроллерах PIC, потому что к тому времени начал мыслить их ассемблером, и он меня полностью устраивал в голове просчитывал нужное количество тиков в прерываниях, чтобы получить real time точность и т.д. Но сам камень итеративно менял несколько раз, потому что раздувались хотелки и в итоге пришёл к PIC16F1939 в корпусе TQFP. Почти что максимум, что мог предложить Microchip на 8-bit архитектуре.

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

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

Здесь нужно сделать лирическое отступление про образ жизни. Тогда я сдружился с моим лучшим другом Славой, очень известным в узких кругах CG. Слава как-то решил, что хочет делать 3D-мультики ну хотя бы на уровне Pixar и начал делать. Мы с ним быстро опознали друг в друге упоротых людей и сблизились настолько, что я постоянно жил у Славы. Днём в основном мы мотались по делам, а ночи напролёт дули кофе и пилили проекты. Кстати, какой-то мультик не без помощи Pixar мы в итоге сделали, но это совсем другая история)

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

В итоге как-то поздно вечером я вернулся домой и папа мне подсунул статью из технического журнала. Он знал, что я чёто-то там делаю с микроконтроллерами и время от времени подкидывал интересные, на его взгляд, материалы.

И каково было изумление, когда я понял, что в статье описывается решение моей боли а именно, как в операционной системе работает планировщик задач. Я был ослеплён изящностью и красотой этой идеи, и тут же сел писать демку планировщика. Часам к четырём ночи она была готова, я не думая прыгнул в машину и прикатил к Славе, нашёл его сидящим с краснющими глазами за мониторами и, сбиваясь, в эйфории рассказал про случившийся прорыв. Слава выслушал, посмотрел оценивающе и сказал: "Иди спать, педрила". Так появилось гордое название PeOS в пунктах преимуществ устройства и Pe это не pedal.

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

Почему не взял готовый? Да тупой потому что. Даже Git не знал зачем нужен. Ну и хотелось во всём разобраться, сделать идеальный девайс.

Хорошо запомнил ощущение сюра, как в Томске Борис Гребенщиков с Аквариумом изрядно накидались в гостинице, плясали на столах, а я в центре этой вахканалии пишу на ассемблере свою операционную систему.

Как вам такое? Самый жёсткий был модуль экрана со всеми шрифтами и анимациями там около 7 тысяч строкКак вам такое? Самый жёсткий был модуль экрана со всеми шрифтами и анимациями там около 7 тысяч строк

Выбор ассемблера привёл к понятной проблеме. Вы видели когда-нибудь абсолютно нечитаемый код? Скорее всего вы видели эталон достижений в области программирования по сравнению с тем, что стало результатом работы. 30+ килобайт ассемблерного кода это очень много.

Но дальше сюр будет только крепчать.

Прототипы

Начало работы полностью происходило в симуляторе, но довольно скоро дошло время и до проектирования схемотехники. Схему для первой версии педали чертил в простейшем Sprint Layout. Для второй же версии потребовался инструмент серьёзнее изучил Eagle и разводил платы там.

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

Слева россыпь прототипов, а справа финальный в демо-корпусеСлева россыпь прототипов, а справа финальный в демо-корпусе

Прототипов было с десяток. Сначала неказистые макеты частей системы, потом начал объединять их на одной плате с прицелом засовывания в корпус. Но даже таких было штук 5 или 6 перед финальной версией.

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

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

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

Это разная графика на разных экранах. Логотип я потом переделал на более строгий. Но больше всего меня прикалывала иконка ноги, которая символизировала шаг. Прикольно)Это разная графика на разных экранах. Логотип я потом переделал на более строгий. Но больше всего меня прикалывала иконка ноги, которая символизировала шаг. Прикольно)

UI весь нарисовал сам. А потом перерисовал. Тогда мы со Славой время от времени нехило рубились в танки, поэтому в педальке все цифры стали трафаретными, как будто с военной техники.

Для соединения педали с компьютером разобрался с Objective-C и написал программу (о, боги, как-то я заглянул в код и чуть не заплакал кровью) под Mac OS. Помню, как у меня бомбило, когда узнал, что в Objective-C есть специальный объект NSNumber для числа. Я привык в 1 байт упаковывать 4-битное число и 4 буля и мне это показалось кощунственным разбазариванием ресурсов.

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

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

USART
Педалька должна была встраиваться в MIDI-цепочку. MIDI это последовательный однонаправленный протокол с токовой петлёй на физическом уровне. Мне нужен был 1 MIDI-вход для получения данных извне, 1 MIDI-выход для отправки данных вовне и 1 MIDI-выход для отправки данных в процессор Whammy, собственно для прямой функции девайса, ради чего затевался сыр-бор.

Я был абсолютно и непоколебимо уверен, что USART-ов в PIC16F1939 ну минимум 2 или 3, а оказалось, что ОН ОДИН. Это случилось, когда почти вся функциональность была закончена и более-менее протестирована, а оставалась только реализация MIDI. Я настолько тогда охренел, что растерялся.

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

В общем, выкрутился ужасно. На выход USART'а добавил переключатель физических выходов MIDI на транзисторе. И в зависимости от назначения байт отправлялся в один или другой выход. По факту предельная скорость передачи данных снизилась в 2 раза, но на практике с этим решением вообще никаких проблем.

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

В итоге задача свелась к делению 16-ричного числа на 3. А дешёвый 8-битный контроллер не то чтобы вообще умел это делать. Как бы там есть сложение, вычитание и сдвиг, которым можно делать умножение и деление на степень двойки. Но не другого числа.

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

Релиз

Педаль вышла в свет в 2014-ом. Для релиза первой версии заказал 10 плат у китайцев за 5 долларов. Пришло 11, что безмерно обрадовало. Платы собрал сам и после самодельных плат с трещинами в дорожках и недотравами это был просто глоток свежего воздуха.

Справа изготовление платки, два прототипа разного уровня проработки. Чёрный уже предсерийныйСправа изготовление платки, два прототипа разного уровня проработки. Чёрный уже предсерийный

Цену выставил, как мне тогда казалось, солидную 5 000 за коробочку. Штук шесть у меня купили в первый же день ближайшие знакомые. Ощутил запах победы и что всё не зря. Но дальше продажи полностью остановились. Надо было что-то делать.

Не долго думая, решил использовать способ, сработавший раньше, но с небольшим дополнением. Педаль сфотографировали у знакомого фотографа по-моему за бутылку виски, которая и фигурирует на фотке. Мой друг собрал сайт, который можно посмотреть в веб-архиве. Угадайте, чего не найти на сайте? Правильно, кнопки купить!)

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

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

Звёздный сюр

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

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

Это Уэс на NAMM 2014. Сам я туда, конечно же, не попал, но Уэс здорово мне помог, век не забудуЭто Уэс на NAMM 2014. Сам я туда, конечно же, не попал, но Уэс здорово мне помог, век не забуду

Hed P.E.
Одним из первых во внимание попал Wesley Geer (экс Hed P.E.). Он приезжал в Новосибирск вместе с Korn, оказался супер-общительным чуваком и с ним удалось посидеть в баре. Договорились, что я отправлю ему педаль на NAMM с кем-то из Нск (сам я не могу поехать, бабок не было), так и вышло. До сих пор время от времени переписываемся.

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

Так произошло с группой Drowning Pool. Их гитарист CJ Pierce активно использовал Digitech Whammy в музыке и был отличным кандидатом под "моего" артиста. Через знакомых рассказал их менеджеру про педаль, но так я был вообще на мели, не мог педаль подарить. В итоге менеджер купил педальку и мы здорово пообщались с CJ-ем. Я объяснил ему все функции девайса, он офигел и предложил помочь их продавать в Штатах. Я обрадовался, но опять не хватило опыта дожать тему. Мы просто потерялись.

Та самая ВаммиТа самая Вамми

Но из той встречи я вынес неожиданный бонус. Моя педалька это контроллер для Whammy, который поддерживал 3 вида процессоров. Но своих Whammy у меня тогда не было. И если пятую версию было найти легко, то четвёртую постоянно приходилось одалживать, чтобы проверить новую партию педалей. А у СиДжея тогда в туре сломалась его Whammy IV. Я его не просил, но он просто взял расписался и подарил её мне.

Я так обрадовался, ппц. Починил её дома, аккуратно почистил, чтобы сохранить историческую задроченность и до сих пор тестирую на ней новые девайсы. На фотке в заголовке она на заднем фоне красная. Помните, в самом начале фильма XXX Вин Дизель сбрасывает в пропасть красный корвет С5 под песню "Let the body hit the floor"? Ещё такой мем был с попугаем. Кароче это та самая Whammy, которая в их песнях записана.

Наив
Уже не помню, как продал педальку Валере Аркадину из Наива и Матрикса. Но это один из российских музыкантов, который очень много экспериментировал с её звуком. Она вошла в песню "На пределе", Валера написал большое произведение на заказ с её использованием, рассказал о ней в журнале In/Out. Кароче, Валера, каждый раз когда ты звонишь, чувствую безумную радость и уважение, но и угрызения совести, что до сих пор не выпустил новых девайсов.

Korn
Дальше узнал, что ещё раз приезжают Korn и попросил Wesley рассказать о педальке Хэду и Манки. Он не обломался и Манки вписал меня в список приглашённых группой гостей в Нск. К сожалению, Хэд тогда свалился с камнями в почках в Красноярске и остался там в больнице, так что не пообщались, но педалек я всё равно подарил две.

Дядьки оказались очень адекватными увлечёнными людьмиДядьки оказались очень адекватными увлечёнными людьми

Это была очень странная встреча на стадионе в Нск мы болтали с Манки, подошёл Джонотан Дэвис и восхитился педалью, рассказал про свой второй проект Killbot. В итоге Манки записал мой номер телефона и email, мы обменялись контактами и я ушёл даже не подозревая, что вообще произошло сейчас.

Crosses и Deftones
Как-то на даче я помогал родителям, копал картошку, и пошёл посидеть в тенёк, потому что припекало. На телефоне высветилось новое письмо заказ с сайта. Имя заказчика Shaun Lopez. "Что за латинос?" подумал я.

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

Слева моя реальность в тот день, когда Шон купил педаль. А справа она у него среди другого хламаСлева моя реальность в тот день, когда Шон купил педаль. А справа она у него среди другого хлама

Так произошло и в тот раз. Но уже через несколько секунд я чуть не навалил в штаны, потому что Шон оказался продюсером Deftones, другом Чино Морено и музыкантом в one-man проекте Crosses. Ну а мы тогда сильно фанатели по Deftones.

Я предложил ему 50% скидку за упоминание Smirnov Electronics на его сайте и традиционную фотку в инстаграм. Собственно, Шон без возражений выполнил свою часть обязательств.

Limp Bizkit
С лимпами была история прям по касательной. Вес на самом деле Wes не использует Whammy в Limp Bizkit и вообще её не то чтобы любит, но у него есть и другие проекты. Плюс иногда он в инсте разыгрывает всякий ненужный ему хлам.

Уэс и Фред в тот приезд здорово угорели. Мы их свозили в деревню в местный колорит, где Уэс (да, на фото он) снимался в видео с козой и жигойУэс и Фред в тот приезд здорово угорели. Мы их свозили в деревню в местный колорит, где Уэс (да, на фото он) снимался в видео с козой и жигой

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

Заключение

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

Как резюме, хочу подчеркнуть смотрите, как много я совершил ошибок. Собрал почти все: от неверно выбранной технологической платформы до совершенно не организованных продаж и развития, профукал почти все шансы, упустил время. Но главное-то остаётся с нами музыканты играют на педальках и раз в месяц-два до сих пор ребята интересуются покупкой (безуспешно), как подтверждение, хоть и очень локальной, но ценности.

Так что если у вас в голове есть идеи, но вы не знаете как их сделать, просто делайте. Не думайте, делайте. А как начнёте делать думайте, и тогда это точно приведёт к какой-нибудь крутой истории.

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

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

Stay Heavy \m/

Подробнее..

О том, как мы температуру в ЦОД мерили

21.04.2021 14:19:32 | Автор: admin

Если у вас большой и серьезный ЦОД, то параметрия температурных режимов не является проблемой. Существуют проверенные решения, например, программируемые контроллеры TAC Xenta, которые работают через LonWorks. Именно так мы собираем данные в московском ЦОД Datahouse. Но непосвящённому смертному весьма непросто собрать правильные показатели из этой связки и выводить их в мониторинг в нужном виде. К тому же решение промышленное и достаточно дорогостоящее. Поэтому при строительстве новой гермозоны
в Екатеринбурге мы решили поэкспериментировать и внедрить альтернативное решение по измерению температуры в холодных и горячих коридорах.

Ничто не предвещало беды

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

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

Из первой партии завелось 15 датчиков. Так как острой потребности в остальных не было, пока работали с ними. К моменту прихода второй партии выявили, что часть уже установленных в шину датчиков имеет поведение новогодней елки: показывают некорректные данные, отдают checksum error или отваливаются по таймауту.

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

Вот так это выглядело:

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

Не ищем легких путей

Вскрыли, посмотрели: микросхемы идентичны. Значит дело в прошивке.

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

Пока разбирались с работоспособностью датчиков поняли, как без особых сложностей обнаружить кривую версию прошивки. Если кроме Modbus работают обычные текстовые команды READ, PARAM, AUTO, STOP значит прошивка хорошая. В мертвых прошивках текст не отдается.

Решили взять прошивку с этих живых 8 датчиков, купили программатор Nu-Link,
но хитрые китайцы заблокировали чтение прошивки. То есть перезалить можно, а считать - нельзя. Запросы правильных прошивок у поставщика потерпели фиаско:
Я продаван, я не разработчик.

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

За основу был взят Keil, пакет С51, позволяющий работать с 8-битными MCU.

В начале я научил читать сенсор SHT 20 (который собственно и снимает температурные данные), потом научил передавать эти данные по Modbus. В виду того, что этот MCU ни что иное как Nuvoton N76E003AT20, то вся база знаний, видимо, сосредоточена в руках наши китайских друзей.

В итоге i2c и Modbus сделал быстро, а вот с таймерами пришлось повозиться. Чтобы не было коллизий в шине, добавил возможность смены SLAVE_ID без выключения датчика в Китайской версии прошивки после смены адреса его нужно было обязательно выключать, что не очень удобно.

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

Так стало:

Если говорить про результаты измерений, то нормальные показания появляются только
в помещении с явной циркуляцией и движением воздуха. Без продува датчик показывает 30С. Это объясняется тем, что внутри установлен стабилизатор напряжения, который преобразует 24В в 3.3В, переводя разность в тепло.

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

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

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

Подробнее..

Ненормативная схемотехника ATmega8 кто сказал, что выше головы не прыгнешь?

30.04.2021 12:16:20 | Автор: admin
Вот уж несколько лет, как я увлёкся микроконтроллерами, а именно семейством AVR. Ещё на этапе освоения Ардуино (в этот момент часть аудитории поплевались и ушли читать другие статьи) я пытался выдавить из неё больше, чем задумано. Меня всегда больше интересовали нестандартные решения обычных задач. Сейчас я знаю об AVR намного больше, чем ещё пару лет назад, и всё больше убеждаюсь, что знаю очень мало.

С чего начинает среднестатистический начинающий электронщик? Правильно, с часов! Как только научился пользоваться голыми контроллерами, а не платами Ардуино, захотел сделать часы на Атмеге, голой Атмеге без кучи ключей и буферных микросхем. И обязательно со статической индикацией, а не с динамической (ну не люблю я её). Собственно, статическая индикация программно гораздо проще, а ведь мы простых путей не ищем. Но зато возникают другие проблемы, которые несколько раз меня останавливали в самом начале пути. Понятно, что если взять Атмегу пожирней, задействовать в ней половину ножек и полпроцента памяти, задача упрощается донельзя, собственно, такие настольные часы на ATmega128 работают у меня на столе пару лет.

Но это не наш метод. Я с самого начала хотел именно ATmega8, как самую доступную и самую дешёвую (в том числе в дип-корпусе). Всё то же самое можно и на ATmega48, но её попробуй ещё найди, разве что у Вас в ящике стола валяется их много с незапамятных времён.
Посмотрим на картинку, известную всем, кто недавно интересуется AVR.

Глядя на неё, легко посчитать, что мы можем задействовать на часовой индикатор 20 ножек, ещё на двух у нас будет кварц (куда от него денешься, внутреннее тактирование не прокатит, нам ведь от часов нужна точность какая-никакая). Ну и сброс. На четыре семисегментных индикатора нужно 28 ног, ну даже 27, ведь десятки часов можно отображать цифрами 1 и 2, а ноль не отображать. Ещё когда идея только зарождалась, я это количество сократил до 22 ножек, ведь для отображения десятков часов можно обойтись цифрой 1, и оба её сегмента зажигать от одной ноги контроллера. Но как ни крути, пары ног мне всё же не хватало, даже при том что я давно знал о возможности использования как ввода-вывода ножки Reset, но так ещё ни разу и не попробовал (если не считать ATtiny13, её не так жалко было), ведь параллельного программатора у меня нет, а любая отладка это минимум несколько итераций прошивки, вряд ли всё идеально выйдет с первого раза. Так и лежал этот замысел в закромах мозга, и ждал своего времени, пока как-то мне не пришла в голову ещё одна интересная идея.
Посмотрел я однажды на светодиодную ленту (120 светодиодов на метр) и понял, что можно легко и просто сделать электронные часы любого размера с её использованием. Просто нарезаем сегменты желаемой длины и наклеиваем их на подходящее основание, для больших размеров сегмент может складываться из двух и более лент по ширине. В моём случае отрезаем сегменты по шесть светиков, таким образом длина сегмента составляет 50 мм, высота цифры 100, для моей задумки офисных часов вполне достаточно.

Но вот незадача, напряжение питания ленты 12 вольт, а AVR хочет не больше 5 вольт (люди утверждают, что и 8 вольт выдерживают, но я проверять пока не буду, да и не поможет). То есть нужно ставить 22 ключа для включения всех сегментов. Если применить драйвер ULN2003, хватит (почти) трёх штук, это копейки, но, как я писал выше, это не наш метод, хотя этот вариант можно приберечь для часов побольше. Ну никак это не вписывается в концепцию голая Атмега.
И вот тут-то начинается самое интересное. Напряжение питания ленты 12 вольт, первые признаки жизни белые светодиоды (три штуки последовательно) начинают подавать при более чем 7.5 вольт, и, что очень важно, до этого напряжения ток через ленту практически равен нулю (можете проверить). На ножке контроллера, работающей в режиме выхода, может быть уровень 0 вольт или 5 вольт (третий вариант рассмотрим позже), и если подключить сегмент анодом к +12 вольт, а катодом к выходу контроллера, напряжение на сегменте будет равняться соответственно 12 вольт (светит) или 7 вольт (не светит). Даже если бы через погасший сегмент протекал мизерный ток, он ушёл бы к плюсу питания контроллера через защитный диод, которые в AVR гораздо более выносливые, чем нас пугали в обучающих статьях. Третий вариант ножка в режиме входа, напряжение на сегменте 7 вольт благодаря защитному диоду, стало быть сегмент не светит. Конечно, как только я обдумал всё это в теории, сразу же проверил на практике, залив в контроллер простой блинк для одной из ног.

А так как сейчас в силу некоторых обстоятельств мои возможности для экспериментов слегка ограничены, напряжение питания для сегмента случайно оказалось более 18 вольт, и сегмент гас не полностью, так что я успел разочаровано подумать, что подпалил выход. Когда уменьшил напряжение (на тот момент даже нечем было померить), всё стало на свои места, так что, благодаря нелепой случайности, теперь я точно знаю, что для схемы не смертельно небольшое повышение напряжения выше 12 вольт. С напряжением определились, а что с током? На моей ленте (и скорей всего на вашей тоже) стоят резисторы по 150 ом, ток через сегмент не более 20 мА, только сегмент в данном случае это три светика, а каждый сегмент моих часов состоят из двух сегментов ленты, так что 40 мА. Вроде даже вписываемся в даташит, но на 20 выходов это уже 800 мА, что намного выше дозволенных 200 мА на корпус. Ток через ленту очень зависит от напряжения на ней, и нелинейно падает даже при небольшом снижении, что является большим минусом при обычном использовании ленты, и плюсом в данном случае, ведь реальное напряжение на сегментах равно 12 вольт минус падение на ключе (около 0.6 вольт), а ещё при желании можно снизить напряжение питания, понизив тем самым яркость часов. Так что страшные 800 мА несложно снизить раза в два. В любом случае, я был уверен, что это не станет проблемой, да и приобретённый опыт ценнее, чем возможность потери одной Атмеги. Вот так просто ATmega8 коммутирует индикатор с напряжением в два с лишним раза выше, чем её собственное напряжение питания. Именно это я имел ввиду в заголовке статьи. Хотя способ совсем не нов, по такому же принципу работает советская микросхема К155ИД1 высоковольтный дешифратор управления газоразрядными индикаторами, где сравнительно низковольтные выходы (до 60 вольт) коммутировали индикаторы с напряжением зажигания 150 вольт и выше.

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

Аноды к +5 вольт через один на всех резистор, для отладки прошивки этого за глаза. Первоначально я остановился на варианте использования в качестве двух недостающих ног пинов подключения кварца (итого 22 выхода), а тактировать контроллер от внешнего генератора минутных импульсов через (внимание!)

вход сброса. Это ещё один занимательный лайфхак, который описан здесь Как сохранять переменные arduino, при reset
Можете попробовать залить в Ардуино простой код и посмотреть результат работы в Мониторе порта, периодически сбрасывая её кнопкой или с клавиатуры:
пробник
#define NO_INIT __attribute__((section(".noinit")))void setup() {    static unsigned NO_INIT nonInitCounter;    nonInitCounter=nonInitCounter+2048;    Serial.begin(9600);    Serial.print("Setup counter: ");    Serial.println(nonInitCounter);}void loop(){}

Не буду приводить свой код, дабы не подвергаться критике лишний раз (я не самый лучший
программист), но если в двух словах, можно создать глобальную переменную, которая сохраняется при перезагрузке контроллера. Раз в минуту под воздействием внешнего импульса сброса Атмега перезагружается, и начинает заново отрабатывать прошивку: считывает переменную, преобразует её в показания часов и минут, выводит на индикатор, увеличивает переменную на одну минуту. Дальше опять сброс, и всё по-новой. Пришлось порыться в интернете и опробовать два вида программного сброса, ведь внешнего генератора у меня пока не было, а жмакать сброс всё время вручную не сильно удобно.
пример функции
void softReset() {  //программный сброс //http://kazus.ru/forums/showthread.php?t=13540&page=3#  asm volatile ("rjmp 0x0000");//Sketch uses 2412 bytes  //программный сброс //https://www.cyberforum.ru/post11307314.html  //((void(*)(void))0)();//Sketch uses 2416 bytes}

Что интересно, эта переменная может сохраняться даже после отключения питания (не всегда). И это не EEPROM, если кто подумал. По мере отладки я пришёл к использованию нескольких таких переменных.
Кто-то скажет: какой внешний генератор, а как же концепция голой Атмеги? На момент опытов я был уверен, что в качестве генератора выступит некая козявка размером с рисинку и ценой в пару копеек. Но когда всё заработало в теории и пришло время с козявкой определиться, меня ждал облом. Единственное, что меня могло бы устроить, это старая добрая К176ИЕ12, кто бы мог подумать! На дворе двадцать первый век, а в природе нет простого доступного аппаратного генератора минутных импульсов.
Не беда, подумал я. Можно и раз в секунду. Немного переписал код, проверил работу, отлично. Но опять же, для внешнего генератора я нашёл только DS1307 (и её аналоги). Да, стоит копейки, для обвязки достаточно кварца (и ещё несколько необязательных элементов). Вот только чтобы она генерировала секундные импульсы, ей нужно подать команду по шине I2C. Чем подать? И какую? (Теперь я уже знаю, но на тот момент не было с чем пробовать). Короче, не подходит. Ладно, последняя надежда. Некоторые западные часы 90-х тактировались частотой сети 50 гц. Возможно, сейчас у нас частота стабильней, чем была 30 лет назад. Попробовал сымитировать работает. Если бы воплотил в жизнь, было бы оригинально контроллер, перезагружающийся 100 раз в секунду. Но я пока отмёл этот вариант и стал искать другие.
Итак, я мог использовать 22 выхода (что мне достаточно) и пин сброса в качестве входа. Но не срослось Вернёмся к варианту с кварцем. Минус две ноги на кварц получаем 20, даже если ножку сброса сделать портом ввода-вывода, получим 21, всё равно не хватает одного, хоть ты тресни! Но недавно я нашёл способ, о котором задумывался ещё два года назад, что-то не получилось тогда. У нас есть пин AREF для опорного напряжения, относительно которого происходят аналоговые измерения. И на этот пин мы можем программно подать напряжение питания в качестве опорного или встроенное опорное напряжение 2.56 (для ATmega8). Проверил, измерил, действительно можно получить на ножке AREF напряжение 0 вольт, 2.56 вольт, или 5 вольт.
превращаем AREF в ещё один выход
// превращаем AREF в ещё один выходvoid setup() {}void loop() {  ADMUX = 7 + 64; // уровень 1 (5 вольт) // 7-номер аналогового входа//почему 7 аналоговый вход? А потому что физически у Атмеги входы от 0 до 5,//и назначение аналоговым входом входа 7 не помешает работе остальных в качестве выходов  delay(3000);  ADMUX = 7 + 64 + 128; // уровень 2.56 вольт // 7-номер аналогового входа  delay(3000);  ADMUX = 7; // уровень 0  delay(3000);}

Нюанс: это если замерять относительно общего провода. Если мерить относительно +5 вольт, во всех трёх случаях получаем ноль. То есть нагрузку можно подключить между AREF и общим. Экспериментально установил, что можно получить ток до 20 мА при 2.56 вольт на ноге и до 40 мА при 5 вольт на ноге, этого вполне достаточно, чтоб зажечь светодиод, к примеру. Вот он, заветный двадцать второй выход! Конечно, напрямую управлять двенадцативольтовым сегментом не получится, нужен транзистор. Под руку попался легендарный КТ315, сколько лет я к нему не прикасался, как по мне, это одно из лучших произведений советской электроники, наряду с микрухой К155ЛА3. Когда-то я КТ315 и КТ361 даже в качестве ключей импульсного трансформатора питания применял, при напряжении порядка 60 вольт, и неоднократно. Здесь же нагрузка для него плёвая, и я принципиально даже резистор в базу не поставлю (20 мА вполне себе допустимый ток базы).
Ну что ж, думал я, 22 выхода есть, отлажу код с 21 выходом, а 22-й на ножке сброса оставлю напоследок, когда всё остальное уже заработало. Ещё ведь и кнопки установки времени нужно куда-то приткнуть, а ног не осталось вовсе. Если б Атмега была в SMD-корпусе, можно было бы воспользоваться входами А6 и А7, выходами они всё равно не умеют. Но у меня корпус DIP, так что такой роскоши позволить себе не могу. Зато ведь можно выходы сделать входами, правда, в это время придётся погасить индикацию, но для опроса кнопок достаточно нескольких миллисекунд, никто и не заметит (как я ошибался!). Значит, перевожу ножки кнопок в режим входов, подтягиваем программно к питанию, и ждём пару миллисекунд. Если в это время какая-то из кнопок замкнута на минус, ждём ещё 20 миллисекунд, убеждаемся, что кнопка всё ещё нажата, и производим соответствующее действие в программе. Кнопки: минуты плюс, минуты минус, часы плюс, коррекция плюс. Коррекция подстройка значения секунд раз в сутки в зависимости от суточного ухода показаний. Всё заработало почти с первого раза. Нюанс: при замкнутой кнопке сегмент, подключенный к той же ножке, оказывается включён, такой вот побочный эффект, что поделать. Поэтому кнопки изменения минут подключаю к выводам контроллера, которые управляют сегментами единиц часов, а кнопки изменения часов подключаю к выводам контроллера, которые управляют сегментами единиц минут. Выставляя минуты, совсем не обращаешь внимания, что с показаниями часов что-то не так, и наоборот.
Ну вот всё и получилось в макете с семисегментными индикаторами. Но параллельно я пытался накопать информации по поводу применения ноги сброса в качестве порта ввода-вывода. А там всё довольно туманно и неопределённо. Основное, что об этом пишут: 1 выход с открытым коллектором (не точно), 2 выход чрезвычайно слаботочный (тоже не наверняка). Даже если использовать внешний транзистор, нужно точно знать, открытый коллектор (сток) или нет, ведь тогда состояние выхода нужно инвертировать. А перепрошить я уже не смогу. В общем, опять двадцать пять! Вернее, двадцать один. Двадцать один гарантированный выход вместо 22. Опять одного не хватает. Опять я мечусь в поисках решения.
Возвращаюсь к AREF. Я могу получить три разных уровня на нём. Значит можно зажечь один сегмент, или два одновременно, или ни одного. Я обратился к своей же прошлой публикации (как давно это было!):Ненормативная схемотехника: семисегментный индикатор на ATtiny13
Чтобы попробовать использовать решение с совместным включением двух сегментов. Перебирая возможные пары сегментов, и осознавая, что подобное усложнение портит всю картину, я внезапно додумался, что для цифры десятков минут поле поиска значительно сужается цифра ведь может быть только от 0 до 5. И тут пришло озарение:

во всех этих цифрах сегменты А и D или вместе светятся, или вместе погашены! Не нужно никаких трёх состояний, достаточно просто соединить сегменты А и D вместе и подключить к одному выходу. И теперь 21 выхода хватит всем. Довожу код, проверяю на макетке с семисегментным индикатором, бинго!
Всё, пора делать финальный вариант. Нарезаю светодиодную ленту, наклеиваю сегменты на подходящее основание, которым оказался кусок пластиковой вагонки (примерно 150х350 для цифр высотой 100). К каждому сегменту нужно подвести +12 вольт и проводник от соответствующего выхода контроллера.

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

Сегменты цифр разведены по контроллеру именно в таком порядке для упрощения кода.
Первая цифра (единица, напоминаю) состоит из цельного куска ленты длиной 10 см. Подключаю 5 вольт питания контроллера и 12 вольт питания индикации, включаю. Вот он, торжественный момент, всё красиво светится, часики работают. Кстати, на КДПВ в начале статьи в качестве светофильтра на индикаторах лежит лист обычной бумаги для принтера, яркости хватает с избытком.
Мне не очень нравится американская система отображения времени, когда часы дважды в сутки считают до двенадцати. Я применил свою: с нуля часов до 19, и затем 8, 9, 10, 11. А с учётом того, что часы офисные, в 8 вечера их редко кто увидит.

В такие моменты ощущаешь некоторое разочарование, что прям вот так сразу заработало, даже как-то неинтересно. Поначалу я упорно не хотел замечать некое мерцание индикаторов, пока мне на него не указали коллеги. В макете этого мерцания не было видно совсем, а тут прям бросается в глаза. Выше я писал, что при опросе кнопок после перевода ножек в режим ввода сделана пауза в пару миллисекунд, без этой паузы остаточный потенциал на ножке воспринимался как нажатие. Так вот тих двух миллисекунд, в течение которых индикаторы потушены, оказалось достаточно для мерцания индикации. И это при том, что опрос происходит два раза в секунду. То есть глаз замечает двухмиллисекундную паузу дважды в секунду, чего я никак не ожидал. Было подозрение, что вследствие переходных процессов контроллер каждый раз перепроверяет, действительно ли нажаты кнопки (а это по 20 мсек на каждую). Я внёс некоторые изменения в код, временно отключив все лишние функции, но подозрения не подтвердились. В результате помогли следующие изменения: для опроса кнопок перевожу те выходы, которые задействованы под кнопки, в высокий уровень, тем самым погасив соответствующие сегменты, только после этого перевожу те же ножки в режим входа. Таким образом от паузы в 2 мсек можно избавиться совсем, но я оставил на всякий случай 300 микросекунд, проблема исчезла полностью.
Что мы имеем в итоге? Ножка сброса контроллера работает по прямому назначению и используется только при заливке прошивки, ножки кварца подключены к кварцу, как и положено. Двадцать ног работают как порты ввода-вывода, и нога AREF управляет ключевым транзистором, как раз на него и навешена цифра десятков часов (1). Ещё четыре ножки питания, никто не отлынивает, все ноги задействованы. По поводу кварца: я всерьёз рассматривал вариант применения часового кварцевого резонатора на 32768 Гц (считал его более точным), но отказался от идеи, побороздив интернет. Оказывается, запустить Атмегу с часовым кварцем не так-то просто и нет никаких гарантий работоспособности, а плюсов от применения не особо. Экономичность нас не интересует в данном случае, основное потребление индикация. С точностью тоже всё неопределённо. А суточный уход вполне компенсируется программно. Зато большим плюсом является простота подключения, кварц на 8 или 16 МГц без проблем работает даже без конденсаторов. В результате вся схема состоит (если не считать индикаторы и питание) из Атмеги, кварцевого резонатора, и транзистора, припаянных прямо к панельке. Питание контроллера обеспечивается малогабаритным стабилизатором из серии 7805, ток через него мизерный, но на всякий случай я припаял его теплоотводом к кусочку оцинковки примерно 30х30 мм. В целом же часы питаются от внешнего блока питания на 12 вольт, который, по сути, дороже всех комплектующих. Фактический ток потребления часов 560 мА при показаниях часов 18-08 (это максимальное количество сегментов, которые можно засветить одновременно), получается около 28 мА на сегмент. Это при напряжении питания часов 11.7 вольт, ещё 0.3 вольта падает на диоде, включенном для защиты от неправильного подключения и чтоб немного снизить напряжение и ток соответственно. Падение на выходных ключах Атмеги около 0.56 вольт. Все токовые режимы превышены, но Атмега справляется, честь и хвала творцам! Запаса яркости избыточно, напряжение питания можно ещё снижать. Если тактировать Атмегу от внутреннего генератора, то можно ножки кварца отдать индикатору, а время считывать по I2C с DS1307. Опять же будут заняты все ноги, но зато питание часов можно будет отключать хоть на неделю, а время продолжит тикать. Хотя точность DS1307 совсем не радует, и по моему опыту, и по отзывам в интернете. Зато на ней есть дополнительный выход с открытым коллектором, которому можно дать команду мигать с частотой 1 Гц, и навесить на него разделительную точку. В моих часах разделительных точек пока нет, можно разрезать ту же ленту на отдельные светодиоды и подключить постоянно к напряжению питания. Мигать не будет, но я думаю над этим. Может, кто подскажет, как выжать ещё каплю из Атмеги?
И, напоследок, код для тех, кто захочет повторить. Компилировал и прошивал в ArduinoIDE. Я не программер, так что примите как есть, если кто предложит лучше, с удовольствием выложу.
Особо чувствительным не смотреть
Я предупреждал
bool Flag;uint8_t TimS;uint16_t TimH = 12; // время при включении 12-34uint16_t TimH_;uint16_t TimM = 34;uint16_t TimH0;uint16_t TimH1;uint16_t TimM0;uint16_t TimM1;uint16_t TimKorr = 7; // коррекия по умолчанию 7 - это 0 секунд, если 0 - это -28 сек, если 14 - это +28 сек// массив для цифрint semisegm_[10] = {B01011111, B00000110, B01101011, B01100111, B00110110, B01110101, B01111101, B00000111, B01111111, B01110111};void setup() {                                                         //  PORTB = 0;  PORTC = 0;  PORTD = 0;  // инициализация Timer1  cli();  // отключить глобальные прерывания  TCCR1A = 0;   // установить регистр в 0  TCCR1B = 0;   // установить регистр в 0  // Таймер переполняeтся каждые 65535 отсчетов при коэффициенте деления 1024 или за 4,194с  OCR1A = 62499; // установка регистра совпадения (4 секунд)  TCCR1B |= (1 << WGM12);  // включить CTC режим > сброс таймера по совпадению  TCCR1B |= (1 << CS10);   // Установить биты CS10 CS12 на коэффициент деления 1024  TCCR1B |= (1 << CS12);  TIMSK |= (1 << OCIE1A);  // для ATMEGA8  sei(); // включить глобальные прерывания}   void loop() {  if (TimM > 59) {    TimM = 0;    TimH++;    Flag = 0;  }  if (TimH > 23)TimH = 0;  if (TimM < 0) {    TimM = 59;  }  if (TimH == 0) {    if (TimM == 0) {      if (TimS == 7) {        if (Flag == 0) {          TimS = TimKorr;          Flag = 1;        }      }    }  }  TimH_ = TimH;  if (TimH > 19)TimH_ = TimH - 12;  TimH0 = TimH_ / 10;  TimH1 = TimH_ % 10;  TimM0 = TimM / 10;  TimM1 = TimM % 10;  Led(TimH0, TimH1, TimM0, TimM1);  Key();}// функция индикацииvoid Led(uint16_t TimH0, uint16_t TimH1, uint16_t TimM0, uint16_t TimM1) {  DDRB = semisegm_[TimM1];  DDRC = semisegm_[TimM0];  DDRD = semisegm_[TimH1];  bitWrite(DDRD, 7, bitRead(semisegm_[TimM1], 6));  ADMUX = 199 * TimH0; // уровень 2.56 на AREF  PORTB = 0;  PORTC = 0;  PORTD = 0;  delay(500); // полсекунды просто отображаем время}// функция опроса кнопокvoid Key() {  bitWrite(PORTB, 2, 1);  bitWrite(PORTD, 2, 1);  bitWrite(PORTD, 1, 1);  bitWrite(PORTD, 0, 1);  bitWrite(DDRB, 2, 0);  bitWrite(DDRD, 2, 0);  bitWrite(DDRD, 1, 0);  bitWrite(DDRD, 0, 0);  delayMicroseconds(300);   if (bit_is_clear(PINB, 2)) {      delay(20);    if (bit_is_clear(PINB, 2)) {      TimH++;    }  }  if (bit_is_clear(PIND, 1)) {    delay(20);    if (bit_is_clear(PIND, 1)) {      TimM++;    }  }  if (bit_is_clear(PIND, 0)) {    delay(20);    if (bit_is_clear(PIND, 0)) {      TimM--;    }  }  if (bit_is_clear(PIND, 2)) {    delay(20);    if (bit_is_clear(PIND, 2)) {      TimKorr++;      if (TimKorr > 14)TimKorr = 0;      Led(0, 8, TimKorr / 10, TimKorr % 10);      delay(500);    }  }}ISR(TIMER1_COMPA_vect) // Выполняем 1 раз в 4 секунды.{  TimS++;  if (TimS > 14) {    TimM++;    TimS = 0;  }}


Подробнее..

Перевод Лучшие одноплатники на базе чипа RP2040 в 2021 году. Часть 1

11.06.2021 02:20:41 | Автор: admin

С момента выхода Raspberry Pi Pico мы опубликовали несколько статей о системах на базе чипа RP2040 от обзора возможностей одноплатника самой компании до более продвинутых систем других производителей. Сейчас их выпущено уже столько, что публиковать обзор всех подобных плат просто нет смысла. Зато можно разместить подборку лучших систем.

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

Немного о критериях выбора плат с RP2040


Какой размер и сколько требуется пинов? У меньших по размеру плат вроде Pimoroni Tiny RP2040 меньшее количество пинов, чем у оригинальной малинки, так что такие платы можно использовать в относительно ограниченном количестве проектов.

Нужна ли беспроводная связь? С полноценным модулем Wi-Fi и Bluetooth поставляется лишь Arduino Nano RP2040 Connect. Но помимо нее производители предлагают дополнения к Pico вроде Adafruit Airlift board или Pimoroni Pico Wireless Pack.

Экосистема. Понятно, что Raspberry Pi Pico совместима со всей экосистемой продукции Raspberry Pi Foundation. Есть и другие платы, вроде Adafruit Feather RP2040, которые совместимы с представителями продуктовой линейки FeatherWing. Возможности таких плат отличаются от возможностей оригинальной системы.

Наличие дополнительных коннекторов. Это, в первую очередь, Stemma QT, Qwiic и Grove, которые идеально подходят как для простых, так и сложных проектов. У Pico нет дополнительных коннекторов, зато они есть у плат других производителей.

Выбираем лучшие платы с RP2040 чипом


Raspberry Pi Pico


  • Плюсы: низкая цена, всего $4, небольшой размер, простота в использовании, встроенный ADC.
  • Минусы: нет USB-C, только три ADC-пина.

Первый в мире и самый дешевый одноплатник (вернее, микроконтроллер) на базе RP2040 стоит всего 4 доллара США. У него полноценный 40-пиновый GPIO. Поскольку де-факто это стандарт, некоторые сторонние платы совместимы с распиновкой Pico, включая Kitronik Robotics Board или Pimoroni. Они добавляют к функциональности Pico беспроводную связь, несколько светодиодов или другие элементы.

Конечно, покупая самую недорогую модель, вам придется идти на компромиссы. Во-первых, у Pico всего три analog-to-digital пина, тогда как у некоторых сторонних плат четыре или больше. Кроме того, у Pico всего 2 МБ памяти, что весьма немного, хотя и достаточно для решения многих задач.

Плюс ко всему, у Pico micro-USB коннектор, разработчики (вероятно с целью удешевления) решили не устанавливать USB-C. Тем не менее, учитывая обширную экосистему, Pico можно считать must have микроконтроллером для разработчика.

Adafruit Feather RP2040


  • Плюсы: удачный форм-фактор, STEMMA QT, FeatherWing, возможность подключения и зарядки аккумулятора, разметка пинов на обеих сторонах платы.
  • Минусы: нет подтягивающих резисторов на пинах I2C и нет мониторинга батареи.

Adafruit, партнер Raspberry Pi по чипу RP2040, выпустила несколько отличных плат RP2040 за короткий промежуток времени. У компании есть собственный набор форм-факторов. Самая большая плата, Feather, как раз и получила чип RP2040. У Feather RP2040, совместимого с экосистемой FeatherWings, меньше контактов, чем у Raspberry Pi Pico. Но все же разрабочтики выбрали оптимальный минимум.

Потери в пинах GPIO компенсируются возможностью подключения LiPo / Li-Ion батареи, отличной разметкой контактов и наличием Stemma QT, плюс разъема Adafruit, который выбирают для компонентов, которые подключаются посредством I2C. Благодаря Stemma QT у нас нет проблем с подключением и полярностью, что позволяет нам сосредоточиться на проекте, а не на поиске нужных пинов.

Если вы ищете самую универсальную плату RP2040 на рынке, вы ее нашли. Конечно, стоит она дороже, чем Raspberry Pi Pico, но Adafruit Feather RP2040 отличный продукт, который можно использовать в вашем следующем проекте.

Сytron Maker Pi Pico


  • Плюсы: низкая стоимость, простота использования, дополнительные фичи, дополнительные светодиоды и коннекторы.
  • Минусы: с ESP-01 придется повозиться

Raspberry Pi Pico это увлекательный и недорогой способ заняться программированием и электроникой. Правда, во многих случаях к Pico придется докупать компоненты, чтобы расширить сферу его применения. Maker Pi Pico это большое количество дополнительных функций с небольшим размером. При этом стоимость устройства менее 10 долларов, включая предварительно распаянный Raspberry Pi Pico.

За 10 долларов вы получаете просто потрясающее количество функций: кардридер micro SD, зуммер / аудиоразъем 3,6 мм, NeoPixel, контакты GPIO свободны для использования, плюс шесть разъемов Grove для работы с совместимыми компонентами. У каждого из контактов GPIO есть полезный, который можно использовать для быстрой отладки. Включенный в комплект ESP-01 обеспечивает базовый доступ к Wi-Fi. А еще компания Cytron выпустила обновленное руководство о том, как подключить Maker Pi Pico к беспроводной сети. За 10 долларов сложно найти что-то получше.

Adafruit QT Py RP2040


  • Плюсы: небольшой размер, порт Stemma QT, USB-C.
  • Минусы: ограниченное количество GPIO пинов.

QT Py RP2040 от Adafruit похож на Pimoroni Tiny 2040. У платы тоже небольшой размер, плюс тщательно подобранный выбор контактов GPIO для проектов. QTPy RP2040 от Adafruit можно без проблем распаять на плате большего размера. Плюс у системы есть разъем Stemma QT / Qwiic, который обеспечивает дополнительное соединение I2C для работы с совместимыми компонентами. Отличное решение для прототипирования.

Низкая стоимость и простота использования QTPy RP2040 не единственные преимущества платы. Здесь есть еще форк Adafruit MicroPython, CircuitPython, со множеством библиотек для работы с компонентами Stemma QT / Qwiic. Даже если у вас уже есть Raspberry Pi Pico, QTPy RP2040 все равно должен быть частью вашего проекта.

Pimoroni Tiny 2040


  • Плюсы: небольшой размер, больше ADC-пинов, чем у Pico, есть кнопка Reset.
  • Минусы: плата несовместима с add-on системами для Pico, сложности с распайкой, высокая цена.

Размер этой платы составляет всего лишь треть от и так небольшого Raspberry Pi Pico. Разработчики позаботились о сохранении всех функций Pico с добавлением новых. Есть, например, дополнительный аналоговый вход. Из-за небольшого размера платы она стала дороже, причем ровно в три раза, чем Pico. Стоимость Pimoroni Tiny 2040 составляет $12.

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

Подробнее..

Разработка контроллера резервного питания. Трассировка

14.06.2021 12:13:27 | Автор: admin

В предыдущей статье http://personeltest.ru/aways/habr.com/ru/post/557242/ была описана схемотехника контроллера резервного питания. Такой контроллер может пригодится в разнообразных технических системах и устройствах. Поэтому конструктив платы был выбран максимально нейтральный с возможностью выноса элементов управления на отдельную панель.

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

  • электромагнитная совместимость удовлетворяющая стандартам

  • высокая теплоотдача и большая рассеиваемая мощность без радиаторов и вентиляторов

  • низкая цена печатной платы

  • минимальные размеры

  • возможность изменять варианты сборки

  • обеспечение электробезопасности и электрической прочности

  • ремонтопригодность с минимальной оснасткой и оборудованием

  • пригодность для коррекций ошибок трассировки и схемотехники

  • технологичность сборки и невысокая цена сборки

  • максимальная тестируемость после сборки

  • удобство монтажа в целевой системе

Естественно что весь этот список невозможно не только максимально удовлетворить, но даже держать в памяти затруднительно.
Поэтому эмпирически выбираем два-три высших приоритета с которыми работаем в первую очередь.
Низкая серийность позволяет нам сдвинуть цену вниз по приоритетам. Боль прошлого опыта заставляет нас поднять ремонтопригодность и тюнингируемость на пару уровней выше чем это принято обычно.
В верхней строчке может оказаться электробезопасность и соответствие стандартам по ЭМС.
И не только потому что без этого регуляторы просто не позволят эксплуатировать плату. Плохая ЭМС может поднять результирующую цену платы многократно, поскольку приведет к длительному циклу отладки, к повышенному проценту ремонтов, удорожанию технической поддержки и многому другому. Соответственно вердикт - плата должна иметь не менее 4-х слоев.

В 4-х слоях удастся сделать максимально большие полигоны земли и питания, а также полигоны отвода тепла.
Толщину платы выбираем из соображений жесткости и из перечня доступных вариантов у производителя плат. Внутренние слои расположены ближе к внешним чтобы иметь лучшую емкостную связь с дорожками в верхних слоях и обеспечивать лучшую ЭМС, а также для лучшей передачи тепла от SMD компонентов в стороны от них.
Шесть слоев конечно было бы лучше, но нельзя забывать что цена еще не выкинута из списка приоритетов.

Вопрос выбора программных инструментов для трассировки решается в пользу Altium Designer. Как считают его разработчики он на сегодня наиболее массово применяемый инструмент в данной области.
Цена за standalone лицензию Altium до акций могла обойтись пределах 10 тыс. евро, еще 3 тыс. евро возможно придется отдать за опцию PDN Analyzer если потребуется точнее рассчитать потери и наводки в проводниках.
К счастью есть возможность поработать с триальной версией Altium и при должной сноровке выполнить трассировку необходимой нам платы в триальный период.

Результат работы показан ниже:

Вид трассировки с отображением всех 4-х слоев Вид трассировки с отображением всех 4-х слоев Вид сверху и вид снизуВид сверху и вид снизу

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

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

Расположение основных узлов на платеРасположение основных узлов на плате

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

Для данной платы были созданы следующие основные правила:

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

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

Слева диалог установки зазоров, справа диалог установки минимального пути для класса цепей 220VСлева диалог установки зазоров, справа диалог установки минимального пути для класса цепей 220V

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

Чтобы иметь возможность разным цепям назначать разные зазоры по отношению к другим цепям на схеме задаются классы цепей. Классы назначаются с помощью специальных визуальных элементов присоединяемых к цепям, как показано на рисунке. При щелчке на элементе открывается диалог, где можно написать имя класса цепи.
Затем в редакторе печатной платы в диалоге правил каждому классу обнаруженному на схеме можно записать свое правило со значением зазора. Естественно для классов можно задать и иные категории правил, например ширину проводников, типы переходных отверстий разрешенных для класса, способы подключения к полигонам, термо-барьеры и проч. Но для данной платы это не используется.
Обратной стороной правил является то что их может стать слишком много и тогда поддерживать правила при рефакторинге станет труднее чем делать ручные операции.

Площадки и их особенности

Как и при разработке программного обеспечения так и при разработке платы не имеет смысла сразу уделять много внимания форме площадок. Для этого есть рефакторинг и в Altium этот рефакторинг делать достаточно удобно.
Но несколько советов все же дам.

Площадки со скруглениямиПлощадки со скруглениями

Во первых, все площадки скругляю (или почти все, насколько видится целесообразным) .
Это заметно повышает плотность трассировки и заполняемость полигонами. Исключение составляют только первые пины многовыводных компонентов. Их делаю прямоугольными чтобы сразу визуально отличить и понять ориентацию компонента с какой бы стороны платы мы не смотрели (если это не SMD компонент)

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

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

Площадки измерительных шунтов.
Оба показанных ниже варианта приемлемы. Но все же первый вариант выдаст менее точное измерение. И погрешность будет выше чем заявленная погрешность номинала самого шунта.

Шелкография

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

Какие рекомендации действительно важны

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

Неоднозначная рекомендация по применению термобарьеровНеоднозначная рекомендация по применению термобарьеровВажная рекомендация в отношении паяльной маски Важная рекомендация в отношении паяльной маски

Рекомендация по установке термо-барьеров. Она напрямую конфликтует с требованиями ЭМИ и теплопередачи, однако выполнение рекомендации обеспечивает исключительное удобство ремонта. Как правило если элемент не греется, не проводит большие токи, то ремонтировать его не приходится, если только не считать тюнинга.
Современные контрактные сборщики, по моему опыту, не отдают плат с дефектом надгробного камня, а исправляют его у себя, если он появляется. Они также и претензий не предъявляют в случае отсутствия термо-барьера.
Что же касается ремонта, то современные паяльники без труда справляются с отпайкой компонентов без термо-барьеров. Словом, рекомендация сомнительная.

Рекомендация для паяльной маски. Это очень важная рекомендация. Области открытой паяльной маски ни в коем случае не должны охватывать соседние площадки если они принадлежат разным цепям. Иначе практически гарантировано где-то возникнет замыкание между площадками и контрактный сборщик отдаст плату с дефектом потому что визуальный контроль его не обнаружит, так как замыкания могут быть под корпусами микросхем.

Проблемы земли
Вопрос о земле - важнейший в цифро-аналогово-силовых схемах. Малейшая неосмотрительность может привести к тяжелым финансовым последствиям либо, в лучшем случае, к значительной деградации качества работы схемы.
У Altium есть инструмент под названием PDN Analyzer для точного расчета потенциалов в проводниках с большими токами к которым относятся и полигоны земель. Однако в данном проекте этот анализатор не использовался. Все же это дорогое и трудоемкое удовольствие оправданное в более сложных проектах. Здесь подход был проще.
Были идентифицированы несколько основных доменов земли которые необходимо максимально разделить:
- цифровая земля микроконтроллера
- аналоговая земля микроконтроллера
- возвратная земля силовых ключей и DC/DC преобразователя
- чувствительная земля DC/DC преобразователя
- непосредственно заземление

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

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

Ниже показана результирующая топология каждой из земель.

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

Следующим этапом будет программирование, наладка и тестирование платы. Но об этом позже.

Подробнее..

Работа с параметрами в EEPROM

02.06.2021 22:14:13 | Автор: admin

Введение

Привет Хабр. Наконец-то у меня появилось свободное время и я могу еще немного поделиться своим опытом, возможно кому-то это будет полезно, и поможет в его работе, и я этому буду безусловно рад. Ну что же,....

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

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

int address = 0;float val1 = 123.456f;byte val2 = 64;char name[10] = "Arduino";EEPROM.put(address, val1);address += sizeof(val1); //+4EEPROM.put(address, val2);address += sizeof(val2); //+1EEPROM.put(address, name);address += sizeof(name); //+10

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

Кроме того, обычно студенты используют РТОС, а потому нужно понимать, что обращение к EEPROM из разных потоков может привести либо к фейлам, либо ко всяким там дедлокам. Поэтому если студент использует EEPROM, я вначале прошу нарисовать дизайн, чтобы показать как он собирается работать этой подсистемой.

Обычно все сводится к двум вариантам:

  • Доступ к EEPROM только из одного места. Типа такой EepromManager , который запускается в отдельной задаче и проходится по списку кешеруемых EEPROM параметров и смотрит, было ли в них изменение, и если да, то пишет его в EEPROM.

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

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

  • Второй способ - пишем всегда сразу по месту.

    Плюс в том, что пользователь всегда получает достоверный ответ. Мы не задумываясь пишем параметр в EEPROM там где надо, и это выглядит просто.

    Но проблем от этого не меньше: так как мы можем писать хоть что, хоть откуда, хоть куда - скажем журнал ошибок из разных подсистем из разных задач, то придется задуматься о блокировке ресурса EEPROM.

    Кроме того возможно проблема с быстрыми протоколами, когда ответить нам нужно в течении ограниченного времени, скажем 5 мс, а те кто работал с EEPROM знают, что записывается там все постранично. Ну точнее, чтобы записать однобайтовый параметр, EEPROM, копирует целую страницу во свой буфер, меняет в этом буфере этот один несчастный байт, стирает страницу, и затем записывает буфер (ну т.е. всю страницу) и того на запись одной страницы сразу тратится от 5 до 10 мс, в зависимости от размера страницы.

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

//Записываем 10.0F в EEPROM по адресу, где лежит myEEPROMData параметр myEEPROMData = 10.0F;

Но так мы делать не будем, потому что иногда нам понадобится по месту вернуть статус операции записи, вдруг EEPROM битая или проводки отпаялись. И посему мы будем делать, что-то похожее на это:

//Записываем в EEPROM строку из 5 символов по адресу параметра myStrDataauto returnStatus = myStrData.Set(tStr6{"Hello"}); if (!returnStatus){std::cout << "Ok"}//Записываем в EEPROM float параметр по адресу параметра myFloatDatareturnStatus = myFloatData.Set(37.2F); 

Ну что же приступим

Анализ требований и дизайн

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

Давайте поймем, что мы вообще хотим. Сформируем требования более детально:

  • Каждая наша переменная(параметр) должна иметь уникальный адрес в EEPROM

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

  • Мы не хотим постоянно лазить в EEPROM, когда пользователь хочет прочитать параметр

    • Обычно EEPROM подключается через I2C или SPI. Передача данных по этим интерфейсам тоже отнимает время, поэтому лучше кэшировать параметры в ОЗУ, и возвращать сразу копию из кеша.

  • При инициализации параметра, если не удалось прочитать данные с EEPROM, мы должны вернуть какое-то значение по умолчанию.

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

  • Все должно быть дружелюбным простым и понятным :)

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

CachedNvData

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

При вызове метода Init() мы должны полезть в EEPROM и считать оттуда нужный параметр с нужного адреса.

Адрес будет высчитываться на этапе компиляции, пока эту магию пропустим. Прочитанное значение хранится в data, и как только кому-то понадобится, оно возвращается немедленно из копии в ОЗУ с помощью метода Get().

А при записи, мы уже будем работать с EEPROM через nvDriver. Можно подсунуть любой nvDriver, главное, чтобы у него были методы Set() и Get(). Вот например, такой драйвер подойдет.

NvDriver

Остался еще один штрих, придумать, как автоматически формировать адрес каждого параметра. Для того, чтобы адрес высчитывался автоматически, необходимо, чтобы все параметры для EEPROM были зарегистрированы в каком-нибудь списке. Тогда список может сам посчитать адрес параметра по его положению в списке и собственно вернуть его когда надо.

Например, если у нас есть 3 параметра:

//Длина параметра 6 байтconstexpr CachedNvData<NvVarList, tString6, myStrDefaultValue,  nvDriver> myStrData;//Длина параметра 4 байтаconstexpr CachedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;//Длина параметра 4 байтconstexpr CachedNvData<NvVarList, std::uint32_t, myUint32DefaultValue,  nvDriver> myUint32Data; 

То когда мы сделаем какой-то такой список:

NvVarList<100U, myStrData, myFloatData, myUint32Data>

У нас бы у myStrData был бы адрес 100, у myFloatData - 106, а у myUint32Data - 110. Ну и соответственно список мог бы его вернуть для каждого из параметра.

Собственно нужно чтобы этому списку передавался начальный адрес, и список параметров в EEPROM. Также нужно чтобы у списка был метод GetAdress(), который возвращал бы адрес нужного параметра.

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

Сделаем такой базовый класс, назовем его NvVarListBase:

NvVarListBase

В прицнипе то и все.

Код

А теперь самая простая часть - пишем код. Комментировать не буду, вроде бы и так понятно

CaсhedNvData

template<typename NvList, typename T, const T& defaultValue, const auto& nvDriver>class CaсhedNvData{  public:    ReturnCode Set(T value) const    {      //Ищем адрес EEPROM параметра в списке       constexpr auto address =                 NvList::template GetAddress<NvList,T,defaultValue,nvDriver>();      //Записываем новое значение в EEPROM      ReturnCode returnCode = nvDriver.Set(                                address,                                reinterpret_cast<const tNvData*>(&value), sizeof(T));      //Если значение записалось успешно, обновляем копию в ОЗУ      if (!returnCode)      {        memcpy((void*)&data, (void*)&value, sizeof(T));      }      return returnCode;    }    ReturnCode Init() const    {      constexpr auto address =                 NvList::template GetAddress<NvList,T,defaultValue,nvDriver>();      //Читаем значение из EEPROM      ReturnCode returnCode = nvDriver.Get(                                address,                                 reinterpret_cast<tNvData*>(&data), sizeof(T));      //Tесли значение не прочиталось из EEPROM, устанавливаем значение по умолчанию      if (returnCode)      {        data = defaultValue;      }      return returnCode;    }    T Get() const    {      return data;    }        using Type = T;  private:    inline static T data = defaultValue;};
template<const tNvAddress startAddress, const auto& ...nvVars>struct NvVarListBase{        template<typename NvList, typename T, const T& defaultValue, const auto& nvDriver>    constexpr static size_t GetAddress()    {       //Ищем EEPROM адрес параметра с типом       //CaсhedNvData<NvList, T, defaultValue, nvDriver>      using tQueriedType = CaсhedNvData<NvList, T, defaultValue, nvDriver>;                  return startAddress +             GetAddressOffset<tQueriedType>(NvVarListBase<startAddress,nvVars...>());    }      private:       template <typename QueriedType, const auto& arg, const auto&... args>       constexpr static size_t GetAddressOffset(NvVarListBase<startAddress, arg, args...>)   {    //Чтобы узнать тип первого аргумента в списке,     //создаем объект такого же типа как и первый аргумент    auto test = arg;    //если тип созданного объекта такой же как и искомый, то заканчиваем итерации    if constexpr (std::is_same<decltype(test), QueriedType>::value)    {        return  0U;    } else    {      //Иначе увеличиваем адрес на размер типа параметра и переходим к       //следующему параметру в списке.        return sizeof(typename decltype(test)::Type) +                 GetAddressOffset<QueriedType>(NvVarListBase<startAddress, args...>());    }  }    };

Использование

А теперь встанем не место студента и попробуем это все дело использовать.

Задаем начальные значения параметров:

using tString6 = std::array<char, 6U>;inline constexpr float myFloatDataDefaultValue = 10.0f;inline constexpr tString6 myStrDefaultValue = {"Habr "};inline constexpr std::uint32_t myUint32DefaultValue = 0x30313233;

Зададем сами параметры:

//поскольку список ссылается на параметры, а параметры на список. //Используем forward declarationstruct NvVarList;   constexpr NvDriver nvDriver;//Теперь можем использовать NvVarList в шаблоне EEPROM параметровconstexpr CaсhedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;constexpr CaсhedNvData<NvVarList, tString6, myStrDefaultValue,  nvDriver> myStrData;constexpr CaсhedNvData<NvVarList, uint32_t, myUint32DefaultValue,  nvDriver> myUint32Data;

Теперь осталось определить сам список параметров. Важно, чтобы все EEPROM параметры были разных типов. Можно в принципе вставить статическую проверку на это в NvVarListBase, но не будем.

struct NvVarList : public NvVarListBase<0, myStrData, myFloatData, myUint32Data>{};

А теперь можем использовать наши параметры хоть где, очень просто и элементарно:

struct NvVarList;constexpr NvDriver nvDriver;using tString6 = std::array<char, 6U>;inline constexpr float myFloatDataDefaultValue = 10.0f;inline constexpr tString6 myStrDefaultValue = {"Habr "};inline constexpr uint32_t myUint32DefaultValue = 0x30313233;constexpr CaсhedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;constexpr CaсhedNvData<NvVarList, tString6, myStrDefaultValue,  nvDriver> myStrData;constexpr CaсhedNvData<NvVarList, uint32_t, myUint32DefaultValue,  nvDriver> myUint32Data;struct NvVarList : public NvVarListBase<0, myStrData, myFloatData, myUint32Data>{};int main(){        myStrData.Init();    myFloatData.Init();    myUint32Data.Init()        myStrData.Get();    returnCode = myStrData.Set(tString6{"Hello"});    if (!returnCode)    {        std::cout << "Hello has been written" << std::endl;    }    myStrData.Get();    myFloatData.Set(37.2F);        myUint32Data.Set(0x30313233);        return 1;}

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

template<const auto& param>struct SuperSubsystem{  void SomeMethod()  {    std::cout << "SuperSubsystem read param" << param.Get() << std::endl;   }};int main(){    SuperSubsystem<myFloatData> superSystem;  superSystem.SomeMethod();}

Собственно и все. Теперь студенты могут работать с EEPROM более юзерфрендли и допускать меньше ошибок, ведь часть проверок за них сделает компилятор.

Ссылка на пример кода тут: https://godbolt.org/z/W5fPjh6ae

P.S Хотел еще рассказать про то, как можно реализовать драйвер работы с EEPROM через QSPI (студенты слишком долго понимали как он работает), но слишком разношерстный получался контекст, поэтому думаю описать это в другой статье, если конечно будет интересно.

Подробнее..

Создание своей оценочной платы для микроконтроллеров

16.04.2021 02:15:22 | Автор: admin

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

Брак или странная задумка инженеров

Земля на ножках для прошивки висела в воздухе, после доработки всё окей.

Я как раз переезжал, и из-за этого у меня появилось много свободного времени, чем я и воспользовался, когда рисовал платы в EDA. Начать было решено с STM8S003F3U6 потому как очень уж полюбилась мне эта серия микроконтроллеров.

Первые наброски выглядели примерно так:
v1v1

Тут я ещё не знал, что для нормальной прошивки мк нужен pull-up резистор на ногу сброса.

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

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

Детальное фото печатной платы

В итоге у меня было куча плат, с пяток контроллеров и разных вторичных компонентов, выпаянных из разных плат. Всё это вылилось в 3 рабочих прототипа.

В итоге у меня было куча плат, с пяток контроллеров и разных вторичных компонентов, выпаянных из разных плат. Всё это вылилось в 3 рабочих прототипа:

Фото ужасное, но переснять не могу уже раздал их)Фото ужасное, но переснять не могу уже раздал их)

Немного о платах

Остановился на 6 светодиодах. Всего 3 группы: питание, 4 GPIO, 1 GPIO(ШИМ) на обратной стороне. У каждой группы есть маленькая напаиваемая перемычка.

USB только питание, по входу стоит диод, защита usb выхода от обратного напряжения которое может пойти если запитать схему >5в напряжением. Стабилизатор выдерживает пиковые 16в, штатное до 14в.

Контроллер может без проблем работать и от 5в, но расчёт резисторов для светодиодов выполнял для 3.3, да и базово считаю что лучше работать с 3.3, ибо потом по привычке можно что-нибудь спалить.

Продолжаем вакханалию

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

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

И так, надо было решать как спаять всё быстро и с минимумом косяков. Трафарета у меня не было и я решил попробовать просто намазать тонким слоем паяльную пасту (благо она была).

Сказано сделано, вот что из этого вышло:

Мелкие капельки припоя что никуда не стекли прилипли прилично, далеко не с первого раза я их смог убрать, так же видно что 2 контроллера немного поплыли и встали криво. Выглядит жуть, но в целом получилось неплохо. Я делал несколько прогонов, первый мк и обвязка, остальное вторым. И по выходу получилось около 70% годных и готовых сразу без танцев плат.

Финальный результат, usb портов к этому времени не подвезли, так что без них.

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

Планы у меня наполеоновские, уже нарисовал и под stm32f0 и под f1 платы, ещё есть идея создать плату на stm32f4(7) с интегрированных ethernet.

Для тех кто дочитал, спойлер след платы!

В следующей статье затрону контроллер и среды, в которых пишу. Ни пуха ни пера и не болейте!

Подробнее..

Раскрашиваем таможенную статистику. Или сколько и каких микросхем ввозят в Россию? (2)

11.05.2021 20:09:12 | Автор: admin

Продолжим анализировать какие иностранные микросхемы используются в России на основании таможенной статистики. Как мы это делаем ? Из данных ФТС выбираем записи в которых указан номинал ввезенной микросхемы, и используя внутреннюю базу данных дополняем эту запись основными параметрами микросхемы, начиная от производителя, разрядностями, диапазонами питания и заканчивая типом корпуса, упаковки и так далее. В данной части посмотрим АЦП/ЦАП и микроконтроллеры.

АЦП/ЦАП

Все типы аналого-цифровых и цифро-аналоговых преобразователей согласно нашей базе разбит на группы:
- АЦП - преобразователи аналогового сигнала в цифру;
- ЦАП - преобразователи цифры в аналоговый сигнал;
- совмещенные АЦП/ЦАП (обычно это разные аудио АЦП/ЦАП, но без кодеков, которые идут другой группой);
- Analog Front End (AFE) - схемы цифро-аналогового и аналого-цифрового преобразования (обычно используются для радиосвязи);
- цифровые потенциометры - схемы совмещающие датчик и АЦП/ЦАП используемые как датчики температуры.

Распределение микросхем по типам.Распределение микросхем по типам.

Больше всего ввозится микросхем AFE - радиоприемопередатчики, но не имеющие в своем составе процесорных ядер - более 182К штук. Самым популярным АЦП является ADS1000A0IDBV. Самым популярным ЦАП - DAC6311IDCK. Самым популярным аудио АЦП/ЦАП - PCM5102APWR. Самый популярный RF приемопередатчик - CC1020RSSR. А самый популярный цифровой датчик - DS18B20+. Практически все от Texas Instruments.

Маржинальность типов микросхемМаржинальность типов микросхем

Самыми маржинальными оказались Аудио АЦП/ЦАП - наверное любители звука готовы переплачивать не только за безкислородный медный кабель. Какие же фирмы лидируют на данном рынке?

Лидеры среди производителей микросхем АЦП/ЦАПЛидеры среди производителей микросхем АЦП/ЦАП

Texas Instruments выпускает самые популярные микросхемы и поставляет больше всего аналого-цифровых микросхем.

АЦП

Самая популярная разрядность чистых АЦП - 12 бит

Распределение разрядности микросхем АЦПРаспределение разрядности микросхем АЦП

16 и более - это в основной сигма-дельта АЦП, которые точные, но обычно не очень быстрые. Какая же самая популярная частота выборки? По частоте преобразования для АЦП распределение выглядит следующим образом

Распределение скорости преобразования микросхем АЦПРаспределение скорости преобразования микросхем АЦП

Самый крутой АЦП, который официально ввезли в Россию - AD9208BBPZ-3000 (14 бит @ 3GSPS).

ЦАП

Распределение ЦАП по разрядности

Распределение разрядности ЦАП Распределение разрядности ЦАП

Распределение скорости преобразования ЦАП

Распределение скорости преобразования ЦАПРаспределение скорости преобразования ЦАП

Микроконтроллеры

Из проанализированных примерно 1 млн микроконтроллеров - самые популярные в России микроконтроллеры от Микрочип (479К), STM(305К) и NXP (126К).

Лидеры среди производителей микроконтроллеровЛидеры среди производителей микроконтроллеров

Самым популярным при этом является микроконтроллер STM8S003F3P6TR. Как это ни странно, но до сих пор 8-ми битные МК самые популярные. Но это скорее всего опять таки из за того, что таможня не видит все ввозимые микроконтроллеры.

Соотношение 8, 16, 32 битных микроконтроллеров (скорее всего неверное)Соотношение 8, 16, 32 битных микроконтроллеров (скорее всего неверное)

Сколько памяти нужно для решения микроконтроллерных задач ?

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

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

По числу выводов корпуса распределение следующее (к сожалению не для всех номиналов у нас были данные)

Распределение числа выводов микроконтроллеровРаспределение числа выводов микроконтроллеров

Итого

В результате среднестатистические АЦП и ЦАП на нашем рынке имеют разрядность 12 бит. Скорость преобразования АЦП на уровне 1 MSPS, а время преобразования ЦАП на уровне от 1KSPS до 100KSPS. Обе микросхемы будут от Texas Insruments. Среднестатистический микроконтроллер будет 8-ми битным, с 16 выводами и 8 Кбайтами Flash, работать с тактовой частотой до 16-20 МГц и выпущенный Микрочипом.

В третьей части статьи будут рассмотрены микросхемы памяти.

Подробнее..

Достучаться до небес, или FSM на шаблонах

05.02.2021 02:04:48 | Автор: admin

Здравствуйте! Меня зовут Александр, я работаю инженером-программистом микроконтроллеров.

Пишу на С/С++, причем предпочитаю плюсы, ибо верую в их эволюционную неизбежность в embedded.

Мир встроенного ПО, язык С++ динамично развиваются, поэтому разработчикам важно не отставать и поддерживать свои скиллы и наработки актуальными моменту.

Я стараюсь следовать этому очевидному посылу, благо небожители ведущие С++ программисты и консультанты щедро делятся своим опытом и идеями на разных площадках (например здесь, или здесь).

Некоторое время назад я посмотрел мощный доклад Сергея Федорова про построение конечного автомата с таблицей переходов на шаблонах.

Если внезапно: "а что такое конечный автомат?"

Конечный автомат, или FSM(finite state maсhine) - один из самых востребованных и популярных приемов в программировании на МК. В свое время за кратким и практическим руководством по готовке FSM я ходил заброшенные, земли.

Одна из идей доклада - определить состояния, эвенты и действия через пользовательские типы, а таблицу переходов реализовать через шаблонный параметр, меня очень

впечатлила
// Transition table definitionusing transitions =  transition_table<  /*  State       Event       Next       */  tr< initial,    start,      running    >,  tr< running,    stop,       terminated >>;};// State machine objectusing minimal = state_machine<transitions>;minimal fsm;//...and then callfsm.process_event(start{});fsm.process_event(stop{});

А если добавить к этому перенос части функциональности кода в компайл тайм, заявленную автором потокобезопасность, улучшенные по сравнению с Boost::MSM выразительность, читаемость кода и скорость сборки, header only модель библиотеки, то - надо брать, решил я.

Вот только попытка собрать и запустить даже простейший пример на STM-ке закончилась матерком компилятора: "cannot use 'typeid' with "-fno-rtti" и "exception handling disabled".

Да, все так. Более того, помимо отключенной поддержки RTTI и исключений, у меня также выставлены флаги -fno-cxa-atexit, -fno-threadsafe-static. А еще в линкере применены настройки --specs=nano.specs (используем урезанную версию стандартной библиотеки с++ newlib-nano), --specs=nosys.specs (применяем легковесные заглушки для системных вызовов).

Зачем же таскать на себе вериги?

Embedded разработчикам хорошо известны особенности и ограничения при разработке встроенного ПО, а именно:

  • лимитированная память с недопустимостью фрагментации;

  • детерменированность времени выполнения;

  • штатно исполняющаяся программа никогда не выходит из main

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

Как закружить в гармоничном танце С++ и bare metal отлично разъяснено у этого автора. Также порекомендую этот доклад.

Исходники проекта докладчика, включая зависимости, - это двадцать файлов со смертоноснейшей шаблонной магией. Перекроить их так, чтобы не юзать typeid и exceptions, простому смертному в моем лице - too much.

Делать нечего, поступимся принципами, включим поддержку RTTI, а вместо throw в исходниках автора проставим заглушки.

На этот раз все собралось. Вот только при использовании тулчейна gcc-arm-none-eabi-9-2020-q2-update и уровне оптимизации -O3, размер исполняемого файла превысил 200Кб.

Это несколько огорчительно, хотя какие могут быть претензии - библиотека изначально разрабатывалась под "большого брата".

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

Итак, с наскоку взять высоту не удалось, и я на некоторое время переключился на другие задачи. Но красота идеи меня не отпускала, и на днях я все-таки решился "достучаться до небес" - написать extra light embedded версию FSM из упомянутого доклада самостоятельно.

Уточню свои хотелки:

  • Оперировать состояниями, эвентами и действиями как пользовательскими типами.

  • Таблицу переходов реализовать в виде шаблонного параметра

  • Перетащить что возможно в компайл тайм

  • Асинхронно и атомарно постить эвенты

  • Переключать состояния за константное время

  • Выйти в итоге на приемлемый по меркам встроенного ПО размер кода

  • Повторить header only модель библиотеки

Забегая вперед, скажу, что в итоге что-то получилось и даже взлетело.

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

Первым делом опишем базовые сущности:

Состояние/State
struct StateBase{};template <base_t N, typename Action = void>struct State : StateBase{  static constexpr base_t idx = N;  using action_t = Action;  };

Здесь и далее base_t - платформозависимый тип, машинное слово. В моем случае это unsigned int.

Состояния пусть будут двух типов - пассивное, в котором никаких действий не происходит, и активное - при нахождении в котором будет выполнятся переданный шаблонным параметром функтор, action_t.

Цель статического члена idx уточню далее по тексту.

Событие/Event
struct EventBase{};template <base_t N>struct Event : EventBase{  static constexpr base_t idx = N;};

Элементарная структура, все ясно.

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

Action
struct action{  void operator()(void){    // do something};

Безусловно, сигнатура operator() может и должна варьироваться от задач приложения, пока же для упрощения остановимся на самом легковесном варианте.

Сторож состояния:

Guard
enum class Guard : base_t{  OFF,  CONDITION_1,  CONDITION_2,  //etc.};

Идея сторожа - допустить переход в новое состояние, только если в данный момент выполнения программы текущее значение сторожа соответствует заданному пользователем значению в типе перехода/transition-a. Если такого соответствия нет, то переход в новое состояние не происходит. Но тут возможны варианты. Например, все же переходить, но не выполнять действие, переданное в состояние. Up to you.

Итак, пока все тривиально. Идем дальше.

Переход:

Transition
struct TrBase{};template <typename Source,          typename Event,          typename Target,          typename Action,          Guard G,          class =          std::enable_if_t<std::is_base_of_v<StateBase, Source>&&          std::is_base_of_v<EventBase, Event> &&          std::is_base_of_v<StateBase, Target>>          >  struct Tr : TrBase{  using source_t = Source;  using event_t  = Event;  using target_t = Target;  using action_t = Action;    static constexpr Guard guard = G;};

Структура Tr тоже элементарна. Она параметризуется типом исходного состояния - Source, типом события Event, по наступлению которого произойдет переход в целевое состояние Target, и типом Guard.

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

Таблица переходов:

Transition table
struct TransitionTableBase{};template<typename... T>struct TransitionTable : TransitionTableBase{    using test_t = typename NoDuplicates<Collection<T...>>::Result;    static_assert(std::is_same_v<test_t, Collection<T...>>,                "Repeated transitions");    using transition_p = type_pack<T...>;    using state_collection = typename NoDuplicates   <Collection<typename T::source_t... ,typename T::target_t...>   >::Result;    using event_collection = typename NoDuplicates  <Collection<typename T::event_t...>    >::Result;    using state_v = decltype(get_var(state_collection{}));  using event_v = decltype(get_var(event_collection{}));  using transition_v = std::variant<T...>;};

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

Структура TransitionTable параметризуется списком переходов/transition-ов, которые собственно и описывают всю логику конечного автомата.

Первым делом нам необходимо подстраховать себя от копипаста и просигналить при компиляции, что у нас повторы в списке. Исполняем это с помощью алгоритма NoDuplicates из всем известной библиотеки Loki. Результирующий тип под псевдонимом test_t сравниваем в static_assert-e с исходным списком переходов.

Далее, допуская что static_assert пройден, параметризуем некую структуру type_pack списком переходов и выведенному типу назначаем псевдоним transition_p. Структура type_pack, а также современные алгоритмы и методы по работе со списками типов собраны в файле typelist.h. Данный хедер написан под чутким руководством этого продвинутого парня.

Тип transition_p понадобится нам далее в конструкторе класса StateMachine.

Следом проходим по списку переходов, вытаскиваем, очищаем от повторов и сохраняем в отдельные коллекции состояния и эвенты. Эти коллекции alias-им как state_collection и event_collection соответственно.

К чему эта эквилибристика?

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

Удобным вариантом для этой цели является std::variant (тафтология умышленна).

Последовательно параметризуем std::variant списком переходов (выведенному типу назначим псевдоним transition_v); списком состояний и списком эвентов и назначаем для удобства псевдонимы state_v и event_v соответственно.

Тут нюанс. Чтобы вывести transition_v нам достаточно пробросить в шаблонный параметр std::variant variadic pack (T...) из шаблонного параметра класса TransitionTable.

А вот чтобы вывести state_v и event_v мы используем

вспомогательную constexpr функцию
template<typename... Types>constexpr auto get_var (th::Collection<Types...>){return std::variant<Types...>{};}

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

Оставшихся к этому моменту читателей я не обрадую - начинается основной замес.

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

Контейнер transitions
template<typename Table>class StateMachine{//other stuffprivate:using map_type =std::unordered_map < Key, transition_v, KeyHash, KeyEqual>;Key key;map_type transitions;};

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

Объект типа Key хранит у себя значения индексов состояния и эвента:

Key
struct Key{  base_t state_idx = 0;  base_t event_idx = 0;};

Теперь стало понятно назначение статических членов idx в базовых сущностях. Я просто не знаю, как писать хэшеры для пустых структур. Тащить в строку название самого типа через typeid и _cxa_demangle для нас не вариант, мы же условились, что не пользуем RTTI.

Контейнер events
template<typename Table>class StateMachine{//other stuffprivate:using queue_type =  RingBufferPO2 <EVENT_STACK_SIZE, event_v, Atomic>;    queue_type events;};

events - очередь, в которую будут прилетать эвенты. Так как это чисто рантаймовая история, необходимо избежать динамических аллокаций. Поэтому реализуем ее на базе статического кольцевого буффера RingBufferPO2, который я позаимствовал здесь (отличная для своего времени работа!).

Помимо указанных контейнеров, в объекте типа StateMachine мы будем хранить текущее состояние/state и значение сторожа/guard:

state and guard
template<typename Table>class StateMachine{//other stuffprivate:state_v current_state;Guard guard = Guard::OFF;};

Саспенс уже не за горами.

Конструктор
template<typename Table>class StateMachine{public:using transition_pack = typename Table::transition_p;StateMachine(){  set(transition_pack{});} // other stuff};

В конструкторе метод set принимает аргументом объект с информацией о списке переходов, пробегается по нему, достает инфо о каждом состоянии и эвенте, заполняет контейнер transitions, а также запоминает начальные состояние и значение сторожа:

Метод set
template <class... Ts>void set (type_pack<Ts...>){(set_impl(just_type<Ts>{}), ...);};template <typename T>void set_impl (just_type<T> t){using transition = typename decltype(t)::type;using state_t = typename transition::source_t;using event_t = typename transition::event_t;Guard g = transition::guard;Key k;k.state_idx = state_t::idx;k.event_idx = event_t::idx;transitions.insert( {k, transition{}} );if (0 == key.state_idx) {key.state_idx = k.state_idx;guard = g;current_state = state_t{};}}

Итак, объект StateMachine сконструирован, пора его как-то шевелить.

Но перед этим забудем как страшный сон суммируем что уже рассмотрели к этому моменту:

  • Определили типы компонентов конечного автомата: состояние/state, событие/event, действие/action, сторож/guard

  • Определили тип переход/transition, который должен параметризоваться типами source state, event, target state, guard.

  • Определили тип таблицы переходов. В качестве шаблонных параметров ему передается список переходов/transition-ов, который и определяет алгоритмы работы автомата.

  • При компиляции в классе TransitionTable, на основе std::variant выводятся типы-коллекции переходов, состояний и эвентов, которые впоследствии при конструировании объекта StateMachine инстанцируются и сохраняются в контейнеры, с которыми уже можно работать в рантайме.

Стержневая идея моей имплементации автомата такова (вдохнули): при наступлении события, мы достаем из его типа индекс (idx), объединяем его с индексом текущего состояния в объекте Key, по которому в контейнере transitions находим нужный нам переход, где получаем знания о целевом состоянии, стороже и действии, которое требуется выполнить в этом переходе, а также сверяем значения сторожа с текущим, для подтверждения или отмены перехода/действия(выдохнули).

Теперь рассмотрим методы API нашего автомата, реализующие эту логику.

Переключать состояния мы можем двумя способами: вызывать немедленный переход методом fsm.on_event(event{}) (шаблонная версиия fsm.on_event<Event>() если тип события известен на этапе проектирования), или можем складывать события в очередь методом fsm.push_event(event{}), чтобы потом, например в основном цикле, разобрать ее методом fsm.process(). Также, если в состояние передано какое-то действие, то мы можем вызывать его методом fsm.state_action().

Рассмотрим их детальнее, начиная с последнего

Метод state action
template <typename... Args>void state_action (const Args&... args){state_v temp_v{current_state};    auto l = [&](const auto& arg){      using state_t =  std::decay_t<decltype(arg)>;    using functor_t = typename state_t::action_t;        if constexpr (!std::is_same_v<functor_t, void>){    functor_t{}(args...);      }  };    std::visit(l, temp_v);}  

В методе мы создаем локальную переменную типа std::variant<State...> temp_v и инициализируем ее текущим состоянием. Далее определяем лямбду, которая послужит аргументом в методе std::visit.

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

Знаю, что иногда лямбда может юзать кучу, но похоже, это не мой случай. Поправьте меня, пожалуйста, если я заблуждаюсь. В этом случае не сложно будет заменить лямбду на callable object.

Метод on_event
template <typename Event,class = std::enable_if_t<std::is_base_of_v<EventBase, Event>>>void on_event(const Event& e){Key k;  k.event_idx = e.idx;  k.state_idx = key.state_idx;  on_event_impl(k);}void on_event_impl (Key& k){transition_v tr_var = transitions[k];    Key &ref_k = key;  Guard &ref_g = guard;  state_v &ref_state = current_state;    auto l = [&](const auto& arg){    using tr_t =  std::decay_t<decltype(arg)>;    using functor_t = typename tr_t::action_t;        if ( GuardEqual{}(ref_g, tr_t::guard) ){          using target_t = typename tr_t::target_t;            ref_k.state_idx = target_t::idx;      ref_state = target_t{};            functor_t{}();      }   };      std::visit(l, tr_var);}

Здесь, как я уже описывал, мы достаем индекс из эвента, объединяем его в Key с индексом текущего состояния, и в качестве ключа передаем в приватный метод on_event_impl(Key& k).

Там мы по принятому ключу достаем из контенера transitions объект типа std::variant<Tr...> и инициализируем им локальную переменную tr_var. Ну а далее - логика, схожая с предыдущим примером. Вызываем std::visit c tr_var и лямдой l, в которой из типа Tr получаем сведения о состоянии, в которое нужно перейти (target_t), стороже (tr_t::guard)и типе действия (functor_t) к исполнению.

Сверив значение сторожа перехода с текущим сторожем, мы или оcуществляем переход, инстанцируя и вызывая functor_t, и сохраняя target_t в переменную с текущим состоянием(current_state), или возвращаемся в исходное состояние. Где ждем смены значения сторожа и нового события.

Метод push_event
template <unsigned int N>void push_event (const Event<N>& e){  events.push_back(e);}

Тут все просто.

Метод set_guard
void set_guard (const Guard& g){  guard = g;}

Вызываем, когда в программе сложились условия для перехода в следующее состояние.

Метод process
void process (void){    state_action();    auto it = transitions.begin();    Key k;  k.state_idx = key.state_idx;    for (uint32_t i = 0; i != events.size(); ++i){        auto v = events.front();     auto l = [&](const auto& arg){      using event_t =  std::decay_t<decltype(arg)>;      k.event_idx = event_t::idx;      it = transitions.find(k);    }        std::visit(l, v);        if ( it != transitions.end() ){            events.pop_front();      on_event_impl(k);      return;        } else {      events.push_back(v);      events.pop_front();    }  }}

При вызове метода мы первым делом выполняем некое полезное действие (если не void), переданное в состояние, state_action().

Ну а далее пробегаемся по очереди эвентов и просто воспроизводим логику, уже описанную для метода fsm.on_event(event{}).

Разумеется, работу с событиями можно значительно ускорить, при этом расширив функционал автомата. Тип Event модернизируем

так
template <base_t N, base_t Priority>struct Event : EventBase{  static constexpr base_t idx = N;  static constexpr base_t pri = Priority;};

Теперь мы можем не пушить все события в одну очередь, а завести, скажем, std::array<queue_t, PRIRITY_NUM>, где индексом ячейки будет служить приоритет события. Тогда у нас получится приняв эвент, вытащить его приоритет, по нему, как по индексу за константное время попасть в нужную очередь событий, которая будет гораздо меньше, чем общая и быстрее в обработке.

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

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

Хорошо, каков же будет практический результат этой разнузданной шаблонной вакханалии?

Детектор нейтрино(нет)
struct green_a {/*toogle green led every 50ms*/}struct yellow_a {/*toogle yellow led every 50ms*/}struct red_a {/*toogle red led every 50ms*/}struct green_f {/*toogle green led every 150ms*/}struct yellow_f {/*toogle yellow led every 150ms*/}struct red_f {/*toogle red led every 150ms*/}using STATE_A(green_s, green_f);using STATE_A(yellow_s, yellow_f);using STATE_A(red_s, red_f);using EVENT(green_e);using EVENT(yellow_e);using EVENT(red_e);using fsm_table = TransitionTable    <    Tr<green_s, yellow_e, yellow_s, yellow_a, Guard::NO_GUARD>,    Tr<yellow_s, red_e, red_s, red_a, Guard::NO_GUARD>,    Tr<red_s, green_e, green_s, green_a, Guard::NO_GUARD>    >;int main(void){  //some other stuff  StateMachine<fsm_table> fsm;  fsm.push_event(red_e{});  fsm.push_event(yellow_e{});  fsm.push_event(green_e{});  while (1){    fsm.process();  }}

В этом примере структуры типа color_a(ction) - это действия при переходе; color_f(unctor) - функторы, которые будут выполняться каждый раз при заходе в стейт, ну и далее понятно.

Объявляем стейты, эвенты, переходы, таблицу переходов. Конструируем из класса StateMachine<fsm_table> наш конечный автомат fsm. Пушим события, заходим в while и наблюдаем аквасветодискотеку на нашей отладке.

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

using even_t = Event<1, 15>;

using state_t = State<1, state_functor>;

Очевидно, почему это плохо. Ручная индексация - практически неизбежные ошибки и очепятки.

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

Как-то так
#define STATE_A(str, act) str = State<name(#str), act>#define EVENT(str) str = Event<name(#str)>constexpr base_t name (const char* n){    base_t  res = 0;    for (base_t i = 0; n[i] != '\0'; i++){        char data = n[i];        for (base_t j = sizeof (char) * 8; j > 0; j--){            res = ((res ^ data) & 1) ? (res >> 1) ^ 0x8C : (res >> 1);      data >>= 1;    }  }  return res;};

После крайнего проекта на работе у меня на руках осталась отладка NUCLEO-H743ZI2, на ней я и запилил тестовый вариант (забирайте здесь).

С оптимизацией -O3 реализация приведенного примера (только сам FSM) заняла 6,8Кб, с HAL-ом и моргалками - 14,4Кб.

Конечно же, пока это не более чем эксперимент, проверка концепции. Но агрегат завелся, черт его дери.

Будет очень здорово, если сообщество укажет на неизбежные факапы и укажет путь к улучшениям. Также смею надеяться, что кто-то выделит из материала и что-то полезное для себя.

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

Подробнее..

Фрэнки

18.02.2021 16:06:37 | Автор: admin

Доброго времени суток!

Меня зовут Александр, я работаю программистом микроконтроллеров, и это история о Фрэнки.

. . .Фрэнки родился чуднм. Родителями были пионэрский задор вашего автора и требования заказчика.

Когда младенца скомпилировали и по его венам потекли животворные байты, мой коллега процедил:

- Вы слепили монстра, герр Франкейштейн, но он не лишен некоторого очарования. . .

В то время я писал прошивку для станка с ЧПУ, причем заказчик наложил ограничения - строго Си, никаких сторонних библиотек, допускался только HAL с открытыми исходниками от производителя МК.

Был предоставлен забугорный образец станка, который мы должны были воспроизвести на собственных схемотехнике и ПО.

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

Ну хорошо, а какой тогда должна быть архитектура приложения, когда нет итогового ТЗ и при этом низзя(!) ни в какие сторонние РТОСы и диспетчеры? Как выстроить программу, чтобы не перелопачивать все исходники, если вдруг осознаешь в середине проекта, что логику нужно править?

Напрашивался принцип конструктора Лего. Детальки разных размеров и цветов с унифицированным креплением дают большое разнообразие форм и неограниченные перспективы расширения.

Объединив эту аналогию с возможностью корректно декомпозировать workflow станка на набор состояний, я сформулировал следующие принципы будущей программы:

  1. каждое состояние (стейт) - одна деталька Лего

  2. связь между стейтами через единый интерфейс

  3. что именно делает стейт должно быть понятно с первого взгляда

Пункт 3 требует детализации:

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

3.2. модули должны быть самодокументированы

3.3. один модуль - одна строка кода

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

3.5. модули общаются между собой и внешним окружением через message box

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

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

Станок мы в итоге разработали, оттестировали и сдали. Пока, тьфу-тьфу, фурычит без нареканий.

На днях, поддавшись светлой ностальгии, я переписал Фрэнки на С++, и хотел бы поделиться результатом с хабросообществом. Намерения при этом у меня самые корыстные - из возможной обратной связи узнать что-то новое о себе о нашем ремесле. Начнем.

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

int main (void){    //some vendor's HAL stuff    Device device;    while(1){        device.action();    }}

Эх, с девочкой продакт-менеджером это бы прокатило, но вас-то не проведешь :)

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

class StateSlow final: public StateInterface<StateSlow> {private:void on_enter_impl (void) {     // some state's specific code  }void tracks_go_impl (void) {Track< topic::no, 500, restart_blink_slow >::go(topic::green);Track< topic::green, 250, toggle_green >::go(topic::yellow);Track< topic::yellow, 250, toggle_yellow >::go(topic::red);Track< topic::red, 250, toggle_red >::go(topic::green);Track< topic::no, 5000, stop_blink >::go(topic::state);Track< topic::state, 0, breaker >::go(msg::exit);}States_e on_exit_impl (void) {States_e next_state = States_e::SF;if ( DeviceHandler::get_failed_track() ) {next_state = States_e::ES;} else if ( Post::get_msg(msg::exit).confirmed ){next_state = States_e::SS;}if (next_state != States_e::SF){DeviceHandler::set_state_status(status_e::stopped);}return next_state;}friend class StateInterface<StateSlow>;};using SS = StateInterface<StateSlow>;

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

Здесь стейт - это класс StateSlow, который наследует единый интерфейс от класса StateInterface. Для реализации статического полиморфизма применен паттерн CRTP, но об этом чуть позже.

В интерфейсе всего три метода:

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

  • tracks_go_impl(). Здесь поочередно вызываются модули/Tracks, собственно и реализующие функционал стейта. У модулей есть общие черты с т.н. сопрограммами/корутинами. Но так как все удобные подходящие имена уже заняты (coroutines, fibers, threads), я окрестил их tracks в значении путь/тропа/курс. Track вызывается или по подписке, или по таймеру, или по обоим вместе; может быть вызван единожды или многократно. В качестве полезной нагрузки принимает в себя указатель на функцию с произвольным количеством аргументов.

  • one_exit_impl(). В этом методе мы разбираем результаты работы стейта, возникшие ошибки и по итогам определяем в какой стейт прыгаем дальше. Если работа стейта пока не окончена и все штатно, остаемся в текущем стейте. Конкретно в приведенном выше фрагменте кода, мы последовательно проверяем два условия:

  • есть ли "упавшие" track-и, и при наличии таковых переходим в ErrorState.

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

Итак, что же конкретно происходит в приведенном стейте? Читаем сверху вниз.

Метод on_enter_impl() пуст - никакой специфичной работы при каждом входе в стейт выполнять не нужно.

В tracks_go_impl() выполняются 6 треков. Первый не подписан ни на какое уведомление (topic::no), запустится по таймеру через 500мс с момента первого входа в стейт и выполнит переданную параметром шаблона функцию restart_blink_slow(). Последняя принимает в качестве аргумента уведомление topic::green и при своем вызове отправляет его подписчику. По завершении restart_blink_slow() возвращает код ret_e::оk, отключая свой трек.

Второй трек подписан на уведомление topic::green, при его получении он запустится по таймеру через 250мс и выполнит переданную ему функцию toggle_green(), передав ей как аргумент уведомление topic::yellow. Функция toggle_green() выполнит свою работу, вышлет подписчикам уведомление topic::yellow и вернет код ret_e::repeat, перезапустив свой трек.

Функции, передаваемые в треки могут принимать произвольное количество аргументов, и по соглашению фреймворка должны возвращать один из трех кодов. Код ret_e::repeat перезапускает трек; код ret_e::ok отключает трек; код ret_e::err отключает трек и сохраняет в классе DeviceHandler (о нем чуть позже) номер "упавшего" трека для последующей обработки ошибок в методе on_exit_impl().

Оставшие треки считываем аналогично, а метод one_exit_impl() мы уже рассмотрели выше.

Ну и в конце мы назначаем короткий alias для имени стейта, что пригодится нам позднее.

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

Начнем с основного фрагмента ДНК Фрэнки - с состояния (стейта).

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

С++ помогает писать типобезопасный код, поэтому мы опишем стейт как класс. У стейтов должен быть единый интерфейс, поэтому заведем базовый тип, от которого унаследуем рабочие стейты.

Виртуальный интерфейс нам, как разработчикам встроенного ПО, не очень подходит, ибо vtable откусывает память, а сам виртуальный вызов стоит дорого. Поэтому воспользуемся комбинацией паттернов "CRTP" и "Шаблонный метод" (здесь и далее по тексту за разъяснением используемых техник направляю к этому автору). Еще раз взглянем, как рабочий стейт наследует базовому:

class StateSlow final: public StateInterface<StateSlow>{...}

Класс StateSlow наследует шаблонному классу StateInterface, параметризованному StateSlow. Этот рекурсивный трюк, определяющий саму природу паттерна CRTP, позволяет базовому классу еще на этапе компиляции получить информацию о классе-наследнике. Что дает нам возможность вызывать методы производного класса из объекта базового без дополнительных накладных расходов (пруф). Помотрим на реализацию базового класса:

Class StateInterface
template <typename T>class StateInterface{public:    using derived_t = T;using derived_ptr = T*;StateInterface(){}States_e operator() (void){on_enter();tracks_go();return on_exit();};private:derived_ptr derived(void){return static_cast<derived_ptr>(this);}void on_enter_impl (void){}void tracks_go_impl (void){}void on_enter_base (void){derived()->on_enter_impl();}States_e on_exit_base (void) {return derived()->on_exit_impl();}void on_enter (void) {if (DeviceHandler::get_state_status() == status_e::stopped){Publisher::clear_box();Post::clear_box();DeviceHandler::clear_state_param();DeviceHandler::set_state_status(status_e::active)ж              derived()->on_enter_base();}}void tracks_go (void) {derived()->tracks_go_impl();}States_e on_exit (void) {DeviceHandler::reset_track_cnt();return derived()->on_exit_base();}};

Это шаблонный класс, он параметризуется типом рабочего стейта. Его интерфейс определен сигнатурой operator(), и он нам уже знаком:

метод on_enter() содержит обязательную логику для всех стейтов, а также вызывает метод унаследованного стейта derived()->on_enter_base().

метод on_exit() также содержит логику, общую для всех стейтов, и вызывает метод унаследованного стейта derived()->on_exit_base().

метод tracks_go() подменяется вызовом метода derived()->tracks_go_impl() в котором содержится логика стейта, выраженная модулями/track-ами (см. выше).

Метод on_exit() возвращает тип перечисления States_e, по значению которого будет осуществляться переключение между стейтами:

#define STATESSF,SS,ESenum class States_e : base_t{  STATES};

Единственный дефайн кода фреймворка содержит список названий типов стейтов. Дефайны в С++ стараются не использовать, но в данном случае он оправдан, так как сокращает точки настройки проекта. Второй раз дефайн STATES используется в конструкторе класса Device, о чем чуть позже.

В приватном сегменте класса StateInterface требуют особых пояснений два метода-пустышки:

void on_enter_impl (void){}void tracks_go_impl (void){}

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

Общая обязательная логика при входе в стейт, реализованная в методе on_enter() самодокументирована и понятна.

Класс DeviceHandler предоставляет доступ к общим параметрам стейтов и треков. Класс Publisher ответственен за систему уведомлений. Класс Post процессирует систему сообщений. Сообщения, в отличие от уведомлений, могут нести payload - указатели на данные.

Эти три класса объединяет их дизайн - они реализованы через паттерн static Singleton. Действительно, мы пишем встроенное ПО, которое будет работать в одном конкретном устройстве, поэтому очевидно, что доступ к функционалу этих классов должен следовать принципу "одного окна".

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

Для примера приведу реализацию класса Post:

Class Post
// in message.hpptypedef struct{void* body{nullptr};bool confirmed{false};}MessageParam;using post_t = std::array<MessageParam, MAX_MESSAGE_NUM>;class Post{public:  template<typename T>  static void push_msg (msg m, T* data);  static void push_msg (msg m);  static MessageParam& get_msg (msg m);  static void clear_box(void;private:  Post() = delete;Post(const Post&)= delete;Post& operator=(const Post&)= delete;static post_t mbox;static MessageParam res;};// in message.cpppost_t Post::mbox;MessageParam Post::res;             

Отправка сообщения с данными может выглядеть примерно так:

void some_func (void){  // some code    static UserType val{15, 20};    Post::push_message(msg, &val);}

Рассмотрим далее класс Track:

Class Track
template<topic N, base_t T, auto* Func>struct Track{static_assert( !std::is_null_pointer_v<decltype(Func)> );template<typename... Args>static void go (Args... args){    bm_clock::duration dur;TrackParam& p = DeviceHandler::get_track_param(); switch (p.entry){    //track logic    }}};

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

Так как мы пишем в плюсовом окружении, для работы с таймером сподручно использовать инструментарий стандартной библиотеки <chrono>. Для этого нам необходимо переопределить стандартную функцию now(), возвращающую локальное время вашей системы. Возможную реализацию этого приема вы найдете в хедере bm_chrono.hpp.

Итак, к этому моменту нам понятна основная идея фреймворка; мы рассмотрели реализацию стейта и треков; выяснили, что взаимодействие между сущностями фреймворка и внешней средой осуществляется через сервисы уведомлений и сообщений.

Осталось прояснить, кто и как жонглирует состояниями.

Class Device
class DeviceImpl;class Device{public:Device();void action(void);private:Device(const Device&)= delete;Device& operator=(const Device&)= delete;static DeviceImpl& impl(void);DeviceImpl& impl_;};

Класс Device реализован через паттерн Singleton в варианте Мейерса в комбинации с идиомой pimpl. Такой подход позволяет подчеркнуть разделение интерфейса класса от его реализации. Весь полезный функционал скрыт в классе DeviceImpl.

Class DeviceImpl
class DeviceImpl{public:using state_p = type_pack<STATES>;using state_v = std::variant<STATES>;using state_a = std::array<state_v, size(state_p{})>;constexpr DeviceImpl() : idx(0) {    set(state_p{});  }private:template <class... Ts>constexpr void set (type_pack<Ts...>){(set_impl(just_type<Ts>{}), ...);};template <typename T>constexpr void set_impl (just_type<T> t){using state_t = typename decltype(t)::type;std::size_t i = find(state_p{}, t);states[i].emplace<state_t>(state_t{});}public:state_a states;base_t idx;};

Класс DeviceImpl преимущественно в компайл тайм конструирует и сохраняет контейнер состояний(стейтов), переданных фреймворку. С помощью уже знакомого дефайна STATES мы выводим и алиасим необходимые типы. state_p - это просто пак типов стейтов. state_v - объект этого типа будет содержать конкретный стейт. state_a - тип контейнера стейтов.

В конструкторе DeviceImpl с помощью методов set() и set_impl() мы создаем объекты состояний и размещаем их в контейнере. Необходимые для этого размер контейнера и индексы мы вычисляем в компайл тайм с помощью constexpr функций size() и find(). Подробнее о них и других современных способах работы со списками типов рассказано здесь.

Вернемся к классу Device. В его конструкторе мы создаем объект DeviceImpl и сохраняем ссылку на него:

Device::Device() : impl_( impl() ){}DeviceImpl& Device::impl(void){static DeviceImpl instance;return instance;}

Переключение состояний реализовано в единственном методе класса Device - action().

void Device::action(void){base_t temp_idx;auto l = [&temp_idx](auto&& arg){    temp_idx = static_cast<base_t>( arg() );  };std::visit(l, impl_.states[impl_.idx]);impl_.idx = temp_idx;}

В нем мы создаем объект лямбды, выполняющей единственное действие - вызов оператора () у текущего стейта и получение кода возврата, который будет в качестве индекса контейнера указывать на ячейку со следующим стейтом. Передав в качестве параметра сконструированный объект лямбды в std::visit, мы посетим объект std::variant с текущим стейтом и сохраним код возврата в виде индекса.

Что ж, с теорией покончено, переходим к практике. Как пользоваться Фрэнки? Совсем несложно.

В заголовочнике defs.hpp алиасите базовый тип своей платформы, задаете максимальные количества треков на один стейт, и сообщений/уведомлений - на весь проект. Там же, в определениях классов topic и msg в ходе работы над вашим проектом перечисляете нужные вам сообщения и уведомления. Выглядеть это будет примерно так:

defs.hpp
using base_t = std::uint32_t;constexpr base_t MAX_SUBSCRIPTION_NUM = 8;constexpr base_t MAX_MESSAGE_NUM = 8;constexpr base_t MAX_TRACK_NUM = 8;enum class topic: base_t{no,green,yellow,red,state,};enum class msg: base_t{no,stop,exit,  test};

Далее по примерам из статьи определяете классы своих рабочих стейтов, создаете им удобные псевдонимы и перечисляете их в дефайне STATES там же, в defs.hpp. Подключаете заголовочники получившихся стейтов в файл frankie.cpp. Ну и росчерком мастера вносите в main те самые две строчки, с которых мы начали.

Уфф, Уроборос наконец-то укусил свой хвост.

Если заинтересовались, забирайте Фрэнки здесь, не обижайте его. Там же выложен пример для отладки NUCLEO- H743ZI2 (-std=gnu++17, -O3, программа грузится из RAM). Весит проект с этими настройками примерно 12Kb, сам фреймворк - 5,5Kb.

В примере три стейта: быстрая змейкая, медленная змейка и аварийный стейт. Стартуем с быстрой змейкой, затем переключаемся на медленную. В стейте быстрой змейки первый трек веерно рассылает уведомления трекам с моргалками, в медленной - треки моргалок подписаны друг на друга. Медленная змейка завершается с ошибкой и происходит переход в аварийный стейт с красной моргалкой. И все по новой.

Берем отладочную плату, шьем и выпускаем magic smoke запускаем пример.

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

В то же время нужно учитывать, что это решение для soft real-time систем. Дествительно, стейты и треки выполняются последовательно, время их реакции зависит о переданных функций. Если же в вашем проекте есть условия, при которых требуется быстрая реакция системы за константное время, Фрэнки можно прокачать context switcher-ом, например, как показал в своей статье уважаемый @lamerok.

На этом все, спасибо за внимание!

Подробнее..

Перевод Собираем и устанавливаем свою Linux-систему на микроконтроллер STM32MP1

09.06.2021 10:16:43 | Автор: admin

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

В этой статье мы автоматизируем процесс сборки и установки Linux-системы на микроконтроллер STM32MP157-DK2. ОС будет обладать минимальной функциональностью, но зато мы соберём из исходников собственную систему. А поможет нам в этом Buildroot система сборки Linux-дистрибутивов.

Что такое Buildroot?


Сначала вспомним, что Linux-система состоит из достаточно большого количества разных компонентов. Так как мы здесь говорим про embedded-платформы, выделим следующие компоненты:

  1. Загрузчик (обычно для архитектуры ARM это U-Boot): выполняет инициализацию HW, загружает ядро Linux и стартует его.
  2. Собственно, само ядро, управляющее процессами и памятью, содержащее планировщик, файловые системы, сетевой стек и, конечно, все необходимые драйвера для вашей аппаратной платформы.

  3. Пользовательские библиотеки и приложения из open source экосистемы: командные оболочки, библиотеки для работы с графикой, сетью, шифрованием и так далее.

  4. Внутренние пользовательские библиотеки и приложения, реализующие какую-то свою бизнес-логику.

Собрать все эти компоненты воедино мы можем двумя способами:

  1. Использовать готовый бинарный дистрибутив, например от Debian, Ubuntu или Fedora.

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

  2. Использовать систему сборки, например,Buildroot илиYocto/OpenEmbedded.

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

Мы выбираем второй способ. В этой статье используем Buildroot, потому что с этой системой сборки достаточно просто разобраться и она подходит для embedded-платформ.

Buildroot это набор make-файлов и скриптов, которые автоматизируют загрузку исходного кода различных компонентов, их извлечение, настройку, сборку и установку. В конечном итоге он генерирует образ системы, готовый к прошивке и обычно содержащий загрузчик, файл-образ ядра Linux и корневую файловую систему.

Важно отметить, что Buildroot не поставляется с исходными кодами Linux, с U-Boot или с другими компонентами. Он всего лишь содержит набор скриптов и инструкций, описывающих, какой исходный код загружать и какие настройки использовать при сборке.

Как собрать Linux-систему с Buildroot?


Начнём с установки самой системы Buildroot, а потом перейдём к её настройке:

git clone git://git.buildroot.net/buildrootcd buildroot

Обычно настройка Buildroot делается с помощью команды make menuconfig, которая позволяет указать необходимые опции для вашей системы. Но мы вместо этого используем свою конфигурацию, которую мы создали заранее специально для STM32MP157-DK2. Она находится в моём репозитории.

git remote add tpetazzoni https://github.com/tpetazzoni/buildroot.gitgit fetch tpetazzonigit checkout -b stm32mp157-dk2 tpetazzoni/2019.02/stm32mp157-dkА теперь дадим Buildrootу команду использовать нашу конфигурацию.make stm32mp157_dk_defconfig

Мы могли бы сразу приступить к сборке, так как эта конфигурация работает нормально. Но здесь я хочу показать, как можно изменить конфигурацию и ускорить сборку. Мы изменим всего один параметр. Для этого запустим утилиту menuconfig (она встроена в Buildroot). Если кто-то из вас уже настраивал ядро Linux, этот инструмент должен быть вам интуитивно понятен, поскольку это просто утилита для настройки.

make menuconfig

Если команда не сможет работать из-за отсутствия библиотеки ncurses, установите пакет libncurses-dev или ncurses-devel (точное название пакета будет зависеть от версии Linux ОС, на которой вы запускаете Buildroot). Библиотека ncurses предназначена для управления вводом-выводом на терминал.

Успешно выполнив menuconfig, перейдём в подменю Toolchain. По умолчанию в Toolchain Type выбрана опция . Нужно изменить её на , нажав ENTER.

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


Выходим из menuconfig и сохраняем изменения. Теперь пришло время поработать с командой make. Я люблю всё логировать, поэтому она будет выглядеть вот так:

make 2>&1 | tee build.log

На этом этапе система сборки Buildroot проверит наличие всех необходимых пакетов. Если чего-то не обнаружит, то выполнение команды прервётся. Если это произойдёт, на сайте Buildroot посмотрите раздел System requirements > Mandatory packages и установите все необходимые зависимости. После этого можно запускать команду заново.

На моей машине команда make работала 10 минут. После сборки появится набор каталогов и файлов (самое интересное лежит в output/images):

  • output/images/zImage: здесь лежит ядро Linux;

  • output/images/stm32mp157c-dk2.dtb: блоб-файл дерева устройств (Device Tree Blob);
  • output/images/rootfs.{ext4,ext2}: файл-образ корневой файловой системы ext4, которая на сегодняшний день является самой популярной;

  • output/images/u-boot-spl.stm32: загрузчик первой стадии;

  • output/images/u-boot.img: загрузчик второй стадии;

  • output/images/sdcard.img: финальный образ для SD-карты, сгенерированный на основе предыдущих образов.

Прошивка и тестирование системы


Запишем sdcard.img на карту microSD:

sudo dd if=output/images/sdcard.img of=/dev/mmcblk0 bs=1M conv=fdatasync status=progress

Не забудьте проверить, что в вашей системе карта microSD определяется как /dev/mmcblk0 (и на всякий случай предупреждаю: после того, вы запишете туда образ, вся информация на этой карточке будет затёрта)!

Подключите карту к микроконтроллеру.

Соедините USB-кабелем ваш компьютер и micro-USB разъём с надписью ST-LINK CN11 на плате. Ваша машина должна распознать устройство с именем /dev/ttyACM0, через которое вы сможете получить доступ к последовательному порту платы. Установите на свой компьютер и запустите программу для общения с последовательным портом. Лично мне очень нравится picocom:

picocom -b 115200 /dev/ttyACM0

Он подходит для embedded-систем, так как занимает минимальный объём памяти (менее 20 КБ) и имеет подробную документацию.

Наконец, включите плату, воткнув кабель USB-C в разъём PWR_IN CN6. Затем на последовательный порт начнут приходить сообщения. Нам важно, что в конце появится приглашение залогиниться в системе Buildroot. Можно войти в систему с пользователем root, пароль вводить не нужно.


Этапы загрузки системы и вход


Рассмотрим основные этапы процесса загрузки, изучая сообщения, которые приходят на последовательный порт:

U-Boot SPL 2018.11-stm32mp-r2.1 (Apr 24 2019  10:37:17 +0200)

Это сообщение от загрузчика первой стадии: код, содержащимся в файле u-boot-spl.stm32 скомпилирован как часть загрузчика U-Boot. Его непосредственно загружает STM32MP157. Загрузчик первой стадии должен быть достаточно маленьким, чтобы поместиться во внутреннюю память STM32MP157.

U-Boot 2018.11-stm32mp-r2.1 (Apr 24 2019  10:37:17 +0200)

Это сообщение от загрузчика второй стадии, который был выгружен из внутренней памяти устройства во внешнюю память загрузчиком первой стадии. Загрузчик второй стадии это файл u-boot.img, который также является частью загрузчика U-Boot.

Retrieving file: /boot/zImageRetrieving file: /boot/stm32mp157c-dk2.dtb

Эти сообщения печатает загрузчик второй стадии: мы видим, что он загрузил образ ядра Linux (файл zImage) и блоб дерева устройств (файл stm32mp157c-dk2.dtb), описывающий нашу аппаратную платформу. Хорошо, U-Boot загрузил оба файла в память: теперь он готов к запуску ядра Linux.

Starting kernel ...Это последнее сообщение U-Boot, после этого управление передаётся ядру.[  0.000000] Booting Linux on physical CPU 0x0[  0.000000] Linux version 4.19.26 (thomas@windsurf) (gcc version 8.2.1 20180802 (GNU Toolchain for the A-profile Architecture 8.2-2018.11 (arm-rel-8.26))) #1 SMP PREEMPT Wed Apr 24 10:38:00 CEST 2019

И сразу появляются первые сообщения ядра Linux, показывающие версию Linux и дату/время сборки. Далее идут другие, не слишком интересные сообщения Нам нужно дождаться вот этого:

[  3.248315] VFS: Mounted root (ext4 filesystem) readonly on device 179:4.

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

Starting syslogd: OK[...]Welcome to Buildrootbuildroot login:

И вот, наконец, появляется то самое сообщение от Buildroot с просьбой залогиниться.

После входа в систему вы получите доступ к командной оболочке Linux. Введя команду ps, можно посмотреть список процессов, команда ls/ покажет содержимое корневой файловой системы и так далее.

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

echo 255 > /sys/class/leds/heartbeat/brightnessecho 0 > /sys/class/leds/heartbeat/brightness

Как сделать это с нуля?


Углубляемся в основы конфигурирования Buildroot


В начале статьи мы говорили про настройку и оптимизацию конфигурации Buildroot. По идее, для этого нужно сначала изучить основы конфигурирования в этой системе. Поэтому вернёмся в прошлое к команде make menuconfig.

В меню Target options выбрана архитектура ARM Little Endian, а в Target Architecture Variant указан Cortex-A7. На этом процессоре как раз построен наш микроконтроллер.

В меню Build options используем все значения по умолчанию.

Как я писал выше, в меню Toolchain вместо кросс-компилятора по умолчанию был выбран пункт <External toolchain>.

В меню System configuration мы произвели следующие изменения:

  1. Оверлей-каталоги корневой файловой системы определены как board/stmicroelectronics/stm32mp157-dk/overlay/. Эта опция сообщает Buildroot, что содержимое этого каталога должно быть скопировано в корневую файловую систему в конце сборки. Такой подход позволяет добавлять собственные файлы в корневую файловую систему.

  2. Для пользовательских скриптов, запускаемых после создания образов файловой системы, задано значение support/scripts/genimage.sh, а для параметра Extra arguments, передаваемого в пользовательские скрипты, задано значение -c board/stmicroelectronics/stm32mp157-dk/genimage.cfg. Это означает, что Buildroot должен вызвать скрипт genimage.sh в самом конце сборки: его цель сгенерировать финальный образ для SD-карты, который мы уже использовали.

В меню Kernel мы выбрали версию Linux и путь для конфигурации ядра:

  1. Мы загрузили исходники ядра Linux с Github с помощью макроса Buildroot под названием github. В соответствии с выбранной версией (v4.19-stm32mp-r1.2), Buildroot пошёл в репозиторий (https://github.com/STMicroelectronics/linux/) и загрузил оттуда ядро.

  2. Мы указали путь для конфигурации ядра: board/stmicroelectronics/stm32mp157-dk/linux.config. Конфигурацию ядра также можно кастомизировать для ваших нужд (эта тема выходит за рамки данной статьи).

  3. Мы включили опцию <Build a Device Tree Blob (DTB)> и записали в In-tree Device Tree Source file names имя нашего файла stm32mp157c-dk2. И тогда Buildroot сможет сформировать и использовать дерево устройств именно для нашей платформы.

  4. Мы установили для Install kernel image значение </boot in target>, поэтому образ ядра и дерево устройств будут находится в каталоге/bootкорневой файловой системы. U-Boot будет загружать их как раз оттуда.

В меню Target packages оставили значения по умолчанию: активен только пакет BusyBox. BusyBox очень популярный инструмент для embedded-платформ: это легковесная альтернатива Linux shell и другим инструментам командной строки (cp, mv, ls, vi, wget, tar). Для нашей системы нам хватит одного BusyBox!

В меню Filesystem images активировали ext2/3/4root filesystem и выбрали ext4. Эта файловая система отлично подходит для SD-карт.

Теперь в меню Bootloaders активируем U-Boot, для которого выполняем следующий набор действий:

  1. Загружаем U-Boot из репозитория https://github.com/STMicroelectronics/u-boot.git с Git-тегом v2018.11-stm32mp-r2.1

  2. U-Boot используем с предустановленной конфигурацией stm32mp15_basic, которую мы указываем для нашей платы с помощью defconfig.

  3. Вообще, эта конфигурация предполагает использование сторожевого таймера STM32. Но в нашей минималистичной версии Linux его нет. Поэтому мы пойдём в файл board/stmicroelectronics/stm32mp157-dk/uboot-fragment.config и отключим сторожевой таймер для текущей конфигурации. Если мы захотим расширить возможности нашей Linux-системы и добавить его, то использование таймер нужно вновь разрешить.

  4. Подменю U-Boot binary format: тут нужно предупредить Buildroot, что для загрузчика второй стадии должен быть создан образ u-boot.img, и именно его нужно будет поместить в output/images.

  5. Мы также сообщаем Buildroot, что на базе нашей конфигурации для U-Boot будет собран загрузчик первой стадии (файл spl/u-boot-spl.stm32). Его тоже нужно будет разместить в output/images.

  6. В окружение U-Boot мы добавляем опцию DEVICE_TREE=stm32mp157c-dk2. Она понадобится в процессе сборки U-Boot, чтобы использовать дерево устройств именно для нашей платформы.

  7. В меню Host utilities мы подключаем пакет genimage.

Вся эта конфигурация сохраняется в простом текстовом файле configs/stm32mp157_dk_defconfig, который мы загружали изначально при запуске make stm32mp157_dk_defconfig.

Углубляемся в процесс сборки Buildroot


Надеюсь, после изучения основ конфигурирования стало понятнее, как происходит сборка (для простоты я опустил несколько промежуточных шагов):

  1. Загрузка и установка компилятора с веб-сайта ARM и установка библиотек C и C ++ на нашу корневую файловую систему.

  2. Загрузка исходного кода ядра Linux из репозитория STMicroelectronics, сборка ядра в соответствии с нашей конфигурацией, размещение zImage и stm32mp157c-dk2.dtb в каталоге output/images, размещение корневой файловой системы в каталоге /boot. Кроме того, на этом этапе происходит установка модулей ядра в корневую файловую систему.

  3. Загрузка исходного кода U-Boot из репозитория STMicroelectronics, его сборка в соответствии с нашей конфигурацией, размещение u-boot-spl.stm32 u-boot.img в каталоге output/images.

  4. Загрузка исходного кода Busybox с официального сайта, его сборка в соответствии с нашей конфигурацией и установка в корневую файловую систему.

  5. Копирование содержимого оверлей-каталогов в корневую файловую систему.

  6. Создание ext4-образа корневой файловой системы и его установка в output/images/rootfs.ext4

  7. Вызов скрипта genimage.sh, который сгенерирует образ для SD-карты, output/images/sdcard.img

Теперь давайте посмотрим на файл board/stmicroelectronics/stm32mp157-dk/genimage.cfg, который советует утилите genimage, как нужно правильно генерировать образ для SD-карты:

image sdcard.img {hdimage {gpt = true}partition fsbl1 {image = u-boot-spl.stm32}partition fsbl2 {image = u-boot-spl.stm32}partition uboot {image = u-boot.img}partition rootfs {image = rootfs.ext4partition-type = 0x83bootable = yessize = 256M}}

Какие конкретно указания даны в этом скрипте:

  1. Создать файл sdcard.img

  2. Этот файл должен содержать несколько разделов в соответствии с таблицей GPT partition table. Это нужно, чтобы встроенный ROM микроконтроллера STM32MP157 смог найти загрузчика первой стадии.

  3. Два первых раздела должны называться fsbl1 и fsbl2. Там должен храниться сырой бинарный код загрузчика первой стадии (отмечу, что в разделах не установлено никакой файловой системы). В коде ROM, встроенном в STM32MP157, жёстко прописано: нужно искать загрузчик первой стадии в первых двух разделах с именами, начинающимися с fsbl.

  4. Третий раздел (тоже без файловой системы) с именем uboot, по аналогии с предыдущим пунктом, хранит сырой бинарный файл загрузчика второй стадии. Действительно, загрузчик первой стадии должен найти загрузчика второй стадии в третьем разделе SD-карты (это определено в конфигурации U-Boot и может быть при необходимости изменено ).

  5. Четвёртый раздел содержит образ файловой системы ext4, созданный Buildroot. Этот образ фактически является нашей корневой файловой системой, вместе с BusyBox, стандартными библиотеками C/C ++, а также файлом-образом ядра Linux и блоб-файлом дерева устройств.

Последний раздел помечен как загрузочный (bootable). Это важно, потому что конфигурация U-Boot для аппаратной платформы STM32MP157 по умолчанию следует концепции U-Boot Generic Distro Concept. При загрузке U-Boot будет искать раздел, помеченный как загрузочный, а затем внутри файловой системы, содержащейся в этом разделе, искать файл /boot/extlinux/extlinux.conf, чтобы узнать, как загрузить систему.

Файл extlinux.conf находится внутри оверлей-каталога нашей файловой системы (board/stmicroelectronics/stm32mp157-dk/overlay/boot/extlinux/extlinux.conf), в корневой файловой системе он будет определяться как /boot/extlinux/extlinux.conf и U-Boot легко найдёт его.

Вот что внутри этого файла:

label stm32mp15-buildrootkernel /boot/zImagedevicetree /boot/stm32mp157c-dk2.dtbappend root=/dev/mmcblk0p4 rootwait

Таким образом мы говорим U-Boot, чтобы он загружал образ ядра из /boot/zImage, дерево устройств из /boot/stm32mp157c-dk2.dtb. А строка root=/dev/mmcblk0p4 rootwait должна быть передана ядру Linux во время загрузки. Именно в этом выражении (root=/dev/mmcblk0p4) хранится информация о том, где находится корневая файловая система.

Итак, сформулируем этапы загрузки собранной Linux-системы на нашей аппаратной платформе с учётом новых подробностей:

  1. Встроенный в STM32MP157 ROM ищет разделы GPT, чьи имена начинаются с fsbl. Если успешно, то загружает их содержимое во внутреннюю память STM32 и запускает загрузчик первой стадии.

  2. В соответствии с хардкодом, он обязан загрузить из третьего раздела SD-карты загрузчика второй стадии. Так, загрузчик первой стадии инициализирует внешнюю RAM, грузит в неё загрузчика второй стадии и стартует его.

  3. Загрузчик второй стадии выполняет ещё одну инициализацию, а затем ищет раздел, помеченный как загрузочный (bootable). Он обнаруживает, что таковым является четвёртый раздел. Он загружает файл /boot/extlinux/extlinux.conf, благодаря которому узнаёт, где расположены ядро и дерево устройств. Он загружает их и запускает ядро с аргументами, указанными всё в том же файле extlinux.conf.

  4. Ядро Linux работает до момента монтирования корневой файловой системы, расположение которой указано в параметре root=/dev/mmcblk0p4. После монтирования корневой файловой системы ядро запускает первый пользовательский процесс.

  5. В данном случае первый пользовательский процесс это /sbin/init (спасибо, BusyBox!). Он стартует несколько служб, а потом приглашает пользователя войти (ввести логин).

P.S. Вы можете найти исходный код для Buildroot, использованный в этой статье, вот здесь.



Облачные серверы от Маклауд быстрые и безопасные.

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

Подробнее..

Перевод SparkFun Pro Micro RP2040 функциональная плата с чипом от Raspberry ценой в 10

05.05.2021 00:09:59 | Автор: admin

Прошло совсем немного после анонса собственного чипа от Raspberry Foundation, а плат с этой микросхемой вышло немало. Есть относительно дорогие, есть не очень. Одна из наименее дорогих плат SparkFun Pro Micro RP2040. Ее цена составляет $10.

Она неплохо подходит как для профессиональных, так и для домашних проектов. Разработчики оснастили ее как GPIO, так и коннектором Qwiic, что позволяет подключать ее к другим системам. В продолжении подробности.

Коротко о главном


У SparkFun целый модельный ряд плат RP2040. Но система, которая здесь описана наиболее типичный представитель линейки. Она недорогая, это узнаваемый форм-фактор, плюс сравнимые с конкурентами характеристики. Кстати, она похожа на Adafruit Feather RP2040, плату, которую мы уже описывали, но у новой модели есть 16 МБ флеш-памяти.

Характеристики:
  • SoC: ARM Cortex M0+ c 133 МГц.
  • SRAM: 264 КБ
  • Внутренняя память: 16 МБ
  • GPIO: 20 GPIO пинов следующей конфигурации 10 x PWM, 10 x Digital I/O, 4 x Analog 12-bit ADC, 1 x I2C (Qwiic), SPI, 2 x UART, программируемый IO, 1 x WS2812 / NeoPixel.
  • USB: USB C

Дизайн SparkFun Pro Micro RP2040



Будучи почти такого же размера, как ItsyBitsy RP2040 от Adafruit, SparkFun Pro Micro RP2040 имеет узнаваемый дизайн, идеальной подходящий для использования в макетной плате. Но система прекрасно подходит и для работы над домашними проектами ее без проблем можно распаять или подключить к чему угодно (ну, почти).

Ярко-красный цвет платы выделяет ее из обилия плат черного и зеленого цветов. Выводы GPIO помечены при помощи шелкографии причем на обеих сторонах платы, так что запутаться не получится. Питание можно подавать как через USB-C порт, так и через RAW / + и GND. Правда, нельзя превышать характеристики в 5В и 600 мА. На противоположном конце платы располагается разъем Qwiic.

Разъемы такого типа совместимы с форматом Adafruit Stemma QT, благодаря чему к плате можно подключать широкий спектр моделей других плат, датчиков, сенсоров и дисплеев. Есть и переходники, которые еще больше расширяют возможности подключения дополнительных систем. Ну а доступ к UART, контактам SPI осуществляется через GPIO.

Использование SparkFun Pro Micro RP2040



Тесты проводились с последними версиями MicroPython и CircuitPython. Для CircuitPython тестировалась базовая GPIO функциональность с использованием светодиода и кнопки. Все это работало без проблем. Второй тест заключался в подключении WS2812 / NeoPixel, установке библиотеки neopixel CircuitPython. Все это заняло несколько минут, после чего все заработало, как и требовалось.

Третий тест подключение емкостного тач-сенсора при помощи кабеля Qwiic / Stemma QT. С MicroPython удалось протестировать использование I2C устройств посредством Qwiic-коннектора. Первым таким устройством стал OLED-экран, который был запрограммирован на вывод серии графических образов и анимаций. Четвертый тест подключение четырехзначного семисегментного дисплея с питанием от TM1637. Здесь уже потребовался переходник. В общем-то, и в этом случае проблем никаких не было. Использовался генератор случайных четырехзначных чисел и прокрутка текста.

Pro Micro RP2040 от SparkFun объединяет в себе лучшие возможности двух плат Adafruit RP2040: ItsyBitsy RP2040 и QT Py RP2040. Как и ItsyBitsy RP2040 от Adafruit, Pro Micro RP2030 от SparkFun предлагает большой выбор контактов GPIO, а также разъем Qwiic / Stemma QT и USB-C, как и QT Py.

Юзкейсы для платы


Как и говорилось выше, она отлично подходит для любых проектов. Можно сделать, например, робота. Разъем Qwiic и 16 МБ флеш-памяти позволяют использовать Pro Micro RP2040 в проектах по сбору данных.


При цене в $10, как у ItsyBitsy RP2040, эта плата обладает дополнительными возможностями, включая большое количество GPIO-пинов и Qwiic / Stemma QT коннектор. При этом размеры платы очень небольшие.

Если вам нужна хорошая плата среднего размера с неплохим выбором функций, то SparkFun Pro Micro RP2040 лучший выбор.

Подробнее..

Перевод Одноплатник Pimoroni Pico LiPo как микроконтроллер от Raspberry, только лучше

02.06.2021 20:09:34 | Автор: admin

После того, как Raspberry Pi Foundation разработала и анонсировала свой процессор RP2040, на рынке появилось несколько одноплатников на его основе, включая собственную разработку Raspberry. Среди наиболее заметных RP2040-проектов стоит отметить Adafruit Py RP2040 и Pimoroni Tiny 2040. Есть еще и Adafruit Feather RP2040 с Cytron Maker Pi Pico.

Напомним, что сам по себе проект Raspberry Pi Pico это, по сути, микроконтроллер с GPIO и возможностью программирования. Плату можно применять для эмуляции самых разных типов интерфейсов, включая ретро-системы. У Pico от Raspberry относительно небольшой набор функций, но своих денег плата стоит. Сейчас появился новый девайс, который называется Pimoroni Pico LiPo. Это как бы Pico на стероидах.

Его стоимость $17, то есть в 4 с немногим раз больше, чем у оригинального Pico. Но и возможности шире, так что девайс может пригодиться тем разработчикам, кому не хватало возможностей микроконтроллера от Raspberry. Так, здесь есть функция заряда аккумулятора, USB-C порт, 16 МБ флеш-памяти и коннектор Stemma QT / Qwiic.

Подробнее о характеристиках



Система на чипе RP2040
Двухьядерный Arm Cortex M0+, частота 133 МГц.
264KB SRAM, и 4 / 16MB Flash памяти
GPIO 26 мультифункциональных 3.3V GPIO пинов
2 SPI, 2 I2C, 2 UART, 3 12-bit ADC, 16 PWM каналов
1 x User LED (GPIO 25)
8 Programmable I/O (PIO) state machines for custom peripheral support.
Stemma QT / Qwiic коннектор
SWD debug breakout
Модуль можно распаивать на платах
Power USB C для данных и питания
2 pin JST коннектор для LiPo / Li lon аккумуляторов. Мониторинг батарей + LED для отображения режима работы батареи.

Дизайн и использование Pimoroni Pico LiPo



Pico LiPo идеально работает с MycroPython. Для того, чтобы получить максимальную отдачу от платы, нужно использовать CircuitPython, в особенности при работе с компонентами Stemma QT / Qwiic. Если по какой-то причине вам необходимо работать с MicroPython и при этом взаимодействовать с устройствами Stemma QT / Qwiic, то стоит попробовать последний проект от Adafruit. Вот здесь можно загрузить все необходимое, работает с Pico LiPo весьма гладко.

Как и говорилось выше, Pico LiPo это Raspberry Pi Pico на стероидах. При таком же размере и аналогичной GPIO распиновке у девайса есть функция зарядки батарей, Stemma QT / Qwiic и кнопка включения. Наиболее важная возможность таки зарядка аккумуляторов. Режим зарядки контролируется чипом MCP73831. Ток небольшой 215 мА, но в ходе теста LiPo батарея была заряжена полностью.


Есть защита аккумулятора XB6096I2S, что предотвращает возможное превышение параметров заряда. Выделенного MicroPython или CircuitPython модуля для мониторинга заряда нет. Для определения факта заряда используется 24-й пин. А для мониторинга 29-й. Всего у девайса три аналоговых входа столько же, сколько и у малинки, но меньше, чем у Adafruits Feather RP2040. Да, на один пин меньше, но если учитывать, что его используют для контроля заряда батареи, то с потерей смириться проще.

Положительный момент в этом возможность использования аккумулятора в качестве базового бесперебойника питания. Так, проект можно запитать от USB-C, но если питание отключится, в дело вступает батарея. Распиновка у Pico LiPo такая же, как и у малинки. Плюс есть коннектор Stemma QT, благодаря которому подключение внешнего устройства становится очень простой задачей.

Наличие этого коннектора реальный прорыв для I2C-девайсов. Дело в том, что у ряда производителей есть множество совместимых компонентов, включая датчики температуры, емкостные входы, экраны и т.п. Используя емкостный сенсорный датчик MPR121 и последнюю версию CircuitPython 7 для Pico LiPo, мы создали демонстрационную модель системы с применением Stemma QT-коннектора.


Система заработала сразу и без проблем. Кроме того, мы видим множество проектов с применением Stemma QT / Qwiic. Рядом с этим коннектором есть трехпиновой коннектор JST-SH это отладка. Контакты используются для получения данных из работающего RP2040 без использования UART. Используя эти контакты и другой Pi Pico в качестве дебаг-хоста мы можем работать с процессором, SRAM, отображенной памяти I/O в выбранной среде разработки. Если вы разрабатываете критически важные RP2040-приложения, то это ключевая особенность. Ну а для большинства из нас просто интересная функция.

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

Кнопка BOOT обычно применяется для ввода системы в режим прошивки. Но у этой платы есть еще возможность задействовать кнопку в коде. На плате три разных светодиода, включая power, статус заряда батареи и пользовательский светодиод, подключенный к 25 контакту.

Как и упоминалось выше, распиновка у платы такая же, как у малинки, так что использовать ее можно точно там же и точно так же, где применяется Raspberry Pi Pico. При желании этот микроконтроллер можно заменить на LiPo в уже готовом проекте и получить больше функций. Мы проверили это утверждение, заменив микроконтроллер от Raspberry на новую плату вот в этом проекте. После замены все заработало без танцев с бубном.

Юзкейсы для Pimoroni Pico LiPo



У LiPo есть все, что дает Raspberry Pi Pico, плюс дополнительные функции. Даже одна лишь возможность подключения батареи стоит тех $17, которые просят разработчики за свой микроконтроллер. Его можно применять в самых разных проектах, от световых мечей на базе NeoPixel до интернета вещей. Pico LiPo можно применять в разных роботехнических проектах, но для сервоприводов, двигателей и т.п. понадобится собственный источник питания GPIO платы не даст ток больше 600 мА.

Подробнее..

Перевод Lisp для микроконтроллеров

07.05.2021 10:12:10 | Автор: admin


Lisp для плат Arduino, Adafruit M0/M4, Micro:bit, ESP8266/32, RISC-V и Teensy 4.x.

Новость!


ARM версия 3.6b теперь поддерживает save-image (сохранение образа) на всех платах ATSAMD21

В последнем релизе ARM uLisp, версия 3.6b, можно сохранять образ всего вашего рабочего пространства Lisp во флэш-память плат ATSAMD21, на которых не предоставляется отдельный чип DataFlash.

Таким образом, поддержкаsave-imageдобавляется к Adafruit Neo Trinkey, Adafruit Gemma M0, Adafruit Feather M0, Arduino Zero, Arduino MKRZero и Seeedstudio Xiao M0.

uLisp это версия языка программирования Lisp, специально спроектированная для работы на микроконтроллерах с ограниченным объемом ОЗУ, от Arduino Uno на основе ATmega328 до Teensy 4.0/4.1. Независимо от платформы, можно использовать ровно одну и ту же программу на uLisp.

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

Кроме того, язык Lisp идеально подходит для изучения фундаментальных концепций программирования. В нем предусмотрена работа со строками, обработка списков и сборка мусора, а также этот язык отлично подходит для выражения сложных идей например, научить робота выбираться из лабиринта или найти кратчайший путь на карте. uLisp не только поддерживает базовый набор функций Lisp, но и содержит расширения для Arduino, поэтому данный язык идеально подходит для управления Arduino.

Актуальную версию uLisp можно бесплатно скачать со страницыDownload uLisp.

Проекты uLisp



Управление NeoPixels на ассемблере


Трассировка лучей при помощи uLisp


Беджик Lisp




Матричные часы


Приложение для GPS-картографирования


Интерфейс термопары

Требования


ОЗУ: не менее 2 кбайт.

Память для хранения программ: не менее 32 кбайт.

ЭCППЗУ, флеш или cегнетоэлектрическая оперативная память (FRAM): при наличии используется для сохранения и загрузки рабочего пространства uLisp.

8/16-битные платформы


Версии uLisp для 8-битных и 16-битных платформ поддерживают целые числа в диапазоне от -32768 до 32767.

AVR-версия

AVR-версия uLisp поддерживает следующие платы:

Arduino Unoили другие карты на основе ATmega328. На ней достаточно памяти для простого приложения на uLisp с использованием коротких символьных имен. ВсеПростые примеры пойдут на Arduino Uno.

Arduino Mega 2560или другие платы на основе ATmega2560. С ними у вас будет достаточно памяти для весьма сложного приложения; см., например,Animals,Tweetmaze,Route finder иInfinite precision arithmetic.

ATmega1284.Хотя, в Arduino нет официальной платы на этой основе, ATmega1284 легко подключить к макетной плате, причем, здесь предоставляется целых 16 кбайт ОЗУ.

Платы ATmega4809. Arduino Nano Every и отладочная плата Microchip Curiosity Nano бюджетные платофрмы на основе ATmega4809.

32/64-битные платформы


Версии uLisp для 32-битных платформ поддерживают работу с целыми числами в диапазоне от 2147483647 до -2147483648, а также с 32-битными числами с плавающей точкой.

ARM

ARM-версия uLisp поддерживает следующие платы:

Arduino Zero. Эта плата основана на ядре SAMD21 ARM Cortex-M0+ и предоставляет 256 кбайт флеш-памяти и 32 кбайт ОЗУ.

Arduino MKRZero. Похожа на Arduino Zero, основана на ядре SAMD21 ARM Cortex-M0+ и предоставляет 256 кбайт флеш-памяти и 32 кбайт ОЗУ. В ней имеется гнездо для SD-карты, поэтому SD-карту можно использовать для сохранения и загрузки образов uLisp.

Платы Adafruit M0. ПлатыAdafruit Gemma M0, Adafruit ItsyBitsy M0 и Adafruit Feather M0 все основаны на микроконтроллере ATSAMD2148 MГц ARM Cortex M0+. У них схожие возможности и производительность; основные отличия между этими платами заключаются в их коэффициенте формы.

Платы Adafruit M4. Платы Adafruit Metro M4 Grand Central, Adafruit Metro M4, Adafruit ItsyBitsy M4, и Adafruit Feather M4 все основаны на микроконтроллере ATSAMD51120 MГц ARM Cortex M4.

Adafruit PyGamer и PyBadge. Adafruit PyGamer и PyBadge это портативные игровые платформы на основе микроконтроллера ATSAMD51120 MГц ARM Cortex M4, оснащенные цветным тонкопленочным дисплеем размером 160x128.

Платы Adafruit nRF52840.Adafruit CLUE и Adafruit ItsyBitsy nRF52840 обе основаны на микроконтроллере Nordic Semiconductor nRF52840 64 MГц ARM Cortex-M4,с 1 Mбайт флеш-памяти для хранения программ и 256 Кбайт ОЗУ.

BBC Micro:bit.Эта плата основана на микроконтроллере Nordic Semiconductor nRF51822 ARM Cortex-M0. Она работает на частоте 16 MГц и предоставляет 256 Кбайт флеш-памяти для программ и 16 Кбайт ОЗУ.

Maxim MAX32620FTHR.Эта плата основана на микроконтроллере Maxim MAX32620ARM Cortex-M4F, работающем на частоте 96 MГц, с 2048 Кбайт флеш-памяти и 256 Кбайт ОЗУ.

Teensy 4.0 и 4.1. Они основаны на процессоре NXP iMXRT1062 ARM M7, работающем на частоте 600 MГц с 1 Мбайт ОЗУ.

Версия ESP8266/ESP32

Версия uLisp для ESP8266/ESP32 поддерживает следующие платы:

Платы ESP8266 . Эти платы основаны на 32-битном микропроцессоре Tensilica Xtensa L106, работающем на частоте 80 MГц, с 4 Мбайт флеш-памяти и 80 Кбайт ОЗУ. В них встроена функция Wi-Fi.

Платы ESP32. Эти платы основаны на 32-битном микропроцессоре Tensilica Xtensa LX6, работающем на частоте 160 или 240MГц, с 4 Мбайт флеш-памяти и 80 Кбайт ОЗУ. В них встроена функция Wi-Fi и двухрежимный Bluetooth.

Версия RISC-V

Версия uLisp для RISC-V поддерживает следующие платы:

Платы Sipeed MAiX RISC-V. Эти платы основаны на двухъядерном 64-битном процессоре Kendryte K210 RISC-V, 400 МГц, предоставляют 8 Мбайт ОЗУ и 16 Мбайт флеш-памяти. Они схожи по производительности.

Другие платформы


Эти платы поддерживаются более ранними версиями uLisp:

Arduino Due. Эта плата основана на ядреAT91SAM3X8E ARM Cortex-M3 и предоставляет 512 Кбайт флеш-памяти, 96 Кбайт ОЗУ и часы 84 МГц.

Версия STM32

Платы STM32. Платы STM32 Maple Mini и Blue Pill основаны на процессореSTM32F103ARM Cortex-M3, работающем на частоте 72 МГц, с 128 Кбайт флеш-памяти и 20 Кбайт ОЗУ.

Версия MSP430

Версия uLisp MSP430 поддерживает следующие платы:

MSP430 F5529 LaunchPad. Она использует флеш-память для сохранения образов и предоставляет достаточно памяти для весьма сложного приложения.

MSP430 FR5969 LaunchPad. Эта версия использует cегнетоэлектрическую оперативную память (FRAM) для рабочего пространства и для сохранения образов, благодаря чему памяти у вас в распоряжении будет предостаточно.

MSP430 FR5994 LaunchPad. Эта версия использует cегнетоэлектрическую оперативную память (FRAM) для рабочего пространства и для сохранения образов, благодаря чему памяти у вас в распоряжении будет предостаточно.

MSP430 FR6989 LaunchPad. Эта версия использует cегнетоэлектрическую оперативную память (FRAM) для рабочего пространства и для сохранения образов,а также поддерживает вывод текста на встроенный ЖКИ-дисплей.

Производительность


См.Производительность.

Спецификация


В целом, этот язык является подмножеством Common Lisp, и программы на uLisp также должны работать под Common Lisp. Но обратите внимание: там одно пространство имен для функций и переменных; иными словами, нельзя использовать одно и то же имя для функции и переменной.

На 8/16-битных платформах предоставляются списки, символьные последовательности, целые числа, символы, строки (на 32-битных платформах) и потоки.

Кроме того, на 32-битных платформах предоставляются числа с плавающей точкой и массивы, в том числе, битовые массивы.

Целое число это последовательность цифр, которой может предшествовать знак + или -. На 8/16-битных платформах целые числа могут быть в диапазоне от -32768 до 32767. На 32-битных платформах целые числа могут быть в диапазоне от 2147483647 до -2147483648. Целые числа можно вводить в шестнадцатеричной, восьмеричной или двоичной системе, при помощи нотаций #x2A, #o52, или #b101010, все эти варианты равны 42.

На платформах, обладающих более чем 2 Кбайт ОЗУ поддерживаются произвольные символьные имена, определяемые пользователем. В качестве символьной может использоваться любая последовательность, не являющаяся целым числом; так, например, 12a допустимая символьная последовательность. На платформах всего с 2 Кбайт символьное имя может содержать до трех символов из множеств a-z, 0-9, или $, * или or -.

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

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

В uLisp есть сборщик мусора, работающий с применением алгоритма пометок (mark and sweep). Сборка мусора занимает менее 1 мсек на Arduino Uno или менее 3 мсек на Arduino Mega 2560 (см.Производительность).

uLisp также содержит простой редактор программ (см.Работа с редактором программ), средство трассировки и приятный принтер (см.Отладка в uLisp).

Пример


На следующем примере показано, как можно использовать uLisp.

Загрузив uLisp себе на микроконтроллерную плату, с ним можно наладить коммуникацию, вводя или вставляя команды в монитор порта или воспользовавшись последовательным терминалом. Подробнее см. Использование uLisp.

Допустим, вы подключили красный LED-индикатор к аналоговому выводу пина 9 на Arduino Uno. Затем можете ввести команду на Lisp:

(analogwrite 9 128)

чтобы установить LED в 128, что соответствует половинной яркости.

Чтобы не пришлось писать эту команду всякий раз, когда вы хотите установить красный LED, можно написать функциюred:

(defun red (x) (analogwrite 9 x))

Теперь можно добиться того же эффекта, что и выше, просто написав:

(red 128)

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

Допустим, вы используете потенциометр, который подключили к плате, чтобы варьировать напряжение на аналоговом входе A0. Можно определить функциюdim, чтобы потенциометр сам корректировал яркость LED, вот так:

(defun dim () (loop (red (/ (analogread 0) 4)))

и запустить, написав:

(dim)

Наконец, можно сохранить образ uLisp в ЭСППЗУ и указать, чтоdimдолжна выполняться при загрузке, введя:

(save-image 'dim)

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

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



Облачные серверы от Маклауд быстрые и безопасные.

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru