1. Как появилась идея?
Всем привет, совсем недавно я начал изучать протокол USB на STM32F103C8, а именно HID-устройства. Я такой человек, который не сильно любит теорию, но обожает учиться всему на практике, поэтому я тут же начал думать над будущим проектом. И я вспомнил, что совсем недавно заказал себе wifi модуль — ESP8266.
И тут ко мне пришла идея: почему бы не объединить STM32f103c8 и ESP8266 в одно устройство — UsbDeviceHacker (так я его назвал). Моя идея заключалось в том, чтобы управлять мышкой и клавиатурой дистанционно по api, который будет работать на веб сервере, запущенном на ESP. Соответственно, ESP будет выступать в роли master устройства, а STM32 в роли slave.
Многие могут спросить: почему не сделать все сильно проще и вместо wifi использовать bluetooth, однако использование wifi будет намного интереснее, потому что наш веб сервер будет разворачиваться в локальной сети, и, соответственно, каждое устройство в ней сможет управлять девайсом просто отправив HTTP запрос на нужный ip.
2. Модель проекта
Перед началом реализации нужно описать модель работы устройства, начну с ESP8266. Wifi модуль подключается к внешней точке доступа, и создает веб сервер в ее локальной сети. В его api 8 endpoint (POST), каждый их которых отвечает за свой функционал.
Точки /mouse
и /keyboard
позволяют напрямую обращаться к мышке и клавиатуре, т.е. использовать согласно их официальным документациям. Точка /animation
уже использует готовый функционал, который облегчает работу с устройством.
После отправки корректного запроса ESP8266 формирует посылку и отправляет ее по UART прямо к STM32F103C8. Blue pill принимает данные и проверяет их на корректность, если все хорошо, то она отправляет report в USB.
Полную архитектуру проекта можно посмотреть в miro.
3. Реализация ESP8266
Прошивку для ESP8266 я писал, используя PlathormIO, в Visual Studio Code. Я не буду сильно останавливаться на объяснении кода, а просто расскажу все в общем виде, иначе статья может сильно растянуться. В любом случае вы сможете все сами просмотреть, репозиторий публичный, вот ссылка.
В целом структура проекта выглядит так :
Рабочую папку я проектировал следующим образом: она состоит из 4 папок: configs, controllers, modules, routes, и из файлов main.h
и main.cpp
. Функция InitRouter()
создает эндпоинты с помощью библиотеки ESP8266WebServer.h
, и перенаправляет их на контроллеры. Для контроллеров вместо классов я решил использовать namespace, просто потому что так понятнее и удобнее.
void InitRouter(){ server.on("/mouse/set", Mouse::set); server.on("/mouse/remove", Mouse::remove); server.on("/mouse/click", Mouse::click); server.on("/keyboard/set", Keyboard::set); server.on("/keyboard/remove", Keyboard::remove); server.on("/keyboard/click", Keyboard::click); server.on("/animation/set", Animation::set); server.on("/animation/remove", Animation::remove); }
И далее все основные действия происходят в контроллерах. Вот пример контроллера Animation.h
:
Для парсинга JSON я использую библиотеку ArduinoJson.h
. Она очень удобная и сильно облегчает работу, с ней можно: легко парсить, фильтровать данные и самому создавать JSON объекты.
Посылка для STM32 формируется в массиве data
и отправляется с помощью функции SendDataWithWait(uin8_t *data, uint8_t len)
класса serial
. Для проверки корректности данных в последний байт посылки добавляется контрольная сумма crc8
.
string SendDataWithWait(uint8_t *data, uint16_t len){ uint8_t data_out[len+2] = {HEADER,}; for(int i = 0; i<len; i++){ data_out[i+1] = data[i]; } data_out[len+1] = crc8(data_out, len+1); Serial.write(data_out, len+2); if(AwaitResponse()){ if (status){ status = false; return "ok"; } return "error"; } return "timeout"; }
ESP8266 ждет ответа от STM32 в функции AwaitResponse()
, после чего отправляет ответ клиенту. Вся работа с клиентской частью реализована в файле ClientProcessingModule.h
.
Одна из проблем при создании веб сервера — это его динамический ip. При подключении к роутеру или модему, устройству выдается ip, который меняется со временем. Из-за этого приходится постоянно подключать ESP8266 к сериал порту и смотреть его вручную. Решением проблемы может быть два варианта: 1-ый, если устройство подключается к роутеру можно настроить статический ip; 2-ой отправлять динамический ip на вебсервер со статическим ip.
Я же на данный момент не пользуюсь ни одним из решений, просто потому что пока мне это не нужно.
4. Подключение
Для начала работы с STM32 нужно разобраться с подключением ESP8266 к ней. Т.к blue pill будет подключаться по USB, то питаться ESP будет от нее. Пины VCC и СH_EN подключаются к пину 3.3V на STM32, GND к GND, RX к B10, TX к B11 и на этом все.
На STM32 пины B10 и B11 являются TX и RX USART3 соответственно.
5. Реализация STM32f103C8
Прошивку для STM я писал в CubeIDE. Код для нее весьма простой, он написан полностью на С. Из протоколов подключены UASRT3(HAL), по которому принимаются запросы от ESP, и, собственно, USB, который настроен как HID-device. Также добавлен светодиод на PC13.
Структура проекта состоит из 3 папок: inits, modules, controllers. Данные с USART3 принимаются в файле UsartController.c
, в HAL_UART_RxCpltCallback
, причем принимаются они по одному и собираются в массиве buffer
, после чего вызывается функция ParsingData
, которая проверяет на корректность, с помощью crc8
, и заполняет структуру Action
проверенными данными.
После приема посылки срабатывает нужный case
в main.c
, в котором запускается функция обработки данных, где и происходит основная работа. Далее данные в зависимости от девайса и команды формируют report, который отправляется по USB.
Для того чтобы отправлять репорты и мышки, и клавиатуры одновременно нужно в функцию HID_MOUSE_ReportDesc
, которая находиться в файле usbd_hid.c
, прописать следующие инструкции:
0x05, 0x01, /* Usage Page (Generic Desktop) */ 0x09, 0x02, /* Usage (Mouse) */ 0xA1, 0x01, /* Collection (Application) */ 0x09, 0x01, /* Usage (Pointer) */ 0xA1, 0x00, /* Collection (Physical) */ 0x85, 0x01, /* Report ID */ 0x05, 0x09, /* Usage Page (Buttons) */ 0x19, 0x01, /* Usage Minimum (01) */ 0x29, 0x03, /* Usage Maximum (03) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (0) */ 0x95, 0x03, /* Report Count (3) */ 0x75, 0x01, /* Report Size (1) */ 0x81, 0x02, /* Input (Data, Variable, Absolute) */ 0x95, 0x01, /* Report Count (1) */ 0x75, 0x05, /* Report Size (5) */ 0x81, 0x01, /* Input (Constant) ;5 bit padding */ 0x05, 0x01, /* Usage Page (Generic Desktop) */ 0x09, 0x30, /* Usage (X) */ 0x09, 0x31, /* Usage (Y) */ 0x15, 0x81, /* Logical Minimum (-127) */ 0x25, 0x7F, /* Logical Maximum (127) */ 0x75, 0x08, /* Report Size (8) */ 0x95, 0x02, /* Report Count (2) */ 0x81, 0x06, /* Input (Data, Variable, Relative) */ 0xC0, 0xC0,/* End Collection,End Collection */ // 0x09, 0x06, /* Usage (Keyboard) */ 0xA1, 0x01, /* Collection (Application) */ 0x85, 0x02, /* Report ID */ 0x05, 0x07, /* Usage (Key codes) */ 0x19, 0xE0, /* Usage Minimum (224) */ 0x29, 0xE7, /* Usage Maximum (231) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ 0x75, 0x01, /* Report Size (1) */ 0x95, 0x08, /* Report Count (8) */ 0x81, 0x02, /* Input (Data, Variable, Absolute) */ 0x95, 0x01, /* Report Count (1) */ 0x75, 0x08, /* Report Size (8) */ 0x81, 0x01, /* Input (Constant) ;5 bit padding */ 0x95, 0x05, /* Report Count (5) */ 0x75, 0x01, /* Report Size (1) */ 0x05, 0x08, /* Usage Page (Page# for LEDs) */ 0x19, 0x01, /* Usage Minimum (01) */ 0x29, 0x05, /* Usage Maximum (05) */ 0x91, 0x02, /* Output (Data, Variable, Absolute) */ 0x95, 0x01, /* Report Count (1) */ 0x75, 0x03, /* Report Size (3) */ 0x91, 0x01, /* Output (Constant) */ 0x95, 0x06, /* Report Count (1) */ 0x75, 0x08, /* Report Size (3) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x65, /* Logical Maximum (101) */ 0x05, 0x07, /* Usage (Key codes) */ 0x19, 0x00, /* Usage Minimum (00) */ 0x29, 0x65, /* Usage Maximum (101) */ 0x81, 0x00, /* Input (Data, Array) */ 0xC0 /* End Collection,End Collection */
Где report id мышки будет 0x01, клавиатуры — 0x02, т.е. данные например для мыши надо будет отправлять в таком формате:
uint8_t data_out[5] = {0x01, 0, 0, 0, 0}; // первый байт - report id USBD_HID_SendReport(&hUsbDeviceFS, data_out, 5);
Отдельное внимание нужно уделить анимациям, потому что они добавляют готовый функционал в устройство. На данный момент доступно 3 анимации:
1) плавное движение мышки — мышка плавно двигается по оси x или y, можно задать направление и скорость движения от 1 до 10 включительно;
2)автоматический набор текста (работают только латинские символы, цифры и пробел) — поочередный набор текста, можно задать текст и повторы его написания, например набирать «Hello World» каждые 10 секунд.
3)движение мышки по кругу — просто мышка движется по кругу, можно задать скорость от 1 до 10 включительно и радиус движения.
Также 1 и 3 анимациям можно задавать статусы нажатия кнопок мыши, например пусть мышка движется по кругу с нажатой ПКМ или ЛКМ, или все сразу. Всего доступно три статуса: ЛКМ, ПКМ и кнопка у колесика мыши.
Документация по api тут.
6. Тесты
Теперь дошли до самого интересного, а именно к тестам. Подключим устройство к пк (ноутбуку) и дождемся пока ESP подключиться к модему. Я раздал точку доступа с телефона, и также еще подключился к ней с ноутбука, чтобы отправлять запросы с него. В итоге запускать я буду с ноутбука, а останавливать с телефона, отправляя запрос на endpoint /remove
.
Вот ссылка видео с тестами:
На этом и все, как я считаю, получился очень интересный проект, который точно заслуживал траты сил и времени на него. Оставляю ссылку на публичный git репозиторий, в котором вы сможете ознакомиться со всеми деталями. Подписывайтесь на мой тг канал, в котором я публикую информацию о своих проектах и о жизни в целом. Всем желаю удачи, надеюсь, еще увидимся.
ссылка на оригинал статьи https://habr.com/ru/articles/855750/
Добавить комментарий