700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > STM32实现自定义HID复合设备

STM32实现自定义HID复合设备

时间:2019-08-09 16:04:56

相关推荐

STM32实现自定义HID复合设备

复合设备是啥,通俗讲就是一个USB物理设备可以实现多个功能,在主机端可以看到多个设备。通常实现HID复合设备有两种方式。第一种是使用同一个接口,修改报告描述符,增加一个功能集合,同时需要使用报告ID来区分哪一个设备,这样主机端和设备端需要增加报告ID处理,但只需要两个端点来实现功能,对于端口资源较少的MCU可以使用;第二种是使用两个接口,每个接口对应不同的报告描述符,不需要特意使用报告ID来区分,但不同接口使用独立的端点。

本文章使用不同接口来实现,基于stm32 cube例程开发。

开发准备

使用官方STM32Cube_FW_F1_V1.6.0,使用STM32F103XF,因此使用

STM32Cube_FW_F1_V1.6.0\Projects\STM3210E_EVAL\Applications\USB_Device\CustomHID_Standalone

这个工程

移植代码

2.1 修改控制控制USBDP上拉电阻引脚,(内部无法直接控制上下拉,因此增加此电路,否则主机不会枚举设备)

2.2 增加串口1输出调试信息,main函数里添加:

/* uart configuration */

MX_USART1_UART_Init();

添加如下代码实现printf功能

PUTCHAR_PROTOTYPE{HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);return ch;}

2.3 修改usb注册函数,main函数里修改如下代码

USBD_Init(&USBD_Device, &HID_Desc, 0);

/* Add Supported Class/

USBD_RegisterClass(&USBD_Device, &USBD_COMPOSITE);

/Start Device Process */

USBD_Start(&USBD_Device);

2.4 修改desc

在usbd_desc.c,定义自己的desc

#define USBD_VID 0x0483

#define USBD_PID 0x5750

#define USBD_LANGID_STRING 0x409

#define USBD_MANUFACTURER_STRING “man”

#define USBD_PRODUCT_FS_STRING “HID in FS Mode”

#define USBD_CONFIGURATION_FS_STRING “HID Config”

#define USBD_INTERFACE_FS_STRING “HID Interface”

2.5 添加USBD_COMPOSITE

增加如下文件

usbd_composite.c,usbd_composite.h,usbd_customhid_if.c

usbd_customhid_if.h,usbd_keyboard.c,usbd_keyboard.h,usbd_keyboard_if.c,usbd_keyboard_if.h

usbd_composite.c具体内容如下,关键修改为USBD_Composite_CfgFSDesc。

