DIY自己动手做飞船控制面板


亲爱的读者您好!

我想到一个主意,但没有为飞船组装控制面板。 至USB。 具有本机驱动程序支持。 自定义HID。 坚持下去,一切正常,没有任何舞蹈和铃鼓。 结果,我们得到了一种用于太空模拟器的奇异“游戏手柄”。 通常,请自己判断。

起初,我不知道最终会发生什么。 我想要两个主要的操纵杆,例如Soyuz-MS上的操纵杆,几个开关,按钮和几个显示器。

估算了桌子的工作表面后,我选择了控制台的宽度和深度尺寸:500 * 300 mm。 在翻遍建筑仓库和商店寻找建筑材料后,他选择了125mm的高度。 结果,我获得了一块4毫米的胶合板,20 * 12毫米的板条和一块120 * 20毫米的板。

在cad中,快速绘制了遥控器的草图。 我在树上做了很长时间。 三个月 在周末。 这不是因为他的工作如此雄伟,而是因为时间不足。 面板被轻涂,上釉并涂上搪瓷漆,其颜色类似于太空船或飞机的真实面板。



但是现在,将绘画工作放在一边,我将讨论电子填充。

收音机零件是在Ali上购买的。 作为操纵杆,我找到了这些。 通常,使用这种操纵杆的情况是完全的。 工业解决方案太贵了,但是便宜,作为玩具而来,因此是不好的。 这些都是高质量的,但是他们不会知道会持续多久。


其余的小事情没有引起问题。 控制器选择了STM32。 16位ADS1118作为操纵杆的ADC。 我还购买了一个12 V电源,实际上,这个电压可以通过我从“ shah”获得燃油表的事实来解释,我也想在此安装它。


照片中的电源,5和3.3 V的稳定器,STM32,MCP23017,ADS1118

控制器100针STM32F407VET6,已连接到它:

2个选择器到4个位置
1个可变电阻
2个轴开关
4主轴
2个辅助轴
2个控制轴
4个按键开关,每个2个按钮
20个带LED的按钮
4个带LED的主开关
2个带LED的真菌按钮
2个定时器按钮
3个带LED的开关
13个开关
2个ADS1118(ADC)
4个MAX7219(8位LED显示屏)
2个TM1637(显示时间)
1个PCF8574(I / O扩展器,已插入字符合成显示器中)


产生的结构

我决定,对于MK的数百个支路来说,这太过分了,我在这里添加了I / O扩展器:四个MCP23017,每个16个输入或输出。 展望未来,我将说,在400 kHz的I2C总线速度下,轮询扩展器输入的延迟约为每个芯片0.13 ms。 也就是说,它的余量覆盖了1 ms的最小USB轮询时间。

为了不使用无用的请求来驱动I2C总线,MCP23017的中断输出在输入状态改变时置位。 我也在项目中应用了它们。 事实证明,由于联系的嘎嘎声,这些中断毫无用处。

ADS1118 ADC并没有跟上USB速度,其声明的性能最高为每秒820个样本,即1.2 ms,而它的多个输入已经通过多路复用器连接到ADC。 我在一个芯片上使用了2个输入,因此值的更新时间为2.4毫秒。 不好,但是你能做什么? 不幸的是,Ali上没有其他16位快速ADC。


里面看起来像这样,但是在安装电线之后情况更糟

CPU程序以PLC程序的样式编写。 没有阻止请求。 核心不等待外围设备,没有时间和地狱,在下一个周期将进行审问。 项目中也没有RTOS,我尝试过,我遇到了1 ms的最小任务等待时间-如果我们需要通过USB以1 ms的频率发送数据,结果会很慢。 结果,我意识到我将使用不带osDelay()的操作系统,然后为什么要使用RTOS? 就像在PLC中一样,将程序指令一个接一个地放入无限循环就足够了。

当然使用了CubeMX和HAL库。 顺便说一句,我最近改用HAL,想知道便利性。 我不知道为什么它仍然不太流行,主要的事情是首先弄清楚它,然后它会非常简单。 感觉就像您在编程arduino。

该设备将具有USB自定义HID。 HID是鼠标,键盘,游戏板,操纵杆等等。 而且有风俗。 所有这些都不需要操作系统的驱动程序。 更准确地说,它们已经由开发人员编写。 定制设备的好处在于,我们可以自行决定结合上述所有设备的功能。

