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

Я очень люблю видеоуроки. Почему-то информацию с текста лично я воспринимаю весьма и весьма плохо. А есть вещи, в которых информации на бумаге не так много. Возьмём, к примеру, уроки игры на балалайке: на YouTube есть замечательные разборы, как играть ту или иную мелодию (кому интересно, ищем по имени «Сергей Воронцов»), но повторить и заучить последовательность с потокового видео сложно. Надо или записывать на бумагу (судя по комментариям под уроками, некоторые так и делают), или докупать дополнительные материалы (но я не жадный, а домовитый).

g5_maxwworsrjbxqh3n08jytdj4.png

В общем, в моём балалаечном случае, да и при многих других, надо идти так: посмотрел фрагмент, поставил на паузу, повторил несколько раз за автором, продолжил просмотр. Если что-то непонятно — отмотал назад, посмотрел повнимательнее. Но как это всё мотать, если руки заняты? Тем более, в моём случае они заняты пусть не огромным баяном, но всё-таки какой-никакой, а балалайкой. Значит, работать надо при помощи ног.
Много-много лет назад, когда всё прогрессивное человечество пользовалось клавиатурами PS/2, принесли нам для проекта десяток USB-клавиатур. Разумеется, мы подключили их к одной машине, нажали на одной из них Ctrl, на второй — Alt, а на третьей — Del. Эффект был достигнут. С тех пор я знаю, что USB-клавиатуры работают полностью параллельно, поэтому в помощь основной можно подключить ещё одну, которая будет досылать некоторые коды вместо неё.

Так и родилась идея сделать педаль, которая прикидывается клавиатурой и шлёт коды управления плеером. В качестве плеера я исходно выбрал VLC Player, так как у него хорошо документированы управляющие кнопки, но впоследствии выяснилось, что выбранные мною коды имеют точно такое же назначение и при просмотре роликов на YouTube в полноэкранном режиме. Это хорошо, так как при многократных онлайн просмотрах за показы рекламы, вроде, автору что-то перепадает. Смотреть в скачанном виде было бы не так этично.

Итак, начинаем проектирование устройства.

Логика работы


Сначала я хотел сделать несколько педалей, каждая на свою функцию. Исходно этих функций я насчитал штуки 3–4: вкл, выкл, мотать туда, мотать обратно. Но потом прикинул, сколько пластика на это уйдёт, плюс — как я буду разбираться в педалях на ощупь, и загрустил. Поэтому было решено кардинально пересмотреть всю логику. Какие функции вообще нужны? Пуск-стоп, но это часто висит на одной и той же клавише, обычно — на пробеле. И отмотка назад. У VLC Player — это «Shift+Влево». У YouTube, как оказалось, тоже. И, если так подумать — больше функций и не надо.

Прекрасно! Давайте сделаем кратковременное нажатие педали функцией «Пуск-стоп», а длинное — функцией «Отмотать назад».

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

Механика


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

Педали в огромном количестве продаются на Ali Express, искать следует по слову Foot Switch. Цены там начинаются от 30 рублей, но при такой цене стоимость доставки может быть около четырёхсот рублей. Поискав, я выяснил, что вполне реально найти такую педаль, для которой сумма «цена плюс доставка» составит 150 рублей. Но ждать придётся в среднем пару месяцев (я тут недавно плату для SD карт все три месяца ждал).

Поэтому я пошёл по более быстрому пути: сделал педаль на 3D принтере. Личный опыт (и все варианты с Thingiverse) показывают, что ось должна быть металлическая, иначе всё быстро сломается. Я взял имеющийся у меня мебельный болт M6×60 с приложенной к нему гайкой, а также имеющуюся в соседних «Радиодеталях» кнопку (PSW-13) и обрисовал их вокруг пластиком, как смог. Вот так выглядели исходные материалы:

fxh9fljxy-gcfvlvf5s2ovu5sis.jpeg

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

xn6hzqfjerkxrderg2kvpnad30o.jpeg

Вот почти собранный вариант:

