USB панель управления космическим кораблем своими руками

  • Tutorial

Здравствуйте, дорогие читатели!

Пришла мне тут одна идейка, а не собрать ли пульт управления космическим кораблем. На USB. С нативной поддержкой драйверов. Custom HID. Чтобы воткнул и всё работает, без всяких танцев и бубнов. В итоге, получился некий монструозный «геймпад» для космических симуляторов. В общем, судите сами.

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

Прикинув рабочую поверхность моего стола, выбрал размеры пульта по ширине и глубине 500*300 мм. А пошарив по строительным складам и магазинам в поисках стройматериалов, выбрал высоту 125мм. В итоге приобрел лист 4 мм фанеры, рейки 20*12 мм и доску 120*20 мм.

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



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

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


Остальная мелочь проблем не вызвала. Контроллер выбрал STM32. В качестве АЦП для джойстиков 16-битные ADS1118. Также был куплен блок питания на 12 В. Собственно такое напряжение объясняется тем, что мне в руки попал указатель уровня топлива от «шахи», который я тоже хотел сюда же пристроить.


На фото блок питания, стабилизаторы на 5 и 3.3 В, STM32, MCP23017, ADS1118

Контроллер 100-выводный STM32F407VET6, к нему подключается:

2 селектора на 4 положения
1 переменный резистор
2 переключателя осей
4 основных оси
2 вспомогательных оси
2 регулирующих оси
4 клавишных переключателя по 2 кнопки каждый
20 кнопок со светодиодами
4 основных выключателя со светодиодами
2 кнопки-грибка со светодиодами
2 кнопки таймеров
3 выключателей со светодиодами
13 переключателей
2 ADS1118 (АЦП)
4 MAX7219 (8-знаковые LED-дисплеи)
2 TM1637 (дисплей-часы)
1 PCF8574 (расширитель I/O, воткнут в знакосинтезирующий дисплей)


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

Чего-то многовато будет для сотни ног МК, решил я, и добавил сюда же расширители входов-выходов: четыре штуки MCP23017, на 16 входов или выходов каждый. Забегая вперед, скажу, что задержка опроса входов у расширителя получилась около 0.13 мс на одну микросхему, при скорости шины I2C 400кГц. То есть это с запасом перекрывает минимальное время опроса USB в 1 мс.

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

АЦП ADS1118 несколько не успевает за скоростью USB, заявленная производительность у него составляет максимально 820 отсчетов в секунду, что равно 1.2 мс, при этом, он имеет несколько входов, которые внутри через мультиплексор уже подключены к АЦП. Я использовал 2 входа на одну микросхему, поэтому время обновления значений составляет 2.4 мс. Плоховато, но что поделаешь? К сожалению на али других 16-битных быстрых АЦП нет.


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

Программа CPU написана в стиле программы ПЛК. Никаких блокирующих запросов. Ядро периферию не ждет, не успела и хрен с ним, на следующем цикле опросит. Никаких RTOS в проекте тоже нет, попробовал, уперся в минимальное время ожидания задачи 1 мс — получается медленно, если нам надо отправлять данные по USB с частотой 1 мс. В итоге понял, что буду использовать ОС без osDelay(), а тогда зачем RTOS? Просто, как в ПЛК, располагать инструкции программы один за другим внутри бесконечного цикла вполне достаточно.

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

Девайс у нас будет USB custom HID. HID есть mouse, keyboard, gamepad, joystick, какие-то еще. А есть custom. Всё это не требует драйверов от операционной системы. Точнее, они уже написаны разработчиком. Кастомный девайс хорош тем, что мы сами комбинируем возможности всех вышеназванных устройств по своему усмотрению.

Вообще, USB штука очень сложная, имеет мануал почти в тысячу страниц и с наскока её не взять. Кто не хочет читать тяжелые мануалы, есть великолепная статья USB in a NutShell, погуглите. Также у неё есть перевод. Всё же попытаюсь некоторые моменты объяснить «на пальцах».

USB — пакетированная передача данных с кучей уровней и абстракций. Девайс у нас — никаких данных запрашивать не может, всю передачу инициализирует хост. Хост пишет и запрашивает данные в так называемые конечные точки, физически это некоторые буферы в памяти МК. Чтобы хост понимал по каким конечным точкам можно писать, а какие конечные точки читать и какие данные он может интерпретировать как кнопки и оси нашего устройства и, вообще, что это тут у нас за устройство, в начале коннекта он запрашивает дескрипторы девайса. Этих дескриптеров много и составлять их сложно и можно как угодно, и ошибиться тоже, где угодно. Физически они представляют собой массив байт.

На самом деле, CubeMX сгенерирует код инициализации Custom HID лучше нас.





