打印
[蓝牙芯片]

【ch585评估板测评】基于CH585的Xbox 360 for Windows手柄

[复制链接]
894|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

[i=s] 本帖最后由 lilijin1995 于 2024-12-31 00:20 编辑 [/i]<br /> <br />

基于CH585的Xbox 360 for Windows手柄

概述

XInput是微软为了方便游戏厂商在PC和Xbox 360之间移植游戏而开发的一个接口标准,也被包含在微软的DirectX中。这一标准能同时应用于Xbox 360和Windows XP SP3以上的系统,具备设置简单、通用性强和支持震动功能等特点。目前,大部分支持手柄的PC游戏都支持微软的XInput标准,而部分游戏则支持较老的DirectInput标准。

基于CH585芯片实现Xbox 360 for Windows手柄的Xinput功能,需要硬件和软件两方面的设计。硬件设计方面,可能包括设计一个基于CH585的评估板,该板应包含必要的摇杆和按键,以模拟Xbox 360手柄的功能。软件设计则主要涉及USB HID(Human Interface Device)相关的描述符修改,以及实现Xinput协议。

在实现过程中,首先需要修改设备描述符,包括设置正确的厂商ID(VID)和产品ID(PID),这两个ID分别用于标识设备和产品,对于Xbox 360手柄,微软的VID是0x045E,而Xinput的PID可能是0x028E(这一信息可能因具体实现而异)。接下来,需要修改配置、接口、HID、端点和厂商描述符,以符合Xinput的要求。

完成硬件和软件设计后,还需要进行下载和验证,以确保手柄能够正确地与PC通信,并模拟Xbox 360手柄的功能。此外,对于非Xinput标准的手柄,可以通过下载Xinput Emulator等模拟软件,对手柄按键进行映射,以实现类似的功能。

请注意,以上信息仅供参考,具体实现过程可能因硬件和软件的不同而有所差异。在实际操作中,建议查阅相关的技术文档和资料,以确保实现的准确性和可靠性。

硬件设计:

之前有设计过一款基于CH582的评估板,采用顶板+底板的模式,这次咱们就只需要用到底板,通过杜邦线接线的方式实现;

image.png

下图是接好线的硬件;

608b2c9bb0d72468ca824995b421dc5.jpg

软件设计

软件是基于CH585EVT的USB->USBHS ->DEVICE->CompositeKM修改而来

image.png

USB描述符

USB HID(Human Interface Devices,人机接口设备)相关描述符是用于描述HID设备的特性和功能的。以下是USB HID相关描述符的简单描述:

一、USB标准描述符

虽然这些不是HID特有的描述符,但它们在HID设备的配置和识别中起着重要作用:

  1. 设备描述符:描述设备的基本信息,如供应商ID、产品ID、版本号等。
  2. 配置描述符:描述设备的配置信息,如功耗、接口数量等。
  3. 接口描述符:描述设备的功能接口,对于HID设备,其bInterfaceClass的值必须为0x03。
  4. 端点描述符:描述设备上用于数据传输的端点信息,包括端点地址、传输类型、最大数据包大小等。
  5. 字符串描述符:提供设备的文字描述信息,如制造商名称、产品名称等。

二、HID设备类特定描述符

  1. HID描述符
    • 作用:关联于接口描述符,主要描述HID规范的版本号、HID通信所使用的额外描述符、报表描述符的长度等。
    • 结构:包括bLength(描述符长度)、bDescriptorType(描述符类型,此处为0x21)、bcdHID(HID规范版本号)、bCountry(硬件目的国家的识别码)、bNumDescriptors(支持的附属描述符数目)、bDescriptorType(hid相关描述符的类型)、wDescriptorLength(报告描述符长度)等字段。如果HID设备有多个下级描述符(如报告描述符和物理描述符),则HID描述符的长度会相应增加。
  2. 报告描述符
    • 作用:描述HID设备的输入和输出报告格式。报告是HID设备用来传送数据的主要方式,包括输入报告(由设备发送给主机)和输出报告(由主机发送给设备)。报告描述符提供了设备报告的详细信息,如数据域、逻辑范围等,使得主机能够正确解析和处理报告中的数据。
    • 特点:报告描述符的语法不同于USB标准描述符,它是以项目(items)方式排列而成,无一定的长度。报告描述符已经能够组合出很多种情况,需要PC上的HID驱动程序提供解析器(Parser)来对描述的设备情况进行重新解释,进而组合生成出本HID硬件设备独特的数据流格式。
  3. 实体描述符(可选)
    • 作用:用来描述设备的行为特性。HID设备可以根据其本体的设备特性选择是否包含实体描述符。

