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

Программирование микроконтроллеров

Перевод Чиптюн-музыка на 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, и обошлись они примерно по доллару за штуку. Заказ был обработан довольно быстро, правда некоторые из них пришли с дефектами травления.

Подробнее..

CAT-интерфейс для трансивера Радио-76

13.05.2021 12:17:12 | Автор: admin
В предыдущей публикации о трансивере Радио-76 упоминалось о синтезаторе частоты с CAT-интерфейсом. В этой статье тема CAT-интерфейса будет раскрыта подробней.

CAT-интерфейс (Computer Aided Transceiver) предназначен для управления частотой, видами модуляции и другими функциями радиостанции с помощью компьютера.

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

Аппаратное решение


Аппаратное решение не несёт никакой новизны. Синтезатор создавался из того, что было под рукой: плата Black Pill c микроконтроллером STM32F411CEU6, дисплей SSD1306 и микросхема синтезатора частоты Si5351A-B-GT.

Схема подключения Si5351A-B-GT приведена ниже.


Подтягивающие резисторы R1, R2 устанавливаются на выводы дальнего от микроконтроллера устройства на шине I2C. Сигнал с выхода CLK0 подаётся на первый смеситель основной платы трансивера Радио-76. Сигнал с выхода CLK2 подаётся на второй смеситель трансивера. Делители напряжения на резисторах R3, R6 и R4, R5 препятствуют перегрузке смесителей.

Вся схема собрана на печатной плате переходника SSOP-DIP:


Из имеющихся у меня в наличии кварцевых резонаторов на частоту 25MHz и 27MHz ни один на этих частотах не запустился. Параллельно включенные резисторы и конденсаторы ситуацию не спасали. На фотографии кварц, который запустился на частоте 24MHz, когда параллельно ему был включен резистор номиналом 1МОм.

Конфигурация микроконтроллера


Проект создан на основе платы Black Pill c микроконтроллером STM32F411CEU6 в среде разработки STM32CubeIDE:


Шина I2C подключена к выводам PB9, PB10 микроконтроллера. К выводам PB0, PB1, PB2 подключены тангента (PTT, Push-To-Talk) и контакты телеграфного ключа (KEY_DIT, KEY_DAH). Вывод PC13 служит для аппаратного переключения режима приём/передача (RX/TX). Режиму TX соответствует сигнал низкого логического уровня, при этом светится индикатор на плате.

Виртуальный COM-порт создан на основе IP Commucation Device Class. Максимальное количество интерфейсов равно двум. Размер буферов задан равным 64 Bytes.

Программная реализация CAT


Ссылка на репозиторий: https://github.com/dmitrii-rudnev/radio-76-cat

За основу решения была принята система команд для управления популярным трансивером Yaesu FT-817. Описание работы CAT-интерфейса этой радиостанции занимает в руководстве пользователя всего четыре страницы.

Управляющие программы сторонних производителей обычно используют для связи с радиостанцией драйвер OmniRig, созданный канадским радиолюбителем Alex Shovkoplyas (VE3NEA). Описанная реализация CAT-интерфейса использует ограниченный набор команд Yaesu FT-817, поддерживаемый этим драйвером.

При настройке OmniRig для работы с публикуемым решением нужно выбрать в конфигураторе OmniRig тип трансивера FT-817, COM-порт, к которому подключен CAT, и установить скорость порта 9600 бит/с.

Для управления радиостанцией один раз в 200 мс через COM-порт посылается пять байт данных. Пятый байт содержит команду управления, первые четыре байта могут содержать данные или быть пустыми. Радиостанция распознает команду, выполняет её и посылает отклик.

Описанная реализация CAT-интерфейса поддерживает работу в составе радиостанции двух генераторов плавного диапазона VFO A и VFO B. Наличие двух VFO позволяет работать на разнесённых частотах (режим Split), когда приём осуществляется на частоте одного VFO, а передача на частоте другого.

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

Помимо этого, хотя для Радио-76 это и не актуально, оставлена возможность переключения вида модуляции.

Собственно трансивер реализован переменной trx со структурой, приведённой ниже:
typedef enum{  MODE_LSB = 0x00,  MODE_USB = 0x01,  MODE_CW  = 0x02,  //CW-USB  MODE_CWR = 0x03,  //CW-LSB  MODE_AM  = 0x04,  MODE_FM  = 0x08,  MODE_DIG = 0x0A,  //DIG-U  MODE_PKT = 0x0C   //DIG-L} Mode;typedef struct{  Mode mode;     //используемая модуляция из списка  uint64_t vfoa; //частота VFO A в герцах  uint64_t vfob; //частота VFO A в герцах  uint8_t vfo;   //активный VFO: 0, если активен VFO A; 1, если VFO B  uint8_t split; //режим работы на разнесенных частотах: 1, если включен   uint8_t is_tx; //режим передачи: 1, если включен  uint32_t sysclock; //системное время  uint8_t systicks;  //счётчик прерываний SysTick до десяти} TRX_TypeDef;

Системное время используется для отслеживания тайм-аутов. Инкремент trx.sysclock происходит по каждому десятому прерыванию SysTick.

Переключение режима RX/TX осуществляет программный модуль ptt_if.c.

Переключение в режим передача (TX) и возврат в режим приём (RX) производится двумя разными способами:
1. По нажатию (TX) тангенты и её отпусканию (RX) (низкий/высокий уровень на входе PTT).
2. При получении по CAT команды FT817_PTT_ON (0x08) (TX) и получении по CAT команды FT817_PTT_OFF (0x88) (RX).

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

Обработка состояния телеграфного ключа, а также линий RTS и DTR виртуального COM-порта в публикуемой реализации CAT-интерфейса не предусмотрена.

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

Модуль cat_if.c содержит драйвер CAT-интерфейса.

Обработчик состояния CAT-интерфейса запускается в бесконечном цикле main.c. Обработчик проверяет наличие подключения по виртуальному COM-порту, и если оно есть, то проверяется наличие данных в буфере CAT-интерфейса.

Если данные в буфере есть, обработчик извлекает из буфера пять байт данных и распознаёт команду по списку поддерживаемых. Если команда распознана, запускается обработчик команды, который обращается или к ptt_if.c, или к vfo_if.c. По результатам обработки формируется отклик, который передаётся в компьютер через виртуальный COM-порт.

Наиболее часто трансивер получает две команды: FT817_GET_FREQ (код команды 0x03) и FT817_READ_TX_STATE (0xF7). По ним он возвращает частоту настройки, вид модуляции, текущий режим приём/передача и режим работы на разнесённых частотах.

Виртуальный COM-порт


Виртуальный COM-порт создан на основе IP Commucation Device Class.

Команды CAT передаются из приёмного буфера CDC в приёмный буфер CAT функцией CDC_Receive_FS из состава файла usbd_cdc_if.c, расположенного в папке USB_DEVICE\App.

Отклик на команды передаётся из обработчика команд CAT-интерфейса запуском функции CDC_Transmit_FS.

Для корректной работы COM-порта в его буфер необходимо прописать параметры подключения:
static int8_t CDC_Init_FS(void){  /* USER CODE BEGIN 3 */  USBD_CDC_HandleTypeDef   *hcdc;  USBD_CDC_LineCodingTypeDef line_coding =  {    /* 9600 8n1 */    .bitrate    = 9600U, /* Data terminal rate, in bits per second */    .format     = 0U,    /* Stop bits: 0 - 1 Stop bit */    .paritytype = 0U,    /* Parity:    0 - None */    .datatype   = 8U,    /* Data bits */  };  hcdc = (USBD_CDC_HandleTypeDef*) hUsbDeviceFS.pClassData;  memcpy ((uint8_t*) hcdc, &line_coding, 7U);  /* Set Application Buffers */  USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, 0);  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);  return (USBD_OK);  /* USER CODE END 3 */} 

Без этой записи в буфере OmniRig к COM-порту может и не подключиться.

От автора



Данное решение CAT-интерфейса может работать с любыми доступными радиолюбителям синтезаторами частоты и контроллерами дисплеев с минимальными переделками main.c и vfo_if.c.

Мне будет очень приятно, если эта публикация поможет кому-нибудь реализовать управление по CAT своим радиоприёмником или радиостанцией.

Подробнее..

Перевод Практический взгляд на Raspberry Pi Pico с точки зрения STM32

19.06.2021 14:07:57 | Автор: admin
Сравнительно недавно Raspberry Pi Foundation выпустила плату Raspberry Pi Pico, основанную на микроконтроллере (Micro Controller Unit, MCU) RP2040. Эта плата привлекла большое внимание членов сообщества разработчиков различных электронных устройств. Появилось довольно много проектов, в которых используются программируемые модули ввода-вывода (Programmable I/O, PIO) Raspberry Pi Pico. Например, это проект PicoDVI, в котором конечные автоматы PIO используются для вывода DVI-сигнала.



Но с появлением Raspberry Pi Pico связано не только радостное возбуждение разработчиков электроники. Это событие заставило сообщество задаться важным вопросом о том, окажет ли появление платы какое-то ощутимое влияние на тех, кто пользуется STM32, SAM и другими микроконтроллерами, основанными на Cortex-M. Станет ли микроконтроллер RP2040 жизнеспособным выбором для некоторых из проектов, в которых используются похожие MCU? Учитывая то, что в состав RP2040 входит двухъядерный процессор ARM Cortex-M0+, кажется справедливой идея использования этого микроконтроллера там же, где применяются 32-битные MCU от ведущих производителей компонентов такого рода, в частности, от STMicroelectronics.

Сможет ли небольшой проект Raspberry Pi Foundation показать инженерам STM как надо делать микроконтроллеры, или создателям платы на RP2040 стоит пересмотреть некоторые из своих гипотез? Сложно ли будет портировать на RP2040 низкоуровневый код, рассчитанный на STM32?

Сложно ли перенести STM32-проект на RP2040?


Короче говоря, когда я обратила внимание на RP2040, мне подумалось, что будет интересно попытаться портировать на новый микроконтроллер мой C++-фреймворк для STM32. Правда, эта идея меня заинтересовала не из-за двухъядерного ARM Cortex-M0+. У меня есть двухъядерные микроконтроллеры STM32H7 (M4 и M7), которые, за счёт более совершенных характеристик, легко обойдут RP2040. Сильнее всего меня заинтриговали программируемые модули ввода-вывода RP2040, возникало такое ощущение, что они достойны того, чтобы познакомиться с ними поближе.


Плата Raspberry Pi Pico, основанная на RP2040 подключена к одноплатному компьютеру Raspberry Pi, играющему роль SWD-адаптера (оригинал)

Основываясь на опыте работы с STM32 я поняла, что смогу быстро портировать некоторые файлы, создав в репозитории проекта ветку RP, рассчитанную на другую архитектуру, и принявшись за дело. Ведь и в основном проекте, и в новой ветке код будет рассчитан на Cortex-M. Обычно работа с новым ARM-микроконтроллером заключается в том, чтобы найти даташит, справочное руководство и CMSIS-файлы для соответствующего устройства. А потом существующий низкоуровневый код можно легко адаптировать под новый подход к именованию периферийных устройств и под новую схему регистров, учитывая то, что фундаментальные компоненты нового и старого микроконтроллеров (SysTick, NVIC и так далее) ничем не отличаются.

Может, я поступила слишком опрометчиво, но я заказала плату Raspberry Pi Pico, даже не поинтересовавшись тем, есть ли для неё CMSIS-файлы, и даже не взглянув в справочное руководство по ней. Позже я, к своему удивлению, выяснила, что пока нет даже и речи о наличии CMSIS-файлов для Raspberry Pi Pico, или хотя бы о возможности взаимодействия RP2040 с другими устройствами из экосистемы Cortex-M. Но при этом SVD-файл для MCU RP2040 имеется в Pico SDK, а на основе этого файла можно создать заголовочный файл для устройства. Благодаря проекту cmsis-pi-pico в моём распоряжении, в итоге, оказалось рабочее решение.

Решение моей задачи можно было бы и упростить


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


Последовательность загрузки RP2040 (даташит RP2040, рисунок 15) (оригинал)

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

Первая сложность, которую нужно было преодолеть для того чтобы научиться работать с RP2040, заключалась в понимании особенностей цепочечного процесса загрузки микроконтроллера. Тут всё очень похоже на то, как в прошлом, на обычных компьютерах, была организована загрузка с дискет, или то, как устроена загрузка с HDD/SSD. А именно внешняя QSPI Flash ROM рассматривается MCU лишь как устройство, которое, возможно, содержит загрузочные данные. Загрузчик первой фазы загрузки интегрирован в MCU и располагается в ROM по адресу 0x0000 0000. Он обращается к интерфейсу QSPI и пытается загрузить из него 256 байт данных. Потом будет проверен CRC32-хеш этих данных. Если проверка пройдёт успешно, они будут признаны загрузчиком второй фазы загрузки.

Загрузчик второй фазы может решать множество задач, при этом некоторые задачи он должен решать в обязательном порядке. Этот процесс, реализованный в RP2040, если сравнить его с аналогичным процессом в некоторых знаменитых клонах STM32, тоже имеющих SPI ROM (вроде отличных клонов компании GigaDevice), не так понятен, не так хорошо документирован, не так прозрачен, как мог бы быть. В нём много такого, в чём можно запутаться.

Говорят, что хорошие художники копируют


У меня ушло достаточно много времени на то, чтобы понять, как подход к управлению тактированием периферийных устройств, принятый в STM32, соотносится с системной архитектурой RP2040. Я внимательно читала даташит RP2040 и всех вокруг спрашивала об этом. Как оказалось, RP2040-версия системы управления тактированием периферии называется RESETS. Эта система является полной противоположностью той, что применяется в STM32. А именно, нужно установить условие сброса периферийного блока в 0 для того чтобы включить его тактирование. Так, чтобы включить тактирование GPIO, нужно переключить бит 8 в RESETS_RESET (PADS_BANK0).


Функциональная схема GPIO-пина RP2040 (оригинал)

Когда я это поняла, я посмотрела раздел документации по GPIO-периферии (раздел 2.19). И кое-что тут же бросилось мне в глаза. А именно, то, что я там увидела, совершенно не похоже на то, как устроена практически вся GPIO-периферия, с которой я когда-либо сталкивалась. В частности, речь идёт о периферии STM32, AVR и SAM.

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

А теперь, когда я через всё это прошла, полагаю, можно будет просто переписать мой код, после чего он заработает на RP2040?

Причуды загрузки


Как уже было сказано, загрузчик второй фазы загрузки должен быть расположен в начале образа прошивки. Я считала, что это должен быть какой-то достаточно стандартный код, поэтому просто взяла готовый ASM-код, который выдал официальный Pico SDK, и использовала его при сборке примера Blinky. Добавив этот код к RP2040-порту моего проекта Nodate, я смогла без проблем собрать Blinky.

Запись результирующего ELF-бинарника в RP2040 стала ещё одним приключением. Дело в том, что на плате Raspberry Pi Pico нет встроенного SWD-адаптера, чего-то в духе ST-Link. А микроконтроллеру на двухъядерном Cortex-M нужен могоканальный SWD-адаптер. Единственным подобным устройством, которое было у меня под рукой, оказался адаптер, интегрированный в плату Nucleo-STM32H7. Поэтому я решила использовать кастомный форк OpenOCD, созданный Raspberry Pi Foundation. Его я запустила на Raspberry Pi.

После столь основательной подготовки мне удалось успешно прошить RP2040, но ничего не заработало. Беглая проверка вызвала у меня такое ощущение, что в ходе загрузки мне не удалось выйти за пределы исходного загрузчика и добраться до прошивки, находящейся в SPI ROM. Сейчас мне сложно дать ответ о причинах происходящего. Это могла быть проблема с ASM-кодом второй фазы загрузки, это могла быть ошибка в экспериментальных CMSIS-файлах RP2040, которые создавала не я. Это могло быть и что-то совершенно иное.

Продолжение следует?



Raspberry Pi Pico (оригинал)

После того, как я потратила много часов на то, чтобы завести RP2040 с использованием CMSIS-файлов и файлов загрузчика второй фазы загрузки, мне кажется, что можно немного отстраниться от ситуации и переоценить происходящее. А именно, с того момента, когда начинала формироваться моя точка зрения на Raspberry Pi Pico, в запросе по поводу CMSIS-файлов появились сведения о том, что официальные CMSIS-файлы, возможно, появятся в Pico SDK 1.2.0. Это довольно-таки приятно.

Полагаю, любому, кто хочет поближе познакомиться с RP2040, пользуясь инструментами, ставшими индустриальным стандартом, имеет смысл дождаться этого релиза Pico SDK. А после того, как в моём распоряжении окажутся официальные CMSIS-файлы, я, вероятно, начну с переделывания примера Nodate Blinky, а потом попробую поработать с PIO. Перспектива создавать собственные интерфейсы кажется мне весьма привлекательной. И хотя возможности Raspberry Pi Pico не так мощны, как возможности CPLD или FPGA, они, всё равно, способны лечь в основу интереснейших проектов.

Возникает такое ощущение, что авторы даташита для RP2040 (он, скорее, похож на смесь справочного руководства и даташита) иногда забывают о том, что в нём должно быть описание микроконтроллера, а не чего-то другого. В эти моменты он превращается в учебное руководство по Pico SDK. Хотя материалы этого даташита и способны принести пользу тем, кто стремится освоить Pico SDK, тем, кто хочет написать что-то своё, пользы от него, однозначно, меньше, чем от более привычного даташита.

Полагаю, тех, кто захочет написать для Raspberry Pi Pico что-то своё, не особенно порадуют такие особенности платы, как запутанная работа с GPIO-периферией, сложный процесс загрузки, необходимость в загрузчике второй фазы загрузки, непрозрачность внешней ROM. В общем тому, кому интересна плата Raspberry Pi Pico, пока приходится ориентироваться на официальный SDK.

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

Пользовались ли вы Raspberry Pi Pico?


Подробнее..

Нахлобучиваем домофонные ключи iButton с помощью Flipper Zero

09.06.2021 22:15:01 | Автор: admin


Flipper Zero проект карманного мультитула для хакеров в формфакторе тамагочи, который мы разрабатываем. Предыдущие посты [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14]

iButton это общее название для формата электронного ключа в форм-факторе металлической таблетки. Еще его называют Dallas Touch Memory. Часто его ошибочно называют магнитным ключом, но это неправильно, ничего магнитного в нем нет. Внутри iButton полноценный микрочип, работающий по цифровому протоколу.
В статье разберемся что такое формат ключей iButton от физического устройства до протоколов, а также трюки, которые можно с ним делать при помощи Flipper Zero.

Что такое iButton


Название iButton это продукт фирмы Dallas Semiconductor, в 1991 году выпустившей на рынок ключ под торговой маркой Touch Memory, потом замененной на iButton.


Схематическое устройство ключа iButton: в центре корпуса контакт плюс, потом пластиковая изоляция, и внешняя часть корпуса это минус

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

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

Считыватель


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

Касание контактов iButton ключа с домофонным считывателем

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

Как устроен iButton во Flipper Zero


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

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


Прототипы конструкций контактной площадки iButton во Flipper Zero, которые мы печатали на 3D-принтере в процессе разработки

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

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

Финальная конструкция получилась компромиссной: 100% ключей считывается успешно, примерно 80% считывателей успешно работают с эмуляцией. В оставшихся 20% считывателей приходится корячиться, чтобы контакт достал до нужных стенок. Для этих редких случаев можно использовать внешние контакты GPIO, на которые выведены контакты ibutton: подключиться макетными проводами и ткнуть их в считыватель.

Режим считывателя


В режиме считывателя флиппер ожидает поднесения ключа, при этом готов прожевать сразу три типа ключей: Dallas, Cyfral, Metakom. Флиппер сам определит тип ключа при чтении. Название протокола ключа отобразится на экране над ID номером.

Чтение ключа ibutton формата Dallas. Прочитанный ключ сохраняется на SD-карту.

Для считывания ключа необходимо зайти в меню iButton > Read и приложить читаемый к контактной площадке. Считанный ключ можно сразу эмулировать, записать на болванку, либо сохранить на SD-карту. Хоть контактная площадка находится на задней стороне от экрана, можно быстро наловчиться читать ключи не разворачивая флиппер, просто на ощупь.

В режиме чтения iButton используются два правых контакта Flipper Zero

Режим эмуляции iButton