/*** @file usbd_composite.c* @author* @version * @date * @brief KB + HID 复合设备* @note* @attention */#include "usbd_composite.h"#include "usbd_customhid.h"#include "usbd_customhid_if.h"#include "usbd_keyboard.h"#include "usbd_keyboard_hid_if.h"static USBD_CUSTOM_HID_HandleTypeDef *pHIDData;static USBD_CUSTOM_HID_HandleTypeDef *pKBData;static uint8_t USBD_Composite_Init (USBD_HandleTypeDef *pdev,uint8_t cfgidx);static uint8_t USBD_Composite_DeInit (USBD_HandleTypeDef *pdev,uint8_t cfgidx);static uint8_t USBD_Composite_EP0_RxReady(USBD_HandleTypeDef *pdev);static uint8_t USBD_Composite_Setup (USBD_HandleTypeDef *pdev,USBD_SetupReqTypedef *req);static uint8_t USBD_Composite_DataIn (USBD_HandleTypeDef *pdev,uint8_t epnum);static uint8_t USBD_Composite_DataOut (USBD_HandleTypeDef *pdev,uint8_t epnum);static uint8_t *USBD_Composite_GetFSCfgDesc (uint16_t *length);static uint8_t *USBD_Composite_GetDeviceQualifierDescriptor (uint16_t *length);USBD_ClassTypeDef USBD_COMPOSITE ={USBD_Composite_Init,USBD_Composite_DeInit,USBD_Composite_Setup,NULL, /*EP0_TxSent*/USBD_Composite_EP0_RxReady,USBD_Composite_DataIn,USBD_Composite_DataOut,NULL,NULL,NULL,NULL,USBD_Composite_GetFSCfgDesc,NULL,USBD_Composite_GetDeviceQualifierDescriptor,};/* USB composite device Configuration Descriptor *//* All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */__ALIGN_BEGIN uint8_t USBD_Composite_CfgFSDesc[USBD_COMPOSITE_DESC_SIZE] __ALIGN_END ={0x09, /* bLength: Configuration Descriptor size */USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */USBD_COMPOSITE_DESC_SIZE,/* wTotalLength: Bytes returned */0x00,0x02, /*bNumInterfaces: 2 interface*/0x01, /*bConfigurationValue: Configuration value*/0x00, /*iConfiguration: Index of string descriptor describingthe configuration*/0xC0, /*bmAttributes: bus powered */0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*//************** Descriptor of CUSTOM HID interface ****************//* 09 */0x09, /*bLength: Interface Descriptor size*/USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/0x00, /*bInterfaceNumber: Number of Interface*/0x00, /*bAlternateSetting: Alternate setting*/0x02, /*bNumEndpoints*/0x03, /*bInterfaceClass: CUSTOM_HID*/0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/0, /*iInterface: Index of string descriptor*//******************** Descriptor of CUSTOM_HID *************************//* 18 */0x09, /*bLength: CUSTOM_HID Descriptor size*/CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: CUSTOM_HID*/0x11, /*bCUSTOM_HIDUSTOM_HID: CUSTOM_HID Class Spec release number*/0x01,0x00, /*bCountryCode: Hardware target country*/0x01, /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/0x22, /*bDescriptorType*/USBD_CUSTOM_HID_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/0x00,/******************** Descriptor of Custom HID endpoints ********************//* 27 */0x07,/*bLength: Endpoint Descriptor size*/USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/CUSTOM_HID_EPIN_ADDR,/*bEndpointAddress: Endpoint Address (IN)*/0x03,/*bmAttributes: Interrupt endpoint*/CUSTOM_HID_EPIN_SIZE, /*wMaxPacketSize: 64 Byte max */0x00,0x10,/*bInterval: Polling Interval (20 ms)*//* 34 */0x07, /* bLength: Endpoint Descriptor size */USB_DESC_TYPE_ENDPOINT,/* bDescriptorType: */CUSTOM_HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/0x03,/* bmAttributes: Interrupt endpoint */CUSTOM_HID_EPOUT_SIZE,/* wMaxPacketSize: 64 Bytes max */0x00,0x10,/* bInterval: Polling Interval (20 ms) *//************** Descriptor of Keyboard interface ****************//* 41 */0x09, /*bLength: Interface Descriptor size*/USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/0x01, /*bInterfaceNumber: Number of Interface*/0x00, /*bAlternateSetting: Alternate setting*/0x02, /*bNumEndpoints*/0x03, /*bInterfaceClass: Keyboard*/0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/0, /*iInterface: Index of string descriptor*//******************** Descriptor of Keyboard *************************//* 50 */0x09, /*bLength: Keyboard Descriptor size*/CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: Keyboard*/0x11, /*bCUSTOM_HIDUSTOM_HID: Keyboard Class Spec release number*/0x01,0x00, /*bCountryCode: Hardware target country*/0x01, /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/0x22, /*bDescriptorType*/USBD_KEYBOARD_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/0x00,/******************** Descriptor of Keyboard endpoints ********************//* 59 */0x07,/*bLength: Endpoint Descriptor size*/USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/KEYBOARD_HID_EPIN_ADDR,/*bEndpointAddress: Endpoint Address (IN)*/0x03,/*bmAttributes: Interrupt endpoint*/KEYBOARD_HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */0x00,0x10,/*bInterval: Polling Interval (20 ms)*//* 66 */0x07, /* bLength: Endpoint Descriptor size */USB_DESC_TYPE_ENDPOINT,/* bDescriptorType: */KEYBOARD_HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/0x03,/* bmAttributes: Interrupt endpoint */CUSTOM_HID_EPOUT_SIZE,/* wMaxPacketSize: 64 Bytes max */0x00,0x10,/* bInterval: Polling Interval (20 ms) *//* 73 */};/* USB Standard Device Descriptor */__ALIGN_BEGIN uint8_t USBD_Composite_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC] __ALIGN_END ={USB_LEN_DEV_QUALIFIER_DESC,USB_DESC_TYPE_DEVICE_QUALIFIER,0x00,0x02,0x00,0x00,0x00,0x40,0x01,0x00,};/*** @brief USBD_Composite_Init* Initialize the Composite interface* @param pdev: device instance* @param cfgidx: Configuration index* @retval status*/static uint8_t USBD_Composite_Init (USBD_HandleTypeDef *pdev,uint8_t cfgidx){uint8_t res = 0;pdev->pUserData = &USBD_CustomHID_fops;res += USBD_CUSTOM_HID.Init(pdev,cfgidx);pHIDData = pdev->pClassData;pdev->pUserData = &USBD_KEYBOARD_fops_FS;res += USBD_KEYBOARD.Init(pdev,cfgidx);pKBData = pdev->pClassData;return res;}/*** @brief USBD_Composite_DeInit* DeInitilaize the Composite configuration* @param pdev: device instance* @param cfgidx: configuration index* @retval status*/static uint8_t USBD_Composite_DeInit (USBD_HandleTypeDef *pdev,uint8_t cfgidx){uint8_t res = 0;pdev->pClassData = pHIDData;pdev->pUserData = &USBD_CustomHID_fops;res += USBD_CUSTOM_HID.DeInit(pdev,cfgidx);pdev->pClassData = pKBData;pdev->pUserData = &USBD_KEYBOARD_fops_FS;res += USBD_KEYBOARD.DeInit(pdev,cfgidx);return res;}static uint8_t USBD_Composite_EP0_RxReady(USBD_HandleTypeDef *pdev){return USBD_CUSTOM_HID.EP0_RxReady(pdev);}/*** @brief USBD_Composite_Setup* Handle the Composite requests* @param pdev: device instance* @param req: USB request* @retval status*/static uint8_t USBD_Composite_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req){switch (req->bmRequest & USB_REQ_RECIPIENT_MASK){case USB_REQ_RECIPIENT_INTERFACE:switch(req->wIndex){case USBD_HID_DATA_INTERFACE:pdev->pClassData = pHIDData;pdev->pUserData = &USBD_CustomHID_fops;return(USBD_CUSTOM_HID.Setup(pdev, req));case USBD_KB_INTERFACE:pdev->pClassData = pKBData;pdev->pUserData = &USBD_KEYBOARD_fops_FS;return(USBD_KEYBOARD.Setup (pdev, req));default:break;}break;case USB_REQ_RECIPIENT_ENDPOINT:switch(req->wIndex){case CUSTOM_HID_EPIN_ADDR:case CUSTOM_HID_EPOUT_ADDR:pdev->pClassData = pHIDData;pdev->pUserData = &USBD_CustomHID_fops;return(USBD_CUSTOM_HID.Setup(pdev, req));case KEYBOARD_HID_EPIN_ADDR:case KEYBOARD_HID_EPOUT_ADDR:pdev->pClassData = pKBData;pdev->pUserData = &USBD_KEYBOARD_fops_FS;return(USBD_KEYBOARD.Setup (pdev, req));default:break;}break;}return USBD_OK;}/*** @brief USBD_Composite_DataIn* handle data IN Stage* @param pdev: device instance* @param epnum: endpoint index* @retval status*/uint8_t USBD_Composite_DataIn (USBD_HandleTypeDef *pdev,uint8_t epnum){switch(epnum){case HID_INDATA_NUM:pdev->pClassData = pHIDData;pdev->pUserData = &USBD_CustomHID_fops;return(USBD_CUSTOM_HID.DataIn(pdev,epnum));case KB_INDATA_NUM:pdev->pClassData = pKBData;pdev->pUserData = &USBD_KEYBOARD_fops_FS;return(USBD_KEYBOARD.DataIn(pdev,epnum));default:break;}return USBD_FAIL;}/*** @brief USBD_Composite_DataOut* handle data OUT Stage* @param pdev: device instance* @param epnum: endpoint index* @retval status*/uint8_t USBD_Composite_DataOut (USBD_HandleTypeDef *pdev,uint8_t epnum){switch(epnum){case HID_OUTDATA_NUM:pdev->pClassData = pHIDData;pdev->pUserData = &USBD_CustomHID_fops;return(USBD_CUSTOM_HID.DataOut(pdev,epnum));case KB_OUTDATA_NUM:pdev->pClassData = pKBData;pdev->pUserData = &USBD_KEYBOARD_fops_FS;return(USBD_KEYBOARD.DataOut(pdev,epnum));default:break;}return USBD_FAIL;}/*** @brief USBD_Composite_GetHSCfgDesc* return configuration descriptor* @param length : pointer data length* @retval pointer to descriptor buffer*/uint8_t *USBD_Composite_GetFSCfgDesc (uint16_t *length){*length = sizeof (USBD_Composite_CfgFSDesc);return USBD_Composite_CfgFSDesc;}/*** @brief DeviceQualifierDescriptor* return Device Qualifier descriptor* @param length : pointer data length* @retval pointer to descriptor buffer*/uint8_t *USBD_Composite_GetDeviceQualifierDescriptor (uint16_t *length){*length = sizeof (USBD_Composite_DeviceQualifierDesc);return USBD_Composite_DeviceQualifierDesc;}/*** @}*//*** @}*//*** @}*//************************ (C) COPYRIGHT WEYNE *****END OF FILE****/