通常,USB东西非常复杂,它的手册将近一千页,您不能一snap而就。 谁不想读繁重的手册,在Google NutShell中有一篇很棒的USB文章。 她也有翻译。 我将尝试在“手指上”解释一些要点。

USB-具有大量级别和抽象的打包数据传输。 设备与我们同在-它无法请求任何数据,主机会发起整个传输。 主机向所谓的端点写入数据并向其请求数据,实际上这些是MK内存中的一些缓冲区。 为了使主机了解可以写入哪些端点,要读取哪些端点以及可以将其解释为设备按钮和轴的数据以及通常在此处拥有的哪种设备,在连接开始时它会请求设备描述符。 这些描述符很多,很难组合它们,您可以随心所欲地在任何地方出错。 从物理上讲,它们是字节数组。

实际上,CubeMX将比我们更好地生成自定义HID初始化代码。





请注意数字3下的最后一张图片。这是描述符的大小(以字节为单位),它确定了设备上的轴和按钮。 该描述符在HID描述符工具中生成。 有一些自学的例子。 通常,这是我的描述符。 为了便于理解,尚无用于显示的数据,但是操纵杆的所有按钮和轴均存在。 它需要放置在usbd_custom_hid_if.c文件中。 默认情况下,此句柄使多维数据集为空。

HID描述符(大小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(油门)轴),然后在单词INPUT(Data,Var,Abs)之后,系统将假定我们具有“ Gas”轴。 可变轴的尺寸及其编号由参数LOGICAL_MAXIMUM,MINIMUM,REPORT_SIZE,REPORT_COUNT设置,这些参数必须在INPUT之前。

有关这些参数以及什么(数据,变量,绝对值)的更多详细信息,可在人机接口设备的设备类定义(HID)v1.11中找到

以下是根据我的描述符初始化油门轴的示例。 在此示例中,节气门的值范围为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句柄;它已经在我们的多维数据集中创建。 您可能需要在包含文件中包含必要的文件,在该文件中首次初始化此句柄,并写入外部USBD_HandleTypeDef hUsbDeviceFS; 这样你就可以和他一起工作。 第二个参数是指向我们的结构的指针,第三个参数是该结构的大小(以字节为单位)。

填充控制器并使其闪烁后,您会注意到USB缓慢移动。 来自面板的数据无法快速更新。 为了快速起见,在usbd_customhid.h文件中,您需要将#define CUSTOM_HID_EPIN_SIZE更改为最大值0x40,#define CUSTOM_HID_EPOUT_SIZE也设置为0x40。 在usbd_customhid.c文件中,在端点描述符“ / * bInterval:轮询间隔(20 ms)* /”中找到注释,并将每个端点的描述符字节更改为0x01(仅两次)。 这将对应于1 ms的数据交换。


应该是这样的。 标准设备,无需安装任何驱动程序

通常,对管理功能了解得很少。 这很容易做到,并且所有按钮和轴都已经起作用。 仍然需要使显示正常工作。 我做了大约六个月,半年以来,面板一直在一个长箱子里收集灰尘。 没时间 因此,我决定以这种形式对文章进行布局,否则可能会冒出来的风险。

对于显示器,一切都与轴相同。 对于它们,我们需要补充我们的设备HID描述符,仅表明它们是显示,并且主机将发送输出数据,而不是接受输入数据。

HID设备的手柄已显着增长。 在这里,我已经应用了Report ID参数,以免阻塞完整数据的发送/接收缓冲区和端点,并区分我们收到的哪种电报。 报告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) 

输出在静态int8_t函数CUSTOM_HID_OutEvent_FS(uint8_t event_idx,uint8_t状态)中处理 ,该函数默认位于usbd_custom_hid_if.c中。

静态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 */ } 


仅需在PC上编写一个程序,即可将必要的报告发送到转向显示器。 但是,要检查MK代码,最好使用ST: USB HID Demonstrator程序 。 它使您可以从PC发送带有任何内容的报告。


LED显示屏测试

到目前为止,我已经完成了。 不知道我是否会再次开始。

在模拟器中玩比用键盘更有趣。 但并不是那么多,以至于产生了直接的哇音效果。 键盘,看起来也像控制面板。 但是控制操纵杆的轴至少是不寻常的。 感觉像个宇航员。 没错,要完全浸入水中,需要穿太空服。

我希望你有兴趣。 出现错别字,不正确和妄想。 那些想深入研究代码的人可以在这里看到。

问候

Source: https://habr.com/ru/post/zh-CN434854/


All Articles