返回列表 发新帖我要提问本帖赏金: 50.00元(功能说明)

[APM32F1] 103 USB 键盘+虚拟串口复合设备配置

[复制链接]
2238|4
 楼主| Alden 发表于 2023-9-19 16:45 | 显示全部楼层 |阅读模式
本帖最后由 Alden 于 2023-9-19 17:04 编辑

#申请原创# #技术资源#

APM32F103具有USB全速接口,可以做USB从机的多种功能。USB协议中为了提供对多样设备的支持,定义了许多外部设备子类,常见的包括:
  • 人机交互类设备HID(Human Interface Device)
  • 通信类设备CDC(Communicate Device Class)
  • 大容量存储设备MSC(Mass Storage Class)
  • 视频类设备UVC(USB Video Class)
  • 音频类设备UAC(USB Audio Class)
HID (Human Interface Device) 是一种用于连接人机交互设备的USB设备类别。它定义了一组通用的协议和规范,用于支持键盘、鼠标、游戏控制器等各种输入设备的连接和交互。
CDC(Communication Device Class)是USB组织定义的一类专门给各种通信设备(电信通信设备和中速网络通讯设备)使用的USB子类,常用于虚拟串口。
USB大容量存储设备类(The USB mass storage device class)是一种计算机和移动设备之间的传输协议,它允许一个通用串行总线(USB)设备来访问主机的计算设备,使两者之间进行文件传输,常用于存储器读写和模拟U盘。


SDK中提供的例程就有虚拟串口CDC、鼠标HID、模拟U盘MSC的功能。

除了这些USB做单项功能的,USB还可以配置复合设备,能同时集成了多个不同类型的外设,可以实现多个功能的同时使用

接下来就尝试在极海SDK中增加HID KeyBoard+CDC 虚拟串口的复合设备配置
修改就基于极海APM32F10x_SDK_V1.8中已有的USB_CDC_VirtualCOMPort例程,就不用重新配置虚拟串口部分了。
实现要对USB的描述符进行修改,能让电脑识别出来是个什么设备。
1、USB设备描述符
重点是bDeviceClass改成0xEF,告诉电脑这是个复合设备。
  1. const uint8_t g_usbDeviceDescriptor[USB_DEVICE_DESCRIPTOR_SIZE] =
  2. {
  3.                 0x12,  /*bLength:长度,设备描述符的长度为18字节*/
  4.     USBD_DESC_DEVICE, /*bDescriptorType*/
  5.     0x00,0x02,                     /*bcdUSB = 2.00 */
  6.     0xEF,                       /*bDeviceClass*/
  7.     0x02,                       /*bDeviceSubClass*/
  8.     0x01,                       /*bDeviceProtocol*/
  9.     0x40,                       /*bMaxPacketSize40---------------------------*/
  10.     0x83,0x05,                  /*idVendor (0x0583)*/
  11.     0x50,0x57,                  /*idProduct = 0x5750*/
  12.     0x00,                       /*bcdDevice rel. 2.00*/
  13.     0x02,
  14.     1,                          /*Index of string descriptor describing
  15.                                               manufacturer */
  16.     2,                          /*Index of string descriptor describing
  17.                                              product*/
  18.     3,                          /*Index of string descriptor describing the
  19.                                              device serial number */
  20.     0x01                        /*bNumConfigurations*/
  21. };