qsyagb0p-qbpgmwwc78tvtj7vcc.jpeg

А вот — педаль в сборе:

fcasfu9dy0rvteff4oyskczhvq0.jpeg

В принципе, STL-файлы я приложу, но решение получилось не идеальное. Тем не менее, любой желающий сможет повторить его (если найдёт болт M6×60 с соответствующей гайкой и кнопку PSW-13). На первое время хватит, а потом — можно штампованную механику и с АЛИ заказать.

USB-устройство


Хоть в последнее время я активно и продвигал PSoC, в педали его использовать — неслыханное расточительство. Оптимальным решением считаю макетную плату на базе STM32F103C8T6, которую сейчас можно заказать на ALI Express примерно за 120 рублей (включая доставку).

dbkuerxaai9tfg8t6bjymx2s9xc.jpeg

Можно, конечно, порассуждать, что есть и более дешёвые платы на базе AVR, но разница в цене там не существенная, а время на разработку, за счёт отладки через JTAG, у STM32 существенно меньше. Час времени (пусть даже домашнего) — он тоже кое-чего стоит. Так что по суммарному критерию «цена устройства + цена трудозатрат» STM32F103 — это оптимальный вариант для данной задачки. Тем более, что у меня таких макеток полтора десятка как раз на такой случай припасено, поэтому время ожидания доставки теперь для меня равно нулю.

Я порылся в сети в поисках готовых USB-клавиатур на базе этого кристалла. На Хабре нашёл статью, как сделать USB-мышь. Но мне нужна клавиатура. На тему клавиатур нашёл много умных советов на форумах (но хотелось чего-то готового) и пару каких-то готовых, но сложных проектов на GitHub (а хотелось чего-то понятного). Но кто ищет, тот всегда найдёт. Вот замечательная статья, в которой есть всё, что нужно (хоть в коде и имеется, не побоюсь этого нерусского слова, потенциальный DeadLock, но код я и свой напишу). Главное — в ней понятно расписаны все шаги, как из готовой «рыбы», сделанной в Cube MX, получить именно USB-клавиатуру. Единственное, чего не сделал автор, не опубликовал коды клавиш. Добавим их сюда:

ij0bkwmzj94ve2cqynqsn25ms3y.jpeg

Ложка дёгтя состоит в том, что в современной версии Cube MX имена файлов, функций и даже констант чуть отличаются от указанных в той статье, но по аналогии всё находится быстро. Я мог бы написать новый вариант текста, но где гарантия, что разработчики не изменят всё снова? Они это любят делать. В целом, можете посмотреть аналоги в моём примере.

Итак. Я создал базовый проект в Cube MX, внёс все изменения, которые рекомендует упомянутая выше статья. Что дальше? Дальше добавляем описание порта кнопки. Я выбрал PB12 (чисто потому, что соответствующий контакт расположен на углу макетки). Так как я всегда работаю с железом через библиотеку mcucpp Константина Чижова, убираем макроопределение »-C99» в свойствах проекта, main.c переименовываем в main.cpp, после чего в добавляем объявление:

typedef Mcucpp::IO::Pb12 pedal1;

В функцию main () добавляем инициализацию порта (включение тактирования порта, установка направления ножки, включение подтяжки):

   
        pedal1::ConfigPort::Enable();
        pedal1::SetDirRead();
        pedal1::SetPullUp (pedal1::Port::PullUp);


Собственно, с инициализацией всё. Основное тело я решил построить по образу и подобию Verilog процесса. Не знаю почему, просто захотелось. Процесс обычно вызывается по тактовым импульсам. Я решил, что в качестве тактов прекрасно берутся системные тики. Поэтому штатную функцию обработчика системных тиков в файле stm32f1xx_it.c дополнил так:

-hqyrk8ex2a6vmby9pa16sqgwdi.png

То же самое текстом:
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  HAL_SYSTICK_IRQHandler();
  /* USER CODE BEGIN SysTick_IRQn 1 */
        TickProcess();

  /* USER CODE END SysTick_IRQn 1 */
}



