STM32F3Discovery上的PPM到USB适配器,或者我们通过STM32Cube将飞机模型控制台作为HID游戏杆连接到计算机



在本文中,我将告诉您如何:
  • 在STM32CubeMX中创建一个项目,并设置计时器以捕获外部信号。
  • 从飞机模型控制台解码PPM信号。
  • 在STM32上制作人机界面设备并编写您的HID报告描述符。
  • 在赛车直升机上的模拟器中飞行。:)

前言


最近,FPV在第250类直升机上的竞赛(FPV-第一人称视角)越来越受欢迎。数字250表示电动机对角线之间的距离,这是小型机动直升机所特有的。此类设备建立在可承受跌落和碰撞的耐用碳纤维框架上。直径为5到6英寸且具有较大台阶(桨叶倾斜角)的螺旋桨安装在功能强大的电机上,以实现最动态的飞行。来自模拟航向便携式摄像机的图像以5.8 GHz的频率传输到飞行员的监视器或视频眼镜。由于通过WiFi进行的数字传输会产生较长的延迟(200-300毫秒),因此视频始终会在模拟频道上播放。要录制壮观的剪辑,请在其上放置运动相机(GoPro,Mobius,SJcam,Xiaomi Yi等)。


以下是一些有关FPV直升机的精彩视频:



在建造我的微型直升机之前,我想乘模拟器飞行,看看我是否会对FPV比赛感兴趣。对于培训,FPV FreeRider模拟器非常适合。它价格便宜,具有免费的演示版本,并且根据经验丰富的飞行员的说法,它非常准确地模仿了飞行的真实机制。

您可以通过键盘或操纵杆在模拟器中控制飞机。键盘不适合用于导航,因为这些按钮只能发送离散值(按下/未按下按钮),并且不可能发送平滑变化的中间值。来自游戏机的带有模拟摇杆的操纵杆要好得多,但它们的摇杆非常小,因此您无法足够准确地控制设备。模拟器的理想选择是通过专用适配器连接到计算机的飞机模型控制台,因此操作系统将其视为操纵杆。

我已经拥有一架直升机,可以进行休闲飞行和摄影,但又太大又笨重,无法进行比赛。因此,有一个遥控器-Turnigy 9X(在第一个图中)。在背面,它有一个连接器,用于连接输出PPM信号的适配器。该信号是一个短脉冲,间隔为1到2毫秒,其持续时间与控件的位置相对应(在解码部分中与该控件有关的更多信息)。



我必须说,用于将遥控器从PPM连接到USB的适配器已经发布了很长时间,并且已经出售。可以在中国以5美元的价格购买类似的闪存驱动器适配器,而在俄罗斯的商店则要贵一些。AVR控制器上也有开源适配器项目

但是当深夜,当莫斯科所有飞机模型商店都已经关门时,我立刻想到了飞行的强烈愿望。我不想在早上等待,因为没有时间用ATmega来中毒和焊接该板,所以我决定在STM32F3Discovery板上制作一个PPM-USB适配器,该适配器长期处于闲置状态,就在手边。

需要什么


为了制作适配器,您将需要:


发现调试板非常昂贵。所描述的F3售价约为20美元,其功能对于这样一个简单的项目是多余的。我之所以使用它,是因为在撰写本文时,这是我在家中发现的唯一带有硬件USB的主板。对于那些还没有购买它的人,我建议您注意一下带有AliExpressSTM32F103C8T6控制器的小型开发板,价格为3美元,那里的ST-Link编程器也是如此。该过程与本文中描述的过程没有什么不同。除非一开始必须选择其他控制器,否则请指明是否存在石英谐振器,并使用稍微不同的引脚排列。



在STM32CubeMX中创建项目


STM32Cube是意法半导体(STMicroelectronics)开发的软件包,目的是使STM32器件开发人员的生活更加轻松。它由CubeMX图形实用程序,HAL驱动程序和中间件组件组成。

CubeMX是用于创建项目和初始化外围设备的工具。要开始使用,只需选择控制器,选中所需模块的框,在菜单中选择所需模式,然后在几个字段中输入所需值。 CubeMX将生成项目并将所需的库连接到该项目。设备的开发人员将只编写应用程序的逻辑。