综上所述,USB HID相关描述符共同构成了对HID设备的完整描述,使得主机能够正确识别、配置和使用这些设备。

CH585的USB描述符的代码实现是在usb_desc.c,usb_desc.h中实现,这里我们修改成了我们Xinput对应的

/********************************** (C) COPYRIGHT *******************************
 * File Name          : usb_desc.h
 * Author             : WCH
 * Version            : V1.0.0
 * Date               : 2024/07/31
 * Description        : All descriptors for the keyboard and mouse composite device.
 *********************************************************************************
 * Copyright (c) 2024 Nanjing Qinheng Microelectronics Co., Ltd.
 * Attention: This software (modified or not) and binary are used for
 * microcontroller manufactured by Nanjing Qinheng Microelectronics.
 *******************************************************************************/

/*******************************************************************************/
/* Header File */
#include "usb_desc.h"

/*******************************************************************************/
/* Device Descriptor */
const uint8_t MyDevDescr[] =
    {
        0x12,                                              // bLength
        0x01,                                              // bDescriptorType
        0x00, 0x02,                                        // bcdUSB
        0xFF,                                              // bDeviceClass
        0xFF,                                              // bDeviceSubClass
        0xFF,                                              // bDeviceProtocol
        DEF_USBD_UEP0_SIZE,                                // bMaxPacketSize0
        (uint8_t)DEF_USB_VID, (uint8_t)(DEF_USB_VID >> 8), // idVendor
        (uint8_t)DEF_USB_PID, (uint8_t)(DEF_USB_PID >> 8), // idProduct
        0x10, DEF_IC_PRG_VER,                              // bcdDevice
        0x01,                                              // iManufacturer
        0x02,                                              // iProduct
        0x03,                                              // iSerialNumber
        0x01,                                              // bNumConfigurations
};

/* Configuration Descriptor */
const uint8_t MyCfgDescr[] =
    {
        /* Configuration Descriptor */
        0x09,       // bLength
        0x02,       // bDescriptorType
        0x30, 0x00, // wTotalLength
        0x01,       // bNumInterfaces
        0x01,       // bConfigurationValue
        0x00,       // iConfiguration
        0xA0,       // bmAttributes: Bus Powered; Remote Wakeup
        0xFA,       // MaxPower: 100mA

        /* Interface Descriptor (Vendor-Specific) */
        0x09, // bLength
        0x04, // bDescriptorType
        0x00, // bInterfaceNumber
        0x00, // bAlternateSetting
        0x02, // bNumEndpoints
        0xFF, // bInterfaceClass
        0x5D, // bInterfaceSubClass
        0x01, // bInterfaceProtocol: Keyboard
        0x00, // iInterface

        /* Unrecognized Class-Specific Descriptor */
        0x10, // bLength
        0x21, // bDescriptorType
        0x10,
        0x01,
        0x01,
        0x24,
        0x81,
        0x14,
        0x03,
        0x00,
        0x03,
        0x13,
        0x02,
        0x00,
        0x03,
        0x00,
        /* Endpoint Descriptor  */
        0x07,       // bLength
        0x05,       // bDescriptorType
        0x81,       // bEndpointAddress: IN Endpoint 1
        0x03,       // bmAttributes
        0x20, 0x00, // wMaxPacketSize
        0x04,       // bInterval: 4mS

        /* Endpoint Descriptor*/
        0x07,       // bLength
        0x05,       // bDescriptorType
        0x02,       // bEndpointAddress: IN Endpoint 2
        0x03,       // bmAttributes
        0x20, 0x00, // wMaxPacketSize
        0x08        // bInterval: 8mS
};

/* Language Descriptor */
const uint8_t MyLangDescr[] =
    {
        0x04,
        0x03,
        0x09,
        0x04};