Прошу обратить внимание на последней картинке под цифрой 3. Это размер дескриптора в байтах, который и определяет какие оси и кнопки есть на нашем девайсе. Генерируется этот дескриптор в программе HID Descriptor Tool. Там есть несколько примеров для самостоятельного изучения. Вообще, вот мой дескриптор. Там пока отсутствуют данные для дисплеев, для простоты понимания, но присутствуют все кнопки и оси джойстиков. Его нужно поместит в файл usbd_custom_hid_if.c. По-умолчанию, куб этот дескриптор делает пустым.

HID Descriptor (размер 104 байта)
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */
	0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
	0x15, 0x00,                    // LOGICAL_MINIMUM (0)
	0x09, 0x04,                    // USAGE (Joystick)
	0xa1, 0x01,                    // COLLECTION (Application)
	0x05, 0x02,                    //   USAGE_PAGE (Simulation Controls)
	0x09, 0xbb,                    //   USAGE (Throttle)
	0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
	0x27, 0xff, 0xff, 0x00, 0x00,  //   LOGICAL_MAXIMUM (65535)
	0x75, 0x10,                    //   REPORT_SIZE (16)
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x05, 0x01,                    //   USAGE_PAGE (Generic Desktop)
	0x09, 0x01,                    //   USAGE (Pointer)
	0xa1, 0x00,                    //   COLLECTION (Physical)
	0x09, 0x30,                    //     USAGE (X)
	0x09, 0x31,                    //     USAGE (Y)
	0x95, 0x02,                    //     REPORT_COUNT (2)
	0x81, 0x02,                    //     INPUT (Data,Var,Abs)
	0xc0,                          //   END_COLLECTION
	0x05, 0x01,                    //   USAGE_PAGE (Generic Desktop)
	0x09, 0x32,                    //   USAGE (Z)
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x33,                    //   USAGE (Rx)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x34,                    //   USAGE (Ry)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x35,                    //   USAGE (Rz)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x36,                    //   USAGE (Slider)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x39,                    //   USAGE (Hat switch)
	0x15, 0x01,                    //   LOGICAL_MINIMUM (1)
	0x25, 0x08,                    //   LOGICAL_MAXIMUM (8)
	0x35, 0x00,                    //   PHYSICAL_MINIMUM (0)
	0x46, 0x0e, 0x01,              //   PHYSICAL_MAXIMUM (270)
	0x65, 0x14,                    //   UNIT (Eng Rot:Angular Pos)
	0x75, 0x08,                    //   REPORT_SIZE (8)
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x05, 0x09,                    //   USAGE_PAGE (Button)
	0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
	0x29, 0x40,                    //   USAGE_MAXIMUM (Button 64)
	0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
	0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
	0x75, 0x01,                    //   REPORT_SIZE (1)
	0x95, 0x40,                    //   REPORT_COUNT (64)
	0x55, 0x00,                    //   UNIT_EXPONENT (0)
	0x65, 0x00,                    //   UNIT (None)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION	             */
};


По сути, его можно составлять как угодно, сначала задаются параметры USAGE PAGE и необходимый USAGE, например ось USAGE (Throttle), а затем после слова INPUT (Data,Var,Abs) система будет считать, что у нас есть ось «Газ». Размерность переменной оси и их кол-во задается параметрами LOGICAL_MAXIMUM, MINIMUM, REPORT_SIZE, REPORT_COUNT, которые должны стоять перед INPUT.

Более подробно про эти параметры, а также, что такое (Data,Var,Abs), можно прочесть в Device Class Definition for Human Interface Devices (HID) v1.11.

Ниже приведен пример инициализации оси Throttle из моего дескриптора. В данном примере Throttle имеет диапазон значений 0-65535, что соответствует одной переменной uint16_t.

	0x05, 0x02,                    //   USAGE_PAGE (Simulation Controls)
	0x09, 0xbb,                    //   USAGE (Throttle)
	0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
	0x27, 0xff, 0xff, 0x00, 0x00,  //   LOGICAL_MAXIMUM (65535)
	0x75, 0x10,                    //   REPORT_SIZE (16)
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)

А да, еще, допустим, можно не писать LOGICAL_MAXIMUM, MINIMUM, REPORT_SIZE, REPORT_COUNT каждый раз, хост будет определять это значение по предыдущему параметру. Это иллюстрируют оси, которые идут один за другим, без указания размера и кол-ва:

	0x09, 0x32,                    //   USAGE (Z)
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x33,                    //   USAGE (Rx)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x34,                    //   USAGE (Ry)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x35,                    //   USAGE (Rz)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x36,                    //   USAGE (Slider)

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