HAL驱动程序(硬件抽象层)是用于处理微控制器的模块和外围设备的API。 HAL允许您将开发人员创建的应用程序顶层与寄存器分开,并使程序代码在STM32控制器系列之间尽可能可移植。

中间件或中间组件包括FreeRTOS操作系统,用于处理文件系统的库,USB库,TCP / IP等。

看来现在可以用鼠标编程,而不用手动将位写入寄存器了。但是,简单和方便并不能消除您需要阅读文档的事实,尤其是在需要以最快的速度,最低的功耗或在非标准模式下使用外围设备的情况下。STM32Cube尚未涵盖微控制器所有功能的100%,但正在接近这一目标。意法半导体不时更新Cube,扩展功能并修复错误。因此,如果您已经安装了Cube,请检查它是否为最新版本。

初始设定



从选择控制器开始处理项目。启动STM32CubeMX,点击新建项目。在“ MCU选择器”选项卡上,您可以从过滤器中选择所需的控制器。由于我们已经完成了调试板,因此在Board Selector选项卡上,我们找到了STM32F3Discovery。选择板子后,将显示带有高亮和带符号的引脚的控制器图像。

窗口上部有四个大选项卡:
引脚分配 -用于配置引脚功能和预设模块。我们目前正在处理。
时钟配置 -时钟设置,PLL,分频器。
配置 -外围设备和中间件的更详细的配置。
功耗计算器 -计算微控制器的功耗。



在“ 引脚排列”选项卡的左侧菜单中,可以使用所需的外围设备,并在控制器电路上为微控制器的任何输出选择功能。左侧的某些项目是警告图标。这意味着模块(在本例中为ADC,DAC,OPAMP2,RTC)现在不能完全使用,因为它们的某些输出已被其他功能占用。

控制器电路上已配置的引脚以绿色突出显示。由于我们选择的不是裸露的控制器而不是捆扎带,而是选择现成的F3-Discovery调试板,因此已经配置了一些输出,例如,将蓝色按钮连接到PA0,将LED连接到PE8 ... 15。在Discovery上与某些外部设备连接的那些引脚以橙色突出显示,但尚未配置它们的外围模块。如您所见,这些是用于USB的引脚,石英谐振器,用于陀螺仪和罗盘的SPI和I2C引脚,用于USB的DP和DM。灰色结论目前尚未使用,我们可以将其用于我们的目的。

输入选择

我们将捕获脉冲的持续时间,因此输入必须连接到任何计时器的通道之一。此外,Turnity 9X的信号电平与STM32的电源电压不同,不是3.3V,而是5V。我们懒得焊接分压器,所以您需要选择可承受5V的输入(这些输入称为5V耐压)。合适的引脚可以在STM32F303VCT6 数据手册的“ 引脚和引脚描述”部分中找到STM32F3中有许多定时器,它们分散在几乎所有引脚上。一个方便的选择是PC6。它可以承受5伏电压,位于电路板的左下角,靠近GND。将第三定时器TIM3_CH1的第一通道分配给该引脚。



时钟设定

为了使USB正常工作,微控制器必须以非常稳定的频率提供时钟,这就是为什么几乎所有USB设备都具有石英谐振器的原因。内置的RC发生器的频率稳定性不足以支持USB。但是在STM32F3 Discovery板上,由于某种原因,开发人员很贪婪,没有放石英石。但是,如果仔细研究电路,您会发现MCO信号已连接至输入PF0-OSC_IN,石英应连接至此。它来自装有石英的同一板上的ST-Link编程器。 “ OSC时钟”部分的F3发现用户手册(UM1570)表示8 MHz正在发送到此线路。



因此,微控制器由外部源提供时钟。此模式称为旁路。在RCC部分的外设设置菜单中,选择时钟高速时钟,请选择BYPASS Clock Source



在进行更详细的时钟设置之前,我们在外围菜单中注意到,微控制器将充当USB设备。



现在,您可以转到下一个大选项卡-Clock Configuration。在这里,我们将看到一个巨大的图,该图显示了微控制器中存在哪些时钟信号,它们来自何处,如何分支,相乘和除法。我用黄色突出显示了应该注意的那些参数。