/* Manufacturer Descriptor */
const uint8_t MyManuInfo[] =
    {
        0x0C,
        0x03,
        'e',
        0,
        'p',
        0,
        'l',
        0,
        'a',
        0,
        'y',
        0};

/* Product Information */
const uint8_t MyProdInfo[] =
    {
        0x0E,
        0x03,
        'x',
        0,
        'i',
        0,
        'n',
        0,
        'p',
        0,
        'u',
        0,
        't',
        0};

/* Serial Number Information */
const uint8_t MySerNumInfo[] =
    {
        0x16,
        0x03,
        '0',
        0,
        '1',
        0,
        '2',
        0,
        '3',
        0,
        '4',
        0,
        '5',
        0,
        '6',
        0,
        '7',
        0,
        '8',
        0,
        '9',
        0};

在USB2_DEVICE_IRQHandler中断函数里面需要修改相关的请求,我们会提供附件代码,这里不再赘述;

应用代码实现

ADC+DMA+通道切换模式

/*
 * adc.c
 *
 *  Created on: Dec 24, 2024
 *      Author: Administrator
 */
#include "adc.h"
#include "xinput.h"
#include "stdint.h"
uint16_t adcBuff[20];
volatile uint8_t DMA_end = 0;
static volatile int16_t X = 0, Y = 0;
volatile int16_t lastX = 0, lastY = 0;
void adc_init(void)
{
    /* DMA单通道采样:选择adc通道0做采样,对应 PA4引脚 */
    PRINT("\n3.Single channel DMA sampling...\n");
    GPIOA_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_Floating);
    GPIOA_ModeCfg(GPIO_Pin_5, GPIO_ModeIN_Floating);
    ADC_ExtSingleChSampInit(SampleFreq_8_or_4, ADC_PGA_1_4);
    ADC_ChannelCfg(0);
    ADC_ExcutSingleConver();  // 时间足够时建议再次转换并丢弃首次ADC数据
    ADC_AutoConverCycle(192); // 采样周期�? (256-192)*16个系统时�?
    ADC_DMACfg(ENABLE, (uint32_t)&adcBuff[0], (uint32_t)&adcBuff[20], ADC_Mode_Single);
    PFIC_EnableIRQ(ADC_IRQn);
}
/*  Re-maps a number from one range to another
 *
 */