#pragma pack(push, 1)
typedef struct _myReportStruct
{
  uint16_t Throttle;
  uint16_t X;
  uint16_t Y;
  uint16_t Z;
  uint16_t Rx;
  uint16_t Ry;
  uint16_t Rz;
  uint16_t Slider;
  uint8_t Hat; // 0 - none, 1 - up, 2 - up-right, 3 - right, 4 - down-right...
  uint32_t Buttons1; // 32 buttons of 1 bit each
  uint32_t Buttons2; // 32 buttons of 1 bit each
}myReportStruct;
#pragma pack(pop)

volatile myReportStruct Desk;

Эту структуру можно посылать хосту функцией

USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t *) &Desk, sizeof(Desk));

Первый параметр — хендл USB, он у нас уже создан кубом. Возможно, понадобится подключить include-ом необходимый файл, где впервые инициализируется этот хендл и прописать extern USBD_HandleTypeDef hUsbDeviceFS;, чтобы с ним можно было работать. Второй параметр — указатель на нашу структуру и третий — размер структуры в байтах.

После заливки и прошивки контроллера можно заметить, что чего-то USB медленно шевелится. Данные с нашей панели обновляются не быстро. Чтобы было быстро, в файлах usbd_customhid.h нужно поменять #define CUSTOM_HID_EPIN_SIZE на максимальное значение 0x40, #define CUSTOM_HID_EPOUT_SIZE тоже поставить 0x40. В файле usbd_customhid.c найти комментарии в дескрипторе эндпойнтов "/* bInterval: Polling Interval (20 ms) */" и поменять байт дескриптора на 0x01 для каждого эндпойнта, всего два раза. Что будет соответствовать 1 мс обмена данными.


Должно получиться нечто подобное. Стандартное устройство без установки каких-либо драйверов

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

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

Дескриптор HID девайса значительно разросся. Здесь я уже применил параметры Report ID, чтобы не забивать буфер приема/передачи и эндпойнты полными данными и различать, что за телеграмма нам пришла. Report ID представляет собой байт uint8_t со значением, который идет вначале телеграммы. Значением мы сами задаем в дескрипторе HID девайса.