检查输入频率输入频率为8 MHz。
我们将PLL Source Mux开关设置HSE(高速外部)以从外部源而不是内部源提供时钟。
PLL-锁相环或PLL-锁相环用于将外部频率倍增。将PLLMul乘数设置为9。然后我们将达到STM32F303的最大可能频率-72 MHz。
系统时钟复用器必须位于PLLCLK位置以使时钟频率乘以PLL。
对于USB模块,需要48 MHz,因此在USB前面放置一个1.5分频器。
注意频率APB1定时器时钟位于电路的左侧。它用于计时器,对将来对我们很有用。
如果任何频率配置错误,超过最大可能值或开关处于无效位置,则CubeMX将以红色突出显示此位置。

计时器设定

为了测量脉冲持续时间,我们将在输入捕捉模式下启动TIM3定时器。在参考手册,节下通用定时器(TIM2 / TIM3 / TIM4),有示出的定时器的操作的图。我用颜色突出显示了输入捕捉模式下使用的信号和寄存器。


以绿色突出显示的时钟信号连续进入CNT计数器寄存器,并在每个时钟周期将其值增加1。在预分频器PSC分频器中,时钟频率可能会降低以降低计数速度。外部信号
输入到TIMx_CH1边缘检测器它识别输入信号的边沿-跃迁从0到1或从1到0。当登记前,它给以黄色突出显示两个命令:
-一个命令写入的值CNT计数器捕获/比较1个寄存器(CCR1)寄存器和呼叫CC1I中断
-用于从模式控制器的命令,通过该命令将CNT重置为0,并再次开始倒计时。

这是时间轴中流程的说明:



发生中断时,我们将使用捕获的值执行操作。如果输入脉冲太频繁,并且中断处理程序中发生的动作太长,则在我们读取前一个之前,可以覆盖CCR1的值。在这种情况下,当来自CCR1的数据自动填充内存中准备好的阵列时,您需要检查Overcapture标志或应用DMA(直接内存访问)。在我们的例子中,最短的脉冲持续时间为1毫秒,并且中断处理程序将简单而又短,因此不必担心重写。

引脚选项卡,并设置TIM3定时器外设菜单



从模式:重置模式-表示在某些情况下定时器将复位为0。
触发源:TI1FP1-用来复位和启动定时器的事件是从TI1输入捕获的信号沿。
ClockSource:内部时钟 -计时器由微控制器的内部发生器提供时钟。
通道1:输入捕捉直接模式 -捕捉CCR1寄存器中第一个通道的间隔。

在下一个较大的“ 配置”选项卡上,我们将进行其他计时器设置。





预分频器是定时器分频器。如果为0,则频率直接从时钟总线APB时钟获取-72 MHz。如果预分频器为1,则将频率除以2,并变为36 MHz。将除数设置为71,以便将频率除以72。然后,定时器频率将为1 MHz,间隔将以1微秒的分辨率进行测量。
计数器周期 -设置最大可能的16位值0xFFFF。该周期对于生成时隙(例如,PWM)很重要。但是周期对于捕获信号并不重要;我们将使它对于任何输入脉冲来说都很大。
极性选择:下降沿 -定时器值将在输入信号的下降沿捕获。
在“ NVIC设置”选项卡上,放置一个dawTIM3全局中断,因此与第三计时器相关的事件将产生中断。

USB设备设置

我们已经注意到,该控制器将是USB设备。由于操纵杆属于HID设备的类别,因此在“ 中间件-> USB_DEVICE”菜单中,选择FS IP的类别:人机接口设备类别(HID)然后,CubeMX将HID设备的库连接到项目。



让我们转到中间件”部分配置”选项卡上USB_DEVICE设置



供应商ID产品ID是两个16位标识符,对于每种USB设备型号都是唯一的。 VID对应于设备制造商,每个制造商都根据自己的考虑分配PID。我找不到VID和PID的官方列表,只找到发烧友支持的标识符库。要获得自己的供应商ID,您需要访问usb.org上的USB Implementers Forum,并支付几千美元。负担不起VID的小型公司或开放源代码开发人员可以请求USB芯片制造商,并正式为其项目获得VID / PID对。此类服务由FTDI或Silicon Laboratories提供。