int32_t map(int32_t x, int32_t in_min, int32_t in_max, int32_t out_min, int32_t out_max)
{
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
void adc_handler(void)
{

    uint8_t i;
    uint32_t sumx = 0, sumy = 0;
    uint16_t xtemp = 0, ytemp = 0;
    ADC_ChannelCfg(0);
    DMA_end = 0;
    ADC_DMACfg(ENABLE, (uint32_t)&adcBuff[0], (uint32_t)&adcBuff[20], ADC_Mode_Single);

    ADC_StartAutoDMA();
    while (!DMA_end)
        ;
    DMA_end = 0;
    ADC_DMACfg(DISABLE, 0, 0, 0);

    for (i = 0; i < 20; i++)
    {
        sumx += adcBuff[i];
    }
    xtemp = sumx / 20;
    PRINT("xtemp = %d\n", xtemp);
    if (xtemp > X_AD_MAX)
    {
        xtemp = X_AD_MAX;
    }
    else if (xtemp < X_AD_MIN)
    {
        xtemp = X_AD_MIN;
    }

    if (xtemp > (X_AD_CENTER + 5))
    {
        X = map(xtemp, (X_AD_CENTER + 5), X_AD_MAX, 0, INT16_MAX);
    }
    else if (xtemp < (X_AD_CENTER - 5))
    {
        X = map(xtemp, X_AD_MIN, (X_AD_CENTER - 5), INT16_MIN, 0);
    }
    else
    {
        X = 0;
    }

    PRINT("X = %d\n", X);

    ADC_ChannelCfg(1);
    DMA_end = 0;
    ADC_DMACfg(ENABLE, (uint32_t)&adcBuff[0], (uint32_t)&adcBuff[20], ADC_Mode_Single);

    ADC_StartAutoDMA();
    while (!DMA_end)
        ;
    DMA_end = 0;
    ADC_DMACfg(DISABLE, 0, 0, 0);

    for (i = 0; i < 20; i++)
    {
        sumy += adcBuff[i];
    }
    ytemp = sumy / 20;
    PRINT("ytemp = %d\n", ytemp);
    if (ytemp > Y_AD_MAX)
    {
        ytemp = Y_AD_MAX;
    }
    else if (ytemp < Y_AD_MIN)
    {
        ytemp = Y_AD_MIN;
    }
    if (ytemp > (Y_AD_CENTER + 5))
    {
        Y = map(ytemp, (Y_AD_CENTER + 5), Y_AD_MAX, 0,INT16_MIN );
    }
    else if (ytemp < (Y_AD_CENTER - 5))
    {
        Y = map(ytemp, Y_AD_MIN, (Y_AD_CENTER - 5), INT16_MAX, 0);
    }
    else
    {
        Y = 0;
    }
    if (X != lastX || Y != lastY)
    {
        lastX = X;
        lastY = Y;
        set_xinput_report(READY);
    }
    PRINT("Y = %d\n", Y);
}
void get_xy(int16_t *x, int16_t *y)
{
    *x = X;
    *y = Y;
}
__INTERRUPT
__HIGH_CODE
void ADC_IRQHandler(void) // adc中断服务程序
{
    if (ADC_GetDMAStatus())
    {
        ADC_StopAutoDMA();
        R32_ADC_DMA_BEG = ((uint32_t)adcBuff) & 0x1ffff;
        ADC_ClearDMAFlag();
        DMA_end = 1;
    }
    if (ADC_GetITStatus())
    {
        ADC_ClearITFlag();
    }
}

adc中断服务程序这里一定要注意ADC_GetITStatus和ADC_ClearITFlag中断一定要清,不然会在中断里面出不来;找wch的FAE解决,给我的方案是叫我清中断,并解释可能是库的处理问题;

按键处理

/*
 * button.c
 *
 *  Created on: Dec 26, 2024
 *      Author: Administrator
 */
#include "button.h"
#include "xinput.h"
static uint8_t button = 0, last_button = 0;
void button_init(void)
{
    GPIOB_ModeCfg(GPIO_Pin_0, GPIO_ModeIN_PU);
    GPIOB_ModeCfg(GPIO_Pin_1, GPIO_ModeIN_PU);
    GPIOB_ModeCfg(GPIO_Pin_2, GPIO_ModeIN_PU);
    GPIOB_ModeCfg(GPIO_Pin_3, GPIO_ModeIN_PU);
    GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU);
    GPIOB_ModeCfg(GPIO_Pin_5, GPIO_ModeIN_PU);
    GPIOB_ModeCfg(GPIO_Pin_6, GPIO_ModeIN_PU);
    GPIOB_ModeCfg(GPIO_Pin_7, GPIO_ModeIN_PU);
}

void button_handler(void)
{

    // button1
    if (GET_BUTTON1_STATUS() == 0)
    {
        button |= BIT0;
    }
    else
    {
        button &= ~BIT0;
    }
    // button2
    if (GET_BUTTON2_STATUS() == 0)
    {
        button |= BIT1;
    }
    else
    {
        button &= ~BIT1;
    }
    // button3
    if (GET_BUTTON3_STATUS() == 0)
    {
        button |= BIT2;
    }
    else
    {
        button &= ~BIT2;
    }
    // button4
    if (GET_BUTTON4_STATUS() == 0)
    {
        button |= BIT3;
    }
    else
    {
        button &= ~BIT3;
    }
    // button5
    if (GET_BUTTON5_STATUS() == 0)
    {
        button |= BIT4;
    }
    else
    {
        button &= ~BIT4;
    }
    // button6
    if (GET_BUTTON6_STATUS() == 0)
    {
        button |= BIT5;
    }
    else
    {
        button &= ~BIT5;
    }
    // button7
    if (GET_BUTTON7_STATUS() == 0)
    {
        button |= BIT6;
    }
    else
    {
        button &= ~BIT6;
    }
    // button8
    if (GET_BUTTON8_STATUS() == 0)
    {
        button |= BIT7;
    }
    else
    {
        button &= ~BIT7;
    }
    if (button != last_button)
    {

        set_xinput_report(READY);

        last_button = button;
    }
}