В режиме эмуляции ключа, Флиппер сам выступает ключом и программно эмулирует ключ iButton из памяти. ID ключа для эмуляции во Flipper Zero можно добавить двумя способами:

  • Считать существующий ключ сохранить ID ключа на SD-карту и выбирать нужный ключ в любой момент
  • Вручную ввести ID ключа даже если в руках нет нужного ключа, но его ID известен, его можно ввести вручную. Так, например, можно сфотографировать ID ключа и отправить его другу с флиппером, без необходимости передавать физический ключ


Для запуска эмуляции ключа нужно зайти в меню iButton > Saved, выбрать нужный ключ и запустить Emulate. На экране появится надпись с ID ключа, который эмулируется. После этого можно подносить Флиппер к считывателю. Важно помнить, что в этом режиме используются другие пины на контактной площадке Флиппера.

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

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

iButton через внешний GPIO


Контакт iButton на нижней крышке также выведен на гребенку GPIO. Это можно пользовать для подключения к нестандартным считывателям, ключам, любым устройствам работающим по протоколу 1-Wire вроде датчиков. Мы используем эти контакты для анализа сигналов через осциллограф. При этом, этот контакт не совсем честный GPIO, потому что имеет подтяжу к 5V.


Контакты iButton соединены с гребенкой GPIO. Порт iButton работает в режиме open-drain и подтянут к напряжению 5 В через резистор 1 кОм.

Протокол 1-Wire



В протоколе 1-Wire всегда есть главное устройство Master и ведомые Slave

Ключи Dallas обмениваются данными по протоколу 1-wire. Всего один контакт на передачу данных(!!) в обе стороны, от мастера к слейву и наоборот. Протокол 1-wire работает по модели Master-Slave. В этой топологии устройство Master всегда инициирует общение, а Slave следует его указаниям.

При контакте ключа (Slave) с домофоном (Master) чип внутри ключа включается, получив питание от домофона и происходит инициализация ключа, после чего домофон запрашивает ID ключа. Далее мы разберем подробно этот процесс.

Флиппер умеет работать в режимах Master и Slave. В режиме чтения ключа Флиппер выступает в роли считывателя, то есть работает как Master. А в режиме эмуляции ключа, флиппер прикидывается ключом, то есть работает в режиме Slave.


При чтении ключа Флиппер выступает мастером, а при эмуляции с домофоном работает как slave

Формат данных в ключе Dallas



Домофон считывает из iButton 8 байт (64 бита) информации, чтобы решить, открывать дверь или нет.

Структура данных этих 8 байт следующая:

  • 1 байт код семейства (Family Code), для iButton он всегда равен 0x01
  • 6 байт серийный номер ключа
  • 1 байт контрольная сумма СRC




Код семейства у ключей Dallas всегда 0x01. Если у вас этот код отличается, то скорее всего, это не ключ от домофона.

Серийный номер в некоторых случаях выгравирован на ключе, но может:

  • Не содержать все 8 байт
  • Иметь последовательность символов задом-наперед
  • Иметь начало в непонятном месте


На оригинальном ключе iButton выгравирован ID, но его формат записи немного отличается от представления Флиппере: сперва идет family code, потом инвертированный серийный номер, потом контрольная сумма

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

Ошибки чтения



При некорректном чтении ключа Flipper Zero сообщает об ошибках. Возможные ошибки:
  • Некорректная контрольная сумма ошибка в CRC
  • Неправильный код семейства когда family code отключается от 0x01, Флиппер ругается что это не ключ iButton.


Возможные ошибки при чтении ключей Dallas: неправильный байт CRC CRC ERROR; байт Family-code не равен 0x01 THIS IS NOT A KEY.

Ввод ID вручную


Если ID ключа известен, его можно ввести во Флиппер вручную. Это удобно когда самого физического ключа нет, например можно передать нужны байты просто в чате или скинув другу фото. На видео показан пример создания нового ключа Cyfral из 2 байт. Новый сгенерированный ключ сохраняется на SD-карту.


Создание нового ключа вручную с помощью ручного ввода айдишника

При создании ключа нужно выбрать его тип: Dallas, Cyfral или Metakom. От этого будет зависеть длина ID и протокол, используемый при эмуляции. После ввода ID Флиппер предложит ввести имя нового ключа, либо использовать сгенерированное.

Запись ключей 1-Wire Dallas


Существуют ключи Dallas, которые можно записать и которые нельзя. Популярные перезаписываемые iButton болванки: RW1990, TM2004, TM01C. Процесс записи имеет свои нюансы, разберем их.

Запись болванки может требовать повышенного напряжения например, для записи менее популярной RW2000 требуется напряжение 8 В (правда это Cyfral, но смысл понятен).

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

Существуют болванки, которые могут подходить ко всем типам ключей(Dallas/Cyfral/Metakom).

Запишем ключ домофона на болванку.



Чтобы записать ключ нужно из раздела iButton зайти в Saved и выбрать ключ. После чего перейти в раздел Write и прислонить перезаписываемый ключ к контактной площадке.

Русские народные ключи Cyfral, Metakom




Протоколы Metakom и Cyfral отечественные русские изобретения. Не популярны в мире.

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

Cyfral и Metakom не принимают никакие команды. При подаче питания на ключ, он сразу начинает бесконечно посылать ID за счет изменения сопротивления. Таким образом, логические уровни определяются по сопротивлению ключа. По документации ключей условно принимается, что информационные слова кода выдаются начиная с младшего бита.
В интернете есть документация на данные ключи;)

Cyfral




Логические уровни в Cyfral, так же как и в Dallas имеют временные ограничения: если сопротивление остается низким около 50 мкс это логический 0, если 100 мкс это логическая 1.

Формат передаваемых данных специфичен.
Cyfral циклично отправляет 9 нибблов (1 ниббл = 4 бита): 1 стартовый и 8 ID. Нибл может иметь всего 4 значения для ID и одно значение для стартового слова. Все остальные записи некошерные.



В итоге ID записывается в 2 байта (Всего 8 нибблов ID. 4 ниббла = 16 состояний = 1 байт информации).

Бывает, что домофоны Cyfral занимаются проверкой ключа, подавая некорректные данные.
У Cyfral нет никаких контрольных сумм. Хочешь убедиться прочитай ключ еще раз, а хочешь пять. Так как сигнал аналоговый, то чтение сигнала требует наличия АЦП или компаратора. Проще всего использовать компаратор, выход которого является низким или высоким уровнем напряжения.
С эмулированием ключа на практике проблем не возникает. Замыкания на землю (отсутствие сопротивления) вполне достаточно, чтобы ключ выдать логический 0.

Metakom




Ключ Metakom посылает 4 байта, где каждый байт заканчивается битом четности.
Metakom имеет 3 примитива передачи:
Синхронизирующий бит;
Бит 0;
Бит 1;

Структура посылки выглядит так:
Синхронизирующий бит;
4 байта информации;
+ 7 бит данных;
+ 1 бит четности.

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

Смотрим на 1-Wire через осциллограф




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

С помощью перетяжек напряжения и удержания уровней 1-wire имеет 4 примитива для работы на шине:
  • импульс сброса (RESET);
  • импульс присутствия (PRESENCE);
  • отправка бита 0;
  • отправка бита 1 и она же по совместительству чтение бита.



Чтение ключа Dallas на Flipper. Состоит из команд Search ROM и Read ROM. Каждая команда имеет Reset sequence.

Чтение ключа на Flipper Zero устроено так: поиском проверяется наличие ключа, а затем происходит чтение ID. Это сделано, чтобы избежать случайных совпадений с другими ключами Cyfral/Metakom, тайминги которых могут случайно совпасть с требуемыми.

На осциллограмме виден длинный сигнал из 2 команд, где каждая состоит из:
  • Инициализации команды:
    • Импульс сброса;
    • Импульс присутствия;

  • Передачи команды для Slave;
  • Ответа Slave на принятую команду.


Разберем более детально каждый из пунктов.


Reset sequence инициализация команды. Состоит из Импульса Сброса и Импульса Присутствия. Импульс Сброса уровень опускает Master. Импульс Присутствия уровень опускает Slave.

Инициализация (reset sequence) состоит из двух импульсов:
  1. Импульс Сброса (Reset pulse);
  2. Импульса Присутствия (Presence pulse).

Для Импульса Сброса линию к земле подтягивает Master (домофон).
Для Импульса Присутствия линию к земле подтягивает Slave (ключ).

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


Команда чтения ID с ответом. Тайм-слот бита информации состоит из 2 участков: синхронизации и значения бита. В КОМАНДАХ за уровни напряжения на обоих участках отвечает Master. В ОТВЕТЕ на команду чтения за синхронизацию отвечает Master, за значение бита отвечает Slave.

После инициализации команды происходит обмен информацией:
  • отправка команды для Slave;
  • ответ Slave на команду.


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

Важно, что все сигналы имеют конкретные временные ограничения, которые обязательно должны быть соблюдены! Бывает, что производители домофонов упарываются, и соблюдают какие-то свои временные задержки, но на Руси дураков не бьют, и мы их трогать не станем.
Еще у iButton есть режим Overdrive, в котором временные задержки сокращены, из-за чего передача информации происходит быстрее. Есть вероятность, что в дикой природе существуют домофоны, работающие в этом режиме Но на практике они не встречались.

Стандартные команды 1-wire ключей Dallas


Для iButton характерны команды размером 1 байт (8 бит).
Зачастую домофон использует команды поиска и чтения ID (Search ROM и Read ROM).

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

Стандартные команды iButton для Regular режима следующие:



Бонус про домофонные ключи

Бонус про домофонные ключи


Почему старые домофоны это плохо?


Некоторые старинные домофоны с ключами Dallas имеют в памяти базу ID ключей, заполненную не полностью. Незаполненные поля имеют некоторое значение, иногда соответствующее всем нулям (0x00) или всем единицам (0xFF). Для проверки домофона на дремучесть создаются два ключа: один со всеми нулями, другой со всеми единицами.
Эти ключи содержат неправильный код семейства (не 0x01) и неправильный CRC (вообще не контрольная сумма)!!! Да, бывают и такие исключения.

Как почтальоны разносят по подъездам рекламу?


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

Что такое мастер ключ?


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

Играясь заблокировали домофон?


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

Какие болванки нам известны


В ходе работы с iButton мы зафиксировали некоторые известные нам болванки ключей. Вот они, на здоровье!




Наши соцсети


Все обновления по проекту первым делом публикуются в Telegeram-канале @zhovner_hub
Подробнее..

Рождение Гуся как создаётся умный стрелковый тренажёр

18.05.2021 00:13:00 | Автор: admin

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

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

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


Одной прекрасной ночью, накануне сдачи экзамена по безопасному владению оружием, мне приснился сон - я был в тире и стрелял по световым мишеням. Сначала показалось, что я очутился в Counter Strike и отрабатываю AIM (меткость), но вскоре дошло это тренировка! Я не просто стрелял по произвольным мишеням, холостил, отрабатывал чёткость и плавность движений. Тир как будто был наделён интеллектом, контролировал каждое моё движение и корректировал программу занятий. Оказалось, нас много, все стрелки тренируются и соревнуются, каждый у себя дома.

Это было чертовски увлекательно!

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

Идея была объединить игру, тренировку и современные технологии.

Гусиные пёрышки

Гусь каждому придётся по вкусу, он прост и ненавязчив. Умён и хитёр. Его лазерные мишени не так просто подстрелить, когда с ним соревнуешься. А когда тренируешься он мягок и покладист, только держи на мушке. Благодаря своему зоркому глазу Гусь всё видит, а электронному уму ещё и точно знает, когда и куда стреляли.

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

Гусиные навыки

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

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

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

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

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

Arduino, STM32, ESP32 - о железяках...

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

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

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

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

Обсудив с коллегой, мы решили закончить войну с ардуинкой за ресурсы и попробовать микроконтроллер STM32. Взвесив все за и против, решено было для начала взять также отладочную плату, но сразу помощнее, так как наши амбиции росли, а планируемый функционал расширялся. Популярная BluePill на STM32F103 на фоне возросших амбиций уже тоже смотрелась слабовато. Поэтому пока мы остановились на плате WeAct на STM32f411 c 512Kb Flash и 128Kb оперативной памяти.

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

Шина питания и автономность

По задумке устройство однозначно должно быть автономным с запасом питания минимум на час-два активной тренировки. При этом по схемотехнике шина питания должна включать как минимум две линии линия стабильного напряжения 3.3В и линия ~5-6В для питания сервоприводов.

Первая идея была простой взять за основу вариант с последовательным 2S подключением пары аккумуляторов 18650. Имея до 9В на выходе, ставим понижающий импульсный преобразователь, формирующий стабильные 5-6 В шины сервоприводов, и к ней же в свою очередь подключаем отладочные платы микроконтроллеров (у них на бору вариант стабилизатора AMS1117).

Вариант оказался абсолютно не из той оперы... Во-первых, сервоприводы при движении создают помехи, которые сгладить не просто, конденсаторы тут не помогали. Во вторых, сложности с зарядкой. Требовалось дополнительно ставить модуль балансировки заряда и подбирать подходящий блок питания. А хотелось-то сделать "как у всех" поддержать стандартную зарядку USB Type-C.

Взвесив за и против, мы выбрали путь повышения напряжений и схему параллельного подключения аккумуляторов 2P. Тем более, что под руками оказалась замечательная плата TP4056 в варианте с USB Type-C, с ней проблема контроля заряда и разряда аккумулятора оказалась закрыта.

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

На выручку пришли старые добрые линейные стабилизаторы серии LM780x. Со стабилизатором LM7805 и парой конденсаторов линии питания наконец стали ровными, не зависимо от работы сервоприводов.

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

Голова целеуказателя и видео-модуль

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

В качестве первого и пока устоявшегося варианта на эту роль отлично подошла плата ESP32-CAM. Одновременно на этот же модуль была возложена вся коммуникация, Bluetooth (BLE) для взаимодействия с насадкой-контроллером и датчиком спуска на оружии, а WiFi для интеграции с облаком и обновлений.

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

Насадка-контроллер и датчик спуска

В качестве начинки для насадки-контроллера сразу решено было использовать знакомый ESP32, но на этот раз ESP32-WROOM-32 без отладочной платы. Bluetooth (BLE) для связи насадки с базой, а сам микроконтроллер для обработки показаний с датчиков гироскопа/акселерометра и распознавания голосовых команд с микрофона.

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

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

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

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

Здесь напрашивается несколько вариантов. Первый и самый простой использовать стабилизатор на 3.3В (тот же AMS1117) и работать с аккумулятором в верхней половине заряда. Другими словами, заведомо использовать более мощный аккумулятор, но разряжать его не ниже допустимого для стабилизатора минимального напряжения. Второй вариант использовать buck&boost преобразователь, способный выдавать стабильные 3.3В как при большем, так и меньшем входном напряжении классно и удобно. Но на практике этот вариант оказался дорогим в реализации, такие чипы сложно найти в наличии и они бьют по цене устройства. И третье решение в лоб использовать дешевый повышающий преобразователь на 5В и после него понижающий стабилизатор на 3.3В.

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

Включение и выключение устройств

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

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

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

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

  • удерживание кнопки при выключенном устройстве включает;

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

  • кнопка при включенном устройстве работает как тактовая, подключенная к входному пину микроконтроллера;

  • длительное удерживание кнопки при включении устройства вводит прошивку в режим обновления;

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

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

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

Калибровка лазера

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

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

Лабораторная птица

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

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

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

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

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

Гусь в Facebook, VK, Intagram, Telegram.

Подробнее..

Разработка контроллера резервного питания. Схемотехника

04.06.2021 10:04:57 | Автор: admin

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

Контроллер работает совместно с тремя внешними модулями:

  • сетевым источником питания 220 VAC в 24 VDC

  • инвертором 24 VDC в 220 VAC

  • аккумуляторной батареей 24 В

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

Преимущества

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

Применение

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

Принцип работы

Особенностью архитектуры данного контроллера является его тесная связь с потребителями.
Для того чтобы не превысить предельно допустимую мощность источника питания и инвертера и не спровоцировать проседания выходных напряжений контроллер получает от потребителей информацию о планируемом подключении нагрузок и сообщает потребителям о текущем состоянии сетевого напряжения. Таким образом потребители не будут пытаться получить недоступную мощность или предпринимать действия способные быстро и непрогнозируемо привести к исчерпанию аккумулятора. Связь потребителей с контроллером осуществляется через один из цифровых интерфейсов : CAN, RS485, USB.

При наличии сетевого напряжения контроллер пропускает напряжение 24 В от внешнего источника питания к потребителям через управляемый ключ 1 (см. структурную схему) и напряжение 220 В через реле Р1. Ключ 1 работает одновременно также как ограничитель входного напряжения, ограничитель тока, защита от переполюсовки и идеальный диод. Ключ 1 не дает проникать обратному току на источник питания в случае отключения сетевого напряжения. Может показаться, что это излишняя мера, но некоторые промышленные источники питания MEAN WELL не включаются, когда на их входе уже присутствует напряжение. Одновременно с подачей тока потребителям контроллер заряжает аккумулятор (если аккумулятор этого требует). При этом ток заряда аккумулятора балансируется с током уходящим потребителям так чтобы не превысить допустимый ток источника питания.
Цифровое управление и повышающее/понижающий (Buck-Boost) преобразователь зарядника позволяют точно учитывать степень заряда и разряда аккумулятора и выбирать правильные профили заряда в зависимости от состояния аккумулятора.

При отсутствии сетевого напряжения контроллер пропускает через ключ 2 напряжение 24 В потребителям от аккумулятора. Напряжение при этом может варьироваться от 27 В (полностью заряженный) до 20 В (полностью разряженный). Потребители должны быть готовы работать в таком диапазоне, что обычно не представляет проблемы. Напряжение 220 В подается через реле Р2 от внешнего инвертера. Сам инвертер питается от аккумулятора через ключ 3. Инвертер может поддерживаться как в горячем резерве так и быть отключенным (что более экономично). Однако из выключенного состояния инвертеры обычно выходят несколько секунд и это затягивает переключение.

Основные характеристики

  • максимальный коммутируемый переменный ток напряжения 200 В - 35A

  • максимальный коммутируемый постоянный ток напряжения 24 В - 15 А

  • максимальный ток на входе инвертера - 50 А

  • максимальный ток заряда аккумулятора при наличии радиатора - 10 А

  • максимальный ток заряда аккумулятора без радиатора - 4 А

  • тип аккумулятора - cвинцово-кислотная батарея 24 B

  • тип микроконтроллера - MKE18F512VLL16 (ARM Cortex-M4F, 32-Bit, 168MHz, 512KB (512K x 8) FLASH, 64 KB SRAM, -40C ~ 105C)

  • Цифровые интерфейсы: CAN гальвано-изолированный, RS485 гальвано-изолированный, USB 2.0 Full Speed VCOM

  • Два гальвано-изолированных цифровых выхода

  • Дисплей с энкодерным управлением

  • Встроенные измерители токов, напряжений, мощностей и прочего по входным и выходным линиям 220 и 24 В.

  • Утечка тока аккумулятора в отключённом состоянии не более 200 мкА

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

Схема

Лист 1. Идеальный диод источника питания, идеальный диод цепи питания системы от аккумулятора и DC/DC преобразователь зарядника. Лист 1. Идеальный диод источника питания, идеальный диод цепи питания системы от аккумулятора и DC/DC преобразователь зарядника. Лист 2. Микроконтроллер, цифровые интерфейсы, дисплей, стабилизаторы питанияЛист 2. Микроконтроллер, цифровые интерфейсы, дисплей, стабилизаторы питанияЛист 3. Ключ питания инвертера, измерители в цепи переменного тока, коммутаторы цепи переменного токаЛист 3. Ключ питания инвертера, измерители в цепи переменного тока, коммутаторы цепи переменного тока

Структурная схема

Структурная схема с указанием номеров разъемов и точек измерения напряжений и токов. (для увеличения открыть в отдельном окне)Структурная схема с указанием номеров разъемов и точек измерения напряжений и токов. (для увеличения открыть в отдельном окне)

Особенности схемы