Саму же функцию TickProcess () я поместил в main. cpp. Сначала рассмотрим её целиком, затем — по частям.

Вот функция полностью:
uint8_t report [8] = {0,0,0,0,0,0,0,0};
uint32_t* pReport = (uint32_t*)report;
extern "C"
void TickProcess()
{
        // Число должно нацело делиться на 20!!!
        static const int shortTime = 700;        // Время короткого щелчка по кнопке
        static const int longTime = 2000; // Время срабатывания автоповтора
        static int localTick = 0;
        static int tickDuringButton = 0;
        static bool bButtonStatePrev;
        bool bButtonState = pedal1::IsSet();
                
        // кнопку только что нажали
        if ((!bButtonState) && bButtonStatePrev)
        {
                tickDuringButton = 0;
        }
        // Кнопку только что отпустили
        if (bButtonState && (!bButtonStatePrev))
        {
                // Её удерживали в течение короткого времени
                // Но больше, чем 50 мо (антидребезг)
                if ((tickDuringButton >100)&&(tickDuringButton < shortTime))
                {
                        // Кладём код пробела
                        report [2] = 0x2C;
                }
        }
        // Кнопку давно удерживают
        if ((!bButtonState) && (!bButtonStatePrev))
        {
                if ((tickDuringButton == shortTime)||(tickDuringButton > longTime))
                {
                        // Кладём Shift+Влево
                                report [0] = 2;         // Shift
                                report [2] = 0x50;      // Влево
                }
        } 

        // Имитируем автомат Верилоговский
        bButtonStatePrev = bButtonState;
        tickDuringButton += 1;
        
        if (localTick++ % 20 == 0)
        {
                        USBD_HID_SendReport (&hUsbDeviceFS,report,sizeof(report));
                        pReport [0] = 0;
                        pReport [1] = 0;
        }
                
}



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

   static int localTick = 0;


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

   if (localTick++ % 20 == 0)
        {
                        USBD_HID_SendReport (&hUsbDeviceFS,report,sizeof(report));
                        pReport [0] = 0;
                        pReport [1] = 0;
        }


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

   bool bButtonState = pedal1::IsSet();


и имеется переменная, в которой хранится её предыдущее состояние (как мы помним, предыдущее состояние — это состояние кнопки при предыдущей обработке прерывания от таймера 1 мс):

   static bool bButtonStatePrev;


Нажатая кнопка даёт значение false, отжатая — true. Таким образом, всегда можно сделать вывод о динамическом состоянии кнопки:

bButtonState bButtonStatePrev Состояние
true true Долговременно отпущена
true false Отпущена на предыдущем такте
false true Нажата на предыдущем такте
false false Долговременно нажата


Мы вводим две константы. Одна задаёт время кратковременного нажатия. Если нажатие длилось менее 700 мс, то оно кратковременное. Вторая задаёт время, когда включается автоповтор. Если просто после семисот миллисекунд начать слать код клавиши, то плеер начнёт мотать слишком резко. Это выявлено опытным путём. Поэтому логика работы такова: через 700 мс посылается одиночный код «Shift+Влево», после чего пользователю даётся возможность отпустить педаль. Если педаль продолжают удерживать, то со второй секунды уже начинается постоянное посылание этого кода до тех пор, пока педаль не отпустят.

   const int shortTime = 700;       // Время короткого щелчка по кнопке
        const int longTime = 2000; // Время срабатывания автоповтора


Время, на протяжении которого кнопка находится в нажатом состоянии, хранится в переменной:

   static int tickDuringButton = 0;


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

   // кнопку только что нажали
        if ((!bButtonState) && bButtonStatePrev)
        {
                tickDuringButton = 0;
        }