2、USB配置描述符
这里配置USB的配置描述符、接口描述符和端点描述符。
HID键盘使用两个端点,端点4(IN)和端点4(OUT)
VCP虚拟串口使用三个端点,端点1(IN)、端点1(OUT)和端点2(IN)
  1. const uint8_t g_usbConfigDescriptor[USB_CONFIG_DESCRIPTOR_SIZE] =
  2. {
  3.     0x09, /* bLength: Configuration Descriptor size */
  4.     USBD_DESC_CONFIGURATION, /* bDescriptorType: Configuration */
  5.     USB_CONFIG_DESCRIPTOR_SIZE,
  6.     /* wTotalLength: Bytes returned */
  7.     0x00,
  8.     0x03,         /* bNumInterfaces: 1 interface */
  9.     0x01,         /* bConfigurationValue: Configuration value */
  10.     0x00,         /* iConfiguration: Index of string descriptor describing
  11.                                  the configuration*/
  12.     0xC0,         /* bmAttributes: Self powered */
  13.     0x32,         /* MaxPower 100 mA: this current is used for detecting Vbus */

  14.     /*************************************功能1 HID键盘**************************************/
  15.     /*IAD描述符*/
  16.     0x08,   //bLength:IAD描述符大小
  17.     0x0B,   //bDescriptorType:IAD描述符类型
  18.     0x00,   //bFirstInterface:功能1 HID键盘的第一个接口描述符是在总的配置描述符中的第几个从0开始数
  19.     0x01,   //bInferfaceCount:功能1 HID键盘有1个接口描述符
  20.     0x03,   //bFunctionClass:同单HID功能时,设备符中的bDeviceClass
  21.     0x00,   //bFunctionSubClass:同单HID功能时,设备符中的bDeviceSubClass
  22.     0x01,   //bFunctionProtocol:同单HID功能时,设备符中的bDeviceProtocol
  23.     0x00,   //iFunction:字符串描述中关于此设备的索引(个人理解是一个字符串描述符中有比如0~5是功能1的字符串,
  24.             //6~10是功能2的字符串,如果是功能2的话,此值为6)
  25.    
  26.       
  27.     /************** Descriptor of Custom HID interface ****************/
  28.     /* 09 */
  29.     0x09,         /* bLength: Interface Descriptor size */
  30.     USBD_DESC_INTERFACE,/* bDescriptorType: Interface descriptor type */
  31.     0x00,         /* bInterfaceNumber: Number of Interface */
  32.     0x00,         /* bAlternateSetting: Alternate setting */
  33.     0x02,         /* bNumEndpoints */
  34.     0x03,         /* bInterfaceClass: HID */
  35.     0x01,         /* bInterfaceSubClass : 1=BOOT, 0=no boot */
  36.     0x01,         /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
  37.     0,            /* iInterface: Index of string descriptor */
  38.     /******************** Descriptor of Custom HID HID ********************/
  39.     /* 18 */
  40.     0x09,         /* bLength: HID Descriptor size */
  41.     0x21, /* bDescriptorType: HID */
  42.     0x10,         /* bcdHID: HID Class Spec release number */
  43.     0x01,
  44.     0x00,         /* bCountryCode: Hardware target country */
  45.     0x01,         /* bNumDescriptors: Number of HID class descriptors to follow */
  46.     0x22,         /* bDescriptorType */
  47.     KEYBOARD_SIZ_REPORT_DESC,
  48.     //KEYBOARD_SIZ_REPORT_DESC,/* wItemLength: Total length of Report descriptor */
  49.     0x00,
  50.     /******************** Descriptor of Custom HID endpoints ******************/
  51.     /* 27 */
  52.     0x07,          /* bLength: Endpoint Descriptor size */
  53.     USBD_DESC_ENDPOINT, /* bDescriptorType: */
  54.     0x84,          /* bEndpointAddress: Endpoint Address (IN) */
  55.     0x03,          /* bmAttributes: Interrupt endpoint */
  56.     0x08,          /* wMaxPacketSize: 8 Bytes max */
  57.     0x00,
  58.     0x20,          /* bInterval: Polling Interval (32 ms) */
  59.     /* 34 */        
  60.     0x07,        /* bLength: Endpoint Descriptor size */
  61.     USBD_DESC_ENDPOINT,        /* bDescriptorType: */
  62.                         /*        Endpoint descriptor type */
  63.     0x04,        /* bEndpointAddress: */
  64.                         /*        Endpoint Address (OUT) */
  65.     0x03,        /* bmAttributes: Interrupt endpoint */
  66.     0x01,        /* wMaxPacketSize: 2 Bytes max  */
  67.     0x00,
  68.     0x20,        /* bInterval: Polling Interval (20 ms) */
  69.     /* 41 */
  70.     /********************************功能2 VCP虚拟串口*****************************/
  71.     /*IAD描述符*/
  72.     /* Interface Association Descriptor(IAD Descriptor)  */
  73.     0x08,   /*  bLength  */
  74.     0x0B,   /*  bDescriptorType*/
  75.     0x01,   /*  bFirstInterface*/
  76.     0x02,   /*  bInterfaceCount*/
  77.     0x02,   /*  bFunctionClass --CDC*/
  78.     0x02,   /*  bFunctionSubClass*/
  79.     0x01,   /*  bFunctionProtocoll*/
  80.     0x00,   /*  iFunction */
  81.    
  82.      /**VCP虚拟串口**/
  83.     /*Interface Descriptor接口描述符*/
  84.     0x09,   /* bLength: Interface Descriptor size */
  85.     USBD_DESC_INTERFACE,  /* bDescriptorType: Interface */
  86.     /* Interface descriptor type */
  87.     0x01,   /* bInterfaceNumber: Number of Interface */   //<接口 1>
  88.     0x00,   /* bAlternateSetting: Alternate setting */
  89.     0x01,   /* bNumEndpoints: One endpoints used 该接口非0端点数*/
  90.     0x02,   /* bInterfaceClass: Communication Interface Class */
  91.     0x02,   /* bInterfaceSubClass: Abstract Control Model */
  92.     0x01,   /* bInterfaceProtocol: Common AT commands */
  93.     0x00,   /* iInterface: */
  94.     /*Header Functional Descriptor类描述符*/
  95.     0x05,   /* bLength: Endpoint Descriptor size */
  96.     0x24,   /* bDescriptorType: CS_INTERFACE */
  97.     0x00,   /* bDescriptorSubtype: Header Func Desc */
  98.     0x10,   /* bcdCDC: spec release number */
  99.     0x01,
  100.     /*Call Management Functional Descriptor*/
  101.     0x05,   /* bFunctionLength */
  102.     0x24,   /* bDescriptorType: CS_INTERFACE */
  103.     0x01,   /* bDescriptorSubtype: Call Management Func Desc */
  104.     0x00,   /* bmCapabilities: D0+D1 */
  105.     0x01,   /* bDataInterface: 1 */
  106.     /*ACM Functional Descriptor*/
  107.     0x04,   /* bFunctionLength */
  108.     0x24,   /* bDescriptorType: CS_INTERFACE */
  109.     0x02,   /* bDescriptorSubtype: Abstract Control Management desc */
  110.     0x02,   /* bmCapabilities */
  111.     /*Union Functional Descriptor*/
  112.     0x05,   /* bFunctionLength */
  113.     0x24,   /* bDescriptorType: CS_INTERFACE */
  114.     0x06,   /* bDescriptorSubtype: Union func desc */
  115.     0x00,   /* bMasterInterface: Communication class interface */
  116.     0x01,   /* bSlaveInterface0: Data Class Interface */
  117.     /*Endpoint 2 Descriptor端点描述符*/
  118.     0x07,   /* bLength: Endpoint Descriptor size */
  119.     USBD_DESC_ENDPOINT,   /* bDescriptorType: Endpoint */
  120.     0x82,   /* bEndpointAddress: (IN2) */
  121.     0x03,   /* bmAttributes: Interrupt */
  122.     VIRTUAL_COM_PORT_INT_SIZE,      /* wMaxPacketSize: */
  123.     0x00,
  124.     0xFF,   /* bInterval: */

  125.     /*Data class interface descriptor类描述符*/
  126.     0x09,   /* bLength: Endpoint Descriptor size */
  127.     USBD_DESC_INTERFACE,  /* bDescriptorType: */
  128.     0x02,   /* bInterfaceNumber: Number of Interface */
  129.     0x00,   /* bAlternateSetting: Alternate setting */
  130.     0x02,   /* bNumEndpoints: Two endpoints used */
  131.     0x0A,   /* bInterfaceClass: CDC */
  132.     0x00,   /* bInterfaceSubClass: */
  133.     0x00,   /* bInterfaceProtocol: */
  134.     0x00,   /* iInterface: */
  135.     /*Endpoint 3 Descriptor端点描述符*/
  136.     0x07,   /* bLength: Endpoint Descriptor size */
  137.     USBD_DESC_ENDPOINT,   /* bDescriptorType: Endpoint */
  138.     0x01,   /* bEndpointAddress: (OUT1) */
  139.     0x02,   /* bmAttributes: Bulk */
  140.     VIRTUAL_COM_PORT_DATA_SIZE,             /* wMaxPacketSize: */
  141.     0x00,
  142.     0x00,   /* bInterval: ignore for Bulk transfer */
  143.     /*Endpoint 1 Descriptor 端点描述符*/
  144.     0x07,   /* bLength: Endpoint Descriptor size */
  145.     USBD_DESC_ENDPOINT,   /* bDescriptorType: Endpoint */
  146.     0x81,   /* bEndpointAddress: (IN1) */
  147.     0x02,   /* bmAttributes: Bulk */
  148.     VIRTUAL_COM_PORT_DATA_SIZE,             /* wMaxPacketSize: */
  149.     0x00,
  150.     0x00    /* bInterval */
  151. };