CUSTOM_HID_ReportDesc_FS
//AXIS
	0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
	0x09, 0x04,                    // USAGE (Joystick)
	0xa1, 0x01,                    // COLLECTION (Application)28
	0x05, 0x02,                    //   USAGE_PAGE (Simulation Controls)
	0x09, 0xbb,                    //   USAGE (Throttle)
	0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
	0x27, 0xff, 0xff, 0x00, 0x00,  //   LOGICAL_MAXIMUM (65535)
	0x75, 0x10,                    //   REPORT_SIZE (16)
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x85, 0x01,					   //   REPORT_ID (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x05, 0x01,                    //   USAGE_PAGE (Generic Desktop)
	0x09, 0x01,                    //   USAGE (Pointer)
	0xa1, 0x00,                    //   COLLECTION (Physical)
	0x09, 0x30,                    //     USAGE (X)
	0x09, 0x31,                    //     USAGE (Y)
	0x95, 0x02,                    //     REPORT_COUNT (2)
	0x81, 0x02,                    //     INPUT (Data,Var,Abs)
	0xc0,                          //   END_COLLECTION
	0x05, 0x01,                    //   USAGE_PAGE (Generic Desktop)
	0x09, 0x32,                    //   USAGE (Z)
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x33,                    //   USAGE (Rx)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x34,                    //   USAGE (Ry)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x35,                    //   USAGE (Rz)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)
	0x09, 0x36,                    //   USAGE (Slider)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)

	//HAT
	0x09, 0x39,                    //   USAGE (Hat switch)
	0x15, 0x01,                    //   LOGICAL_MINIMUM (1)
	0x25, 0x08,                    //   LOGICAL_MAXIMUM (8)
	0x35, 0x00,                    //   PHYSICAL_MINIMUM (0)
	0x46, 0x0e, 0x01,              //   PHYSICAL_MAXIMUM (270)
	0x65, 0x14,                    //   UNIT (Eng Rot:Angular Pos)
	0x75, 0x08,                    //   REPORT_SIZE (8)
	0x95, 0x01,                    //   REPORT_COUNT (1)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)

	//Buttons
	0x05, 0x09,                    //   USAGE_PAGE (Button)
	0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
	0x29, 0x40,                    //   USAGE_MAXIMUM (Button 64)
	0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
	0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
	0x75, 0x01,                    //   REPORT_SIZE (1)
	0x95, 0x40,                    //   REPORT_COUNT (64)
	0x55, 0x00,                    //   UNIT_EXPONENT (0)
	0x65, 0x00,                    //   UNIT (None)
	0x81, 0x02,                    //   INPUT (Data,Var,Abs)

	//LEDs
	0x85, 0x02,					   // REPORT_ID (2)
	0x05, 0x08,                    // USAGE_PAGE (LEDs)
	0x09, 0x4B,                    // USAGE (Generic Indicator)
	0x95, 0x40,                    // REPORT_COUNT (16)
	0x91, 0x02,                    // OUTPUT (Data,Var,Abs)
	0xc0,                          // END_COLLECTION

	//LCD Displays
        0x05, 0x14,                    // USAGE_PAGE (Alphnumeric Display)
	0x09, 0x01,                    // USAGE (Alphanumeric Display)
	0x15, 0x00,                    // LOGICAL_MINIMUM (0)
	0xa1, 0x02,                    // COLLECTION (Logical)

	0x09, 0x32,                    //   USAGE (Cursor Position Report)
	0xa1, 0x02,                    //   COLLECTION (Logical)
	0x85, 0x04,                    //     REPORT_ID (4)
	0x75, 0x08,                    //     REPORT_SIZE (8)
	0x95, 0x01,                    //     REPORT_COUNT (1)
	0x25, 0x13,                    //     LOGICAL_MAXIMUM (19)
	0x09, 0x34,                    //     USAGE (Column)
	0xb1, 0x22,                    //     FEATURE (Data,Var,Abs,NPrf)
	0x25, 0x03,                    //     LOGICAL_MAXIMUM (3)
	0x09, 0x33,                    //     USAGE (Row)
	0x91, 0x22,                    //     OUTPUT (Data,Var,Abs,NPrf)
	0xc0,                          //   END_COLLECTION

	0x09, 0x2b,                    //   USAGE (Character Report)
	0xa1, 0x02,                    //   COLLECTION (Logical)
	0x85, 0x05,                    //     REPORT_ID (5)
	0x95, 0x14,                    //     REPORT_COUNT (20)
	0x26, 0xFF, 0x00,              //     LOGICAL_MAXIMUM (255)
	0x09, 0x2c,                    //     USAGE (Display Data)
	0x92, 0x02, 0x01,              //     OUTPUT (Data,Var,Abs,Buf)
	0xc0,                          //   END_COLLECTION


    0x09, 0x24,                    // USAGE (Display Control Report)
    0x85, 0x06,                    // REPORT_ID (6)
    0x95, 0x01,                    // REPORT_COUNT (1)
    0x91, 0x22,                    // OUTPUT (Data,Var,Abs,NPrf)
	0xc0,                          // END_COLLECTION

	//LED Displays
        0x05, 0x14,                    // USAGE_PAGE (Alphnumeric Display)
	0x09, 0x01,                    // USAGE (Alphanumeric Display)
	0x15, 0x00,                    // LOGICAL_MINIMUM (0)
	0xa1, 0x02,                    // COLLECTION (Logical)

	0x09, 0x2b,                    //   USAGE (Character Report)
	0xa1, 0x02,                    //   COLLECTION (Logical)
	0x85, 0x07,                    //     REPORT_ID (7)
	0x75, 0x08,                    //     REPORT_SIZE (8)
	0x95, 0x28,                    //     REPORT_COUNT (40)
	0x26, 0xFF, 0x00,              //     LOGICAL_MAXIMUM (255)
	0x09, 0x2c,                    //     USAGE (Display Data)
	0x92, 0x02, 0x01,              //     OUTPUT (Data,Var,Abs,Buf)
	0xc0,                          //   END_COLLECTION

	//Other DATA
    0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)
    0x09, 0x01,                    // USAGE (Vendor Usage 1)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x85, 0x08,                    //   REPORT_ID (8)
    0x09, 0x01,                    //   USAGE (Vendor Usage 1)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x27, 0xff, 0xff, 0x00, 0x00,  //   LOGICAL_MAXIMUM (65535)
    0x75, 0x10,                    //   REPORT_SIZE (16)
    0x95, 0x0A,                    //   REPORT_COUNT (10)
    0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)

Обработка Output происходит в функции static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state), которая, по-умолчанию, находится в usbd_custom_hid_if.c.

static int8_t CUSTOM_HID_OutEvent_FS()
static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
{
  /* USER CODE BEGIN 6 */
  uint8_t dataReceiveArray[USBD_CUSTOMHID_OUTREPORT_BUF_SIZE];
  USBD_CUSTOM_HID_HandleTypeDef     *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;

  for (uint8_t i = 0; i < USBD_CUSTOMHID_OUTREPORT_BUF_SIZE; i++)
  {
	dataReceiveArray[i] = hhid->Report_buf[i];
  }

  if (dataReceiveArray[0] == 2) //report ID 2 leds
  {
       // если Report id == 2, то делаем что-то на основе данных в dataReceiveArray[1 + N], например, зажигаем LED 
  }

  if (dataReceiveArray[0] == 4) //report ID 4 cursor position
  {
       // если Report id == 4, то делаем что-то, например устанавливаем курсор на LCD 
  }

  if (dataReceiveArray[0] == 5) //report ID 5 display data
  {
     // если Report id == 5, то делаем что-то, например выводим данные с USB на LCD 
  }

  // и так далее, смотря сколько ID у нас в дескрипторе

  return (USBD_OK);
  /* USER CODE END 6 */
}