如果您连接两个具有相同VID / PID但类型不同的设备(例如,一个是HID设备,另一个是大容量存储),则操作系统将尝试为其安装相同的驱动程序,至少其中一个不管用。这就是为什么不同设备型号的VID / PID对必须唯一的原因。

由于我们不是自己制造设备,所以我们不会出售和分发该设备,因此我们将保留与STMicroelectronics相对应的VID 0x0483,并提出自己的PID。默认情况下,CubeMX为HID设备提供PID 0x5710。例如,将其替换为0x57FF。STM32 PPM-USB适配器

替换产品字符串。该名称将显示在Windows控制面板中的设备列表中。我们不会更改序列号(S \ N)。

Windows检测到它检测到以前从未见过的具有VID,PID和S \ N组合的设备时,系统会为其安装适当的驱动程序。如果已经使用了VID,PID和S \ N的组合,则Windows会自动替换以前使用的驱动程序。例如,当您将闪存驱动器连接到USB时,您会看到此信息。第一次连接和安装需要一些时间。在随后的连接中,驱动器几乎立即开始工作。但是,如果您连接了相同型号但序列号不同的闪存驱动器的另一个实例,则系统将为其安装新的驱动程序,即使它具有相同的VID和PID。

我将解释为什么这很重要。如果您在STM32上创建了带有VID,PID和S \ N的USB鼠标,将其连接到计算机,然后在不更改VID,PID和S \ N的情况下制作了USB游戏杆,则Windows会将新设备视为已经存在的鼠标在系统中使用,并且不会安装操纵杆驱动程序。因此,操纵杆将不起作用。因此,如果要更改设备的类型,而使VID / PID保持不变,请确保更改其序列号。

IDE的项目生成

您需要进行的最后设置是项目生成设置。这是通过Project-> Settings完成的。在这里,我们将设置名称,目标文件夹和所需的IDE,CubeMX将在该IDE下创建项目。我选择了MDK-ARM V5,因为我使用的是Keil uVision5。在“ 代码生成器”选项卡上,可以选中“ 仅复制必要的库文件”复选框,以免不必要的文件使项目混乱。

按下按钮项目->生成代码。 CubeMX将使用可在Keil uVision中打开,无需其他设置即可编译和刷新的代码创建项目。在main.c文件main(void)函数中已插入功能来初始化时钟,端口,计时器和USB。在其中,微控制器模块根据我们在CubeMX中设置的模式进行配置。



在代码中,经常会发现这种构造:

/* USER CODE BEGIN 0 */
(...)
/* USER CODE END 0 */

假定用户将其代码嵌入这些部分中。如果在CubeMX项目设置中启用了“在重新生成时保留用户代码”选项,则在二次生成现有项目期间,这些行之间包含的代码将不会被覆盖。不幸的是,仅保存了由CubeMX创建的部分。节/ *用户代码* /由用户创建将会丢失。因此,如果在IDE中编写了代码之后,您想返回CubeMX并使用新设置再次生成项目,则建议制作该项目的备份副本。建议您

在uVision的固件设置中(Flash- > Configure Flash Tools),启用Flash之后重置选项。因此,微控制器在闪烁后立即启动。默认情况下,它是禁用的,并且每次闪烁后,您必须按板上的Reset(重置)按钮。



PPM解码


PPM-脉冲位置调制-一种编码传输信号的方法,在飞机模型电子设备中非常普遍。它是一个脉冲序列,其时间间隔对应于所传输的数值。



根据此协议,控制台将信息发送到发送无线电模块,该模块插入到背面的控制台中。放置在直升机上的许多接收器可以通过PPM为飞行控制器发送控制信号。此外,几乎所有控制台都具有连接器,用于在培训师-学生模式下连接第二个控制台以及将控制台连接到通常也使用PPM的模拟器。

我们使用逻辑分析仪从Turnigy 9X的仿真器输出中写入信号:



每个序列都编码遥控器上控件的当前状态。通常前四个值(也称为通道)对应于模拟摇杆的位置,而后四个对应于拨动开关或电位计的位置。
控件的最小位置对应于间隔1000μs,最大间隔-2000μs,平均位置-1500μs。脉冲或帧的突发之间间隔较长的时间,间隔为20–25 ms。