3、USB HID报告描述符
  1. const uint8_t s_hidKeyboardReportDescriptor[HID_REPORT_DESCRIPTOR_SIZE] =
  2. {
  3. /*short Item   D7~D4:bTag;D3~D2:bType;D1~D0:bSize
  4.         **bTag ---主条目          1000:输入(Input) 1001:输出(Output) 1011:特性(Feature)        1010:集合(Collection) 1100:关集合(End Collection)
  5.         **                  全局条目         0000:用途页(Usage Page) 0001:逻辑最小值(Logical Minimum) 0010:逻辑最大值(Logical Maximum) 0011:物理最小值(Physical Minimum)
  6.         **                                        0100:物理最大值(Physical Maximum) 0101:单元指数(Unit Exponet) 0110:单元(Unit) 0111:数据域大小(Report Size)
  7.         **                                        1000:报告ID(Report ID) 1001:数据域数量(Report Count) 1010:压栈(Push) 1011:出栈(Pop) 1100~1111:保留(Reserved)
  8.         **                  局部条目        0000:用途(Usage) 0001:用途最小值(Usage Minimum) 0010:用途最大值(Usage Maximum) 0011:标识符索引(Designator Index)
  9.         **                                        0100:标识符最小值(Designator Minimum) 0101:标识符最大值(Designator Maximum) 0111:字符串索引(String Index) 1000:字符串最小值(String Minimum)   
  10.         **                                        1001:字符串最大值(String Maximum) 1010:分隔符(Delimiter) 其他:保留(Reserved)
  11.         **bType---00:主条目(main)  01:全局条目(globle)  10:局部条目(local)  11:保留(reserved)
  12.         **bSize---00:0字节  01:1字节  10:2字节  11:4字节*/
  13.         
  14.         //0x05:0000 01 01 这是个全局条目,用途页选择为普通桌面页
  15.         0x05, 0x01, // USAGE_PAGE (Generic Desktop)
  16.         //0x09:0000 10 01 这是个全局条目,用途选择为键盘
  17.         0x09, 0x06, // USAGE (Keyboard)
  18.         //0xa1:1010 00 01 这是个主条目,选择为应用集合,
  19.         0xa1, 0x01, // COLLECTION (Application)
  20.         //0x05:0000 01 11 这是个全局条目,用途页选择为键盘/按键
  21.         0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)

  22.         //0x19:0001 10 01 这是个局部条目,用途的最小值为0xe0,对应键盘上的左ctrl键
  23.         0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
  24.         //0x29:0010 10 01 这是个局部条目,用途的最大值为0xe7,对应键盘上的有GUI(WIN)键
  25.         0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
  26.         //0x15:0001 01 01 这是个全局条目,说明数据的逻辑值最小值为0
  27.         0x15, 0x00, // LOGICAL_MINIMUM (0)
  28.         //0x25:0010 01 01 这是个全局条目,说明数据的逻辑值最大值为1
  29.         0x25, 0x01, // LOGICAL_MAXIMUM (1)

  30.         //0x95:1001 01 01 这是个全局条目,数据域的数量为8个
  31.         0x95, 0x08, // REPORT_COUNT (8)
  32.         //0x75:0111 01 01 这是个全局条目,每个数据域的长度为1位
  33.         0x75, 0x01, // REPORT_SIZE (1)           
  34.         //0x81:1000 00 01 这是个主条目,有8*1bit数据域作为输入,属性为:Data,Var,Abs
  35.         0x81, 0x02, // INPUT (Data,Var,Abs)

  36.         //0x95:1001 01 01 这是个全局条目,数据域的数量为1个
  37.         0x95, 0x01, // REPORT_COUNT (1)
  38.         //0x75:0111 01 01 这是个全局条目,每个数据域的长度为8位
  39.         0x75, 0x08, // REPORT_SIZE (8)
  40.         //0x81:1000 00 01 这是个主条目,有1*8bit数据域作为输入,属性为:Cnst,Var,Abs
  41.         0x81, 0x03, // INPUT (Cnst,Var,Abs)

  42.         //0x95:1001 01 01 这是个全局条目,数据域的数量为6个
  43.         0x95, 0x06, // REPORT_COUNT (6)
  44.         //0x75:0111 01 01 这是个全局条目,每个数据域的长度为8位
  45.         0x75, 0x08, // REPORT_SIZE (8)
  46.         //0x25:0010 01 01 这是个全局条目,逻辑最大值为255
  47.         0x25, 0xFF, // LOGICAL_MAXIMUM (255)
  48.         //0x19:0001 10 01 这是个局部条目,用途的最小值为0
  49.         0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
  50.         //0x29:0010 10 01 这是个局部条目,用途的最大值为0x65
  51.         0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
  52.         //0x81:1000 00 01 这是个主条目,有6*8bit的数据域作为输入,属相为属性为:Data,Var,Abs
  53.         0x81, 0x00, // INPUT (Data,Ary,Abs)

  54.         //0x25:0010 01 01 这是个全局条目,逻辑的最大值为1
  55.         0x25, 0x01, // LOGICAL_MAXIMUM (1)
  56.         //0x95:1001 01 01 这是个全局条目,数据域的数量为2
  57.         0x95, 0x02, // REPORT_COUNT (2)
  58.         //0x75:0111 01 01 这是个全局条目,每个数据域的长度为1位
  59.         0x75, 0x01, // REPORT_SIZE (1)
  60.         //0x05:0000 01 01 这是个全局条目,用途页选择为LED页
  61.         0x05, 0x08, // USAGE_PAGE (LEDs)
  62.         //0x19:0001 10 01 这是个局部条目,用途的最小值为0x01,对应键盘上的Num Lock
  63.         0x19, 0x01, // USAGE_MINIMUM (Num Lock)
  64.         //0x29:0010 10 01 这是个局部条目,用途的最大值为0x02,对应键盘上的Caps Lock
  65.         0x29, 0x02, // USAGE_MAXIMUM (Caps Lock)
  66.         //0x91:1001 00 01 这是个主条目,有2*1bit的数据域作为输出,属性为:Data,Var,Abs
  67.         0x91, 0x02, // OUTPUT (Data,Var,Abs)

  68.         //0x95:1001 01 01 这是个全局条目,数据域的数量为1个
  69.         0x95, 0x01, // REPORT_COUNT (1)
  70.         //0x75:0111 01 01 这是个全局条目,每个数据域的长度为6bit,正好与前面的2bit组成1字节
  71.         0x75, 0x06, // REPORT_SIZE (6)
  72.         //0x91:1001 00 01 这是个主条目,有1*6bit数据域最为输出,属性为:Cnst,Var,Abs
  73.         0x91, 0x03, // OUTPUT (Cnst,Var,Abs)

  74.         0xc0        // END_COLLECTION
  75. };