uint8_t get_button_status(void)
{
    return button;
}

按键驱动只需要简单的扫描,毕竟按键数量较少;

/*
 * xinput.c
 *
 *  Created on: Dec 26, 2024
 *      Author: Administrator
 */
#include "xinput.h"
#include "adc.h"
#include "button.h"
#include <ch585_usbhs_device.h>
#define XINPUT_BUFFER_SIZE 20
uint8_t xinput_buffer[XINPUT_BUFFER_SIZE] = {0x00, 0x14, 0x00, 0x00, 0x00,
                                             0x00, 0x00, 0x00, 0x00, 0x00,
                                             0x00, 0x00, 0x00, 0x00, 0x00,
                                             0x00, 0x00, 0x00, 0x00, 0x00}; // Holds USB transmit packet data
static ErrorStatus report_flag = NoREADY;


/**
 * [@brief](home.php?mod=space&uid=247401) Returns the status of xinput report transmission
 *
 * [@return](home.php?mod=space&uid=266161) ErrorStatus READY = successful transmission, NoREADY = failed transmission
 */
/******  abc32b94-3e7c-4b82-a397-bbb72a854fb0  *******/ ErrorStatus get_xinput_report(void)
{
    return report_flag;
}

void set_xinput_report(ErrorStatus status)
{
    report_flag = status;
}

void xinput_handler(void)
{
    uint8_t status, button_sta;
    int16_t x, y;
    if (report_flag)
    {
        // XY
        get_xy(&x, &y);
        // xinput_buffer[2] = get_button_status();
        xinput_buffer[LEFT_STICK_X_PACKET_LSB] = LOBYTE(x); // (CONFERIR)
        xinput_buffer[LEFT_STICK_X_PACKET_MSB] = HIBYTE(x);
        // Left Stick Y Axis
        xinput_buffer[LEFT_STICK_Y_PACKET_LSB] = LOBYTE(y);
        xinput_buffer[LEFT_STICK_Y_PACKET_MSB] = HIBYTE(y);
        // Clear DPAD
         xinput_buffer[BUTTON_PACKET_1] &= DPAD_MASK_OFF;
        button_sta = get_button_status();
        if (button_sta & BIT4)
        {
            xinput_buffer[BUTTON_PACKET_2] |= A_MASK_ON;
        }
        else
        {
            xinput_buffer[BUTTON_PACKET_2] &= A_MASK_OFF;
        }

        if (button_sta & BIT5)
        {
            xinput_buffer[BUTTON_PACKET_2] |= B_MASK_ON;
        }
        else
        {
            xinput_buffer[BUTTON_PACKET_2] &= B_MASK_OFF;
        }
        if (button_sta & BIT6)
        {
            xinput_buffer[BUTTON_PACKET_2] |= X_MASK_ON;
        }
        else
        {
            xinput_buffer[BUTTON_PACKET_2] &= X_MASK_OFF;
        }
        if (button_sta & BIT7)
        {
            xinput_buffer[BUTTON_PACKET_2] |= Y_MASK_ON;
        }
        else
        {
            xinput_buffer[BUTTON_PACKET_2] &= Y_MASK_OFF;
        }


        // DPAD Up
        if ((button_sta & BIT0) && !(button_sta & BIT3))
        {
            xinput_buffer[BUTTON_PACKET_1] |= DPAD_UP_MASK_ON;
        }
         // DPAD Down
         if ((button_sta & BIT3) && !(button_sta & BIT0))
         {
             xinput_buffer[BUTTON_PACKET_1] |= DPAD_DOWN_MASK_ON;
         }
         // DPAD Left
         if ((button_sta & BIT1) && !(button_sta & BIT2))
         {
             xinput_buffer[BUTTON_PACKET_1] |= DPAD_LEFT_MASK_ON;
         }
         // DPAD Right
         if ((button_sta & BIT2) && !(button_sta & BIT1))
         {
             xinput_buffer[BUTTON_PACKET_1] |= DPAD_RIGHT_MASK_ON;
         }
        /* Load keyboard data to endpoint 1 */
        status = USBHS_Endp_DataUp(DEF_UEP1, xinput_buffer, sizeof(xinput_buffer), DEF_UEP_CPY_LOAD);

        if (status == READY)
        {
            /* Clear flag after successful loading */
            report_flag = NoREADY;
        }
    }
}