让我们仔细看一下信号:



如您所见,三根摇杆位于中间位置(1、3、4),一根位于极限位置(2)。三个拨动开关已关闭(5、6、7),最后一个已打开(8)。作为适配器的微控制器必须捕获这样的序列,将值添加到数组中,并通过USB作为操纵杆的命令将其发送出去。让我们写一个脉冲序列解码器。

捕获中断

main.c中初始化之后在main while循环之前从通道1以输入捕获捕获模式启动TIM3计时器,并产生捕获中断。为此,请使用HAL中的相应功能:

HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);

main.c中声明htim3结构是TIM3计时器处理程序,它包含与计时器相关的所有结构和变量:初始化参数,指向所有计时器寄存器的指针(计数器值,除法器,所有设置,中断标志),指针在与此计时器配合使用的处理程序DMA等上开发人员无需查找哪个寄存器中的哪些位负责什么,而无需手动设置和重置它们。将处理程序传递给HAL函数就足够了。 HAL库将自行完成其余工作。 HAL结构原理在STM32F3xx HAL驱动程序说明文档中有更详细的描述

(UM1786)。应该注意的是,HAL库本身有充分的文档记录。要了解HAL如何用于计时器以及如何使用它,可以阅读stm32f3xx_hal_tim.hstm32f3xx_hal_tim.c文件中的注释

对于TIM3计时器生成的每个中断,都会调用TIM3_IRQHandler处理程序。它位于stm32f3xx_it.c文件中,依次调用所有计时器的标准HAL_TIM_IRQHandler处理程序,并将指向htim3结构的指针传递给它。

void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */

  /* USER CODE END TIM3_IRQn 0 */
  HAL_TIM_IRQHandler(&htim3);
  /* USER CODE BEGIN TIM3_IRQn 1 */

  /* USER CODE END TIM3_IRQn 1 */
}

如果我们查看HAL_TIM_IRQHandler文件stm32f3xx_hal_tim.c则会看到一个巨大的处理程序,该处理程序检查中断标志的计时器是否导致回调函数,并在执行后清除该标志。如果发生捕获事件,它将调用HAL_TIM_IC_CaptureCallback函数看起来像这样:

__weak void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
/* NOTE : This function Should not be modified, when the callback is needed, the __HAL_TIM_IC_CaptureCallback could be implemented in the user file
*/
}

这意味着我们可以在main.c中覆盖此函数因此,将此回调插入int main(void)函数之前

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
};

我想看看中断是如何执行的。快速添加以下结论之一:

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET);
  __nop();__nop();__nop();__nop();__nop();__nop();
  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_RESET);
};

PE8引脚已被初始化为输出。在打开和关闭之间,插入__nop()指令,这形成1个时钟周期的延迟。这样做是为了使我的$ 8中文逻辑分析仪在24 MHz下运行时不会错过来自72 MHz微控制器的太短脉冲。现在,编译项目Project-> Build target并询问Flash控制器-> Download我们将把PPM从远程连接到PC6,并查看PC6和PE8与分析仪之间的情况。



回调实际上是在正确的时间进行的-在输入信号从1转换为0之后。因此,一切都正确完成了。

捕获和处理捕获的数据

我们将对回调进行编辑,以便将每个捕获的值保持不变地添加到capture_value缓冲区中。如果计时器捕获到非常大的值(大于5000μs),则意味着记录了暂停,数据包已全部接收,可以进行处理。将处理后的值添加到5个元素rc_data数组中。在前四个中,操纵杆位置减小到范围[0; [1000],在第五个中,根据拨动开关设置各个位,这将被解释为游戏手柄上的按下按钮。

uint16_t captured_value[8] = {0};
uint16_t rc_data[5] = {0};
uint8_t pointer = 0;
uint8_t data_ready = 0;

...

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    uint8_t i;
    uint16_t temp;
    //     
    temp = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
    //    , ,  
    if ((temp > 5000) && (!data_ready))
    {
        pointer = 0;
        //        [0;1000]
        for (i = 0; i < 4; i++)
        {
            if (captured_value[i] < 1000)
                captured_value[i] = 1000;
            else if (captured_value[i] > 2000)
                captured_value[i] = 2000;
            rc_data[i] = captured_value[i]-1000;
        };
        //     
        rc_data[4] = 0;
        if (captured_value[4] > 1500)
            rc_data[4] |= (1<<4);
        if (captured_value[5] > 1500)
            rc_data[4] |= (1<<5);
        if (captured_value[6] > 1500)
            rc_data[4] |= (1<<6);
        if (captured_value[7] > 1500)
            rc_data[4] |= (1<<7);
        data_ready = 1;
    }
    else //      
    {
        captured_value[pointer] = temp;
        pointer++;
    };
    if (pointer == 8) //   
        pointer = 0;
}