Зарядник аккумулятора выполнен на регулируемом DCDC преобразователе U23 серии LTC3789.
Выходное напряжение преобразователя задается микросхемой U24 DAC80501 управляемой по интерфейсу I2C. DAC80501 преобразует 16-и битный код в выходное напряжение в диапазоне от 0 до 1.25 В. Резистивный делитель на R94, R96, R100 смешивает напряжение от U24 и выходное напряжение DCDC преобразователя чтобы получить опорное напряжение VFB, для микросхемы LTC3789 оно должно равняться 0.8 В. DCDC преобразователь работает так чтобы напряжение VFB всегда оставалось равным 0.8 В, когда микросхема U24 меняет свое выходное напряжение.
Таким образом DCDC преобразователь способен регулировать свое напряжение от 1.65 В до 31.9 В.
Для расчета схем на базе LTC3789 и подобных существует специальная программа - LTpowerCAD
Вид окна программы для рассматриваемого преобразователя показан ниже:

В целом программа показывает более оптимистичные результаты чем есть в реальности, особенно на малых мощностях. В частности недооценивается влияние паразитных элементов трассировки.
Даже упрощенная модель в программе LTpowerCAD не дает однозначного ответа по оптимальному выбору компонентов, поскольку при разных режимах и комбинациях входных и выходных напряжений и токов значительно меняется вклад разных элементов в нагрев схемы. Т.е. программа не выполняет глобальной оптимизации по всему диапазону рабочих режимов. И приведенная схема была в основном оптимизирована для случая выходного напряжения в 32 В и выходного тока 10А, т.е. самого тяжелого режима при зарядке 24В аккумулятора.
На КПД преобразователя также влияет состояние сигнала DCDC_MODE. Как показала практика в состоянии лог. 0 (forced continuous mode ) катушка индуктивности L5 меньше нагревается чем когда на DCDC_MODE присутствует лог. 1 (pulse-skipping mode)
Сигнал EN_CHARGER в состоянии лог. 0 запрещает работу преобразователя. В выключенном состоянии преобразователь не пропускает напряжение с выхода на вход.

Ключ источника питания SW1. Выполнен на микросхеме U20 LTC4364. Через этот ключ проходит ток от источника питания к потребителям. Когда происходит переключение от питания от аккумулятора этот ключ выключается микроконтроллером.

Часть схемы с ключом источника питания Часть схемы с ключом источника питания

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

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

Ключ аккумулятора SW2. Выполнен на микросхеме U21 LTC4364.

Назначение этого ключа в том чтобы пропустить ток от аккумулятора к потребителям в режиме работы резервного питания. Транзистором Q9 задается два разных уровня выходного напряжения.
При уровне 0 сигнала AIDS_FBC ключ начинает пропускать ток от аккумулятора в систему (т.е. потребителям) только если напряжение в системе упадет ниже 22.9 В (т.е. внешний источник питания не будет способен удержать свое номинальное напряжение)
При уровне 1 сигнала AIDS_FBC ключ пропустит ток если в системе напряжение будет ниже 26.3 В.
Это необходимо когда в систему включается полностью заряженный аккумулятор с напряжением до 32 В чтобы транзисторы ключа не перегрелись из-за слишком большого падения напряжения на них.
Поскольку ключ еще и выполняет функции идеального диода, то ток из системы в аккумулятор через него не проходит.
В обесточенном состоянии и подключении только аккумулятора ключ останется закрытым. Таким образом систему нельзя включить от аккумулятора не подав предварительно напряжение от внешнего источника питания.

Ключ питания инвертора SW3. Выполнен на микросхеме U14 LTC4368.

Этот ключ включает питание на инвертор. Для быстрого переключения на резервное питание инвертор желательно держать включенным. Однако инверторы потребляют значительный ток. Например инвертор MEAN WELL TS-1500-224 мощностью 1.5 КВт

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

Высоковольтная часть. В высоковольтной части коммутация производится с помощью реле K1 и K2 типа AHES4292. Не самый быстрый и надежный способ коммутации, но дело в том что схема предназначена для коммутации самых разнообразных нагрузок и напряжений, в частности для коммутации межфазных напряжений в 3-х фазных сетях (тогда ставятся еще вспомогательные внешние 3-х фазные контакторы). Реле относятся к типу реле безопасности и на схеме они взаимно блокированные. Принято считать что по такой схеме реле такого типа ни при каких обстоятельствах не смогут включиться одновременно, даже когда одно из них залипнет. Значит сетевое напряжение никогда не сможет проникнуть на выход инвертора и погубить его.
Залипание реле контролируется измерителями напряжения на резистивных делителях R51, R52 и R53, R54

Мониторы мощности. Реализованы на микросхемах U15 и U17 типа ACS37800KMACTR-030B3-SPI.
Микросхемы способны измерять переменный ток, напряжение, мощность, действующие их значения, средние от действующих значений за заданное время, способны измерять действующее как по переходу через ноль так и действующее значение постоянных токов. Каждая из микросхем отдельно гальвано-изолирована и может выполнять точные измерения не беспокоясь о способе реализации заземления и зануления источников напряжения и даже измерять при межфазном подключении. Микросхемы измеряют ток амплитудой до 30А.
Считывание данных производится по интерфейсу SPI. На каждую микросхему идет отдельный интерфейс SPI поскольку они не могут совместно работать на одном общем интерфейсе.

Измерители токов и напряжений. Как пример приведен фрагмент схемы ниже -

U22 и U13 здесь измеряют ток. Микросхемы INA240A1 хорошо подходят для условий измерений с большими синфазными помехами. Они двунаправленные. Средняя точка для них формируется общей для всех прецизионной мало-шумящей схемой на операционном усилителе U26 THS4281DBVR. Кроме того INA240A1 достаточно хорошо согласуются в входами АЦП микроконтроллеров.
Микросхема U27 на схеме выполняет роль дифференциального усилителя напряжения для измерения напряжения аккумулятора. Дифференциальный усилитель применен здесь для того чтобы минимизировать ток потребляемый от аккумулятора, когда система обесточена, также дифференциальный усилитель как ни странно упрощает трассировку платы в отношении топологии аналоговых и цифровых земель.

Элементы управления. Для управления платой в первую очередь предназначены коммуникационные интерфейсы, но предусмотрено также и непосредственное ручное управление и настройка. Для этого введен в схему ручной механический энкодер SW1 с двухцветной подсветкой и нажатием PEL12D-4225S-S2024.

Для отображения информации есть OLED дисплей ER-OLED015-2W. Монохромный, 128x64 точки, управляется по интерфейсу SPI. Немного усложненная схема

объясняется тем что дисплею для работы нужно повышенное напряжение 12 В. Ключ питания U34 здесь добавлен скорее для страховки ввиду неопределенности поведения в даташите на дисплей в случае пониженного уровня VCC.

Микроконтроллер MKE18F512VLL16 будет работать на частоте 120 МГц. Его внутренней RAM размером в 64 кБ должно хватить для операционной системы реального времени чтобы управлять несколькими автономными задачами: GUI, измерений, контроля, связи.

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

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

Подробнее..

Обзор PAT9125 оптического датчика филамента для 3d печати

05.06.2021 22:17:23 | Автор: admin

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

PAT9125 это оптический датчик который применяется на 3d принтерах Prusa. Этот датчик может отслеживать не только момент окончания филамента но и момент его застревания. Например если у вас возникла тепловая пробка.

Главная проблема датчика, его нельзя подключить напрямую к плате с Marlin. Поскольку Marlin поддерживает только подключение энкодера, а этот датчик должен быть подключен по интерфейсу I2C. Чтобы разрешить эту проблему я использовал attiny85 digispark. Датчик подключается к Attiny а уже Attiny в свою очередь эмулирует работу энкодера.

Настройка Marlin

В файле Configuration.h нужно раскомментировать параметр #define FILAMENT_RUNOUT_SENSOR . А вот параметр #define FIL_RUNOUT_PULLUP лучше закомментировать поскольку у нас ненастоящий енкодер то подтяжка attiny к линии питания будет только мешать работе.

Кроме того нужно раскомментировать параметр #define FILAMENT_RUNOUT_DISTANCE_MM это скажет Marlin что у нас не концевой выключатель, а энкодер. Значение параметра надо уменьшить по умолчанию там стоит 25мм. Чтоб вы понимали сколько это, принтер успеет уложить два слоя калибровочного куба воздухом прежде чем заметит что что-то не так. Я поставил там 5мм можно и меньше но тогда можно столкнутся с ложными срабатываниями.

В файле Configuration_adv.h нужно раскомментировать #define ADVANCED_PAUSE_FEATURE

В файле Pinout вашей платы надо задать пин к которому будет подключен датчик в параметре #define FIL_RUNOUT_PIN.

Особенности работы датчика

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

Следующая особенность касалась уже Attiny. Сначала я подключил принтер к пину P3 и Attiny просто не стала запускаться. То есть без подключения к принтеру все прекрасно работает, а стоит подключить Attiny не стартует. После этого я подключил принтер к пину P5, Attiny загрузилась но порт просто не работал на нем всегда был высокий потенциал, возможно у меня оказалась бракованная Attiny. Все заработало только когда я подключил принтер к пину P1, пин на котором располагается диод.

Также я хочу отметить что датчик отслеживает отдельно движение по оси X и по оси Y. В моем случае используется ось X, но если вы захотите расположить датчик по другому. Например расположить его перпендикулярно движению филамента, то надо в прошивке Attiny заменить ось X на Y.

Ну и датчик сильно подорожал за последние время. Я его покупал на распродаже 11.11 он стоил 500р, а сейчас он стоит 900р почти двукратное подорожание, видимо дефицит полупроводников сказался.

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

Прошивка для Attiny https://github.com/Deema35/prusa_sensor_marlin2.0

Ссылка на корпус для датчика https://www.thingiverse.com/thing:4878669

Подробнее..

Перетягивание диода или устраиваем соревнование между CANNY 3 TINY PRO и Arduino

08.06.2021 02:17:27 | Автор: admin
Arduino vs CANNY перетягивание диодаArduino vs CANNY перетягивание диода

В предыдущей статье, посвящённой моим попыткам погрузится в увлекательный мир программирования микроконтроллеров, я грозился сделать обзор на "обновку". К сожалению, мне сейчас не хватает навыков и времени чтобы сделать, что-то достойное полноценного обзора. Однако, я всё-таки решил подготовить забавы ради, короткую статью на тему игрушечного соревнования CANNY 3 TINY PRO и неоригинальной Arduino Nano. Соревноваться контроллеры будут в своеобразном аналоге перетягивания каната, на роль которого был выбран двухцветный светодиод марки BL-L2519EGW.

Итак в сегодняшнем материале мы подключим оба контроллера к светодиоду и будем подавать случайный сигнал на его выводы. Правила простые кто большее напряжение подаст у того и кристалл в светодиоде загорится ярче. Попутно мы воспользуемся ЦАП на контроллере CANNY и доработаем стандартный ГСЧ с помощью составного функционального блока.

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

Оглавление:

  1. Введение

  2. Схема

  3. Программа

  4. Заключение

Введение

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

Для того, чтобы повторить "дуэль века" вам потребуется:

  1. Двухцветный светодиод - я использовал BL-L2519EGW, но марка не особо критична.

  2. Контроллер CANNY 3 TINY PRO именно у этого контроллера есть ЦАП, к тому же он самый доступный по цене в линейке контроллеров CANNY.

  3. Совместимый с Arduino контроллер - я использовал неоригинальную Nano, но можно было и UNO.

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

  5. Соединительные провода и макетная плата.

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

Предвосхищая некоторую критику, сразу скажу, что само собой раз контролеры Кэнни стоят в "Дакаровских" Камазах, наверное и CANNY 3 TINY PRO явно предназначен для чего-то большего, чем просто моргать светодиодом. Собственно Arduino Nano тоже может намного больше. Просто я ничего сложнее в этот раз собрать не смог, а поделится впечатлением хотелось.

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

Другие статьи цикла

Схема

Схема соединенийСхема соединений

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

Первым делом объединим "Землю" у двух контроллеров соединив выводы "GND".

Затем, от каждого из контроллеров "подведем" к светодиоду выводы от канала ЦАП.
У Arduino - "D5", у CANNY - "C2" (это единственный выход с ЦАП).
В нашем случае светодиод имеет два вывода и работает он примерно так: если на левой ноге напряжение больше, чем на правой то загорается красный кристалл. Чем больше разница потенциалов, тем ярче он загорится. И наоборот, если на правой ноге напряжение больше, чем на левой, то загорится зеленый. При примерном равенстве потенциалов светодиод вообще не будет светится.

Мы будем "перетягивать канат" 2.5 секунды, нам важно, чтобы контроллеры подали сигнал более-менее синхронно. Для этого (а также для нашей тренировки) контроллеры подадут друг другу сигналы. Выход "D7" Arduino подаст логическую единицу на вход "C6" CANNY. В свою очередь, CANNY с выхода "C4" подаст логическую единицу на вход "D3" Arduino. В программе каждого из контроллеров предусмотрим проверку наличия сигнала, при успешном прохождении которой подается напряжение на светодиод.

Для того, чтобы узнать какое напряжение подали оба контроллера, мы с помощью АЦП CANNY померим напряжение поданное Arduino. Для этого подсоединим выход "C10" CANNY к резистору.

Кстати лично я очень рад что у Canny 3 TINY PRO для того, чтобы включить режим АЦП канала не нужно паять перемычку, как в случае с обычным CANNY 3 TINY.

В итоге должно получится примерно вот-так:

Фото схемыФото схемы

Я правда использовал специальные резисторы из набора, но в остальном схема как на картинке.

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

Перейдем к программной части.

Программа

Обе программы можно скачать с GitHub.

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

/*Synhronized Randomize DAC.See more http://personeltest.ru/aways/habr.com/ru/post/561148/*/int ADC_pin = 5;int input_pin = 3;int output_pin = 7;int synch_signa = 0;int v_min = 10;int v_max = 2550;int synch_signal = 0;float rand_voltage = 0;// the setup function runs once when you press reset or power the boardvoid setup() {  pinMode(ADC_pin, OUTPUT);  pinMode(output_pin, OUTPUT);  digitalWrite(output_pin,HIGH);  pinMode(input_pin, INPUT);  Serial.begin(9600);}// the loop function runs over and over again forevervoid loop() {synch_signal = digitalRead(input_pin);      // read signal from another deviceif (synch_signal) { rand_voltage=random(v_min, v_max) / 10; analogWrite(ADC_pin, rand_voltage); Serial.println(rand_voltage);  delay(2500);   // wait for seconds}else{ delay(500);   // wait for seconds}                }

Программа для контроллера CANNY:

Функциональная диаграммаФункциональная диаграмма
  • При включении контроллер устанавливает на выходе "С4" логическую "1" для синхронизации с Arduino.

  • Канал "C10" в режиме АЦП измеряет напряжение от Arduinio и с помощью функции MAP переводит его в удобный для чтения вид.

  • ШИМ-генератор в сочетании с детектором переднего фронта раз в 2.5 секунды дают сигнал для записи случайного значения в канал "C2".

    • Значение при этом запишется, только если на входе "С6" есть логическая "1" от Arduino.

    • Значение напряжения для подачи на светодиод генерируется случайным образом. Поскольку у CANNY нет встроенного блока для сброса ГСЦ, "случайность" сигнала обеспечивается, сложением "истории" сигналов, поступивших от Arduino.

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

  • Значения напряжений на выводах светодиода от обоих контроллеров передается в виртуальный COM-порт ПК. Данный порт мы можем мониторить в любой программе, например в Hterm, но я для простоты решил использовать Arduino IDE.

    • Чтобы не "заморачиваться" с лишними символами, напряжение контроллеров выводится без точки, например, "c=45" значит, что напряжение на выводе ЦАП CANNY = 4.5В, соответственно "a=27" значит, что на ЦАП выводе Arduino = 2.7В.

Рассмотрим составной блок "Random (min...max)":

Составной блокСоставной блок

В данном блоге мы используем встроенный ГСЧ и функцию MAP для того чтобы выводить не просто числа от 0 до 65000, а в нужном нам диапазоне. Данный блок можно использовать, как библиотечный элемент и повторно использовать в других схемах. Более подробно о том, как работать с составными функциональными блоками, я писал в этой статье.

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

Заключение

Если вы все собрали верно, то получится примерно такой результат:

Различные варианты поданного на СИД напряженияРазличные варианты поданного на СИД напряжения

Слева направо:

  1. У контроллеров ничья, напряжение примерно равно.

  2. CANNY немножко выигрывает.

  3. CANNY ощутимо выигрывает.

  4. Arduino ощутимо выигрывает.

Пример вывода данных из монитора COM-порта (несвязанный с картинкой выше):

Монитор COM-портаМонитор COM-порта

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

Ну и еще под конец хочу сказать, что чертовски любопытно иногда погрузится в другую парадигму программирования. Я получил удовольствие, когда своими руками из функциональных блоков собрал простенький аналог функции "Random". Сейчас подумываю сделать еще несколько "библиотечных" элементов реализующих распространенные функции, которых порой не хватает среди готовых блоков CANNY и набросать об этом статью.

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

Подробнее..

OpenRPNCalc делаем бескомпромиссный калькулятор

18.06.2021 02:21:22 | Автор: admin
Калькулятор как он есть.Калькулятор как он есть.

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

Но зачем?

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

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

Как оказалось, есть небольшая фирма SwissMicros, которая выпускает неплохие копии старых программируемых калькуляторов Hewlett Packard (HP) на основе современных ARM-процессоров и симулятора Free42 с открытым кодом. Но опять же, это не идеал есть некоторые функции (об этом ниже), которые мне пришлось бы программировать, а запускать программы это совсем не то же самое, что нажать на кнопку.

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

Для разнообразия я решил в кои-то веки сделать проект, который не выглядит слишком колхозно, которым реально можно пользоваться, и может быть даже не слишком прячась от коллег по работе. Хотя это и не первый раз, когда я делаю какую-то электронику, до сих пор я в основном возился с DIP-корпусами, макетками и синей изолентой, а тут сам бог велел сделать что-то посовременнее. Соответственно, я получил море новых впечатлений, разбираясь с многими вещами с нуля (программирование для ARM, пайка SMD, разработка в KiCAD и OpenSCAD, 3D-печать). Готовьтесь, сейчас я ими здесь поделюсь. Вдруг кому-то поможет, или кто из более опытных посоветует что-нибудь дельное.

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

Концепция

Итак, будем делать научный, непрограммируемый, калькулятор, в который при желании можно добавлять новые функции. Как бывший член экипажа лунолёта Кон-Тики, я, конечно, обязан был сделать калькулятор с обратной бесскобочной (она же польская, она же RPN) логикой. Благо, её и программировать легче.Ещё одним преимуществом RPN поделился со мной пользователь с сайта Hackaday: такой калькулятор у вас вряд ли кто попросит попользоваться на время.

Итак, что хотелось мне иметь в идеале в своей машинке:

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

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

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

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

  • Стандартные режимы отображения SCI (с мантиссой и порядком) и ENG (с порядком, кратным трём) и изменяемым количеством значащих цифр мантиссы (3-10). В режиме ENG, к тому же, можно для удобства сделать показ префиксов единиц СИ (m, k, M и т. д.).Диапазона double будет более чем достаточно. SwissMicros делает калькуляторы c quarduple precision (что ещё ждать от швейцарской-то фирмы?), но в нашей немудрёной науке, если в вычислении используется больше шести-семи значащих цифр с вычислением что-то не так.

  • Обратная бесскобочная логика со стеком из 4 элементов (X,Y,Z,T) плюс регистр предыдущего результата (LASTx или X1) как у HP или Б3-34. Есть ещё вариант сделать бесконечный стек, как у старших моделей HP, но пока я ограничился более простым вариантом.

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

Электроника

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

С экраном вопросов нет это будет монохромный ЖКИ дисплей Sharp Memory LCD, как у SwissMicros DM42. Судя по многим отзывам, это практически идеальный дисплей с хорошей контрастностью, очень малой потребляемой мощностью, и управляется по последовательной шине SPI. В нашем случае это будет модуль LS027B7DH01размером 2.7 (размер изображения 60x40 мм) и разрешением 400x240 точек. С таким разрешением можно показывать все 4 регистра стека одновременно, да и для режима вычислений с ошибками это будет полезно.Модуль потребляет всего около 20 мкА от 5В в режиме показываю, но ничего не делаю.

Процессор, недолго думая, я тоже взял из DM42: STM32L476, правда, в корпусе LQFP64 (модификация STM32L476RG). В DM42 стоит тот же процессор в корпусе LQFP100 (100 пинов), но нам не нужны ни внешний Flash, ни SD-карта, так что 64 пина хватит за глаза. Процессор может работать на частоте до 80 МГц, есть 128 кБ оперативки и 1 МБ программного флеша ought to be enough for anybody. Ну и ещё много всяческого добра, которым мы по большей части не будем пользоваться.