Xinput里面主要就是给上报数据给我的PC(USB主机)

main函数

/********************************** (C) COPYRIGHT *******************************
 * File Name          : main.c
 * Author             : WCH
 * Version            : V1.0.0
 * Date               : 2024/07/31
 * Description        : Main program body.
 *********************************************************************************
 * Copyright (c) 2024 Nanjing Qinheng Microelectronics Co., Ltd.
 * Attention: This software (modified or not) and binary are used for
 * microcontroller manufactured by Nanjing Qinheng Microelectronics.
 *******************************************************************************/

#include <ch585_usbhs_device.h>

#include "adc.h"
#include "timer.h"
#include "button.h"
#include "xinput.h"


/*********************************************************************
 * @fn      DebugInit
 *
 * [@brief](home.php?mod=space&uid=247401)   ���Գ�ʼ��
 *
 * [@return](home.php?mod=space&uid=266161)  none
 */
void DebugInit(void)
{
    GPIOA_SetBits(GPIO_Pin_14);
    GPIOPinRemap(ENABLE, RB_PIN_UART0);
    GPIOA_ModeCfg(GPIO_Pin_15, GPIO_ModeIN_PU);
    GPIOA_ModeCfg(GPIO_Pin_14, GPIO_ModeOut_PP_5mA);
    UART0_DefInit();
}

/*********************************************************************
 * @fn      main
 *
 * [@brief](home.php?mod=space&uid=247401)   Main program.
 *
 * [@return](home.php?mod=space&uid=266161)  none
 */
int main(void)
{
    SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);
    DebugInit();
    PRINT("Compatibility HID Running On USBHS Controller\n");
    adc_init();
    button_init();
    // timer_init();
    /* Initialize USBHS interface to communicate with the host  */
    USBHS_Device_Init(ENABLE);
    PFIC_EnableIRQ(USB2_DEVICE_IRQn);

    while (1)
    {
        if (USBHS_DevEnumStatus)
        {
            button_handler();
            adc_handler();
            xinput_handler();
        }
        // if (USBHS_DevEnumStatus)
        // {
        //     if (sys_timer_flag.tf.tim_5msflag)
        //     {
        //         sys_timer_flag.tf.tim_5msflag = 0;
        //         button_handler();
        //     }

        //     if (sys_timer_flag.tf.tim_10msflag)
        //     {
        //         sys_timer_flag.tf.tim_10msflag = 0;
        //         adc_handler();
        //     }
        //     if (sys_timer_flag.tf.tim_1msflag)
        //     {
        //         sys_timer_flag.tf.tim_1msflag = 0;
        //         xinput_handler();
        //     }
        // }
    }
}

main函数里面主要就是调用相关函数,本来想用时间片轮询,我这里开试过定时器1ms,10ms中断,结果USB都枚举失败,不知道是不是因为定时器中断影响,按理说USB中断优先级最高才对,我不知道它的这个库的优先级咋设置的,这里就直接注释掉,这样ADC和按键扫描完成就发送这样反而响应更快,不太清楚手柄是否可以测试回报率,鼠标是可以设置的;

总结

现在学了一点bms的皮毛,下一步打算做个三模的,还打算用wch的蓝牙去采集电压和电流,NTC温度等,估算SOC,SOH,做电池包,不过不能商用,可以做保护板,因为一般锂电池不能快递;而且需要有一定的资质才能卖这个电池包吧,还有各种认证;麻烦,做来玩玩先吧!

纯软件行业一般都是跟两大巨头走,微软或甲骨文,BLE和USB我想跟着wch走,我2018年就接触了wch,从USB、以太网到蓝牙,从有线到无线,从51、ARM到RISC-V;希望WCH越做越强;

视频演示

<iframe src="https://player.bilibili.com/player.html?bvid=BV1HV6LYDEAz&page=1&danmaku=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

upload 附件:Xinput01.zip

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

55

主题

163

帖子

7

粉丝