让我解释一下为什么我将与按钮相对应的位不是放在低4位,而是放在第五到第八位。在模拟器中,应该连接Xbox上的游戏板,其中使用了LB,RB,“开始”和“返回”按钮,并且它们的编号从5到8。

在主循环中,data_ready标志将连续旋转,通过该标志将数据发送到计算机。

while (1)
{
  if (data_ready)
  {
    //      
    data_ready = 0;
  }
}

要检查它是如何工作的,请连接遥控器,再次对其进行编译和刷新,然后开始调试Debug-> Start / Stop Debug Session
打开用于跟踪变量的窗口,然后单击“ 查看”->“监视Windows”->“监视1”,然后在其中添加captured_valuerc_data
我们使用“ 调试”->“运行”命令实时开始调试,甚至无需添加断点,就可以看到数字在摇杆之后如何变化。



接下来,您需要以操纵杆命令的形式将数据发送到计算机。

配置HID设备并创建HID报告描述符


USB HID(人机接口设备)是用于人机交互的一类设备。这些包括键盘,鼠标,操纵杆,游戏板,触摸板。 HID设备的主要优点是,它们不需要任何操作系统(Windows,OS X,Android甚至iOS)中的特殊驱动程序(通过USB-Lightning适配器)。详细说明可以在文档“ 设备类定义”中找到。创建PPM-USB适配器需要了解的主要内容是HID报告HID报告描述符

HID设备以预定义的格式将字节数据包发送到计算机。每个这样的程序包都是一个HID报告。设备在连接时会通知计算机有关数据格式的信息,并发送HID报告描述符,数据包描述,以指示数据包包含多少字节以及数据包中每个字节和位的用途。例如,简单鼠标的HID报告由四个字节组成:第一个字节包含有关按下的按钮的信息,第二个和第三个字节包含光标沿X和Y的相对移动,第四个字节包含滚轮的旋转。报告描述符以字节数组形式存储在设备控制器内存中。



在创建描述符之前,我想分别介绍术语。在英语环境中,两个术语很常见- 游戏杆游戏手柄。 “ 操纵杆 ”一词通常称为用一只手握住并向不同方向倾斜的操纵器,而游戏手柄是指用两只手握住的带有按钮和操纵杆的设备。讲俄语的用户通常将操纵杆称为“另一个”。在HID设备的说明中,操纵杆和游戏手柄之间存在差异。飞机模型控制台在功能上与游戏手柄更为相似,因此将来我有时会使用术语“游戏手柄”。

我们生成了一个项目,指示该设备将充当人机接口设备。这意味着USB HID库已连接到项目,并且已经生成了设备描述符。它位于usbd_hid.c文件中,描述了鼠标报告,如下所示:
HID_Mouse_Report_Descriptor
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]  __ALIGN_END =
{
  0x05,   0x01,
  0x09,   0x02,
  0xA1,   0x01,
  0x09,   0x01,

  0xA1,   0x00,
  0x05,   0x09,
  0x19,   0x01,
  0x29,   0x03,

  0x15,   0x00,
  0x25,   0x01,
  0x95,   0x03,
  0x75,   0x01,

  0x81,   0x02,
  0x95,   0x01,
  0x75,   0x05,
  0x81,   0x01,

  0x05,   0x01,
  0x09,   0x30,
  0x09,   0x31,
  0x09,   0x38,

  0x15,   0x81,
  0x25,   0x7F,
  0x75,   0x08,
  0x95,   0x03,

  0x81,   0x06,
  0xC0,   0x09,
  0x3c,   0x05,
  0xff,   0x09,

  0x01,   0x15,
  0x00,   0x25,
  0x01,   0x75,
  0x01,   0x95,

  0x02,   0xb1,
  0x22,   0x75,
  0x06,   0x95,
  0x01,   0xb1,

  0x01,   0xc0
};