Если кнопку только что отпустили, то проверяем время, сколько она была нажата. Если совсем чуть-чуть (сейчас вписано «менее 100 мс») — это дребезг. Это не считается. Игнорируем такое нажатие. Если больше, чем длительность короткого нажатия, тоже ничего не делаем, длинные нажатия обрабатываются ниже. Если же в пределах короткого нажатия — сформируем в буфере репорт с пробелом (который будет отправлен, когда придёт время для этого):

   // Кнопку только что отпустили
        if (bButtonState && (!bButtonStatePrev))
        {
                // Её удерживали в течение короткого времени
                // Но больше, чем 100 мо (антидребезг)
                if ((tickDuringButton >100)&&(tickDuringButton < shortTime))
                {
                        // Кладём код пробела
                        report [2] = 0x2C;
                }
        }


Если кнопку давно удерживают, то мы посылаем «Shift+Влево» в двух случаях:

  • кнопку удерживают ровно 700 мс (одиночная посылка);
  • кнопку удерживают более 2-х секунд (посылка в каждом последующем репорте, пока не отпустят кнопку).
   // Кнопку давно удерживают
        if ((!bButtonState) && (!bButtonStatePrev))
        {
                if ((tickDuringButton == shortTime)||(tickDuringButton > longTime))
                {
                        // Кладём Shift+Влево
                                report [0] = 2;         // Shift
                                report [2] = 0x50;      // Влево
                }
        } 


Вот, собственно, и всё. Больше этот код ничем не отличается от той «рыбы», которую нам сделал Cube MX. Собираем, прошиваем… Нет, без ошибок невозможно (здесь-то они уже все отловлены, но у меня исходно без них не обошлось), выявляем их за 10 минут через JTAG отладку (привет AVRу), шьём, радуемся…

Альтернативная конструкция


В целом, подобные вещи полезны и без балалайки (или иного просмотра видеоуроков). В частности, я люблю читать новости, лёжа в кровати, с ноутбуком на животе. Чтобы скроллировать текст, приходится всё время держать правую руку согнутой, а это приводит к болям в локте. Поэтому я давно мечтал о большой красной кнопке, которой можно было бы скроллировать страницы, не сгибая руку. Собственно, её можно сделать из тех же комплектующих, что и педаль, заменив мебельный болт на небольшое количество термоклея. Итого: кнопка PSW13, макетка STM32F103C8T6, пластик для 3D принтера, термоклей. Ну, и в «прошивке» я заменил коды на «PgDn» по короткому нажатию и «Вверх» по длинному.

t-pkllvtvpapghfjaulfjgzevcw.jpeg

marcqa0tmbl5iypd5sb3al1zvlw.png

Заключение


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

Механика может быть найдена на Ali Express по слову Foot Switch либо может быть напечатана на 3D-принтере. В качестве электроники может быть использована макетная плата STM32F103C8T6. «Прошивка» делается менее, чем за час на базе «рыбы», созданной средой Cube MX от производителя контроллеров STM32. Дополнительный код занимает несколько экранов текста (для разрешения 2K — один экран).

В целом, всё устройство (механика + «прошивка») было полностью спроектировано за один вечер, плюс около четырёх часов ушло на печать механики на 3D-принтере.

Готовые STL-файлы для печати педали можно скачать здесь (элемент Top желательно опустить на 0.4 мм, чтобы избежать большого количества поддержек: поверхность получилась слегка скруглённой, опустив модель, мы сделаем печатную часть плоской).

STL-файлы «большой красной кнопки» можно скачать здесь.

Готовые исходные файлы проекта и HEX-файл «прошивки» можно скачать здесь.

Дата публикации этой статьи выбрана не случайно. Консилиум постановил, что на серьёзном сайте публиковать статью про такие вещи лучше именно в этот день. Но на самом деле, если посмотреть ролики, найденные по словосочетаниям «Алексей Архиповский», «Дмитрий Калинин», «Клюква шоу», «Алексей Коденко», то станет ясно, что академическая балалайка — она как ассемблер. И там, и там сочетаются абсолютная простота и бешеная выразительная мощь. А по словосочетанию «Сергей Воронцов» можно понять, как научиться этим инструментом пользоваться.

© Habrahabr.ru