С клавиатурой вопросов больше. Многие обозреватели жалуются, что у SwissMicros слишком жёсткая клавиатура, быстро кнопки нажимать неудобно, и вообще ничто не может сравниться с классикой HP. Попробуем найти что-то получше, чем купольные кнопки на DM42. Первые попавшиеся тактовые кнопки с AliExpress мне показались слишком тугими. Порывшись по каталогам, я нашёл самые мягкие и достаточно плоские из тех, которые можно заказать, не особо напрягаясь Panasonic EVQQ2B01W с усилием нажатия 50 г (при том, что обычные кнопки, которые продаются на каждом углу, обычно требуют усилие в 150-200 г).

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

Схема всего девайса элементарная и показана на рисунке выше. Собственно, кроме STM32 в стандартном подключении, клавиатурной матрицы и пары разъемов (один для ЖКИ, другой для программатора) там есть только преобразователь напряжения 3В в 5В для питания ЖКИ на очень экономном чипе TPS61222. STM запитан непосредственно от литиевой батарейки. Не знаю, хорошая ли это идея, или лучше было поставить стабилизированный преобразователь. Кварц для тактирования процессора решил не ставить (можно и встроенным RC генератором обойтись), но на всякий случай поставил часовой кварц.

Кстати, по поводу питания ЖКИ. То, что нарисовано сейчас на схеме, хоть и работает, но не совсем правильно. Как оказалось уже после того, как я развел и заказал плату, преобразователь TPS61222 не полностью отключает выходную цепь от питания при низком уровне сигнала 5V_EN, а только выключает сам преобразователь, оставляя на выходе 3В вместо пяти. Надо внимательнее читать даташиты! Попутно оказалось, что и от трех вольт ЖКИ прекрасно работает, и даже контрастность не страдает. Может быть, в следующей версии платы преобразователь можно просто выкинуть?

Рисовал схему и разводил плату в KiCAD. Почти все элементы там нашлись в стандартной библиотеке, кроме 10-пинового разъёма Molex с шагом 0.5 мм для ЖКИ, его пришлось нарисовать самому по образцу какого-то другого с другим шагом.

С лазерным утюгом я не дружу, поэтому плату заказал на одном из специализированных сайтов (в плате нет никаких тонкостей, так что любой дешёвый PCB-сервис должен с ней справиться). Дисплей Sharp и разная мелочь продаётся на AliExpress, а вот с покупкой процессора STM я, похоже, пал жертвой дефицита чипов. Три китайских продавца меня кинули (причём один сделал вид, что всё выслал, тянул две недели, после чего уверял, что посылку задержала таможня, а сам поднял цену на тот же чип раза в три). К счастью, в один прекрасный момент несколько сотен нужных чипов выбросили на сайте Mouser, из которых я и отхватил несколько штук. На том же Маузере я заказал и кнопки Panasonic, т.к. на Али практически все кнопки noname с непонятно какими характеристиками.

Несмотря на мой изначальный страх, пайка SMD пошла на удивление легко, даже разъём LCD и сам STM32 с ножками с шагом в 0.5 мм паяются без проблем. Оказалось, пора уже было забыть про натуральную сосновую канифоль и перейти на современную бездушную паяльную пасту. Немного больше тренировки потребовала пайка разной мелочи типоразмера 0603 (резисторы, конденсаторы).

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

Прошивка STM32

Как оказалось, найти информацию для того, чтобы начать программирование на STM32 с нуля, не так-то просто, похоже, из-за того, что альтернативных инструментов очень много, они быстро появляются и устаревают. Наверное, в конце концов лучше учиться писать на голом gcc, но для начала я хотел взять какой-нибудь IDE в стиле для чайников с визуальным конфигурированием процессора. В результате я использовал STM32Cube IDE. Я так и не смог добиться, чтобы он работал в Ubuntu, поэтому пришлось ставить ради него целую виртуальную машину с Windows 10.

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

Функции для работы с дисплеем Sharp я писал сам по даташиту, и там всё оказалось очень просто. Система команд дисплея состоит, практически, из 2-х команд. Первая это очистка экрана. Вторая передача массива информации, который состоит из номера строки и 50 байт данных строки.Одна тонкость работы с дисплеем когда он включен, ему нужен постоянный внешний сигнал около 1 Гц для периодического изменения полярности электрического поля на ЖК-матрице. Этот сигнал генерируется по прерыванию от внутреннего таймера STM. При выключенном ЖКИ этот сигнал надо также выключать.

Собственно саму реализацию алгоритма работы калькулятора я сперва отладил на большом компьютере, написав заглушки для функций работы с клавиатурой и дисплеем. STM32L476 поддерживает полную математическую библиотеку gcc, более того, вычисления с плавающей точкой там реализованы в железе, так что всё работает очень быстро. Я понизил частоту работы процессора до 8 МГц, чтобы ограничить максимальный потребляемый ток (который тогда получается около 4 мА при полной нагрузке), при этом никаких видимых задержек при вычислениях не появляется. При меньшей частоте начинает заметно тормозить вывод на экран.

Для прошивки я купил один из китайских клонов программатора/отладчика ST-Link v2, которые продают где угодно за копейки. С ним вышла небольшая проблема: судя по всему, мой экземпляр не умеет делать connect under reset, из-за чего STM в состоянии спячки я программировать не могу. Пришлось предусмотреть в прошивке волшебное сочетание кнопок (Shift+RESET), при котором контроллер не уходит в STOP, а ждёт соединения с программатором. Неприятно, но не смертельно.

Вся прошивка занимает примерно 120 кБ программной памяти. При этом большую часть объёма составляют растровые экранные шрифты (размером от 6x8 до 24x40).

Корпус и клавиатура

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

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

Корпус и клавиатура, нарисованные в OpenSCAD.Корпус и клавиатура, нарисованные в OpenSCAD.

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

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

Корпус получился не очень тонким (полная толщина 12.5 мм), хотя внутри он по большей части пустой. Всё из-за того, что гнездо для батарейки CR2032 довольно высокое (5.5 мм) и с этим сложно что-либо поделать. Для следующей версии надо будет изучить вопрос, существуют ли более тонкие держатели для CR2032, которые к тому же можно установить в вырез на печатной плате. Таким образом можно было бы сэкономить как минимум пару-тройку миллиметров толщины.

Не буду здесь долго описывать сагу про печать корпуса скажу только, что он получился лишь с четвёртой или пятой попытки, при этом пришлось тонко настраивать геометрию моего Ender 3 (перпендикулярность осей X и Y), иначе корпус вело винтом после соединения двух половин. Печатал пластиком PETG.

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

Результат

Вот что в результате получилось. Дисплей в режиме отображения ENG с 6 значащими цифрами и активированным режимом вычислений с неопределенностями.Вот что в результате получилось. Дисплей в режиме отображения ENG с 6 значащими цифрами и активированным режимом вычислений с неопределенностями.

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

  • F, G клавиши shift. Пока в основном задействована только F. G нужна только для обратного направления преобразований (), (), а дальше будет использоваться для новых функций, если таковые появятся.

  • Mode изменение режима отображения чисел (FIX, SCI, ENG).

  • Uncr включение/выключение режима вычислений с неопределённостями (UNCERT).

  • Prec переключение количества значащих цифр мантиссы (от 10 до 3 циклически, с F в обратную сторону).

  • Drop, X<>Y, Rot работа со стеком. LASTx вызов результата предыдущей операции.

  • DR переключение измерения углов между градусами и радианами, D<>R то же с преобразованием значения угла в регистре X.

  • RP, PR перевод между декартовыми и полярными координатами.

  • N(x), N(x-y) работают только в режиме неопределённостей, и выполняют, соответственно, вычисление стат. значимости значения в регистре X (значение, деленное на неопредёленность) и значимости разности значений в регистрах X и Y.

  • (), (), p(zxy) те самые специфические функции, которые мало кому нужны: вычисление псевдобыстроты (pseudorapidity), релятивистского гамма-фактора, вычисление импульса в центре масс двухчастичного распада.

  • /-/ обычное изменение знака числа в регистре X, а переключение между вводом значения и ошибки в режиме UNCERT.

Планы на будущее

В принципе, машинка уже сейчас вполне функциональная, но всегда можно что-то улучшить:

  • Есть ещё резервы в плане уменьшения энергопотребления. Сейчас калькулятор расходует около 50 мкА в режиме со включенным дисплеем, и 40 мкА с выключенным. Как я уже говорил, полностью питание с дисплея сейчас не снимается, хотя надо бы это пофиксить. Кроме того, можно улучшить алгоритм опроса клавиатуры: сейчас, когда калькулятор включен и работает дисплей, процессор не засыпает, пока нажатая кнопка не отпущена, и потребляет при этом около 4 мА. Надо бы здесь тоже задействовать внешние прерывания и режим STOP.

  • Функция сканирования клавиатуры понимает только одну нажатую кнопку за раз. Хотелось бы сделать режим two-key rollover, когда регистрируется кнопка, нажатая до того, как отпущена предыдущая, чтобы кнопки надёжнее срабатывали при быстром наборе.

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

  • Клавиатуре всё ещё далеко до HP и даже до Citizen. Кнопки нажимаются легко, но глубина нажатия всего 0.2 мм это не очень комфортно. Не знаю, можно ли сделать что-то сильно лучше в домашних условиях, не заказывая кастомную мембранную клавиатуру.

  • Что хотелось бы из нового функционала. Более удобное отображение значений с неопределённостью, когда есть ненулевой порядок, в виде (10.1)e-10 вместо теперешнего 1e-10 1e-11. Больше регистров памяти (пока только один). Целочисленный режим с булевыми функциями и переводом между двоичной, десятичной и шестнадцатеричной системами. Новые функции по мере надобности (вычисление распределений Пуассона и хи-квадрат пока не сделал, но это дело техники).

По мере работы над проектом, я выкладываю новости в блог Hackaday.io.

P.S. Спасибо @Boomburumза приглашение и советы.

Подробнее..

Кикбрик фитнес-трекер для ударных видов спорта

17.05.2021 16:18:04 | Автор: admin

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

Деритесь в Подольске, заказывайте производство в Шеньжене!

Видео на английском

Сколько проходит времени между твоим желанием ударить и самим ударом? Сколько ударов ты максимум можешь совершить за 1 минуту? Как долго ты сможешь держать темп один маваши в секунду? То, что нельзя измерить, нельзя улучшить!

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

История

Ранее я писал о том как появилась идея http://personeltest.ru/aways/habr.com/ru/post/397107/

Как с помощью сообщества удалось ее улучшить http://personeltest.ru/aways/habr.com/ru/post/397191/

Какие конкуренты есть у Кикбрика http://personeltest.ru/aways/habr.com/ru/post/398441/

Представляю Вам фитнес-трекер для ударных видов спорта. Гаджет подходит для измерения и тренировки скорости реакции, темпа ударов. Совершаем удар после получения сигнала, видим время нашей реакции, тренируемся для улучшения. Измеряем и увеличиваем количество ударов за определенное время. Режим Метронома для удержания темпа ударов. Режим тренировки комбинации ударов. "Кикбрик" показывает общее время Вашей тренировки, общее количество ударов.

Условно можно сказать, что начал разработку 1 сентября 2016г. Так выглядел первый прототип, собранный за 20 минут.

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

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

Следующая степень миниатюризации

Мы сделали проект в EasyEda и заказали прозводство и smd монтаж в jlcpcb. Идея была в том, что в центре всего приложение. Приложение это некая экосистема, где тренеры могут размещать свои видеоуроки по отработке определенных ударов и к ним программу тренировок с кикбрик. Гаджет в данном случае использовался для передачи сырых данных об ускорении и углах в приложение. Сама плата на TI CC2640 c BTLE.

Мы общими усилиями собрали приложение и устройство. Корпус напечатали.

Видео демонстрация для Хабиба. Мои первые слова на аварском.

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

Были два элемента, которые нас не устраивали в самой плате, мы договорились с производителем о смене гироскопа на MPU6050 и смене ножек подключения. За изменение крепления с меня попросили еще $1200, это решил делать сам.

Прошивку и дизайн я разрабатывал сам. Использовал для графики LVGL.

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

Заказ опытной партии в 100шт в Шеньжень

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

После изготовления PCB и автоматизированного SMD можнтажа идет этап монтажа навесных элементов.

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

Загрузка тестового скрипта для проверки всей периферии.

Готовая плата с элементами размещается в корпус.

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

Процесс зарядки и финального тестирования.

В конце процесса упаковка в индивидуальные боксы и упаковка боксов в общую коробку.

Первая партия в 100шт.

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

Устройство опробовали в секциях каратэ, бокса, кикбоксинга, муай тай, рукопашного боя, мма и тхэквондо. Гаджет отлично подходит для самостоятельной работы с мешком, мотивирует работать дольше и эффективнее. Профессионалы оценили тренировку скорости реакции и темпа. Начинающие и любители получают мотивацию работать с мешком дольше. Особенно в восторге от гаджета дети и подростки. Зарядки хватает на 3-4 тренировки. Есть аналоги датчики под бинты, которые стоят от 300$. Они не тренируют реакцию и темп. Не все используют бинты. Существуют умные груши, которые обладают меньшим функционалом, но стоят более 1200 долларов. Их неудобно заряжать, брать с собой в зал или домой.

Проблема сохранения результатов

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

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

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

Экономика

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

100 устройств с кастомизацией - 320.000 руб.

Доставка и таможенное оформление - 45.000 руб.

Печать и литье в силикон креплений - 5.000 руб.

Карабин, стрейч-лента - 2.000 руб.

Печать этикеток - 2.000 руб.

Затраты 3740 руб./устройство.

Если добавить к себестоимости планируемую прибыль в 1160 руб. - получаем стоимость устройства в 4.900 руб. Почему 1160? Чтобы получилась красивая цифра и именно столько стоят 2 похода в зал мне. Т.е., покупая, гаджет каждый еще и меня мотивировать будет.

Если Вы хотите принять участие в проекте, есть идея или хотите для себя приобрести гаджет, пишите мне в WhatsApp или Telegram+79267031561.

Продвижение

Продемонстрировал гаджет на мероприятии "Инновации в спортивной индустрии" на Красном Октября .

Я вступил в Московский инновационный кластер. Сейчас подал заявку на пилотирование с сетью клубов "Ударник".

Подал заявку на участие в ежегодном конкурсе инновационных проектов премии Мэра Новатор Москвы. Результаты ожидаем 25.05.2021

Вариантов продвижения я вижу несколько:

  • продажа через свой сайт, реклама у тематических блогеров

  • партнерство с известным спортсменом

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

  • выход на кикстартер

  • продажа производителю спортивного оборудования

Есть ли у Вас идеи? Какие тренировки можно было бы ещё делать? Как продавать? Как позиционировать? Может есть знакомые, которые имеют опыт краудфандинга?

Подробнее..

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

Подробнее..

Power-line communication. Часть 3 Основные блоки устройства

25.05.2021 00:13:35 | Автор: admin

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

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

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

Zero cross детектор

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

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

В электросетях с частотой 50 Гц, синусоида напряжения пересекает ноль 100 раз в секунду.

Есть несколько вариантов исполнения ZC детектора. Ниже я покажу пример реализации на оптопаре.

Начнём с конца схемы сначала представим, как сигнал с ZC детектора попадает на контроллер.

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

На место ключа ставим оптрон. Оптрон (оптопара) это простой элемент, в котором с одной стороны светодиод, а с другой фототранзистор.

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

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

Для выпрямления можно использовать smd мостовой выпрямитель.

После выпрямления на фотодиод пойдёт пульсирующее с удвоенной частотой напряжение от 0 до 310 В. Разумеется диод не выдержит такого размаха напряжения, поэтому после мостового выпрямителя поставим сопротивление.

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

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

Из datasheet на PLC817Из datasheet на PLC817

На примере PC817 видно, что максимальный ток, который выдержит светодиод - 50 мА. Максимальный коэффициент передачи при 20 мА. И "замыкать ключ" он будет уже и при >1 мА.

SMD резисторы типоразмера 1210 выдерживают рассеивание до 0.5 Вт мощности. Максимальный постоянный ток, который мы может пропускать при 310 вольт равен 0.5/310 = 0.00161 А. С учетом, того что у нас пульсирующее напряжение, округлим до 0.002 А (2 мА). Этого тока достаточно, чтобы "ключ замыкался". Номинал сопротивления при этом равен 310/0.002 = 155000 Ом. Итог: ставим последовательно три SMD резистора, типоразмером 1210, номиналом 51 кОм каждый.

В итоге, схема ZC детектора выглядит примерно так.

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

Схема согласования сигнальных цепей с линией 220 В

Схема согласования закрывает собой компоненты входной и выходной цепей. Входная и выходная сигнальные цепи обычно выполнены на микросхемах усилителях, которые питаются небольшим постоянным напряжением (3-12 В). Подключить их напрямую к 220 В не получится.

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

Эта часть схемы принимает различный вид в разных datasheet на готовые PLC микросхемы. Опишем минимально работоспособный вариант.

Для первых опытов

Можно взять ферритовое кольцо типа 17,5x8,2x5 М2000Н, есть в любом магазине электроники. Провод МГТФ наматываем сразу 3 обмотки в 20 витков.

Конденсатор плёночный из серии MKP или любой аналогичный, который выдерживает от 220 В переменки (с запасом).

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

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

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

Входная цепь измерение полезного сигнала

Входная цепь должна выполнить как минимум две задачи:

  • отфильтровать грубый входящий сигнал, срезав все лишнее;

  • после этого усилить сигнал до приемлемого уровня, подходящего для измерения и оцифровки с помощью ЦАП микроконтроллера.

Фильтрация

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

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

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

Усиление

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

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

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

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

Ссылки на статьи про операционные усилители и их про каскадное подключение оставил в конце статьи.

Выходная цепь генерация полезного сигнала

Задача выходной цепи фильтровать и усиливать сигнал из ЦАП микроконтроллера.

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

Далее сигнал сглаживается фильтром и отправляется в аналоговую часть схемы (усилитель и схема согласования с 220 В).

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

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

При выборе усилителя разбегаются глаза. Не буду рассказывать про всё многообразие, но подскажу вариант для ленивых, как я. Можно использовать одну из готовых микросхем для усиления аудио сигналов в аудиоплеерах. Мощность у них обычно не большая около 1W.

Гуглить их можно по фразе audio amplifier btl 1w. Но тут нужно учесть, что они обычно рассчитаны на аудио сигналы до 20 кГц, и производитель не рассчитывал, что их будут использовать в PLC модеме. Есть модели, которые хорошо усиливают частоты до 100-150 кГц, и обычно в datasheet об этом не пишут.

Плюсы:

  • они очень удобны тем что там встроенная стабилизация сигнала;

  • есть режим mute - мизерное потребление в режиме простоя;

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

Минусы:

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

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

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

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

Итого

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

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


Полезные ссылки

Общее:

Фильтры:

Операционные усилители:

ZC детекторы:

Схемы согласования с 220 В в доках на PLC микросхемы:

Подробнее..