Осталось только написать программу на ПК, которая отправляет нужные репорты, чтобы рулить дисплеями. Впрочем, для проверки кода МК подойдет великолепная программа от ST: USB HID Demonstrator. Она позволяет слать репорты с ПК с каким угодно содержимым.


Тест LED дисплеев

На этом этапе я пока закончил. И не известно, начну ли снова.

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

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

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

Подробнее
Реклама

Комментарии 68

    +6
    Спасибо, интересно. Но не хватает видео с игрой на этом ПУ.
      +1
      Очень крутая идея! Даёшь симуляцию стыковки или посадки на Луну.
        0
        Постараюсь сделать на выходных.
          0

          Сделал. Извиняюсь за качество и звук. На LED дисплеи выводится сырая дата с джойстиков.
            0
            Очень здорово! Монитор может поставить повыше?
              0
              Какой знакомый глобус, аж компа захотелось немедленно включить!
        +1
        А для какого сима это все запиливалось?
          +4
          Подойдет для любого сима, к которому можно подключить джойстик или геймпад. Я чаще всего играю в KSP.
            0
            Попробуйте «Reentry — An Orbital Simulator»
              +1
              Ну тогда сразу Orbiter. Куда уж хардкорнее?
          +3
          Автор, остановись! Это начинается все невинно: там дощечка, тут фанерка, топливомер от «шахи», а через пару лет ты покупаешь гараж, потому что в квартиру это уже не помещается.

          www.creativesimulations.com/Cockpit%20Shell.htm
            +1
            А потом участок

              0
              Блин, круто! Надо такой кокпит для F/A-18 сделать.
                  +1
                  Что-то вроде такого?
                  image
                  Мои попытки построить нечто подобное разбились об недостаток свободного метража в жилище и активное сопротивление супруги. Вот ребенок был бы в восторге, это факт. С интересом и завистью смотрю на проекты подобных домашних кокпитов. Мечта идиота. В конце концов мужчина отличается от мальчика по большому счету только размерами и стоимостью игрушек. (На самом деле — нет)
                    0
                    Офигенно. Это под какой сим? С DCS заведётся? (Edit: увидел домен. Заведётся, конечно)

                    Я не представляю, например, как сделать отображение чего надо на MFCD и, тем более, например, на UFC или панели двигателя и топлива.
                      +1
                      Я когда начал задумываться о постройке своего «гнезда симмера» и пошел гуглить информацию просто офигел, насколько обширный рынок этого дела существует в США (в первую очередь) и в Европе. Куча контор, которые выпускают панельки, приборы, экраны, органы управления, и прочее для нужд замороченных симмеров, даже готовые киты есть из которых можно собрать полную кабину 737, только каркас нужно самому сделать. Крайне недешевое удовольствие, но качество изготовления попадается просто потрясающее, а сборка и подключение не представляет никаких сложностей. Пока остановился на сайтековских панельках для Цессны, вот таких saitek.pro/flight-simulation/pro-flight-panels которые просто собраны под мониторами в самопальный корпус. Не то, конечно, но уже интереснее.
                        +1
                        В DCS не надо делать изображение на MFD — оно уже сделано. Берётся самый обыкновенный дисплей (дополнительный к тому, что вы играете) (или несколько), выбираете мультимониторную конфигурацию, настраиваете (а потом производите более тонкую конфигурацию в каком-то LUA-файле; views.lua?). Дисплей этот берёте маленький (к примеру, 10"), встраиваете его в свою приборную панель, а вокруг размещаете кнопки.

                        Показания с прочих приборов идут через export.lua. «Обычно» этот export.lua общается по ip. На эту тему много чего есть. Примеры:
                        forums.eagle.ru/showthread.php?t=141096 — DCS-BIOS — «физические» кокпиты;
                        forums.eagle.ru/showthread.php?t=179525 — Icarus — «Виртуальные» кокпиты.
                          0
                          Некоторые игры, запущенные в полноэкранном режиме, сильно ограничивают работу второго монитора.

                          На самом деле в windows есть способы монопольного использования монитора (и даже десктоп можно поднять независимый) но это требует погружения в очень старое win api, не удивлюсь если оно уже давно deprecated.
                  +1
                  Вообще, USB штука очень сложная, имеет мануал почти в тысячу страниц и с наскока её не взять

                  Это вы еще спецификацию Bluetooth не смотрели, там около 5 тысяч страниц :)
                    +2
                    В качестве джойстиков, нашел вот такие.
                    Помогите найти их на Али, пожалуйста — как называются? и/или ссылку дайте…
                      +1
                      Joystick Potentiometer JH-D202X-R2/R4
                      0
                      а мягкий/жесткий упор есть на джойстиках, или фиксатор по крену, типа союзовского РУО?

                      Кстати, на Федерации уже одна ручка самолетного типа. Управление самой ручкой и «джойстиком» на ней под большим пальцем
                        0
                        Откуда информация? Если занимаетесь разработкой можете еще чем-то интересным поделиться с общественностью?
                          0
                          Да это, вообще-то, не секрет никакой tass.ru/kosmos/4130417
                            +1
                            Охренеть. Т.е. дублирования управления не будет. Надеюсь, этот гроб в таком виде никогда не полетит.
                              +1
                              С одной стороны меня это тоже удивляет. С другой стороны, вроде бы сейчас все так свои проекты делают. Ручное управление там нужно в крайне редких случаях, ломаться в этой ручке особенно нечему. Возможно, все не так уж страшно, как может показаться дилетанту.
                                0
                                Дублирование вообще то будет, хотя и не совсем классическое. А переход на одну ручку — чтоб в случае чего БИ мог той же ручкой управлять, но своей левой рукой.
                                PS там не гроб, достаточно просторно. На этом снимке экраны уже в опущенном состоянии — когда поднимают, места намного больше
                                  0
                                  Но ручка-то одна. Она не дублирована. Что они будут делать в случае её отказа? Только чинить в полёте.
                                  Следовательно — это гроб. Не во вместимости дело.
                                    0
                                    «ручка» съемная, и запасная примерно в 1,5 метрах хранится. Так что если отказа не будет прямо в 2-5 метрах от стыковочного, проблем никаких. Кстати, на Союзе тоже один действующий комплект РУО-РУД, так что это решение как минимум не хуже
                                      0
                                      Понял, был не прав.
                                      Так что если отказа не будет прямо в 2-5 метрах от стыковочного

                                      А если будет — перейдут на автомат. И то и другое сразу — не откажет.
                              0
                              В рамках разрешенного в принципе мог бы, мы в тренажере Федерации в пятницу только для руководства презентационные снимки делали в новых прототипах летных скафандров )))
                              Но тут пара троллей карму просаживает постоянно, из-за больших пауз приходится редко заходить
                                0
                                Ясно, успехов в вашей работе. Вообще сами как оцениваете настроение в команде, федерации быть?
                                  0
                                  Быть конечно, хотя кое-какие рекомендации по изменениям мы уже дали. А по срокам еще не ясно, от ЦПК заказов даже предварительно не было, только разработчик и мы пока пробуем его. ЦПК больше на Союзовские пока смотрит (замену Океану-5), хочет комбинированный тренажер, не только приводнения (на муромской платформе), но и этап сведения до контакта. Может после этого начнет интересоваться по Федерации.
                                    0
                                    Спасибо! Получил информации больше чем за 2 года слежения за всеми новостями…
                              0
                              а мягкий/жесткий упор есть на джойстиках, или фиксатор по крену, типа союзовского РУО?
                              Про упор затрудняюсь ответить, поскольку с настоящими джойстиками не знаком. Фиксатора нет, это самоцентрирующиеся джойстики, но вроде можно переделать, снять возвратную пружинку, но это скорее всего, будет уже не то.
                                +2
                                По союзовским в общих чертах описать не проблема — левый отвечает за движение (плоское перемещение, РУД), правый за ориентацию, «вращение (РУО).

                                Левый имеет свободный ход — до „мягкого упора“, а если сильнее двигать, то и „жесткий упор“. Оси ручек расположены горизонтально (руки на подставку упираются предплечьем), слева от оси ручки чуть внизу — тумблер движения „вперед-назад“, назад тяга в 2 раза сильнее.
                                Правая ручка тоже с мягким и жестким упором, но может еще и вращаться вокруг оси (для крена корабля). Ориентацию можно зафиксировать сквозным механическим „штифтом“ в ручке РУО (там их 2, на крен отдельный штифт, этот кажется белый, второй красный). Штифт подпружинен, возвращается отщелкиванием при нажатии с другой стороны.
                                В принципе не секрет и уровней мягкого и жесткого, сейчас это 0,87 и 2,7.

                                А по Федерации говорить пока нельзя, по шее дадут )))
                              +5
                              Чёрт, как это непохоже на обычный игровой джойстик! Сразу вспоминается:

                              — А где тут руль? – спросил Гагарин,
                              — Деревня! — буркнул Королёв,
                              — Ещё спроси а где тут вожжи,
                              Ещё «Поехали» скажи…
                              +4

                              Было интересно, но некоторые моменты удивили.


                              1. Зачем там "быстрые" 16-битные АЦП, если оцифровывать джойстики можно и восмью битами, а внутренние 12-битные АЦП STM при 2 мегасемплах можно влегкую расширить до 16 бит за разумное время выборки?
                              2. Зачем ставить крайне медленные i2c расширители, если те же кнопки можно опрашивать матрицей? Вряд ли пульт требует одновременного нажатия нескольких кнопок.
                              3. Почему RTOS должна работать с шагом в 1мс? Время переключения контекста у такого процессора будет меньше 5 мкс, а запросы можно генерировать в таймером прерывании с любой разумной частотой. Ну и 1 кГц — это рекомендуемые тик, его запросто можно сделать и 10 кГц, тем более с таким процессором.
                                0
                                1. Тут, вообще, можно долго дискутировать. Хотелось точности, так как уже на новых геймпадах Xbox стоят 16-битные оси.
                                Но я с вами не согласен, что из 12 бит можно получить 16. Случайный шум и интерполяция там будет.

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

                                3. Честно сказать, я гуглил, как сделать не 1кГц, а 10, но решения не нашел. Таймеры и прерывания можно и без RTOS сделать. На самом деле, без RTOS работает очень быстро. У меня встроен счетчик в бесконечном цикле и, когда он насчитывает 10000 циклов, мигает светодиод. Мигает примерно раз в 0,5 сек. Т.е. скорость даже избыточная. Потому что никаких блокирующих функций нет при работе с периферией.
                                  0
                                  Хотелось точности
                                  у вас точность ограничена вовсе не используемым АЦП.
                                  Заголовок спойлера
                                  image
                                    0
                                    Из статьи:
                                    Я использовал 2 входа на одну микросхему, поэтому время обновления значений составляет 2.4 мс. Плоховато, но что поделаешь? К сожалению на али других 16-битных быстрых АЦП нет.

                                    Плохо? Мне думается что было бы достаточно 20 мс… Дергать джойстиками с такой частотой никто не сможет :)
                                      0
                                      Мне в соседней статье люди утверждали, что чувствуют 10-20мс, поэтому и написал, что плохо. Я сам 2мс задержки не чувствую.
                                        0
                                        2 мс — никто не почувствует, это за пределами возможностей человека, а вот 20 — совсем другое дело. Концерт, исполнитель играет на миди-клавиатуре через комп. Общая задержка от нажатия клавиши до появления звука примерно 20 мс., жалуется, что не попадает в долю. И действительно, я тоже слышу, что он немного мажет мимо ритм-секции. Смена драйвера, уменьшение буферов, и задержка становится на уровне 8-10 мс. Музыкант перестает чувствовать дискомфорт, я тоже перестаю слышать косяки.

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

                                        Попробуйте поиграть на любом музыкальном инструменте через компьютер под фанеру, задержав свой сигнал на 20 мс. Хрена с два в долю попадете.
                                      +1
                                      Точность с потенциометров? Авиасимеры-самодельщики давно поставили на них крест. Джойстики в авиасимах (с ручками, более длинными, чем у вас) нередко двигают на миллиметры и чуть ли не доли миллиметра. Приличные потенциометры изнашиваются, причём их сложно купить, неприличные сразу никуда не годятся.

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

                                      Ныне в моде цифровые датчики — TLE5010, TLE5011, «магрезы», MARS'ы.
                                        0
                                        Но я с вами не согласен, что из 12 бит можно получить 16. Случайный шум и интерполяция там будет.

                                        Посмотрите AVR121: Enhancing ADC resolution by oversampling. Есть много переводов на русский. На потенциометрах неплохо работает, проверял.

                                        Тоже думаю что 16 избыточно, вот пару разрядов таким образом добавить можно.Но наверное вы единственный, кто может это проверить на практике, програмно обрезав несколько разрядов и сравнить ощущения от управления.
                                      0
                                      Здорово! Спасибо за технические детали.
                                        0
                                        Раз речь о пультах, подскажите кто-нибудь пожалуйста, как оптимальнее всего подключить к малинке 218 кнопок? Нажимаемых в любом сочетании.
                                          +1

                                          Взять пару хороших usb-клавиатур ?

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

                                            Напрашивается использование shift registers. Формально ими можно подключить любое количество кнопок. Правда, 74HC165 или 4021 потребуется целых 28 штук, мороки море, и я не знаю, как это выйдет по электричеству.

                                            Ещё можно взять 5 штук Arduino Mega.
                                              0
                                              Надо будет ещё суметь отличить нажатие кнопки на одной клавиатуре от нажатия такой же кнопки на другой.
                                              Не нужно ничего отличать. Нужно для каждой клавиатуры читать соответствующий /dev/input/event
                                              Напрашивается использование shift registers
                                              Да ну нафиг, лучше сразу на транзисторах, чтоб наверняка.

                                              В 21 веке есть достаточное количество специализированных микросхем.
                                              Проще всего их заюзать в виде готового изделия с usb-разъемом.
                                                0
                                                OK. Тем не менее, с «нажимаемых в любом сочетании» всё равно могут быть проблемы. Недаром продают «игровые» клавиатуры.

                                                Не подскажете ли каких-нибудь аналогов 74HC165 — но не на 8 кнопок, а на побольше (16/32/64...)? К ардуинке/blue pill подключать.
                                                  0

                                                  Google(key scan controller)
                                                  Главная сложность — найти в корпусе пригодном для диайвая.

                                                    0
                                                    Как-то плохо гуглится. Главное — работа с клавиатурными матрицами не то, что я имею в виду. 74HC165 работает с каждой кнопкой индивидуально.
                                                      0
                                                      работа с клавиатурными матрицами не то

                                                      Вам, простите, что нужно в итоге? клавиши считывать, или проводами увиться ?


                                                      Хотите 'каждой кнопке по GPIO' — смотрите в стороны io expander'ов на i2c.
                                                      Готовые платки есть на алике. Но плюшки вроде антидребезга умеют далеко не все чипы.

                                                    0
                                                    В реальном управлении кейс «в любом сочетании» практически не используется.
                                                    Количество кнопок, которые имеет смысл нажимать одновременно с какими-то другими, вероятно, можно посчитать по пальцам одной руки.
                                                  0
                                                  в windows api можно работать с клавиатурами независимо, искать RegisterRawInputDevices
                                                    0
                                                    Ещё можно взять 5 штук Arduino Mega.

                                                    А из каких соображений 5 штук? По моему 3 штуки, и не Ардуинки, а просто самых дешёвых чипов типа ATMega8L (или 88) будет достаточно.

                                                    Интересно можно ли это сделать на одной Альтеровской плисине.
                                                +2
                                                У меня вопрос, наверно больше риторический: почему при использовании светодиодных цифровых индикторов повсеместно игнорируют светофильтры для них? Ведь с ними лучше читаемость и выглядить эстетичней.
                                                  +1
                                                  Столько сил потеряли на вырезание панели из фанеры, когда как любое рекламное агентство вырежет лазером или фрезой вам по вашим эскизам что угодно, по цене 60-100р за метр реза.

                                                  По теме поста, неужели все эти кнопочки можно будет прописать в управление симулятором?
                                                    0
                                                    А почему нет? Если посчитать все кнопки и переключатели, которые есть штатно на моем HOTAS Warthog, то их примерно столько же наберется. И все равно для DCS мало, еще остаются на клаве шорткаты
                                                      0
                                                      Зашел в комменты, чтобы написать то же самое. Рез 4мм фанеры даже дешевле сделают — 40 — 50р за метр. Аж больно было когда прочитал, что автор три недели ковырялся с панелью.
                                                        +1
                                                        Аж больно было когда прочитал, что автор три недели ковырялся с панелью.

                                                        Почему? Панель — это интересно. Я скорее поверю, что он так ни во что с её помощью и не играл, потому что не бывает игры, настолько же интересной, как разработка подобной хреновины )))
                                                          0
                                                          Разработка, сборка и настройка это интересно, но человек явно страдал вручную выгрызая все эти отверстия)
                                                            0
                                                            Это он прибедняется. Там всех сверлильно-пилильных работ часа на четыре. Растянуться на три недели это могло в случае дефицита свободного времени: если было по 20 минут и то не каждый день (что вполне вероятно, вон, Паскаль свою машину 10 лет конструировал).

                                                            Автор, кстати и пишет «от нехватки времени».
                                                              0
                                                              А может наслаждался? Ну нравится человеку по 20-30 минут в день спокойно ручками попилить фанерку после напряженного дня на работе, вполне могу понять. А делать где-то на стороне, это же надо чертеж готовить, искать исполнителей (куча времени уйдет только на то, чтоб сравнить предложения и выбрать подходящее), заказ оформлять, ехать забирать потом (курьерская доставка будет дороже, чем вся фанерка с работой вместе), да и кайфа никакого.

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

                                                      Самое читаемое