手动创建HID报告描述符非常耗时。为了简化该任务,有一个称为HID描述符工具(DT)的工具该程序可以为您的设备创建描述符。在随附的归档文件中,您可以找到用于不同设备的描述符的几个示例。



这是一篇有关为鼠标和键盘创建自己的HID描述符的很好的文章(英文)。我将用俄语告诉您如何制作游戏手柄。

控制台发送的HID报告必须包含两个模拟摇杆轴的四个16位值和按钮的16个一位值。总共10个字节。在DT中创建的句柄如下所示:

    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,                    // USAGE (Game Pad)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xe8, 0x03,              //     LOGICAL_MAXIMUM (1000)
    0x75, 0x10,                    //     REPORT_SIZE (16)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //   END_COLLECTION
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x09, 0x33,                    //     USAGE (Rx)
    0x09, 0x34,                    //     USAGE (Ry)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xe8, 0x03,              //     LOGICAL_MAXIMUM (1000)
    0x75, 0x10,                    //     REPORT_SIZE (16)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //   END_COLLECTION
    0x05, 0x09,                    //   USAGE_PAGE (Button)
    0x19, 0x01,                    //   USAGE_MINIMUM (Button 1)
    0x29, 0x10,                    //   USAGE_MAXIMUM (Button 16)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    0x95, 0x10,                    //   REPORT_COUNT (16)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0xc0                           // END_COLLECTION

它看起来不亚于鼠标描述符。但是,如果您了解每一行的含义,那么一切都会变得很容易理解和合乎逻辑。

USAGE显示系统应如何解释更进一步的数据。
用法类型有很多,它们分为几类-用法页面。因此,为了选择特定的用法,必须首先参考相应的USAGE_PAGE。关于什么用法可以在“ 隐藏用法表”文档中找到在描述符的最开始,我们指示将描述操纵杆:
USAGE_PAGE(通用桌面)
USAGE(游戏手柄

COLLECTION合并了几个相关的数据集。
物理收集用于与一个特定几何点(例如,一根模拟棒)有关的数据。应用程序集合用于在一个设备中组合不同的功能。例如,具有集成触控板的键盘可能具有两个Application Collection。我们仅描述操纵杆,这意味着该集合将是其中一个:
COLLECTION(应用程序)
...
END_COLLECTION

之后,您需要指定将描述传输坐标的元素。使用指针用于描述鼠标,操纵杆,游戏手柄,数字转换器:
使用方法(指针)

以下是对集合中的模拟摇杆的描述:
集合(物理)
USAGE (X)
USAGE (Y)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1000)
REPORT_SIZE (16)
REPORT_COUNT (2)
INPUT (Data,Var,Abs)
END_COLLECTION

这里的用法表示使用X和Y这两个轴上的偏差值
.LOGICAL_MINIMUM和LOGICAL_MAXIMUM指定了所传输的值可以变化的程度。
REPORT_COUNT和REPORT_SIZE分别设置要传输多少个数字和什么大小,即两个16位数字。
INPUT(数据,变量,绝对值)表示数据从设备到计算机,并且这些数据可以更改。我们的价值观是绝对的。例如,相对值来自鼠标以移动光标。有时数据被描述为Const,而不是Var。这对于传输非有效位是必需的。例如,在具有三个按钮的鼠标报告中,将传输按钮的3位Var和Const的5位以将传输大小补充为一个字节。
如您所见,X和Y轴的描述组合在一起。它们具有相同的大小,相同的限制。可以如下描述同一根模拟棒,分别描述每个轴。这样的描述符将类似于上一个描述符:
集合(物理)
用法(X)
LOGICAL_MINIMUM(0)
LOGICAL_MAXIMUM(1000)
REPORT_SIZE(16)
REPORT_COUNT(1)
输入(数据,变量,绝对值)
用法(Y)
LOGICAL_MINIMUM(0)
LOGICAL_MAXIMUM(1000)
REPORT_SIZE(16)
REPORT_COUNT(1)
输入(数据,变量,绝对值)
END_COLLECTION