Раскрашиваем таможенную статистику. Или сколько и каких микросхем ввозят в Россию? (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 МГц и выпущенный Микрочипом.

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

Подробнее..

Перевод Сравнение векторных расширений ARM и RISC-V

20.05.2021 22:11:13 | Автор: admin

Сравнение векторного расширения RISC-V (RVV) и масштабируемого векторного расширения ARM (SVE/SVE2).

Микропроцессоры с векторными командами ожидает большое будущее. Почему? Беспилотные автомобили, распознавание речи, распознавание образов, всё это основано на машинном обучении, а машинное обучение на матрицах и векторах.

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


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

Сейчас мы придумали тысячи умных способов получить большую производительность, будь то создание многоядерных процессоров, внеочередное (out-of-order) исполнение, более совершенное предсказание переходов и SIMD-команды.

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

SIMD-инструкции, в отличие от SISD-инструкций, каждая инструкция (зелёный цвет) обрабатывает множество независимых потоков данных (синий цвет).SIMD-инструкции, в отличие от SISD-инструкций, каждая инструкция (зелёный цвет) обрабатывает множество независимых потоков данных (синий цвет).

SIMD-команды, такие, как Neon, MMX, SSE2 и AVX замечательно сработали в мультимедийных приложениях, в таких вещах, как кодирование видео и т.п. Но нам нужно получить большую производительность во многих областях. Векторные команды предлагают большую гибкость в превращении почти любого цикла в векторные команды. Однако есть много различных способов это сделать.

Я описал векторные команды RISC-V здесь:RISC-V Vector Instructions vs ARM and x86 SIMD.

Позже я описал векторные команды ARM:ARMv9: What is the Big Deal?.

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

Это заставило меня обнаружить, что ARM и RISC-V следуют принципиально разным стратегиям. Стоит написать об этом, потому что это одна из моих любимых тем. Я люблю простые, элегантные и эффективные технологии:The Value of Simplicity.

Векторное расширение RISC-V по сравнению с ARM SVE это элемент элегантности и простоты.

Проблема с масштабируемыми векторными командами ARM (Scalable Vector Instructions, SVE)

В процессе изучения SVE, было неочевидно, почему это так трудно для понимания, но когда я взял книгу по RISC-V и перечитал главу по векторному расширению, это стало ясно.

Честно говоря, ARM является большим шагом вперёд по сравнению с большим сложным и беспорядочным ассемблером Intel x86. Давайте не будем про это забывать. Также мы не можем пройти мимо того факта, что ARM не молодая платформа, и содержит много легаси. Когда мы имеем дело с ARM, у нас есть три различных набора команд: ARM Thumb2, ARM32 и ARM64. Когда вы гуглите руководства и пытаетесь их читать, возникает ряд проблем. Люди не всегда понимают, какой набор команд изучать.

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

Вторая проблема в том, что ARM большой. Система команд содержит свыше 1000 команд. Сравните с базовым набором RISC-V, в котором всего лишь 48 команд. Это означает, что читать ассемблер ARM не так просто. Посмотрим на команду SVE:

LD1D z1.D, p0/Z, [x1, x3, LSL #3]

Здесь делается много. Если у вас есть опыт в ассемблере, вы можете догадаться, что префиксLDозначаетLoaD. Но что означает1D? Вы должны это выяснять. Дальше вы должны выяснить, что означают странные суффиксы имён регистров, такие как .Dand/Z. Дальше вы видите скобки[]. Вы можете догадаться, что они составляют адрес, зачем там странная запись LSL #3, что означает логический сдвиг влево (Logic Shift Left)три раза. Что сдвигается? Все данные? Или только содержимое регистраx3? Это снова нужно смотреть в справочнике.

Команды ARM SVE содержат множество концепций, не являющихся очевидными, от которых голова идёт кругом. Мы сделаем глубокое сравнение, но сначала скажем несколько слов о RISC-V.

Красота векторного набора команд RISC-V

Обзор всех команд векторных расширений RISC-V (RVV) помещается на одной странице. Команд немного, и, в отличие от ARM SVE, они имеют очень простой синтаксис. Вот команда загрузки вектора в RISC-V:

VLD v0, x10

Команда загружает векторный регистрvданными, находящимися по адресу, который хранится в обычном целочисленном регистреx10. Но сколько данных загружается? В наборе команд SIMD, таком, как ARM Neon это определяется именем векторного регистра.

LD1 v0.16b, [x10]  # Load 16 byte values at address in x10

Есть другой способ сделать это. Такой же результат достигается таким образом:

LDR d0, [x10]    # Load 64-bit value from address in x10

Эта команда загружает младшую 64-битную часть 128-битного регистраv0. Для SVE2 у нас есть другой вариант:

LD1D z0.b, p0/z, [x10] # Load ? number of byte elementsLD1D z0.d, p0/z, [x10] # Load double word (64-bit) elements

В этом случае регистр предикатаp0определяет в точности, сколько элементов мы загружаем. Еслиp0 = 1110000, мы загружаем три элемента.v0 это 128-битная младшая частьz0.

Регистры имеют одинаковые имена?

Причина этому в том, что регистры d,vиz находятся в одной ячейке. Давайте поясним. У вас есть блок памяти, называемый регистровый файл в каждом CPU. Или, если быть более точным, в CPU расположено много регистровых файлов. Регистровый файл, это память, в которой расположены регистры. Вы не можете получить доступ к ячейкам памяти в регистровом файле, так же как в обычной памяти, вместо этого вы обращаетесь к области памяти, используя имя регистра.

ARM floating point registers are overlapping in the same register file (memory in CPU holding registers).ARM floating point registers are overlapping in the same register file (memory in CPU holding registers).

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

  • z3 регистр SVE2 переменной длины.

  • v3 младшие 128 бит z3. Регистр Neon.

  • d3 младшие 64 битаv3.

  • s3 младшие 32 битd3

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

  • x0-x31 скалярные целочисленные регистры.

  • f0-f31 скалярные регистры с плавающей точкой.

  • v0-v31 векторные регистры. Длина не зависит от ISA.

Сложность векторных команд ARM

Я только поцарапал поверхность векторных команд ARM, потому что их очень много. Просто найти, что делает команда загрузки Neon и SVE2, занимает много времени. Я просмотрел много документации ARM и записей в блогах. Сделать то же самое для RISC-V очень просто. Практически все команды RISC-V можно разместить на двойном листе бумаги. У него есть только три команды загрузки вектораVLD,VLDSиVLDX.

Я не могу сказать, сколько этих команд у ARM. Их очень много, и я не собираюсь становиться профессионалом по программированию ARM. на ассемблере.

Как ARM и RISC-V обрабатывают вектора переменной длины

Это довольно интересный вопрос, так как ARM и RISC-V используют существенно различные подходы и я считаю, что простота и гибкость решения RISC-V просто блестящая.

Вектора переменной длины в RISC-V

Чтобы начать обработку векторов, вы делаете две вещи:

  • VSETDCFG Vector SET Data ConFiGuration. Устанавливает битовый размер каждого элемента, тип, который может быть вещественным, знаковым целым или беззнаковым целым. Также конфигурация определяет, сколько векторных регистров используется.

  • SETVL SET Vector Length. Устанавливает, сколько элементов содержит вектор. Максимальное количество элементов, которое вы не можете превысить MVL(max vector length).

Регистровый файл RISC-V может быть скофигурирован так, чтобы иметь меньше 32 регистров. Может быть, например, 8 регистров или 2 регистра большего размера. Регистры могут занимать весь объём регистрового файла.Регистровый файл RISC-V может быть скофигурирован так, чтобы иметь меньше 32 регистров. Может быть, например, 8 регистров или 2 регистра большего размера. Регистры могут занимать весь объём регистрового файла.

И здесь всё становится интереснее. В отличие от ARM SVE, я могу разделить файл векторных регистров именно так, как я хочу. Пусть регистровый файл имеет размер 512 байт. Я могу теперь объявить, что я хочу иметь два векторных регистра, по 256 байт каждый. Далее я могу сказать, что я хочу использовать 32-битные элементы, другими словами, элементы по 4 байта. Получаем следующее:

Два регистра: 512 байт / 2 = 256 байт на регистр256 байт / 4 байта на элемент = 128 элементов

Это означает, что я могу складывать или умножать 128 элементов просто одной командой. В ARM SVE вы этого сделать не можете. Количество регистров фиксировано, и память аллоцирована для каждого регистра. И RISC-V, и ARM позволяют вам использовать максимум 32 векторных регистра, но RISC-V позволяет вам отключать регистры и отдавать используемую ими память оставшимся регистрам, увеличивая их размер.

Вычисление максимальной длины вектора (Max Vector Length, MVL)

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

Когда программист использует VSETDCFG, чтобы установить типы элементов и количество используемых регистров, процессор использует эту информацию, чтобы вычислить максимальную длину вектора Max Vector Length (MVL).

LI        x5, 2<<25  # Load register x5 with 2<<25VSETDCFG  x5         # Set data configuration to x5

В примере выше происходят две вещи:

  • Включаем два регистра:v0иv1.

  • Устанавливаем тип элементов в 64-битные вещественные числа. Давайте сравним это с ARM Neon, в котором каждый регистр имеет длину 128 бит. Это означает, что Neon может обрабатывать два таких числа параллельно. Но в RISC-V 16 таких регистров можно объединить в один. Это позволяет обрабатывать 32 значения параллельно.

На самом деле это не буквально так. За сценой у нас есть конечное число вещественных умножителей, блоков АЛУ и т.п., что ограничивает число параллельных операций. Однако всё это уже детали реализации.

Итак, мы получили значениеMVL, равное 32. Разработчик не должен напрямую знать это число. Команда SETVLработает так:

SETVL rd, sr  ; rd  min(MVL, sr), VL  rd

Если вы попытаетесь установить Vector Length (VL)в значение 5, это сработает. Однако, если вы попытаетесь установить значение 60, вы получите вместо этого значение 32. Итак, величина Max Vector Length (MVL) важна, она не фиксирована конкретным значением при изготовлении процессора. Она может быть вычислена исходя из конфигурации (типа элементов и количества включенных регистров).

Вектора переменной длины в ARM

В ARM вы не устанавливаете длину вектора явным образом. Вместо этого вы устанавливаете длину вектора косвенно, используя предикатные регистры. Они являются битовыми масками, которыми вы включаете и выключаете элементы в векторном регистре. Регистры предикатов также существуют в RISC-V, но не имеют центральной роли, как в ARM.

Чтобы получить эквивалентSETVLна ARM , используйте командуWHILELT, что является сокращением отWhile Less Than:

WHILELT p3.d, x1, x4

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

i = 0while i < M   if x1 < x4      p3[i] = 1   else      p3[i] = 0  end  i += 1  x1 += 1end

Концептуально, мы переворачиваем биты в регистре предиката p3в зависимости от того, меньше лиx1,чемx4. В данном случаеx4содержит длину вектора. Еслиp3выглядит так, то длину вектора можно считать равной 3.

1110000

То есть вектор переменной длины реализуется за счёт того, что все операции используют предикат. Рассмотрим эту операцию сложения. Представьте, чтоv0[p0]извлекает из v0только те элементы, для которыхp0истинно.

ADD v4.D, p0/M, v0.D, v1.D ; v4[p0]  v0[p0] + v1[p0]

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

Пример кода DAXPY

Рассмотрим сейчас, как функции C могут быть реализованы различными векторными командами:

void daxpy(size_t n, double a, double x[], double y[]) {        for (int64_t i = 0; i < n; ++i) {                y[i] = x[i] * a + y[i];        }}

Почему такое странное имя daxpy? Это простая функция в библиотеке линейной алгебры BLAS, популярной в научной работе. В BLAS эта функция называется daxpyи она очень популярна для демонстрации примеров работы разнообразных SIMD и векторных команд. Она реализует такую формулу:

aX + Y

гдеa скаляр, а XиY вектора. Без векторных команд нужно было бы обрабатывать все элементы в цикле. Но с умным компилятором, эти команды могут быть векторизованы в код, который выглядит на RISC-V так, как показано ниже. Комментарии показывают, какой регистр какой переменной соответствует:

daxpy(size_t n, double a, double x[], double y[]) n - a0  int   register (alias for x10) a - fa0 float register (alias for f10)  x - a1  (alias for x11)  y - a2  (alias for x12

Код:

LI       t0, 2<<25    VSETDCFG t0             # enable two 64-bit float regsloop:    SETVL  t0, a0           # t0  min(mvl, a0), vl  t0    VLD    v0, a1           # load vector x    SLLI   t1, t0, 3        # t1  vl * 2 (in bytes)    VLD    v1, a2           # load vector y    ADD    a1, a1, t1       # increment pointer to x by vl*8    VFMADD v1, v0, fa0, v1  # v1 += v0 * fa0 (y = a * x + y)    SUB    a0, a0, t0       # n -= vl (t0)    VST    v1, a2           # store Y    ADD    a2, a2, t1       # increment pointer to y by vl*8    BNEZ   a0, loop         # repeat if n != 0    RET    

Это код, скопированный из примера. Отметим, что мы не используем именаf иxдля целочисленных и вещественных регистров. Чтобы помочь разработчикам лучше помнить соглашения, ассемблер RISC-V определяет ряд псевдонимов. Например, аргументы функции передаются в регистрахx10-x17. Но нет необходимости запоминать эти номера, для аргументов предусмотрены псевдонимыa0-a7.

t0-t6 псевдонимы регистров временных переменных. Они не сохраняются между вызовами.

Для сравнения мы приведём ниже код ARM SVE. Пометим, какой регистр какую переменную содержит.

daxpy(size_t n, double a, double x[], double y[]) n - x0  register a - d0  float register x - x1  register  y - x2  register i - x3  register for the loop counter

Код:

daxpy:        MOV z2.d, d0            // a        MOV x3, #0              // i        WHILELT p0.d, x3, x0    // i, nloop:        LD1D z1.d, p0/z, [x1, x3, LSL #3] // load x        LD1D z0.d, p0/z, [x2, x3, LSL #3] // load y        FMLA z0.d, p0/m, z1.d, z2.d        ST1D z0.d, p0, [x2, x3, LSL #3]        INCD x3                 // i        WHILELT p0.d, x3, x0    // i, n        B.ANY loop        RET

Код ARM немного короче, так как команды ARM делают больше, чем одно действие. Это является причиной того, что код RISC-V гораздо проще читать. Команды в RISC-V делают что-то одно, и не требуют специального сложного синтаксиса. Такая простая вещь, как загрузка векторного регистра в ARM выглядит сложно:

LD1D z1.d, p0/z, [x1, x3, LSL #3]

Часть в квадратных скобках вычисляет адрес, из которого происходит загрузка:

[x1, x3, LSL #3] = x1 + x3*2 = x[i * 8]

Итак, здесь видно, чтоx1представляет базовый адрес переменнойx.x3 счётчикi. Сдвигом влево на три бита мы умножаем на 8, то есть на количество байт в 64-битном вещественно числе.

Заключение

Как начинающий в векторном кодинге, я должен сказать, что ARM переусложнён. Это не значит, что ARM плохой. Я также изучал систему команд Intel AVX, и она в 10 раз хуже. Я совершенно определённо не хочу тратить время на изучение AVX, принимая во внимание, сколько усилий отняли SVE и Neon.

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

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

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

Люди могут поспорить, что ARM или Intel или что-то ещё проще, потому что по ним больше книг и больше ресурсов. Ничего подобного! Я могу сказать вам на своём собственном опыте, что документация часто представляет собой препятствие, а не помощь. Это означает, что вам нужно раскопать больше материала. Вы найдёте много противоречий, корни которых лежат в устаревших принципах работы.

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

Подробнее..

Stm32 USB на шаблонах C. Продолжение. Делаем CDC

30.05.2021 20:12:29 | Автор: admin

Продолжаю разработку полностью шаблонной библиотеки под микроконтроллеры Stm32, в прошлой статье рассказал об успешной (почти) реализации HID устройства. Еще одним популярным классом USB является виртуальный COM-порт (VCP) из класса CDC. Популярность объясняется тем, что обмен данными осуществляется аналогично привычному и простому последовательному протоколу UART, однако снимает необходимость установки в устройство отдельного преобразователя.

Интерфейсы

Устройство класса CDC должно поддерживать два интерфейса: интерфейс для управления параметрами соединения и интерфейс обмена данными.

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

template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Ep0, typename _Endpoint, typename... _Functionals>class CdcCommInterface : public Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::Comm, _SubClass, _Protocol, _Ep0, _Endpoint>{  using Base = Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::Comm, _SubClass, _Protocol, _Ep0, _Endpoint>;  static LineCoding _lineCoding;  ...

В базовом случае интерфейс должен поддерживать три управляющих (setup) пакета:

  • SET_LINE_CODING: установка параметров линии: Baudrate, Stop Bits, Parity, Data bits. Некоторые проекты, на которые я ориентировался (основным источников вдохновения стал этот проект), игнорируют данный пакет, однако в этом случае некоторые терминалы (например, Putty), отказываются работать.

  • GET_LINE_CODING: обратная операция, в ответ на эту команду устройство должно вернуть текущие параметры.

  • SET_CONTROL_LINE_STATE: установка состояния линии (RTS, DTR и т.д.).

Код обработчика setup-пакетов:

switch (static_cast<CdcRequest>(setup->Request)){case CdcRequest::SetLineCoding:  if(setup->Length == 7)  {    // Wait line coding    _Ep0::SetOutDataTransferCallback([]{      memcpy(&_lineCoding, reinterpret_cast<const void*>(_Ep0::RxBuffer), 7);      _Ep0::ResetOutDataTransferCallback();      _Ep0::SendZLP();    });    _Ep0::SetRxStatus(EndpointStatus::Valid);  }  break;case CdcRequest::GetLineCoding:  _Ep0::SendData(&_lineCoding, sizeof(LineCoding));  break;case CdcRequest::SetControlLineState:  _Ep0::SendZLP();  break;default:  break;}

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

static uint16_t FillDescriptor(InterfaceDescriptor* descriptor){  uint16_t totalLength = sizeof(InterfaceDescriptor);    *descriptor = InterfaceDescriptor {    .Number = _Number,    .AlternateSetting = _AlternateSetting,    .EndpointsCount = Base::EndpointsCount,    .Class = DeviceAndInterfaceClass::Comm,    .SubClass = _SubClass,    .Protocol = _Protocol  };  uint8_t* functionalDescriptors = reinterpret_cast<uint8_t*>(descriptor);  ((totalLength += _Functionals::FillDescriptor(&functionalDescriptors[totalLength])), ...);  EndpointDescriptor* endpointDescriptors = reinterpret_cast<EndpointDescriptor*>(&functionalDescriptors[totalLength]);  totalLength += _Endpoint::FillDescriptor(endpointDescriptors);  return totalLength;}

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

template <uint8_t _Number, uint8_t _AlternateSetting, uint8_t _SubClass, uint8_t _Protocol, typename _Ep0, typename _Endpoint>class CdcDataInterface : public Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::CdcData, _SubClass, _Protocol, _Ep0, _Endpoint>{  using Base = Interface<_Number, _AlternateSetting, DeviceAndInterfaceClass::CdcData, _SubClass, _Protocol, _Ep0, _Endpoint>;  ...

Поскольку мои познания в CDC-устройствах весьма небольшие, из просмотренных примеров я сделал вывод, что управляющий интерфейс почти всегда одинаковый и содержит 4 функциональности: Header, CallManagement, ACM, Union, поэтому добавил упрощенный шаблон интерфейса:

template<uint8_t _Number, typename _Ep0, typename _Endpoint>using DefaultCdcCommInterface = CdcCommInterface<_Number, 0, 0x02, 0x01, _Ep0, _Endpoint, HeaderFunctional, CallManagementFunctional, AcmFunctional, UnionFunctional>;

Применение разработанных классов

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

using CdcCommEndpointBase = InEndpointBase<1, EndpointType::Interrupt, 8, 0xff>;using CdcDataEndpointBase = BidirectionalEndpointBase<2, EndpointType::Bulk, 32, 0>;using EpInitializer = EndpointsInitializer<DefaultEp0, CdcCommEndpointBase, CdcDataEndpointBase>;using Ep0 = EpInitializer::ExtendEndpoint<DefaultEp0>;using CdcCommEndpoint = EpInitializer::ExtendEndpoint<CdcCommEndpointBase>;using CdcDataEndpoint = EpInitializer::ExtendEndpoint<CdcDataEndpointBase>;using CdcComm = DefaultCdcCommInterface<0, Ep0, CdcCommEndpoint>;using CdcData = CdcDataInterface<1, 0, 0, 0, Ep0, CdcDataEndpoint>;using Config = Configuration<0, 250, false, false, CdcComm, CdcData>;using MyDevice = Device<0x0200, DeviceAndInterfaceClass::Comm, 0, 0, 0x0483, 0x5711, 0, Ep0, Config>;

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

template<>void CdcDataEndpoint::HandleRx(){  uint8_t* data = reinterpret_cast<uint8_t*>(CdcDataEndpoint::RxBuffer);  uint8_t size = CdcDataEndpoint::RxBufferCount::Get();  if(size > 0)  {    if(data[0] == '0')    {      Led::Clear();      CdcDataEndpoint::SendData("LED is turn off\r\n", 17);    }    if(data[0] == '1')    {      Led::Set();      CdcDataEndpoint::SendData("LED is turn on\r\n", 16);    }  }  CdcDataEndpoint::SetRxStatus(EndpointStatus::Valid);}

Отладка и тестирование

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

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

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

Сначала отфильтровать по заведомо известному значению. Например, по значению PID, которое присутствует в ответе устройства на запрос GET_DEVICE_DESCRIPTOR. Фильтр: "usb.idProduct == 0x5711". Это позволит быстро определить адрес устройства.

Далее отфильтровать по адресу устройства с помощью оператора contains. Дело в том, что отображаемый адрес состоит из трех частей, последняя из которых является номером конечной точки (можно, конечно, перечислить все адреса). Фильтр: "usb.addr contains "1.19"".

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

Проблема с usbpcap

Для большей мобильности завел себе внешний SSD, на котором установлена Windows 10 To Go (Windows, предназначенная для установки на внешние носители). Хотя Microsoft вроде отказалась от поддержки этой технологии, в целом все работает. Прихожу с диском в новое место, гружусь с него, система подтягивает драйвера и все нормально (и быстро) работает.

Однажды Windows просто не загрузилась с синим экраном "inaccessible boot device". Потратил целые выходные, восстановить так и не смог, пришлось все переустановить. Через некоторое время та же проблема и снова потраченные на переустановку выходные. Спустя пару дней система опять не грузится, начал вспоминать и анализировать, что я такого делал. Выяснил, что проблема возникала после установки как раз WireShark с usbpcap. На одном из форумов наткнулся на сообщение от пользователя, который жаловался на проблему с мышкой/клавиатурой после установки usbpcap. Снес через LiveCD драйвер и Windows запустилась. Не уверен на 100%, но предположение такое: при запуске компьютера Windows начинается загружаться, подгружает драйвера usbpcap, тот блокирует USB, система дальше грузиться не может и падает в BSOD. Очень неочевидное поведение, жаль потраченного времени.

Тестировал написанный код в программе Terminal v1.9b, на скриншоте приведен результат отправки на устройство сообщений "0" и "1".

Полный код примера можно посмотреть в репозитории. Пример протестирован на STM32F072B-DISCO. Как и в случае с HID, громоздкая библиотека (особенно менеджер конечных точек) сильно облегчили реализацию поддержки CDC, на все ушел примерно полный день. Далее планирую добавить еще класс Mass Storage Device, и на этом, наверно, можно остановиться. Приветствую вопросы и замечания.

Подробнее..

Лаконичная реализация конечных автоматов в Matlab, Octave, C

16.06.2021 18:22:08 | Автор: admin

Актуальность


Конечные автоматы (finite state machines, fsm) штука полезная. Особенно они могут быть востребованы в средах, где в принципе нет развитой многозадачности (например, в Octave, который является в значительной степени бесплатным аналогом Matlab) или в программах для микроконтроллеров, где не используется по каким-то причинам RTOS. До недавнего времени у меня не получалось лаконично описать конечный автомат, хотя и очень хотелось это сделать. Лаконично, т.е. без воды, без создания лишних классов, структур данных, и т.д. Сейчас это, кажется, получилось и я спешу поделиться своей находкой. Возможно, я изобрёл велосипед, но возможно также, что кому-нибудь такой велосипед окажется полезен.


Начальные сведения


Конечный автомат задаётся:
  • набором состояний
  • набором событий
  • таблицей переходов (т.е. в каком состоянии по какому событию что делается и в какое новое состояние осуществляется переход)


Цель, которая стояла передо мной


Есть императивный язык, я буду рассматривать Octave, но это может быть и Matlab и C, например. Этот язык поддерживает:
  • функции
  • указатели на функции
  • то, что обычно поддерживают императивные языки (циклы, условные операторы и т.д.)


Хочется, чтоб базовые понятия языка (функции, структуры данных, массивы или что-то ещё) каким-то элегантным образом соответствовали тому, что нужно при реализации FSM. Профит в том, что
  • код будет самодокументированным
  • Doxygen или другие утилиты для анализа кода и генерации документации по коду будут давать дополнительную пользу


Описание идеи


  • Поведение внутри состояния должно описываться функцией. Поэтому функция хороший кандидат для того, чтоб её имя соответствовало состоянию.
  • Событие должно детектироваться тоже функцией, поэтому и для названий событий тоже можно использовать функции
  • Таблицу переходов можно задавать в либо в виде структуры данных, либо в виде switch/case-выражений внутри состояний


В чём проблема задания таблицы переходов в виде структуры данных?
  • Таблица может быть достаточно большой и сложной. В этом случае структура данных перестанет влезать в экран и поддержка такой таблицы будет не такой уж и удобной.
  • Структура данных требует какого-то объекта в памяти. Это дополнительное неудобство.
  • Структура данных требует специального её конструирования (скорее всего пошагового) это делает структуру программы более красивой, но анализировать такую машину состояний потом будет не так-то удобно.


Поэтому здесь я буду использовать switch/case инструкцию.

Единственной структурой данных будет переменная, где будет храниться состояние автомата.
Сами состояния будут идентифицироваться хэндлерами функций (function handlers), которые будут обрабатывать поведение машины в этом состоянии. Например:

function [new_state  data] = state_idle(data)    if data.block_index == 10        new_state = @state_stop;    else        % do something        data.block_index = data.block_index + 1;        printf('block_index = %d\n', data.block_index);    endendfunction [new_state data] = state_stop(data)    % set break flag    data.stop= 1;endfsm_state = @state_idle;data = struct();data.block_index = 0;data.stop = 0;while (1)    [fsm_state data] = fsm_state(data)    if data.stop        break;    endend


В этом коде вся идея, собственно, и описана. В Си, вместо хэндлера функции будет указатель на функцию, всё остальное останется так же.

Пример из жизни :)


В качестве примера я реализовал на Octave игру Life, Джона Конвея. Если сконфигурировать её в режиме 100 х 100, то она будет симулировать работу 10 000 конечных автоматов и при этом работает она достаточно эффективно. В простейшем варианте (без событий), код для игры выглядит следующим образом:

function [new_state] = state_alive(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));    alive_count -= 1;    if (alive_count == 2) || (alive_count == 3)        new_state = @state_alive;    else        new_state = @state_dead;    endendfunction [new_state] = state_dead(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));        if (alive_count == 3)        new_state = @state_alive;    else        new_state = @state_dead;    endend% main scriptaddpath('fsm_life')debug_on_error(1)size_x = 30;size_y = 30;init_alive_percentage = 30;% initialization selection:%init = 'random';%init = 'cycle';init = 'glider';field = cell(size_y, size_x);[field{:}] = deal(@state_dead);switch (init)case 'random'    init_alive_count = round((size_x * size_y) * init_alive_percentage / 100);    for n=(1:init_alive_count)        x = floor((size_x-0.0000001)*rand())+1;        y = floor((size_y-0.0000001)*rand())+1;        field{y,x} = @state_alive;    endcase 'cycle'    field{2,1} = @state_alive;    field{2,2} = @state_alive;    field{2,3} = @state_alive;case 'glider'    field{1,3} = @state_alive;    field{2,3} = @state_alive;    field{3,3} = @state_alive;    field{3,2} = @state_alive;    field{2,1} = @state_alive;endprintf("Initial distribution:\n");cellfun(@(x)x == @state_alive, field)% simulationfor step = (1:100)    field_new = cell(size(field));    for x=(1:size_x)        for y=(1:size_y)            x_min = max(x-1, 1);            x_max = min(x+1, size_x);            y_min = max(y-1, 1);            y_max = min(y+1, size_y);            neighbours = field(y_min:y_max, x_min:x_max);            field_new{y,x} = field{y,x}(neighbours);        end    end    field = field_new;    printf('Distribution after step %d\n', step );    cellfun(@(x)x == @state_alive, field)    figure(1); imagesc(cellfun(@(x)x == @state_alive, field));    pause(0.05);end


Если хочется больше самодокументируемости и явного определения событий, тогда к двум функциям, отвечающим за состояния, добавится ещё 3 функции, отвечающие за события:
function event = event_die(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));    alive_count -= 1;    if (alive_count == 2) || (alive_count == 3)        event = '';    else        event = 'die';    endendfunction event = event_spawn(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));    if (alive_count == 3)        event = 'spawn';    else        event = '';    endendfunction event = event_survive(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));    alive_count -= 1;    if (alive_count == 2) || (alive_count == 3)        event = 'survive';    else        event = '';    endendfunction [new_state] = state_alive(neighbours)    event = '';    event = [event, event_die(neighbours)];    event = [event, event_survive(neighbours)];    switch event    case 'die'        new_state = @state_dead;    case 'survive'        new_state = @state_alive;    otherwise        msg = sprintf('Unknown event: %s\n', event);        error(msg);    endendfunction [new_state] = state_dead(neighbours)        event = event_spawn(neighbours);        switch event    case 'spawn'        new_state = @state_alive;    case ''        new_state = @state_dead;    otherwise        msg = sprintf('Unknown event: %s\n', event);        error(msg);    endend


Основной скрипт в этом случае останется тем же самым.
Подробнее..

Предельная скорость USB на STM32F103, чем она обусловлена?

24.05.2021 14:14:07 | Автор: admin
У данной статьи тяжёлая история. Мне надо было сделать USB-устройства, не выполняющие никакой функции, но работающие на максимальной скорости. Это были бы эталоны для проверки некоторых вещей. HS-устройство я сделал на базе ПЛИС и ULPI, загрузив туда прошивку на базе проекта Daisho. Для FS-устройства, разумеется, была взята голубая пилюля. Скорость получалась смешная. Прямо скажем, черепашья скорость.



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

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

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

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

Подготовка проекта STM32


Создаём проект


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

Создаём новый проект для STM32F103C8Tx. Добавляем туда USB-Device.



Теперь, когда есть USB, добавляем CDC-устройство. При этом заменим ему VID и PID. Дело в том, что у меня есть inf Файл, который ставит драйвер winusb именно на эту пару идентификаторов. При работе через драйвер usbser скорость была ещё ниже. Я решил исключить всё, что может влиять. Буду замерять скорость без каких-либо прослоек.



Теперь добавляем RCC для работы с тактовыми сигналами:



После всего этого (добавили USB и добавили RCC) можно и тактовые частоты настроить, но сначала спасём себя от самоотключающегося блока отладки. Он спрятан надёжно! Вот так сейчас всё выглядит по умолчанию:



А вот так надо



Прекрасно! Теперь можно настроить тактовые частоты. Я всегда это делаю опытным путём. Системная частота должна стать 72 МГц, а частота USB 48 МГц. Это я в состоянии запомнить. Остальное каждый раз заново вывожу.



Ну всё. Для тестового проекта настроек, вроде, достаточно. Заполняем свойства проекта и сохраняем. Лично я в формате MDK ARM. Он же Кейл. Мне так проще.

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

Донастраиваем проект в среде разработки


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



Дописываем код проекта


Наш проект должен просто принимать данные из USB и И всё! Принимать, принимать, принимать! Не будем тратить время на какую-то обработку этих данных. Просто приняли и забыли, приняли и забыли. Обработчик события данные приняты в типовом CDC проекте живёт здесь:



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

Подготовка проекта под Windows


Вариант честной работы с UART мы опустим. Дело в том, что совсем скоро мы будем искать причины тормозов. А вдруг они вызваны драйвером usbser.sys? Нет. Мы возьмём проверенный временем драйвер winusb и будем работать с ним через библиотеку libusb. Кому нравится Linux сможет работать через эту же библиотеку там. Мы тренировались работать с нею в этой статье. А в этой учились работать с нею в асинхронном режиме.

Сначала я вёл работу через блокирующие функции, так как их написать было проще. Мало того, черновые замеры, которые я делал ещё до начала работы над текстом, были вполне красивые. Это ещё не всё, первая метрика, снятая для статьи, тоже была прекрасна и полностью отражала черновые результаты! Потом что-то случилось. График стал каким-то удивительным, правда, чуть ниже я эту удивительность объясню. При работе с блоками меньше чем 64 байта программа съедала 25% процессорного времени, а именно на блоке 64 байта был излом. Мне казалось, что кто-то обязательно напишет в комментариях, что сделай я всё на асинхронных функциях, всё станет намного лучше. В итоге, я взял и всё переписал на асинхронный вариант. Процент потребления процессорного времени на малых блоках действительно изменился. Теперь программа потребляет 28% вместо двадцати пяти Цифры скоростей же не изменились Но асинхронная работа более правильная сама по себе, так что я покажу именно её. Вся теория уже рассматривалась мною в тех статьях про libusb.

Я завожу всё ту же вспомогательную структуру:
    struct asyncParams    {        uint8_t* pData;        uint32_t dataOffset;        uint32_t dataSizeInBytes;        uint32_t transferLen;        uint32_t actualTranfered;        QElapsedTimer timer;        quint64       timerAfter;    };    asyncParams m_asyncParams;

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

Ну, и указатели на объекты передача имеются, куда же без них:
    static const int m_nTransfers = 32;    libusb_transfer* m_transfers [m_nTransfers]; 

Функция обратного вызова отличается от описанной в предыдущих статьях как раз тем, что она считывает показание таймера, если передавать больше нечего. Это произойдёт не единожды, а для каждой из передач (тридцати двух в случае мелких блоков, если блоки крупные их будет меньше, но всё равно не одна). Но на самом деле, это не страшно. Мы это значение будем анализировать только после последнего вызова этой функции. В остальном там всё то же, что и раньше, так что просто покажу код, не объясняя его. Объяснения все были в предыдущих статьях.
void MainWindow::WriteDataTranfserCallback(libusb_transfer *transfer){    MainWindow* pClass = (MainWindow*) transfer->user_data;    switch (transfer->status )    {    case LIBUSB_TRANSFER_COMPLETED:        pClass->m_asyncParams.actualTranfered += transfer->length;        // Still need transfer data        if (pClass->m_asyncParams.dataOffset < pClass->m_asyncParams.dataSizeInBytes)        {            transfer->buffer = pClass->m_asyncParams.pData+pClass->m_asyncParams.dataOffset;            pClass->m_asyncParams.dataOffset += pClass->m_asyncParams.transferLen;            libusb_submit_transfer(transfer);        } else        {            pClass->m_asyncParams.timerAfter = pClass->m_asyncParams.timer.nsecsElapsed();        }        break;/*    case LIBUSB_TRANSFER_CANCELLED:    {        pClass->m_cancelCnt -= 1;    }*/    default:        break;    }}

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

Ну, а параметр blockSize у функции это я в своих статьях уже набил оскомину высказыванием, что при работе с USB скорость зависит от размера блока. До определённого значения она ниже нормальной. Это связано с тем, что хост посылает пакеты медленнее, чем их может обработать устройство. Поэтому я всегда строю графики и смотрю, где они входят в насыщение. Сегодня я буду делать то же самое. Правда, сегодня график в дополнение к банальному росту, имеет непривычную для меня форму, что и сподвигло меня на переделку программы с блокирующего на асинхронный режим. Итак, функция, измеряющая скорость, выглядит так:
Её текст я скрыл под катом.
quint64 MainWindow::MeasureSpeed2(uint32_t totalSize, uint32_t blockSize, uint32_t avgCnt){    std::vector<qint64> gist;    gist.resize(avgCnt);    QByteArray data;    data.resize(totalSize);    m_asyncParams.dataSizeInBytes = totalSize;    m_asyncParams.transferLen = blockSize;    uint32_t nTranfers = m_nTransfers;    if (totalSize/blockSize < nTranfers)    {        nTranfers = totalSize/blockSize;    }    for (uint32_t i=0;i<avgCnt;i++)    {        m_asyncParams.dataOffset = 0;        m_asyncParams.actualTranfered = 0;        m_asyncParams.pData = (uint8_t*)data.constData();        // Готовим структуры для передач        for (uint32_t i=0;i<nTranfers;i++)        {            m_transfers[i] = libusb_alloc_transfer(0);            libusb_fill_bulk_transfer (m_transfers[i],m_usb.m_hUsb,0x02,//,0x01,                                       m_asyncParams.pData+m_asyncParams.dataOffset,m_asyncParams.transferLen,WriteDataTranfserCallback,                                            // No need use timeout! Let it be as more as possibly                                       this,0x7fffffff);            m_asyncParams.dataOffset += m_asyncParams.transferLen;        }        m_asyncParams.timerAfter = 0;        m_asyncParams.timer.start();        for (uint32_t i=0;i<nTranfers;i++)        {            int res = libusb_submit_transfer(m_transfers[i]);            if (res != 0)            {                qDebug() << libusb_error_name(res);            }        }        timeval tv;        tv.tv_sec = 0;        tv.tv_usec = 500000;        while (m_asyncParams.actualTranfered < totalSize)        {            libusb_handle_events_timeout (m_usb.m_ctx,&tv);        }        quint64 size = totalSize;        size *= 1000000000;        gist [i] = size/m_asyncParams.timerAfter;    }    for (uint32_t i = 0;i<nTranfers;i++)    {        libusb_free_transfer(m_transfers[i]);        m_transfers[i] = 0;    }    qint64 avgSpeed = 0;    for (uint32_t i=0;i<avgCnt;i++)    {        avgSpeed += gist [i];    }    avgSpeed /= avgCnt;    if (avgCnt < 4)    {        return avgSpeed;    }    for (uint32_t i=0;i<avgCnt;i++)    {        if (gist [i] < (avgSpeed * 3)/4)        {            gist [i] = 0;        }        if (gist [i] > (avgSpeed * 5)/4)        {            gist [i] = 0;        }    }    avgSpeed = 0;    int realAvgCnt = 0;    for (uint32_t i=0;i<avgCnt;i++)    {        if (gist[i]!= 0)        {            avgSpeed += gist [i];            realAvgCnt += 1;        }    }    if (realAvgCnt == 0)    {        return 0;    }    return avgSpeed/realAvgCnt;}


Сборку статистики в файл csv я делаю так:
void MainWindow::on_m_btnWriteStatistics_clicked(){    QFile file ("speedMEasure.csv");    if (!file.open(QIODevice::WriteOnly))    {        QMessageBox::critical(this,"Error","Cannot create csv file");        return;    }    QTextStream out (&file);    QApplication::setOverrideCursor(Qt::WaitCursor);    for (int blockSize=0x8;blockSize<=0x20000;blockSize *= 2)    {        quint64 speed = MeasureSpeed(0x100000,blockSize,10);        out << blockSize << "," << speed << Qt::endl;    }    out.flush();    file.close();    QApplication::restoreOverrideCursor();}


Первый результат


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



Где-то после размера блока 4 килобайта, скорость упирается в 560 килобайт в секунду. Давайте я грубо умножу это на 8. Получаю условные 4.5 мегабита в секунду. Условность состоит в том, что на самом деле, там ещё бывают вставные биты, да и на пакеты оверхед имеется. Но всё равно, это отстоит очень далеко от 12 мегабит в секунду, положенных на скорости Full Speed (кстати, именно поэтому на вступительном рисунке стоит знак 120, он символизирует данный теоретический предел).

Почему результат именно такой


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


То же самое текстом.
*** +4 usOUT Addr: 16 (0x10), EP: 1E1 90 40 *** +3 usDATA0C3 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD *** +45 usACKD2 *** +4 usOUT Addr: 16 (0x10), EP: 1E1 90 40 *** +3 usDATA14B 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD *** +45 usNAK5A *** +4 usOUT Addr: 16 (0x10), EP: 1E1 90 40 *** +3 usDATA14B 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD*** +45 usACKD2 *** +5 usOUT Addr: 16 (0x10), EP: 1E1 90 40 *** +3 usDATA0C3 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD*** +45 usNAK5A


И так до бесконечности на всех участках, что я смог осмотреть глазами, возвращается то ACK, то NAK. Причём в режиме FS каждая пачка данных передаётся целиком. Принялась она или нет, а всё равно передаётся целиком. Хорошо, что это не роняет всю шину USB, так как до последнего хаба данные бегут на скорости HS в виде SPLIT транзакции. А дальше уже хаб мелко шинкует её на пакеты по 64 байта и пытается отослать на скорости FS.

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

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



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

А почему при блоке 64 байта скорость выше? Я долго думал, и нашёл следующее объяснение: 64 байта это тот размер блока, после которого при работе с FS-устройствами через HS-хабы начинается использование SPLIT-транзакций. Что это такое можно посмотреть в стандарте, там этому посвящён не один десяток страниц. Но если коротко: до хаба запрос идёт на скорости HS, а уже хаб обеспечивает снижение скорости и нарезание данных на FS-блоки. Основная шина при этом не тормозит.

Выше мы видели, что уже через 5 микросекунд после прихода примитива ACK, пошёл следующий пакет, который не был обработан контроллером. А что будет, если мы будем работать блоками по 64 байта? Я начну с примитива SOF.
Смотреть код.
*** +1000 usSOF 42.0 (0x2a)A5 2A 50 *** +3 usOUT Addr: 29 (0x1d), EP: 1E1 9D F0 *** +3 usDATA0C3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 *** +45 usACKD2 *** +37 usOUT Addr: 29 (0x1d), EP: 1E1 9D F0 *** +3 usDATA14B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00*** +45 usACKD2 *** +43 usOUT Addr: 29 (0x1d), EP: 1E1 9D F0 *** +3 usDATA0C3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00*** +45 usACKD2


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

По этой же причине, если мы воткнём между материнской платой и устройством дешёвый USB2-хаб, марку которого я не скажу, так как сильно поругался с магазином, владеющим именем бренда, но судя по ID, чип там VID_05E3&PID_0608, то статистика окажется намного лучше, чем при прямом подключении к материнке:



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

Пробуем двухбуферную систему


Будучи опытным программистом для микроконтроллеров, я знаю, что обычно такая проблема решается путём применения двухбуферной схемы. Пока обрабатывается один буфер, данные передаются во второй. А какая схема используется здесь? Ответ на мой вопрос мы получим из следующего кода:
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev){  /* USER CODE BEGIN EndPoint_Configuration */  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18);  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58);  /* USER CODE END EndPoint_Configuration */  /* USER CODE BEGIN EndPoint_Configuration_CDC */  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x81 , PCD_SNG_BUF, 0xC0);  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x01 , PCD_SNG_BUF, 0x110);  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x82 , PCD_SNG_BUF, 0x100);

Тут, везде написано PCD_SNG_BUF. Вообще, в статье про DMA я уже рассуждал, что если разработчики этой библиотеки что-то не используют, значит и не стоит этого использовать. Но всё же, я попробовал заменить SNG_BUF на DBL_BUF. Результат остался прежним. Тогда я нашёл в сети следующее утверждение:

The USB peripheral's HAL driver (PCD) has a known limitation: it directly maps EPnR register numbers with endpoint addresses. This works if all OUT and IN endpoints with the same number (e.g. 0x01 and 0x81) are the same type, and not double buffered. However, isochronous endpoints have to be double buffered, therefore you have to use an endpoint number that's unused in the other direction. E.g. in your case, set AUDIO_OUT_EP1 to 0x01, and AUDIO_IN_EP1 to 0x82. (Or you can drop this USB stack altogether as I did.)


Попробовал разнести точки результат тот же. В общем, анализ показал, что для точек типа BULK это если и возможно, то только путём переписывания MiddleWare. Короче, не зря там везде одиночные буферы выбраны. Разработчики знали, что делают.

Тормоза в обработчике прерывания


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

Немного потрассируем код. Давайте я поставлю точку останова в функции CDC_Receive_FS, в которую мы попадаем ради каждого блока в 64 байта или меньше. Ну, потому что транзакции, больше чем на 64 байта, будут разрезаны на пакеты такого размера. Вот такой у нас получается стек возвратов в обработчик прерываний. А это ещё часть функций заинлайнилась!



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

Но и это ещё не всё. В функции PCD_EP_ISR_Handler (кстати, в стеке возвратов её не видно, но она есть) мы изымаем данные из FIFO в буфер. В дальнейшем, если бы у нас был свой код, мы бы ещё раз скопировали данные из системного буфера к себе. Имейте в виду, что логичнее сразу задавать указатель на свой массив, двигаясь по нему, благо такая возможность имеется.

Но и это ещё не всё! Ради всё тех же каждых 64 байт, мы вызываем функцию USBD_CDC_SetRxBuffer(), а затем USBD_CDC_ReceivePacket(), которая также расслоится на кучку вызовов, каждый из которых всего лишь чуть-чуть перетасовывает данные. Оцените стек возвратов, когда мы находимся в самом глубоком слое!



А если вы попробуете рассмотреть код функции USB_EPStartXfer(), то вам совсем станет грустно. А ведь всё это безобразие вызывается ради каждого блока в 64 байта. Вот так 64 байта пробежало начинается чехарда с многократной (правда, в нашем случае с отсутствующим функционалом однократной) пересылкой этих данных, а также с вызовом кучи слоёв. Некоторые из них тратят массу тактов процессора на свою важную работу, но некоторые просто слои. Они просто зачем-то перекладывают байтики из структуры в структуру и передают управление дальше. А такты на это всё равно расходуются!

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

Проверяем теорию осциллографом


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

Объявим, скажем, ножку Pa0 для этой цели:
#include <iopins.h>typedef Mcucpp::IO::Pa0 oscOut;//PC13

За что люблю эту библиотеку, так за её простоту. Инициализация ножки выглядит так:
oscOut::ConfigPort::Enable();oscOut::SetDirWrite();

Включили тактирование аппаратных блоков, назначили ножку на выход. Собственно, всё.

И добавим пару строк в функцию обработки прерывания (первая взведёт ножку в единицу, вторая сбросит в ноль):


То же самое текстом.
void USB_LP_CAN1_RX0_IRQHandler(void){  /* USER CODE BEGIN USB_LP_CAN1_RX0_IRQn 0 */oscOut::Set();  /* USER CODE END USB_LP_CAN1_RX0_IRQn 0 */  HAL_PCD_IRQHandler(&hpcd_USB_FS);  /* USER CODE BEGIN USB_LP_CAN1_RX0_IRQn 1 */oscOut::Clear();  /* USER CODE END USB_LP_CAN1_RX0_IRQn 1 */}


Типичный период следования прерываний от 70 до 100 микросекунд. При этом в прерывании мы находимся чуть меньше чем 19 микросекунд. На первом рисунке показан случай, когда данные идут помедленнее (расстояния велики), на втором побыстрее (расстояния меньше). Обе осциллограммы сняты при подключении через хаб.





А вот хорошая и не очень хорошая ситуации, снятые при прямом подключении к материнке. Собственно, на плохом варианте видно, что расстояния удваиваются. Один из пакетов уходит с NAKом, в прерывание мы не попадаем.





Осталось понять, как эти прерывания располагаются относительно USB-примитивов. В этом нам поможет Reference Manual. Момент прихода прерывания я выделил.



Вот теперь всё сходится. После формирования ACKа мы не готовы принимать новые пакеты на протяжении 18 микросекунд. Когда они приходят через 3-5 микросекунд (а именно это мы видим в текстовом логе анализатора выше для плохого случая), контроллер их просто игнорирует, посылая NAK. Когда через 30-40 (что мы наблюдаем в текстовом логе для случая хорошего, хотя, точно подойдёт любое значение, больше чем 19) обрабатывает.

Плюс из текстовых логов мы видим, что влево от прерывания около пятидесяти микросекунд занимает сама OUT-транзакция на аппаратном уровне. Кстати. У нас же скорость FS. Такие сигналы можно ловить обычным осциллографом. Давайте я добавлю активность на шине USB в виде голубого луча. Что получим?

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



Вот такая картинка была, когда я получил производительность 814037 байт в секунду. Извините, но быстрее никак. Либо по шине идут данные, либо мы обрабатываем прерывание. Простоев нет!




Причём 64 байта, с учётом, что я передаю все нули, а значит там может быть вставленный бит это примерно 576 бит. При частоте 12 МГц их передача займёт 48 миллисекунд. То есть, когда между пакетами примерно по 50 миллисекунд, мы имеем дело с пределом скорости. Тут даже NAKов нет.




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




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

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

Неожиданное следствие из снятой осциллограммы


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



А при коротких пакетах, бывает и такое:



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

Выводы


Ну что, пришла пора делать выводы. Как видим, контроллер STM32F103C8T6 не может выжать всю производительность даже из шины USB 2.0 FS.

Хорошо это или плохо? Ни то, ни другое. Есть тысяча и одна задача, где не надо гнаться за производительностью USB, и этот копеечный контроллер прекрасно с ними справляется. Вот там его и надо использовать. (Дополнение: пока статья дежала в столе, на Хабре появилась статья, что уже не копеечный. Цена у местных поставщиков, согласно той статье, выросла в 10 раз. Надеюсь, это временное явление.)

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

А для производительных вещей я в ближайшее время собираюсь изучить контроллер, у которого USB обрабатывается по стандарту EHCI. Там всё на дескрипторах. Заполнил адрес, длину Когда пришло прерывание данные уже готовы Надеюсь Если будет что-то интересное сделаю статью. А здесь сам подход к обработке приходящих данных (они помещаются в выделенную память, а затем программно изымаются оттуда, причём в контексте прерывания) не даёт развить высоких скоростей. По крайней мере, на кристалле F103.

Следующий вывод: добавленный в систему дешёвый USB-хаб даёт неожиданный прирост производительности. Это связано с тем, что он шлёт пакеты с паузами 20-25 микросекунд (в статье подтверждающий лог не приводится для экономии места, но его можно скачать здесь для самостоятельного изучения). Получаем грубо 20 микросекунд задержки, 50 микросекунд передачи. Итого 5/7 от полной производительности. Как раз 700-800 килобайт в секунду при теоретическом максимуме 1000-1100. Так что любой FS-контроллер, включённый через этот хаб, не сможет выдать больше.

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

И, наконец, мы выяснили, что для FS устройств на базе STM32 нет чёткого критерия оптимальной работы со стороны прикладной программы. Одна и та же система, в которую то добавляли, то исключали внешний USB-хаб, работала с максимальной производительностью либо при длине блока 64 байта (без хаба), либо более четырёх килобайт (с хабом). При разработке прикладных программ для PC, требующих высокой производительности, следует учитывать и этот аспект. Вплоть до калибровки параметров под конкретную конфигурацию оборудования.

Послесловие. А что там в режиме HS?


Ради интереса приведу результаты, снятые для HS-устройства на базе ПЛИС. Второй столбец скорость при прямом подключении к материнской плате, третий через тот самый дешёвый хаб. Мне уже интересно, почему там хаб даёт такую просадку, но с этим я разберусь как-нибудь в другой раз.



А пока статья лежала в столе, я взял типовой пример CDC-прошивки от NXP, немного доработал его (без доработки он зависнет при односторонней передаче), залил в плату Teensy 4.1 и снял метрики там. У него контроллер EHCI и скорость HS.



Причина та же самая. Аппаратура, вроде, позволяет поставить в очередь несколько запросов (как для асинхронной работы), но увы, программная часть это явно запрещает:
usb_status_t USB_DeviceCdcAcmRecv(class_handle_t handle, uint8_t ep, uint8_t *buffer, uint32_t length){    if (1U == cdcAcmHandle->bulkOut.isBusy)    {        return kStatus_USB_Busy;    }    cdcAcmHandle->bulkOut.isBusy = 1U;

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



Но это уже тема для другой статьи.
Подробнее..

Программируемое реле easyE4

18.06.2021 20:15:41 | Автор: admin

Предисловие

В свое время наткнулся на очень хорошую статью о програмируемых реле EASY компании Eaton (Moeller). Если кратко, то можно выделить 3 больших серии реле:

  • easy500 - реле 8 входов, 4 выхода, без возможности расширения;

  • easy700 - реле 12 входов, 6/8 выходов, с возможностью подключения одного модуля расширения или коммуникации. Максимальное число точек - 40 (базовый модуль 12+8, модуль расширения 12+8).

  • easy800 - более продвинутая версия 700-й серии, с увеличенным размером памяти, со встроенным протоколом коммуникации easyNet, позволяющим подключить до 8 устройств в одну сеть.

Программируемые реле серий EASY800, EASY500 и EASY700Программируемые реле серий EASY800, EASY500 и EASY700

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


Новинка easyE4

В 2019 году у Eaton вышла обновленная линейка программируемых реле - easyE4. Компания в новой линейке изменила философию продукта. Теперь есть базовый модуль, по размерам как реле серии easy500, имеющий 8 входов и 4 выхода, к которому можно подключить до 11 модулей расширения. При этом функционал базового модуля сопоставим как минимум со "старой" серией easy800.

Новая серия easyE4 и EASY512, подключенные к питающему напряжениюНовая серия easyE4 и EASY512, подключенные к питающему напряжению

По питающему напряжению можно выделить 3 вида:

  • DC - 24VDC

  • UC - 12VDC, 24VDC, 24VAC

  • AC - 100_240VAC, 100_240VDC

Есть еще версии без экрана, итого максимальное количество различных базовых модулей всего 6. В каталогах встречаются модели с push-in клеммами, но они еще только планируются к производству. С данными моделями количество базовых модулей вырастет до 12.

Разберем структуру наименования базовых модулей. Маска наименования следующая:
EASY-E4-[VV]-12[W]C(X)1(P), где:

  • [VV] - тип питающего напряжения - DC, UC, AC;

  • [W] - тип выходов - релейные (R) или транзисторные (T);

  • (Х) - отсутствие дисплея (если дисплей есть, символ не ставится);

  • (P) - push-in клеммы (модель еще не выпущена); если символа нет - клеммы винтовые.

    Примеры:
    EASY-E4-DC-12TC1 - напряжение питания 24 Вольт постоянного тока, транзисторные выходы, с дисплеем (артикул производителя 197213).
    EASY-E4-UC-12RCX1P - напряжение питания 12/24 Вольта, релейные выходы, без дисплея, push-in клеммы (артикул производителя 197505).

Структура наименования моделей расширения. Маска наименования: EASY-E4-[VV]-[AA][B]E1(P), где:

  • [VV] - напряжение питания модуля;

  • [AA] - общее количество точек (входов-выходов);

  • [B] - тип точек (R - релейные, T - транзисторные, A - аналоговые, P- температурные);

  • (P) - push-in клеммы.

    Примеры:
    EASY-E4-AC-16RE1 - напряжение питания 100-240 Вольт, общее количество входов и выходов 16, тип выходов - релейные (артикул производителя 197222).
    EASY-E4-DC-4PE1 - напряжение питания 24 Вольт постоянного тока, 4 температурных входа (артикул производителя 197224).


"Железо"

Рассмотрим базовый модуль на примере EASY-E4-UC-12RC1 (артикул 197211).
Он содержит 8 цифровых (дискретных) входов, (в версиях DC и UC входы I5-I8 могут быть аналоговыми по напряжению - функционал выбирается в ПО для программирования). Есть 4 выхода - релейные (релейные для версий AC, UC, транзисторные для DC). Шестистрочный экран с подсветкой, которая может быть белой, красной, зеленой. Слева от дисплея находится слот под карту microSD, с помощью которой можно обновить прошивку устройства, организовать логгирование, или же сохранить свой загрузочный логотип.

easyE4easyE4

В правой части находится порт для подключения модулей расширения (закрыт заглушкой).
Для коммуникации имеется порт RJ-45 с интерфейсом Ethernet. Доступные протоколы - Modbus TCP и EasyNet. Используя данный порт, можно зайти на встроенный web-сервер (с собственным API), отправиль сообщения на e-mail, также реле может синхронизировать часы реального времени с серверами в интернете - удобная вещь в случае использования функционала, завязанного на астрономические таймеры.

Web-сервер easyE4 с отображением текущего состояния входовWeb-сервер easyE4 с отображением текущего состояния входов

Модули расширения подключаются при помощи комплектного соединителя, который идет с каждым модулем.

Модули расширения с комплектными соединителямиМодули расширения с комплектными соединителями

Всего к одному базовому устройству можно подключить до 11 модулей расширения. Т.к. каждый модуль имеет свои клеммы питания, в одной системе можно использовать различные типы модулей расширения - например, базовое устройство EASY-E4-UC-12RC1, модуль расширения аналоговых входов EASY-E4-DC-6AE1 и модуль 4DI+4RO EASY-E4-AC-8RE1 прекрасно будут работать в одной сборке, позволяя подключить разнообразные устройства ко входам и выходам.

Артикулы 197211+197222+197223Артикулы 197211+197222+197223

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

Если вы работали со "старыми" easy500/700/800 (в коллективе называем эти реле "изиками"), то средой программирования было ПО EasySoft 6. А для подключения реле к компьютеру использовался специальный кабель, который стоит немало.

EASY800 и EASY700 с кабелями программированияEASY800 и EASY700 с кабелями программирования

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

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

EasySoft 7EasySoft 7

Впридачу, есть преемственность - если проект для старых "изиков" открыть в 7 версии, то произойдет автоматическая конвертация с заменой "железа" на новое и переназначением операнд с выводом отчета.

Конвертация "старого" проекта *.e60 Конвертация "старого" проекта *.e60

Демонстрационную версию можно скачать с сайта Eaton. Демо-версия позволяет создать и отдадить проект, но без возможности записать проект в само реле. Для этого необходима лицензия. Сама лицензия стоит денег, но можно схитрить - есть специальные стартовые наборы, состоящие из самого реле, шнурка для программирования (обычный патч-корд, подойдет абслютно любой RJ-45 прямого обжима) и флаера с кодом, который нужно зарегистрировать и получить ключ активации ПО. Набор такой стоит немного дороже самого базового модуля. Так что если нужно несколько базовых модулей - вместо одного покупаете набор, и получаете лицензионное ПО со скидкой:) Особенностью ПО является то, что код с флаера можно зарегистрировать несколько раз, тем самым получив коды активации для нескольких компьютеров. Артикулы для поиска - 197227/197228/197229.

Стартовый набор 197227Стартовый набор 197227

К программному обеспечению хорошо изучить руководство по эксплуатации. Его код MN050009 (англоязычная версия). Загуглив код "MN050009_RU", находится русскоязычное руководство, которого, почему-то, нету на сайте производителя.
Дополнительно посоветую обучающий курс по программированию для старых изиков "EASY Это просто" от Одесского политеха (авторы О.А. Андрюшенко, В.А. Водичев) - там основы основ.


Пример применения

Один из практически реализованных мной простых проектов - управление освещением во дворе. Есть 2 группы опор освещения - основное (КМ2) и дежурное (КМ1). В автоматическом режиме после заката (по астрономическому таймеру) включается всё освещение, после 22.00 остается только дежурное, которое работает до полуночи в будние дни и чуть позже в выходные. Данные временнные отрезки установлены в недельных таймерах.

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

Электрическая схема подключенияЭлектрическая схема подключения

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

Настройки астрономического таймераНастройки астрономического таймера
Подробнее..

USB на регистрах isochronous endpoint на примере Audio device

23.05.2021 14:09:40 | Автор: admin
image<картинка с платой и наушниками>
Еще более низкий уровень (avr-vusb): habr.com/ru/post/460815
USB на регистрах: STM32L1 / STM32F1
USB на регистрах: bulk endpoint на примере Mass Storage
USB на регистрах: interrupt endpoint на примере HID

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

Как ни странно, этот тип конечной точки оказался самым мозговыносящим (и это после всего, что я успел повидать с stm'ками!). Тем не менее, сегодня мы сделаем аудиоустройство и заодно чуть-чуть допилим ядро библиотеки USB. Как обычно, исходные коды доступны:
github.com/COKPOWEHEU/usb/tree/main/4.Audio_L1
github.com/COKPOWEHEU/usb/tree/main/4.Audio_F1

Доработка ядра


Допиливать ядро нужно потому, что у STM изохронные точки могут быть только с двойной буферизацией, то есть, грубо говоря, нельзя сделать 0x01 изохронной, а 0x81 управляющей. То есть в дескрипторе USB это прописать, конечно, можно, но внутренности контроллера это не изменит, и реальный адрес точки просто будет отличаться от видимого снаружи. Что, естественно, повысит риск ошибок, поэтому в эту сторону извращаться не будем.

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

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

Прием и передача пакетов отличается не так сильно, хотя и отняла гораздо больше времени сначала на попытки понять как же она должна работать по логике ST, потом на подгонку заклинания из интернета чтобы все-таки заработало. Как говорилось раньше, если для обычной точки два буфера независимы и отличаются направлением обмена, то для буферизованной они одинаковы и отличаются только смещением. Так что немножко изменим функции usb_ep_write и usb_ep_read чтобы они принимали не номер точки, а номер смещения. То есть если раньше эти функции предполагали существование восьми сдвоенных точек, то теперь 16 одинарных. Соответственно, номер новой полуточки на запись равен всего лишь номеру обычной, умноженному на два, а для usb_ep_read надо еще добавить единицу (см. распределение буферов в PMA). Собственно, это и делается инлайн-функциями usb_ep_write и usb_ep_read для обычных точек. А вот логику буферизованных рассмотрим чуть подробнее.

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

Симметричная ситуация должны была быть с IN точками. Но на практике оказалось, что и проверять, и дергать надо USB_EP_DTOG_RX. Почему не TX я так и не понял Спасибо пользователю kuzulis за ссылку на github.com/dmitrystu/libusb_stm32/edit/master/src/usbd_stm32f103_devfs.c

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

В результате конечные точки научились работать и в буферизованном режиме если не дышать на них слишком сильно.

Для пользователя разница невелика: вместо usb_ep_init использовать usb_ep_init_double, а вместо usb_ep_write и usb_ep_read соответственно usb_ep_write_double и usb_ep_read_double.

Устройство AudioDevice


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

Согласно стандарту USB аудиоустройство представляет собой набор сущностей (entity), соединенных друг с другом в некую топологию, по которой и проходит аудиосигнал. Каждая сущность имеет свой уникальный номер (bTerminalID, он же UnitID), по которому к ней могут подключаться другие сущности или конечные точки, по нему же обращается хост, если хочет изменить какие-то параметры. И он же считается единственным выходом данной сущности. А вот входов может вообще не быть (если это входной терминал), а может быть и больше одного (bSourceID). Собственно записью в массив bSourceID номеров сущностей, от которых текущая получает аудиосигнал, мы и описываем всю топологию, которая в результате может получиться весьма резвесистой. Для примера приведу топологию покупной USB-звуковой карты (цифрами показаны bTerminalID / UnitID):

lsusb и его расшифровка
Bus 001 Device 014: ID 0d8c:013c C-Media Electronics, Inc. CM108 Audio Controller#Тут пока ничего интересногоDevice Descriptor:  bLength                18  bDescriptorType         1  bcdUSB               1.10  bDeviceClass            0   bDeviceSubClass         0   bDeviceProtocol         0   bMaxPacketSize0         8  idVendor           0x0d8c C-Media Electronics, Inc.  idProduct          0x013c CM108 Audio Controller  bcdDevice            1.00  iManufacturer           1   iProduct                2   iSerial                 0   bNumConfigurations      1  #интересное начинается тут  Configuration Descriptor:    bLength                 9    bDescriptorType         2    wTotalLength       0x00fd    bNumInterfaces          4  # общее количество интерфейсов    bConfigurationValue     1    iConfiguration          0     bmAttributes         0x80      (Bus Powered)    MaxPower              100mA    #интерфейс 0 - описание топологии    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        0      bAlternateSetting       0      bNumEndpoints           0      bInterfaceClass         1 Audio      bInterfaceSubClass      1 Control Device      bInterfaceProtocol      0       iInterface              0       AudioControl Interface Descriptor:        bLength                10        bDescriptorType        36        bDescriptorSubtype      1 (HEADER)        bcdADC               1.00        wTotalLength       0x0064        bInCollection           2  # ВАЖНО! количество интерфейсов данных (2)        baInterfaceNr(0)        1  #номер перовго из них        baInterfaceNr(1)        2  #номер второго ##### Топологоия ###### 1 InputTerminal (USB, на динамик)       AudioControl Interface Descriptor:        bLength                12        bDescriptorType        36        bDescriptorSubtype      2 (INPUT_TERMINAL)        bTerminalID             1  # Вот номер данной сущности        wTerminalType      0x0101 USB Streaming        bAssocTerminal          0        bNrChannels             2  # Здесь задается количество каналов        wChannelConfig     0x0003  # А здесь - их расположение в пространстве          Left Front (L)          Right Front (R)        iChannelNames           0         iTerminal               0         # 2 InputTerminal (микрофон)      AudioControl Interface Descriptor:        bLength                12        bDescriptorType        36        bDescriptorSubtype      2 (INPUT_TERMINAL)        bTerminalID             2        wTerminalType      0x0201 Microphone        bAssocTerminal          0        bNrChannels             1        wChannelConfig     0x0001          Left Front (L)        iChannelNames           0         iTerminal               0         # 6 OutputTerminal (динамик), вход соединен с сущностью 9      AudioControl Interface Descriptor:        bLength                 9        bDescriptorType        36        bDescriptorSubtype      3 (OUTPUT_TERMINAL)        bTerminalID             6        wTerminalType      0x0301 Speaker        bAssocTerminal          0        bSourceID               9  # Номера входов указываются здесь        iTerminal               0         # 7 OutputTerminal (USB), вход соединен с сущностью 8      AudioControl Interface Descriptor:        bLength                 9        bDescriptorType        36        bDescriptorSubtype      3 (OUTPUT_TERMINAL)        bTerminalID             7        wTerminalType      0x0101 USB Streaming        bAssocTerminal          0        bSourceID               8        iTerminal               0         # 8 Selector, входы соединены только с сущностью 10      AudioControl Interface Descriptor:        bLength                 7        bDescriptorType        36        bDescriptorSubtype      5 (SELECTOR_UNIT)        bUnitID                 8        bNrInPins               1  # У сущностей с несколькими входами указывается их количество        baSourceID(0)          10  # а потом номера        iSelector               0         # 9 Feature, вход соединен с сущностью 15      AudioControl Interface Descriptor:        bLength                10        bDescriptorType        36        bDescriptorSubtype      6 (FEATURE_UNIT)        bUnitID                 9        bSourceID              15        bControlSize            1        bmaControls(0)       0x01          Mute Control        bmaControls(1)       0x02          Volume Control        bmaControls(2)       0x02          Volume Control        iFeature                0         # 10 Feature, вход соединен с сущностью 2      AudioControl Interface Descriptor:        bLength                 9        bDescriptorType        36        bDescriptorSubtype      6 (FEATURE_UNIT)        bUnitID                10        bSourceID               2        bControlSize            1        bmaControls(0)       0x43          Mute Control          Volume Control          Automatic Gain Control        bmaControls(1)       0x00        iFeature                0         # 13 Feature, вход соединен с сущностью 2      AudioControl Interface Descriptor:        bLength                 9        bDescriptorType        36        bDescriptorSubtype      6 (FEATURE_UNIT)        bUnitID                13        bSourceID               2        bControlSize            1        bmaControls(0)       0x03          Mute Control          Volume Control        bmaControls(1)       0x00        iFeature                0         # 15 Mixer, входы соединены с сущностями 1 и 13      AudioControl Interface Descriptor:        bLength                13        bDescriptorType        36        bDescriptorSubtype      4 (MIXER_UNIT)        bUnitID                15        bNrInPins               2  # Снова массив входов        baSourceID(0)           1  # и их номера        baSourceID(1)          13        bNrChannels             2        wChannelConfig     0x0003          Left Front (L)          Right Front (R)        iChannelNames           0         bmControls(0)        0x00        iMixer                  0 ##### конец топологии ###### Интерфейс 1 (основной) - заглушка без конечных точек    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        1      bAlternateSetting       0      bNumEndpoints           0      bInterfaceClass         1 Audio      bInterfaceSubClass      2 Streaming      bInterfaceProtocol      0       iInterface              0       # Интерфейс 1 (альтернативный) - рабочий с одной конечной точкой    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        1      bAlternateSetting       1      bNumEndpoints           1      bInterfaceClass         1 Audio      bInterfaceSubClass      2 Streaming      bInterfaceProtocol      0       iInterface              0       AudioStreaming Interface Descriptor:        bLength                 7        bDescriptorType        36        bDescriptorSubtype      1 (AS_GENERAL)        bTerminalLink           1        bDelay                  1 frames        wFormatTag         0x0001 PCM      AudioStreaming Interface Descriptor:        bLength                14        bDescriptorType        36        bDescriptorSubtype      2 (FORMAT_TYPE)        bFormatType             1 (FORMAT_TYPE_I)        bNrChannels             2        bSubframeSize           2        bBitResolution         16        bSamFreqType            2 Discrete        tSamFreq[ 0]        48000        tSamFreq[ 1]        44100      Endpoint Descriptor:        bLength                 9        bDescriptorType         5        bEndpointAddress     0x01  EP 1 OUT        bmAttributes            9          Transfer Type            Isochronous          Synch Type               Adaptive          Usage Type               Data        wMaxPacketSize     0x00c8  1x 200 bytes        bInterval               1        bRefresh                0        bSynchAddress           0        AudioStreaming Endpoint Descriptor:          bLength                 7          bDescriptorType        37          bDescriptorSubtype      1 (EP_GENERAL)          bmAttributes         0x01            Sampling Frequency          bLockDelayUnits         1 Milliseconds          wLockDelay         0x0001          # Интерфейс 2 (основной) - заглушка    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        2      bAlternateSetting       0      bNumEndpoints           0      bInterfaceClass         1 Audio      bInterfaceSubClass      2 Streaming      bInterfaceProtocol      0       iInterface              0       # Интерфейс 2 (альтернативный)    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        2      bAlternateSetting       1      bNumEndpoints           1      bInterfaceClass         1 Audio      bInterfaceSubClass      2 Streaming      bInterfaceProtocol      0       iInterface              0       AudioStreaming Interface Descriptor:        bLength                 7        bDescriptorType        36        bDescriptorSubtype      1 (AS_GENERAL)        bTerminalLink           7        bDelay                  1 frames        wFormatTag         0x0001 PCM      AudioStreaming Interface Descriptor:        bLength                14        bDescriptorType        36        bDescriptorSubtype      2 (FORMAT_TYPE)        bFormatType             1 (FORMAT_TYPE_I)        bNrChannels             1        bSubframeSize           2        bBitResolution         16        bSamFreqType            2 Discrete        tSamFreq[ 0]        48000        tSamFreq[ 1]        44100      Endpoint Descriptor:        bLength                 9        bDescriptorType         5        bEndpointAddress     0x82  EP 2 IN        bmAttributes            9          Transfer Type            Isochronous          Synch Type               Adaptive          Usage Type               Data        wMaxPacketSize     0x0064  1x 100 bytes        bInterval               1        bRefresh                0        bSynchAddress           0        AudioStreaming Endpoint Descriptor:          bLength                 7          bDescriptorType        37          bDescriptorSubtype      1 (EP_GENERAL)          bmAttributes         0x01            Sampling Frequency          bLockDelayUnits         0 Undefined          wLockDelay         0x0000##### Конец описания аудиоинтерфейсов ###### Интерфейс 3 "Клавиши громкости и всего остального" (не интересно)    Interface Descriptor:      bLength                 9      bDescriptorType         4      bInterfaceNumber        3      bAlternateSetting       0      bNumEndpoints           1      bInterfaceClass         3 Human Interface Device      bInterfaceSubClass      0       bInterfaceProtocol      0       iInterface              0         HID Device Descriptor:          bLength                 9          bDescriptorType        33          bcdHID               1.00          bCountryCode            0 Not supported          bNumDescriptors         1          bDescriptorType        34 Report          wDescriptorLength      60         Report Descriptors:            ** UNAVAILABLE **      Endpoint Descriptor:        bLength                 7        bDescriptorType         5        bEndpointAddress     0x87  EP 7 IN        bmAttributes            3          Transfer Type            Interrupt          Synch Type               None          Usage Type               Data        wMaxPacketSize     0x0004  1x 4 bytes        bInterval               2



image

Мы же будем делать нечто более простое (заготовку брал отсюда):

image

Здесь видно две независимых ветки распространения сигнала: либо от USB через фичу к динамику, либо из микрофона через другую фичу к USB. Микрофон и динамик не просто так взяты в кавычки: на моей отладочной плате их нет, поэтому вместо собственно звука будем пользоваться кнопками и светодиодами. Впрочем, ничего нового. Фичи в моем случае ничего не делают и добавлены скорее для красоты.

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

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

1. Входной терминал (Input Terminal)
Как следует из названия, именно через него в аудиоустройство попадает звуковой сигнал. Это может быть USB, может быть микрофон обыкновенный, микрофон гарнитурный, даже микрофонный массив.

2. Выходной терминал (Output Terminal)
Тоже вполне очевидно то, через что звук покидает наше устройство. Это может быть все тот же USB, может быть динамик, гарнитура, динамик в мониторе, динамики различных частот и куча других устройств.

3. Микшер (Mixer Unit)
Берет несколько входных сигналов, усиливает каждый на заданную величину и складывает то, что получилось, в выходной канал. При желании можно задать усиление в ноль раз, что сведет его к следующей сущности.

4. Селектор (Selector Unit)
Берет несколько входных сигналов и перенаправляет один из них на выход.

5. Фильтр (Feature Unit)
Берет единственный входной сигнал, меняет параметры звука (громкость, тембр и т.п.) и выдает на выход. Естественно, все эти параметры одинаковым способом прикладываются ко всему сигналу, без взаимодействия логических каналов внутри него

6. Processing Unit
А вот эта штука уже позволяет проводить манипуляции над отдельными логическими каналами внутри каждого входного. Более того, позволяет сделать количество логических каналов в выходном не равным количеству во входных.

7. Extension Unit
Весь набор нестандартных сущностей, чтобы больной фантазии производителей оборудования было раздолье. Соответственно, и поведение, и настройки будут зависеть от этой самой фантазии.

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

Грабли в дескрипторе


В отличие от предыдущих USB-устройств, здесь дескриптор сложный, многоуровневый и склонный пугать виндоусы до BSOD'а. Как мы видели выше, топология у аутиоустройства может быть весьма сложной и развесистой. Под ее описание выделяется целый интерфейс. Очевидно, endpoint'ов он содержать не будет, зато будет содержать список дескрипторов сущностей и описаний к чему подключены их входы. Тут особо описывать смысла не вижу, проще посмотреть в коде и документации. Отмечу только главные грабли: здесь описывается какие интерфейсы с соответствующими конечными точками относятся именно к данному устройству. Скажем, если вы захотите изменить мою конфигурацию и убрать оттуда динамик, придется не просто удалить половину сущностей (слава макросам, хотя бы с подсчетом длины дескриптора проблемы не будет), но и уменьшить поле bInCollection до 1, после чего из следующего за ним массива bInterfaceNr убрать номер лишнего интерфейса.

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

Вторая особенность интерфейсов данных это формат аудиосигнала. В соответствующем дескрипторе задается тип кодирования, количество каналов, разрешение и частота дискретизации (которая задается 24-битным числом). Вариантов кодирования предусмотрено довольно много, но мы будем использовать самый простой PCM. По сути это просто последовательность значений мгновенной величины сигнала без какого-либо кодирования, причем величина считается целым числом со знаком. Разрешение сигнала задается в двух местах (зачем непонятно): в поле bSubFrameSize указывается количество байтов, а в bBitResolution количество битов. Вероятно, можно указать, что диапазон нашей звуковой карты не доходит до полного диапазона типа данных, скажем, int16_t и составляет всего 10 бит.

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

Ах да, чуть не забыл упомянуть очередную пачку BSOD'ов при тестировании неправильных дескрипторов. Еще раз напоминаю: количество интерфейсов данных должно соответствовать числу bInCollection, а их номера следующему за ним массиву!
Скрытый текст
Как представлю отладку подобного кода под виндами, с этими постоянными вылетами, да еще без нормальной консоли. бр-р-р.


Логика работы устройства


Как я уже говорил, для тестов не имеет смысла городить на отладочную плату навесные компоненты, поэтому все тестирование будет осуществляться тем, что уже установлено кнопки да светодиоды. Впрочем, в данном случае это проблемы не составляет: микрофон может просто генерировать синусоиду частотой, скажем, 1 кГц, а динамик включать светодиод при превышении порогового значения звука (скажем, выше числа 10000: при указанных 16 битах разрешения, что соответствует диапазону -32768 +32767, это примерно треть).

А вот с тестированием возникла небольшая проблема: я не нашел простого способа перенаправить сигнал с микрофона на stdin какой-нибудь программы. Вроде бы раньше это делалось просто чтением /dev/dsp, но сейчас что-то поломалось. Впрочем, ничего критичного, ведь есть всякие библиотеки взаимодействия с мультимедией SDL, SFLM и другие. Собственно на SFML я и написал простенькую утилиту для чтения с микрофона и, если надо, визуализации сигнала.

Особое внимание уделю ограничениям нашего аудиоустройства: насколько я понял, изохронный запрос IN отправляется один раз в миллисекунду (а вот OUT'ов может быть много), что ограничивает частоту дискретизации. Допустим, размер конечной точки у нас 64 байта (учитывая буферизацию, в памяти она занимает 128 байт, но хост об этом не знает), разрешение 16 бит, то есть за раз можно отправить 32 отсчета. Учитывая интервал в 1 мс получаем теоретический предел 32 кГц для одного канала. Самый простой способ это обойти увеличить размер конечной точки. Но тут надо помнить, что размер общего буфера PMA у нас всего 512 байт. Минус таблица распределения точек, минус ep0, получаем максимум 440 байт, то есть 220 байт на единственную точку с учетом буферизации. И это теоретический предел.

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

Заключение (общее для цикла)


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

WebUSB. Прошейся из браузера

31.05.2021 12:16:05 | Автор: admin


Разработчики хромиума постоянно пилят огромное количество классных API для разных технологий и железяк. Тут и Web Share, и работа со звуком, Bluetooth, NFC, WebOTP и много чего ещё, более-менее полный список со статусами реализации можно посмотреть здесь. Но больше всего среди них впечатляет WebUSB API, о настоящих возможностях которого я узнал совсем недавно. Оказывается, с его помощью можно не только открывать странички с подключенных устройств, но и прошивать их. Это открывает новый крутейший сценарий работы со всякой мелкой электроникой.

Пара слов про WebUSB


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

WebUSB вышел в релиз в 2017 году, а в последний год его поддержка заметно выросла, так что ещё через годик он наверняка уже не будет восприниматься как какая-то диковинка. Кстати, это не полностью продукт гугла: в разработке стандарта активно участвовала Mozilla, но пока что, как и Apple, не торопится внедрять технологию в свой браузер из-за потенциальных уязвимостей. Конечно, API глубоко протестировано и запускается (очевидно) только под https, но из-за огромной мощности и количества потенциальных векторов атаки их точку зрения можно поддержать. С другой стороны, у 70% пользователей WebUSB уже давно живёт в браузере, и о критических уязвимостях пока не слышно.

Как и в большинстве новых API, WebUSB можно явно разрешить или запретить HTTP-заголовком Feature-Policy или атрибутом allow в iframe:

  Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com

  <iframe allowpaymentrequest allow="usb; fullscreen"></iframe>

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



DFU


Полностью описывать механизм работы и нюансы API можно долго, и для этого есть web.dev и стандарт. Самое интересное в firmware. Так как железку, раздающую сайт, надо ещё научить это делать, разработчики, чтобы два раза не вставать, запилили возможность прошивать её прямо из браузера точнее, не сломали эту возможность :)


zhovner многим открыл глаза этим видео в том числе и мне

К USB-устройству обеспечен прямой доступ, со списком интерфейсов, VID/PID и другой информацией. Данные можно считывать и записывать управляющие команды, отслеживать состояния и так далее. Что из этого следует? Что тут же найдётся сумрачный гений, который запилит удобный инструмент, утилизирующий эти возможности! Так и появился PoC webdfu, позволяющий прошивать устройства в DFU-режиме.



DFU (Device Firmware Upgrade) это спецификация, созданная для упрощения обновления прошивки на USB-устройствах. Она поддерживается почти на всех ОС, работает в специальных программах вроде DfuSe и в оригинальной dfu-util. Как нетрудно догадаться из названия, webdfu служит драйвером USB-DFU для хрома. Он может:

  • Загружать текущую прошивку с устройства (upload)
  • Записывать новую прошивку на устройство (download)
  • Переключать устройство в режим DFU
  • Выполнять upload/download совместимо с STM'овской DfuSe

Не может:

  • Открыть устройство, если нет прав на доступ к нему или отсутствует базовый драйвер (Windows изрядно портит этим нервы)
  • Работать с файлами форматов DfuSe
  • Загружать прошивку в формате .dfu
  • Обрабатывать ошибки записи

А теперь возьмите полминуты паузы и подумайте: то, для чего раньше нужно было качать нужные драйвера, программы и библиотеки под разные ОС и архитектуры, теперь можно делать в пару кликов из браузера, на чём угодно! Будущее уже здесь.

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

Ссылки





На правах рекламы


Виртуальные серверы с мгновенной активацией на Linux или Windows. Сервер готов к работе через минуту после оплаты!

Присоединяйтесь к нашему чату в Telegram.

Подробнее..

Категории

Последние комментарии

© 2006-2021, personeltest.ru