4、修改端点缓存地址
在 usb_config.h中,调整每个USB端点分配的地址。
  1. #define ENDP0_RXADDR        (0x40)
  2. #define ENDP0_TXADDR        (0x80)

  3. #define ENDP1_TXADDR        (0xC0)
  4. #define ENDP1_RXADDR        (0x0F0)
  5. #define ENDP2_TXADDR        (0x110)

  6. #define ENDP4_RXADDR        (0x150)
  7. #define ENDP4_TXADDR        (0x190)
这时候就完成基本的配置,可以让电脑识别到键盘和虚拟串口的设备,但不能正常使用。
接下来就是代码初始化部分的修改。
1、基于本来虚拟串口的初始化增加HID配置
  1. void CDC_Init(void)
  2. {
  3.     USBD_InitParam_T usbParam;

  4.     USBD_InitParamStructInit(&usbParam);

  5.     usbParam.classReqHandler = USBD_ClassHandler;
  6.           usbParam.stdReqExceptionHandler = KeyBoard_ReportDescriptor;//增加HID配置
  7.     usbParam.resetHandler = VCP_Reset;
  8.     usbParam.inEpHandler = USBD_VCP_InEpCallback;
  9.     usbParam.outEpHandler = USBD_VCP_OutEpCallback;
  10.     usbParam.pDeviceDesc = (USBD_Descriptor_T *)&g_deviceDescriptor;
  11.     usbParam.pConfigurationDesc = (USBD_Descriptor_T *)&g_configDescriptor;
  12.     usbParam.pStringDesc = (USBD_Descriptor_T *)g_stringDescriptor;
  13.     usbParam.pStdReqCallback = &stdReqCallback;

  14.     USBD_Init(&usbParam);
  15. }
  16. void KeyBoard_ReportDescriptor(USBD_DevReqData_T *reqData)
  17. {
  18.     uint8_t len;

  19.     if((reqData->byte.bRequest == USBD_GET_DESCRIPTOR) &&
  20.         (reqData->byte.bmRequestType.bit.recipient == USBD_RECIPIENT_INTERFACE) &&
  21.         (reqData->byte.bmRequestType.bit.type == USBD_REQ_TYPE_STANDARD))
  22.     {
  23.         if(reqData->byte.wValue[1] == 0x21)
  24.         {
  25.             len = USB_MIN(reqData->byte.wLength[0], 9);
  26.             USBD_CtrlInData((uint8_t *)&g_configDescriptor.pDesc[0x12], len);
  27.         }
  28.         else if(reqData->byte.wValue[1] == 0x22)
  29.         {
  30.             len = USB_MIN(reqData->byte.wLength[0], g_ReportDescriptor.size);
  31.             USBD_CtrlInData((uint8_t *)g_ReportDescriptor.pDesc, len);
  32.         }
  33.     }
  34.     else
  35.     {
  36.         USBD_SetEPTxRxStatus(USBD_EP_0, USBD_EP_STATUS_STALL, USBD_EP_STATUS_STALL);
  37.     }
  38. }