在第一个摇杆之后,描述了第二个模拟摇杆。它的轴有不同的用法,因此您可以将它们与第一个操纵杆区分开-Rx和Ry:
集合(物理)
使用量(Rx)
使用量(Ry)
LOGICAL_MINIMUM(0)
LOGICAL_MAXIMUM(1000)
REPORT_SIZE(16)
REPORT_COUNT(2)
输入(数据,变量,绝对值)
END_COLLECTION

现在,您需要描述游戏手柄的几个按钮。可以按照以下步骤进行:
USAGE_PAGE(按钮)
USAGE(按钮1)
USAGE(按钮2)
USAGE(按钮3)
...
USAGE(按钮16)

通过使用范围,可以减少相同类型按钮的繁琐录制:
USAGE_PAGE(按钮)
USAGE_MINIMUM(按钮1)
USAGE_MAXIMUM(按钮16)

按钮发送的数据是16个单位值,范围从0到1:
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_SIZE (1)
REPORT_COUNT (16)
INPUT (Data,Var,Abs)

描述符中各行的顺序并不严格。例如,可以在“用法”(按钮)之前写入Logical_Minimum和Logical_Maximum,或者可以交换Report_Size和Report_Count行。
输入命令必须具有用于数据传输的所有必需参数(用法,最小,最大值,大小,计数),这一点很重要。

形成描述符后,可以使用“ 解析描述符”命令检查它是否存在错误。
如果一切正常,则将其导出为扩展名h。在usbd_hid.c文件中将描述符替换为新的描述符,并在usbd_hid.h中将HID_MOUSE_REPORT_DESC_SIZE描述符大小从74调整为61。

使用data_ready标志发送报告。为此main.c我们将包含usbd_hid.h标头文件,并在主循环中调用报告发送功能。rc_data数组的类型为uint16,因此指向它的指针必须转换为8位类型,并且将大小传递为10,而不是5。

#include "usbd_hid.h"
...
while (1)
{
  if (data_ready)
  {
    USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)rc_data, 10);
    data_ready = 0;
  };
};

我们编译项目并再次刷新它。

连接与使用


将USB电缆从ST-LINK USB接口重新连接到USER USB接口。Windows将检测到新设备并自动安装驱动程序。让我们转到“ 控制面板”->“设备和打印机”,并查看带有游戏手柄图标的STM32 USB-PPM适配器设备。



在设备的参数中,您可以看到十字线在操纵杆移动后如何围绕田野移动以及列如何移动,并且按钮符号从拨动开关点亮。不需要校准,因为已经在描述符中设置了最小值和最大值。



启动FPV FreeRider,我们将看到在绘制的虚拟游戏手柄的主屏幕上,操纵杆如何根据我们的遥控器移动。如果由于某种原因未正确分配轴,则可以在“ 校准控制器”部分中重新配置它们
与遥控器上的按钮相对应的拨动开关用于切换飞行模式(杂技/稳定),切换摄像头视图(从板上/从地面),从头开始飞行或暂时打开比赛。



飞!




在视频上-我几天训练的结果。当我以自动调平飞行而不是杂技模式飞行时,就像所有FPV赛车大师一样。在acro模式下,如果松开摇杆,直升机将不会自动返回到水平位置,而是继续以与飞行时相同的角度飞行。以acro模式进行管理要困难得多,但是您可以实现更高的速度,机动性,空中动荡甚至颠倒飞行。

查普大师赛我仍然很遥远,但我仍在继续训练,我可以肯定地说,赛车微型直升机的想法使我更加感兴趣。很快,我肯定会不再在模拟器中从事它的建造和飞行,而是要在严酷的现实中,发生真正的坠毁,螺旋桨损坏,电动机损坏以及电池烧坏的情况。但这是其他文章的主题:)



Keil uVision 5和STM32CubeMX的项目位于GitHub上

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


All Articles