usbd_customhid_if.c内容如下,主要修改CustomHID_ReportDesc,CustomHID_OutEvent,前者为报告描述符,后者为接受数据处理函数。

/********************************************************************************* @file USB_Device/CustomHID_Standalone/Src/usbd_customhid_if.c* @author MCD Application Team* @version V1.6.0* @date 12-May-* @brief USB Device Custom HID interface file.******************************************************************************* @attention** <h2><center>&copy; Copyright � STMicroelectronics International N.V. * All rights reserved.</center></h2>** Redistribution and use in source and binary forms, with or without * modification, are permitted, provided that the following conditions are met:** 1. Redistribution of source code must retain the above copyright notice, * this list of conditions and the following disclaimer.* 2. Redistributions in binary form must reproduce the above copyright notice,* this list of conditions and the following disclaimer in the documentation* and/or other materials provided with the distribution.* 3. Neither the name of STMicroelectronics nor the names of other * contributors to this software may be used to endorse or promote products * derived from this software without specific written permission.* 4. This software, including modifications and/or derivative works of this * software, must execute solely and exclusively on microcontroller or* microprocessor devices manufactured by or for STMicroelectronics.* 5. Redistribution and use of this software other than as permitted under * this license is void and will automatically terminate your rights under * this license. ** THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY* RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.********************************************************************************//* Includes ------------------------------------------------------------------ */#include "usbd_customhid_if.h"/* Private typedef ----------------------------------------------------------- *//* Private define ------------------------------------------------------------ *//* Private macro ------------------------------------------------------------- *//* Private function prototypes ----------------------------------------------- */static int8_t CustomHID_Init(void);static int8_t CustomHID_DeInit(void);static int8_t CustomHID_OutEvent(uint8_t * data);/* Private variables --------------------------------------------------------- */ADC_HandleTypeDef AdcHandle;uint32_t ADCConvertedValue = 0;uint32_t ADC_Prev_ConvertedValue = 0;uint8_t SendBuffer[2];extern USBD_HandleTypeDef USBD_Device;__ALIGN_BEGIN static uint8_tCustomHID_ReportDesc[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END = {/* USER CODE BEGIN 0 */ 0x06,0xA0,0xFF,//用法页(FFA0h, vendor defined)0x09, 0x01,//用法(vendor defined)0xA1, 0x01,//集合(Application)0x09, 0x02 ,//用法(vendor defined)0xA1, 0x00,//集合(Physical)0x06,0xA1,0xFF,//用法页(vendor defined)//输入报告//0x85,0x01,0x09, 0x03 ,//用法(vendor defined)0x09, 0x04,//用法(vendor defined)0x15, 0x80,//逻辑最小值(0x80 or -128)0x25, 0x7F,//逻辑最大值(0x7F or 127)0x35, 0x00,//物理最小值(0)0x45, 0xFF,//物理最大值(255)0x75, 0x08,//报告长度Report size (8位)0x95, 64, //报告数值(64 fields)0x81, 0x02,//输入(data, variable, absolute)//输出报告// 0x85, 0x02,0x09, 0x05,//用法(vendor defined)0x09, 0x06,//用法(vendor defined)0x15, 0x80,//逻辑最小值(0x80 or -128)0x25, 0x7F,//逻辑最大值(0x7F or 127)0x35, 0x00,//物理最小值(0)0x45, 0xFF,//物理最大值(255)0x75, 0x08,//报告长度(8位)0x95, 64, //报告数值(64 fields)0x91, 0x02,//输出(data, variable, absolute)0xC0, //集合结束(Physical)0xC0, //集合结束(Physical)};USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops = {CustomHID_ReportDesc,CustomHID_Init,CustomHID_DeInit,CustomHID_OutEvent,};/* Private functions --------------------------------------------------------- *//*** @brief CustomHID_Init* Initializes the CUSTOM HID media low layer* @param None* @retval Result of the opeartion: USBD_OK if all operations are OK else USBD_FAIL*/static int8_t CustomHID_Init(void){// GPIO_InitTypeDef GPIO_InitStructure;// ADC_ChannelConfTypeDef sConfig;/* Configure the ADC peripheral */#if 0AdcHandle.Instance = ADCx;__HAL_RCC_ADC1_CLK_ENABLE();AdcHandle.Init.ScanConvMode = DISABLE;AdcHandle.Init.ContinuousConvMode = ENABLE;AdcHandle.Init.DiscontinuousConvMode = DISABLE;AdcHandle.Init.NbrOfDiscConversion = 0;AdcHandle.Init.ExternalTrigConv = ADC_SOFTWARE_START;AdcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT;AdcHandle.Init.NbrOfConversion = 1;HAL_ADC_Init(&AdcHandle);/* Configure ADC regular channel */sConfig.Channel = ADCx_CHANNEL;sConfig.Rank = 1;sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;HAL_ADC_ConfigChannel(&AdcHandle, &sConfig);/* Start the conversion process and enable interrupt */HAL_ADC_Start_DMA(&AdcHandle, (uint32_t *) & ADCConvertedValue, 1);/* Configure LED1, LED2, LED3 and LED4 */BSP_LED_Init(LED1);BSP_LED_Init(LED2);BSP_LED_Init(LED3);BSP_LED_Init(LED4);/* Enable GPIOG clock */__HAL_RCC_GPIOG_CLK_ENABLE();/* Configure PG8 pin as input floating for key Button */GPIO_InitStructure.Mode = GPIO_MODE_IT_RISING_FALLING;GPIO_InitStructure.Pull = GPIO_NOPULL;GPIO_InitStructure.Pin = GPIO_PIN_8;HAL_GPIO_Init(GPIOG, &GPIO_InitStructure);/* Enable and set EXTI2_TSC Interrupt to the lowest priority */HAL_NVIC_SetPriority(EXTI9_5_IRQn, 3, 0);HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);#endifreturn (0);}/*** @brief CustomHID_DeInit* DeInitializes the CUSTOM HID media low layer* @param None* @retval Result of the opeartion: USBD_OK if all operations are OK else USBD_FAIL*/static int8_t CustomHID_DeInit(void){/* * Add your deinitialization code here */return (0);}/*** @brief CustomHID_OutEvent* Manage the CUSTOM HID class Out Event * @param event_idx: LED Report Number* @param state: LED states (ON/OFF)*/static int8_t CustomHID_OutEvent(uint8_t * data){printf("CustomHID_OutEvent:\r\n");for(int i=0;i<CUSTOM_HID_EPOUT_SIZE-1;i++){printf( " %#x ",data[i]);}printf("\r\n");#if 0switch (event_idx){case 1: /* LED1 */(state == 1) ? BSP_LED_On(LED1) : BSP_LED_Off(LED1);break;case 2: /* LED2 */(state == 1) ? BSP_LED_On(LED2) : BSP_LED_Off(LED2);break;case 3: /* LED3 */(state == 1) ? BSP_LED_On(LED3) : BSP_LED_Off(LED3);break;case 4: /* LED4 */(state == 1) ? BSP_LED_On(LED4) : BSP_LED_Off(LED4);break;default:BSP_LED_Off(LED1);BSP_LED_Off(LED2);BSP_LED_Off(LED3);BSP_LED_Off(LED4);break;}#endifreturn (0);}/*** @brief EXTI line detection callbacks* @param GPIO_Pin: Specifies the pins connected EXTI line* @retval None*/void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){#if 0if (GPIO_Pin == KEY_BUTTON_PIN){SendBuffer[0] = KEY_REPORT_ID;if (BSP_PB_GetState(BUTTON_KEY) == GPIO_PIN_RESET){SendBuffer[1] = 0x01;}else{SendBuffer[1] = 0x00;}USBD_CUSTOM_HID_SendReport(&USBD_Device, SendBuffer, 2);}#endif}/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

编译下载,插入电脑,检测到两个设备,大功告成

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。