到此配置部分基本完成,接下来到main中增加段测试代码,验证USB配置是否正确。
配置虚拟串口一直打印dataBuf[5]= {0xaa,0xbb,0xcc,0xdd,0xff};
HID键盘根据键值表输出数字1
  1. void Delay(uint32_t i)
  2. {
  3.          while(i--);
  4. }
  5.   unsigned char dataBuf[5]= {0xaa,0xbb,0xcc,0xdd,0xff};
  6.             uint8_t Keyboad_Buf[8]={0,0,0x1E,0,0,0,0,0};
  7. int main(void)
  8. {
  9.     CDC_Init();

  10.     while(1)
  11.     {
  12.                          USBD_TxData(USBD_EP_1, dataBuf, 5);
  13.                         
  14.                           USBD_TxData(USBD_EP_4, Keyboad_Buf, 8);
  15.                         Delay(0xFFFFFF);
  16.     }
  17. }
4c72ab2a7a383dee0417a7f4ccbcd9e.png
烧录代码到开发板,接上USB线到电脑,可以看到Bushound能看到配置的复合设备。

7171089d3c096e284c508e192dc462b.png
使用串口助手能看到虚拟串口能正确打印我们的数据,键盘也正常在发送数字1,证明代码配置没问题,两个功能使用一个USB接口实现了。
SDK的虚拟串口也有做接收回传的功能,可以直接使用。
到此键盘+虚拟串口的例程就简单修改完成了,代码也放在下面了。
APM32F10x_SDK_V1.8.zip (507.44 KB, 下载次数: 27)

打赏榜单

21小跑堂 打赏了 50.00 元 2023-09-26
理由:恭喜通过原创审核!期待您更多的原创作品~

评论

以APM32F103的全速USB接口为依托,开发USB 键盘+虚拟串口复合设备,完成度和资料开放度较好,但是相关技术关键点阐述较为简略,后期可加强此处,增加打赏哦  发表于 2023-9-26 10:44
 楼主| Alden 发表于 2023-9-19 17:05 | 显示全部楼层
52wm 发表于 2023-9-24 22:40 | 显示全部楼层
收藏学习了
leslietian 发表于 2023-9-28 08:47 | 显示全部楼层
收藏学习,感谢楼主
您需要登录后才可以回帖 登录 | 注册

本版积分规则

49

主题

115

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部