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




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

Пришла мне тут одна идейка, а не собрать ли пульт управления космическим кораблем. На 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 дисплеев

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

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

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

С уважением.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (53):

  1. Anton23
    /#20047440 / +5

    Спасибо, интересно. Но не хватает видео с игрой на этом ПУ.

    • TyVik
      /#20047818

      Очень крутая идея! Даёшь симуляцию стыковки или посадки на Луну.

  2. norguhtar
    /#20047628

    А для какого сима это все запиливалось?

    • 8street
      /#20048152 / +1

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

      • MuKPo6
        /#20050348

        Попробуйте «Reentry — An Orbital Simulator»

        • chapter_one
          /#20050688 / +1

          Ну тогда сразу Orbiter. Куда уж хардкорнее?

  3. chapter_one
    /#20048040 / +3

    Автор, остановись! Это начинается все невинно: там дощечка, тут фанерка, топливомер от «шахи», а через пару лет ты покупаешь гараж, потому что в квартиру это уже не помещается.

    www.creativesimulations.com/Cockpit%20Shell.htm

  4. rstepanov
    /#20048564

    Вообще, USB штука очень сложная, имеет мануал почти в тысячу страниц и с наскока её не взять

    Это вы еще спецификацию Bluetooth не смотрели, там около 5 тысяч страниц :)

  5. super-guest
    /#20048828 / +1

    В качестве джойстиков, нашел вот такие.
    Помогите найти их на Али, пожалуйста — как называются? и/или ссылку дайте…

  6. SomaTayron
    /#20049028

    а мягкий/жесткий упор есть на джойстиках, или фиксатор по крену, типа союзовского РУО?

    Кстати, на Федерации уже одна ручка самолетного типа. Управление самой ручкой и «джойстиком» на ней под большим пальцем

    • RigelNM
      /#20049182

      Откуда информация? Если занимаетесь разработкой можете еще чем-то интересным поделиться с общественностью?

      • chapter_one
        /#20049952

        Да это, вообще-то, не секрет никакой tass.ru/kosmos/4130417

        • Zenitchik
          /#20049976

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

          • chapter_one
            /#20050338 / +1

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

          • SomaTayron
            /#20058594

            Дублирование вообще то будет, хотя и не совсем классическое. А переход на одну ручку — чтоб в случае чего БИ мог той же ручкой управлять, но своей левой рукой.
            PS там не гроб, достаточно просторно. На этом снимке экраны уже в опущенном состоянии — когда поднимают, места намного больше

            • Zenitchik
              /#20059050

              Но ручка-то одна. Она не дублирована. Что они будут делать в случае её отказа? Только чинить в полёте.
              Следовательно — это гроб. Не во вместимости дело.

              • SomaTayron
                /#20059102

                «ручка» съемная, и запасная примерно в 1,5 метрах хранится. Так что если отказа не будет прямо в 2-5 метрах от стыковочного, проблем никаких. Кстати, на Союзе тоже один действующий комплект РУО-РУД, так что это решение как минимум не хуже

                • Zenitchik
                  /#20059122

                  Понял, был не прав.

                  Так что если отказа не будет прямо в 2-5 метрах от стыковочного

                  А если будет — перейдут на автомат. И то и другое сразу — не откажет.

      • SomaTayron
        /#20058572

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

        • RigelNM
          /#20058582

          Ясно, успехов в вашей работе. Вообще сами как оцениваете настроение в команде, федерации быть?

          • SomaTayron
            /#20058944

            Быть конечно, хотя кое-какие рекомендации по изменениям мы уже дали. А по срокам еще не ясно, от ЦПК заказов даже предварительно не было, только разработчик и мы пока пробуем его. ЦПК больше на Союзовские пока смотрит (замену Океану-5), хочет комбинированный тренажер, не только приводнения (на муромской платформе), но и этап сведения до контакта. Может после этого начнет интересоваться по Федерации.

            • RigelNM
              /#20058954

              Спасибо! Получил информации больше чем за 2 года слежения за всеми новостями…

    • 8street
      /#20049656

      а мягкий/жесткий упор есть на джойстиках, или фиксатор по крену, типа союзовского РУО?
      Про упор затрудняюсь ответить, поскольку с настоящими джойстиками не знаком. Фиксатора нет, это самоцентрирующиеся джойстики, но вроде можно переделать, снять возвратную пружинку, но это скорее всего, будет уже не то.

      • SomaTayron
        /#20059162 / +1

        По союзовским в общих чертах описать не проблема — левый отвечает за движение (плоское перемещение, РУД), правый за ориентацию, «вращение (РУО).

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

        А по Федерации говорить пока нельзя, по шее дадут )))

  7. dmitryredkin
    /#20050252 / +4

    Чёрт, как это непохоже на обычный игровой джойстик! Сразу вспоминается:

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

    • vvzvlad
      /#20064100

      Традиционно пирожки пишутся без знаков препинания и больших букв.

  8. Polaris99
    /#20051720 / +4

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


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

    • 8street
      /#20052116

      1. Тут, вообще, можно долго дискутировать. Хотелось точности, так как уже на новых геймпадах Xbox стоят 16-битные оси.
      Но я с вами не согласен, что из 12 бит можно получить 16. Случайный шум и интерполяция там будет.

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

      3. Честно сказать, я гуглил, как сделать не 1кГц, а 10, но решения не нашел. Таймеры и прерывания можно и без RTOS сделать. На самом деле, без RTOS работает очень быстро. У меня встроен счетчик в бесконечном цикле и, когда он насчитывает 10000 циклов, мигает светодиод. Мигает примерно раз в 0,5 сек. Т.е. скорость даже избыточная. Потому что никаких блокирующих функций нет при работе с периферией.

      • Zolg
        /#20053004

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

      • ZEvS_Poisk
        /#20055202

        Из статьи:

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

        Плохо? Мне думается что было бы достаточно 20 мс… Дергать джойстиками с такой частотой никто не сможет :)

        • 8street
          /#20060932

          Мне в соседней статье люди утверждали, что чувствуют 10-20мс, поэтому и написал, что плохо. Я сам 2мс задержки не чувствую.

          • chapter_one
            /#20062288

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

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

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

  9. engine9
    /#20051724

    Здорово! Спасибо за технические детали.

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

    • Zolg
      /#20052780

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

  11. psycho-coder
    /#20053442 / +1

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

  12. rPman
    /#20054370 / +1

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

    По теме поста, неужели все эти кнопочки можно будет прописать в управление симулятором?

    • chapter_one
      /#20054836

      А почему нет? Если посчитать все кнопки и переключатели, которые есть штатно на моем HOTAS Warthog, то их примерно столько же наберется. И все равно для DCS мало, еще остаются на клаве шорткаты

    • max1muz
      /#20055306

      Зашел в комменты, чтобы написать то же самое. Рез 4мм фанеры даже дешевле сделают — 40 — 50р за метр. Аж больно было когда прочитал, что автор три недели ковырялся с панелью.

      • Zenitchik
        /#20055988

        Аж больно было когда прочитал, что автор три недели ковырялся с панелью.

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

        • max1muz
          /#20058766

          Разработка, сборка и настройка это интересно, но человек явно страдал вручную выгрызая все эти отверстия)

          • Zenitchik
            /#20059088

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

            Автор, кстати и пишет «от нехватки времени».

          • chapter_one
            /#20061828

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