当前位置: 首页 > news >正文

关于怎么用Cubemx生成的USBHID设备实现读取一体的鼠标键盘设备(改进版)

主要最近做了一个要用STM32实现读取鼠标键盘一体的那种USB设备,STM32的界面上要和电脑一样的能通过这个USB接口实现鼠标移动,键盘的按键。然后我就很自然的去参考了正点原子的例程,可是找了一圈,发现正点原子好像用的库函数,还是自己实现的,然后看了半天都看晕了,感觉自己实现不了,然后就主攻Cubemx实现的USB设备读取了。

在网上找了一圈,终于让我发现了一个可以用的博主的,而且实现了USB读取鼠标键盘一体的设备。
参考网址如下:
https://blog.csdn.net/weixin_50969532/article/details/134313495
基于STM32-USB中间库的多接口主机开发实战
感觉这个博主实现的非常好了,主要实现方法我都是参考这个博主的,只是基于Cubemx实现的还是和博主的实现有一点点的出入,我这里主要跟随博主的实现方法,和博主的文章对比着修改下博主的实现方法,并融合一下,并且实现的更好一点。

使用的硬件

根据网上很多的HID实现方法,大多数都是用F103一类的STM32设备,将STM32设置成Device,模拟鼠标键盘和电脑进行通信,相当于就是用STM32做一个鼠标键盘。但是我们要实现的正好是反着来的,我们要用STM32当做是电脑,读取真正的鼠标键盘的数据,然后在STM32上显示鼠标的移动,按键,键盘的按键。下面是我用的主要硬件。

STM32:正点原子的阿波罗F767开发板

鼠标键盘(一体的):不知道是什么杂牌子,反正写着型号T1,2.4G无线充电鼠标,电压4.2V,载波频率:2402Mhz。

第一步了解USB主机和设备

首先第一步,看下博主的博客。根据博主的说明,我们可以知道,主机和设备之间的USB基础知识。
USB:通用串行总线,是由主机发起通信、从机被动接收的主从机通信机制。

管道:主从机通信的通道,可以理解为两个海岸之间船只来往的航线。要想船只走得通,必须开放这条航线;要想进行通信,必须打开对应的管道。(其实生成的代码里仔细看也会有,生成的Cubemx代码里叫pipe,还分为进出pipe)

端点:主从机传输的最终对象,可以理解为船只来往的码头,航线的两头即端点。要想船只来往,必须开放码头;要想进行通信,必须开放对应的端点。(这个可以理解为Freertos里的队列的进出口,关键Cubemx里也真的是用的队列,队列实现的管道,写入队列和读取队列就是端点)

接口:几个端点的集合,实现某一具体功能。比如这2个码头用来运输食品,这就是一个食品接口;另外4个码头用来运输石油,这是一个石油接口。键鼠一体有两个接口,一个接口是键盘,一个接口是鼠标。(这里就是最重要的,按照博主的说明,接口的数量决定是只有一个鼠标或者一个键盘,还是两个都有。也就是说,如果一个USB的接口插在电脑上,可以接收很多个无线设备的数据,接口几个,就有几个设备)

在这里插入图片描述
这里参考的就是【半路程序媛】https://blog.csdn.net/weixin_50969532/article/details/134313495这位博主的图片,感谢博主的说明。。。

第二步 用Cubemx生成STM32代码

Cubemx来生成的STM32的代码,主要要设置Cubemx的两个部分,第一个,就是USB_OTG_FS,第二个是USB_HOST。(注意,STM32F1都实现不了HOST,只能是Device,所以买错STM32的小伙伴注意了)

然后就是打开Cubemx,选择自己的STM32,我这里用的硬件是F767IGT6,我就选择F767IGT6。
然后就是一般的CUbemx设置,选晶振,设置时钟,配置UART1作为调试接口输出到电脑,Freertos系统配置(是的,我用了系统,没试过不用系统的,但是有系统的话,为啥非要跑裸机呢~)。

好了,设置好了后,就可以开始用Cubemx配置USB设备了,首先是Connectivity里的USB_OTG_FS(我这里有USB_OTG_HS,但是我的设备主要用这个OTG_FS接口)
在这里插入图片描述

这里设置好了后,Middleware and Software里就会有一个USB_HOST(有的Cubemx版本可能不在这里,注意找一下,如果找不到,就是你的STM32不支持HOST),然后就是USB_HOST的配置。
在这里插入图片描述
User Constants不用,就用默认就可以了,然后就是Platform Settings这个设置要说明一下。

Platform Settings设置说明

这个设置的意思是STM32支持其中一个STM32的引脚来控制USB设备的供电,也就是在硬件上要有相同的设置。如果你的硬件电路板上,硬件工程师做了这么一个设置,可以用一个STM32的引脚来给外部USB供电,这个设置你就是必须要设置的,而且一定要问硬件工程师是设置的哪个引脚,引脚要一致。
但是如果你电路板本身就是USB有供电了,那这个的设置就对你毫无用处,随便设置一个不用的引脚吧,就是为了Cubemx能顺利生成代码而已。。而且在生成后的代码里,也没啥用处。。

如果你用的板子和我的一样,都是正点原子的阿波罗,那你要仔细看下硬件原理图了,正点原子的USB的供电引脚是用一个扩展芯片来实现的,叫pcf8574,这个芯片要用IIC驱动才能让USB供电上,不供电的话就算你怎么改代码都读不到USB的。。别被坑了。。。

回到正题,继续配置Cubemx

Cubemx配置好了这两个以后,USB这方面就完成了。然后就是生成代码。下面我们来看下keil5的代码吧。

Keil5上看下生成的代码,修改代码来实现

Keil5生成的代码我们先看下文件树,我这里主要生成的文件是这样的。
在这里插入图片描述
在这里插入图片描述
这三个文件夹是个USB设备有关的,后面我们主要也是要修改这几个文件来实现一体化设备的数据读取。

关于Cubemx生成的代码里,本身是可以直接用的,要实现键盘的话,就本身生成好的就可以直接用。

Cubemx的代码可以直接驱动一个USB设备?

是的,Cubemx的代码确实直接生成好的就是可以直接插上USB设备就能用。
先看main。。。。呃。。我用的freertos的系统,看main没啥用,就不看了,主要看下在哪里初始化USB设备和怎么用吧。

在这里插入图片描述
我这里的这个函数是Cubemx上了freertos系统后,自动生成的一个任务,我直接把初始化放在任务里,然后初始化USB设备,下面的for(;;)里面只用写自己要处理的usb_demo(&hUsbHostFs)就可以了。下面是我的usb_demo。

下面展示一些 内联代码片

void USB_Demo(USBH_HandleTypeDef * phost)
{char c,i;for(i=0;i<USBH_MAX_NUM_INTERFACES;i++)
{
if(InterfaceIndex[i] != 0xff)
{if(USBH_HID_GetDeviceType(&hUSBHost)==HID_KEYBOARD)     //键盘设备{if(Appli_state==APPLICATION_READY){           //获取键盘信息if(k_pinfo!=NULL){c=USBH_HID_GetASCIICode(k_pinfo);           //转换成ASCII码//这里是我自己加的一句,只用输出c就行了printf("%c",c);MYUSR_KEYBRD_ProcessData(c);                //在LCD上显示出键盘字符k_pinfo = NULL;memset(k_pinfo ,0,sizeof(HID_KEYBD_Info_TypeDef ));}}}else if (USBH_HID_GetDeviceType(&hUSBHost)==HID_MOUSE)  //鼠标设备{if(Appli_state==APPLICATION_READY){// MOUSE_Demo(&hUSBHost);if(m_pinfo!=NULL){MYUSR_MOUSE_ProcessData(&mouse_info);       //LCD上显示鼠标信息m_pinfo = NULL;memset(m_pinfo ,0,sizeof(HID_MOUSE_Info_TypeDef ));}}}else                                                   //无法识别的设备{//printf("无法识别的设备\r\n");}
}
}
}

这个代码其实就是楼上博主的demo代码,博主的文章中没有实现的两点是MYUSR_MOUSE_ProcessData(&mouse_info);和 MYUSR_KEYBRD_ProcessData©;
但是这两个函数我们这里可以先不要,因为本身的这个Cubemx生成的代码里,也只能实现键盘的读取,所以,代码运行到了c=USBH_HID_GetASCIICode(k_pinfo);的时候,就可以了,后面只要printf出来c,就是你按下的那个按键。

到这里Cubemx本身的使用就这样子了,实现一个键盘的按键读取,鼠标我还没试过,不过改改也是可以实现的,只是暂时没那个时间啦。
下面就是要根据参考的博主的步伐,改下Cubemx生成的代码,实现一个USB,读取鼠标键盘两个设备。

一个USB,读取鼠标和键盘两个设备

第一步,改代码,主要在usbh_hid.c里

我这里的修改主要对标博主的第三步和第四步。
把原来Cubemx生成的static函数都改成__WEAK,然后自己重写一遍这几个函数,因为这几个函数两个接口都会要用到。
在这里插入图片描述
原来博主的文章里写的是直接改这几个红框后面的函数,我这里按照博主的方法,把这几个函数重写,原来的函数就直接把static改成__WEAK,这样就算再用Cubemx修改生成一遍,改动也会很小。

第二步,重新建立自己的.c和.h文件,把改动都放在自己的文件里

我这里建立的名字是My_USB_MouseKey.h和My_USB_MouseKey.c,第一步的图里的函数都要重写,所以我把重写后的函数都放下面吧,大家复制粘贴一下,然后自己跑一下,看哪里没有包含.h的自己包含一下。

下面展示一些 内联代码片

My_USB_MouseKey.c
/*------------------函数修改的区域------------------------------*/
uint8_t USBH_HID_GetPollInterval(USBH_HandleTypeDef *phost)
{for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++){HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];if ((phost->gState == HOST_CLASS_REQUEST) ||(phost->gState == HOST_INPUT) ||(phost->gState == HOST_SET_CONFIGURATION) ||(phost->gState == HOST_CHECK_CLASS) ||((phost->gState == HOST_CLASS))){return (uint8_t)(HID_Handle->poll);}else{return 0U;}}return 0U;
}USBH_StatusTypeDef USBH_HID_SOFProcess(USBH_HandleTypeDef *phost)
{for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++){HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];if (HID_Handle->state == USBH_HID_POLL){if ((phost->Timer - HID_Handle->timer) >= HID_Handle->poll){HID_Handle->state = USBH_HID_GET_DATA;#if (USBH_USE_OS == 1U)phost->os_msg = (uint32_t)USBH_URB_EVENT;#if (osCMSIS < 0x20000U)(void)osMessagePut(phost->os_event, phost->os_msg, 0U);#else(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);#endif#endif}}}return USBH_OK;
}USBH_StatusTypeDef USBH_HID_InterfaceInit(USBH_HandleTypeDef *phost)
{USBH_StatusTypeDef status;HID_HandleTypeDef *HID_Handle;uint8_t max_ep;uint8_t num = 0U;uint8_t interface;for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++){interface = USBH_FindInterface(phost, phost->pActiveClass->ClassCode, HID_BOOT_CODE, i+1);#include "My_USB_MouseKey.h"InterfaceIndex[i] = interface;if ((interface == 0xFFU) || (interface >= USBH_MAX_NUM_INTERFACES)) /* No Valid Interface */{USBH_DbgLog("Cannot Find the interface for %s class.", phost->pActiveClass->Name);return USBH_FAIL;}status = USBH_SelectInterface(phost, interface);if (status != USBH_OK){return USBH_FAIL;}phost->pActiveClass->pData[i] = (HID_HandleTypeDef *)USBH_malloc(sizeof(HID_HandleTypeDef));HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];if (HID_Handle == NULL){USBH_DbgLog("Cannot allocate memory for HID Handle");return USBH_FAIL;}/* Initialize hid handler */(void)USBH_memset(HID_Handle, 0, sizeof(HID_HandleTypeDef));HID_Handle->state = USBH_HID_ERROR;/*Decode Bootclass Protocol: Mouse or Keyboard*/if (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bInterfaceProtocol == HID_KEYBRD_BOOT_CODE){USBH_UsrLog("KeyBoard device found!");HID_Handle->Init = USBH_HID_KeybdInit;}else if (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bInterfaceProtocol  == HID_MOUSE_BOOT_CODE){USBH_UsrLog("Mouse device found!");HID_Handle->Init = USBH_HID_MouseInit;}else{USBH_UsrLog("Protocol not supported.");return USBH_FAIL;}HID_Handle->state     = USBH_HID_INIT;HID_Handle->ctl_state = USBH_HID_REQ_INIT;HID_Handle->ep_addr   = phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[0].bEndpointAddress;HID_Handle->length    = phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[0].wMaxPacketSize;HID_Handle->poll      = phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[0].bInterval;if (HID_Handle->poll  < HID_MIN_POLL){HID_Handle->poll = HID_MIN_POLL;}/* Check of available number of endpoints *//* Find the number of EPs in the Interface Descriptor *//* Choose the lower number in order not to overrun the buffer allocated */max_ep = ((phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bNumEndpoints <= USBH_MAX_NUM_ENDPOINTS) ?phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bNumEndpoints : USBH_MAX_NUM_ENDPOINTS);/* Decode endpoint IN and OUT address from interface descriptor */for (num = 0U; num < max_ep; num++){if ((phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress & 0x80U) != 0U){HID_Handle->InEp = (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress);HID_Handle->InPipe = USBH_AllocPipe(phost, HID_Handle->InEp);/* Open pipe for IN endpoint */(void)USBH_OpenPipe(phost, HID_Handle->InPipe, HID_Handle->InEp, phost->device.address,phost->device.speed, USB_EP_TYPE_INTR, HID_Handle->length);(void)USBH_LL_SetToggle(phost, HID_Handle->InPipe, 0U);}else{HID_Handle->OutEp = (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress);HID_Handle->OutPipe  = USBH_AllocPipe(phost, HID_Handle->OutEp);/* Open pipe for OUT endpoint */(void)USBH_OpenPipe(phost, HID_Handle->OutPipe, HID_Handle->OutEp, phost->device.address,phost->device.speed, USB_EP_TYPE_INTR, HID_Handle->length);(void)USBH_LL_SetToggle(phost, HID_Handle->OutPipe, 0U);}}}return USBH_OK;
}USBH_StatusTypeDef USBH_HID_InterfaceDeInit(USBH_HandleTypeDef *phost)
{for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++){HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];if (HID_Handle->InPipe != 0x00U){(void)USBH_ClosePipe(phost, HID_Handle->InPipe);(void)USBH_FreePipe(phost, HID_Handle->InPipe);HID_Handle->InPipe = 0U;     /* Reset the pipe as Free */}if (HID_Handle->OutPipe != 0x00U){(void)USBH_ClosePipe(phost, HID_Handle->OutPipe);(void)USBH_FreePipe(phost, HID_Handle->OutPipe);HID_Handle->OutPipe = 0U;     /* Reset the pipe as Free */}if ((phost->pActiveClass->pData[i]) != NULL){USBH_free(phost->pActiveClass->pData[i]);phost->pActiveClass->pData[i] = 0U;}}return USBH_OK;
}USBH_StatusTypeDef USBH_HID_ClassRequest(USBH_HandleTypeDef *phost)
{USBH_StatusTypeDef status         = USBH_BUSY;USBH_StatusTypeDef classReqStatus = USBH_BUSY;for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++){HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];/* Switch HID state machine */switch (HID_Handle->ctl_state){case USBH_HID_REQ_INIT:case USBH_HID_REQ_GET_HID_DESC:USBH_HID_ParseHIDDesc(&HID_Handle->HID_Desc, phost->device.CfgDesc_Raw);HID_Handle->ctl_state = USBH_HID_REQ_GET_REPORT_DESC;break;case USBH_HID_REQ_GET_REPORT_DESC:/* Get Report Desc */classReqStatus = USBH_HID_GetHIDReportDescriptor(phost, HID_Handle->HID_Desc.wItemLength);if (classReqStatus == USBH_OK){/* The descriptor is available in phost->device.Data */HID_Handle->ctl_state = USBH_HID_REQ_SET_IDLE;}else if (classReqStatus == USBH_NOT_SUPPORTED){USBH_ErrLog("Control error: HID: Device Get Report Descriptor request failed");status = USBH_FAIL;}else{/* .. */}break;case USBH_HID_REQ_SET_IDLE:classReqStatus = USBH_HID_SetIdle(phost, 0U, 0U);/* set Idle */if (classReqStatus == USBH_OK){HID_Handle->ctl_state = USBH_HID_REQ_SET_PROTOCOL;}else{if (classReqStatus == USBH_NOT_SUPPORTED){HID_Handle->ctl_state = USBH_HID_REQ_SET_PROTOCOL;}}break;case USBH_HID_REQ_SET_PROTOCOL:/* set protocol */classReqStatus = USBH_HID_SetProtocol(phost, 0U);if (classReqStatus == USBH_OK){HID_Handle->ctl_state = USBH_HID_REQ_IDLE;/* all requests performed*/phost->pUser(phost, HOST_USER_CLASS_ACTIVE);status = USBH_OK;}else if (classReqStatus == USBH_NOT_SUPPORTED){USBH_ErrLog("Control error: HID: Device Set protocol request failed");status = USBH_FAIL;}else{/* .. */}break;case USBH_HID_REQ_IDLE:default:break;}}return status;
}USBH_StatusTypeDef USBH_HID_Process(USBH_HandleTypeDef *phost)
{USBH_StatusTypeDef status = USBH_OK;for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++){HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];uint32_t XferSize;switch (HID_Handle->state){case USBH_HID_INIT:HID_Handle->Init(phost);HID_Handle->state = USBH_HID_IDLE;#if (USBH_USE_OS == 1U)phost->os_msg = (uint32_t)USBH_URB_EVENT;#if (osCMSIS < 0x20000U)(void)osMessagePut(phost->os_event, phost->os_msg, 0U);#else(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);#endif#endifbreak;case USBH_HID_IDLE:status = USBH_HID_GetReport(phost, 0x01U, 0U, HID_Handle->pData, (uint8_t)HID_Handle->length);if (status == USBH_OK){HID_Handle->state = USBH_HID_SYNC;}else if (status == USBH_BUSY){HID_Handle->state = USBH_HID_IDLE;status = USBH_OK;}else if (status == USBH_NOT_SUPPORTED){HID_Handle->state = USBH_HID_SYNC;status = USBH_OK;}else{HID_Handle->state = USBH_HID_ERROR;status = USBH_FAIL;}#if (USBH_USE_OS == 1U)phost->os_msg = (uint32_t)USBH_URB_EVENT;#if (osCMSIS < 0x20000U)(void)osMessagePut(phost->os_event, phost->os_msg, 0U);#else(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);#endif#endifbreak;case USBH_HID_SYNC:/* Sync with start of Even Frame */if ((phost->Timer & 1U) != 0U){HID_Handle->state = USBH_HID_GET_DATA;}#if (USBH_USE_OS == 1U)phost->os_msg = (uint32_t)USBH_URB_EVENT;#if (osCMSIS < 0x20000U)(void)osMessagePut(phost->os_event, phost->os_msg, 0U);#else(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);#endif#endifbreak;case USBH_HID_GET_DATA:(void)USBH_InterruptReceiveData(phost, HID_Handle->pData,(uint8_t)HID_Handle->length,HID_Handle->InPipe);HID_Handle->state = USBH_HID_POLL;HID_Handle->timer = phost->Timer;HID_Handle->DataReady = 0U;break;case USBH_HID_POLL:if (USBH_LL_GetURBState(phost, HID_Handle->InPipe) == USBH_URB_DONE){XferSize = USBH_LL_GetLastXferSize(phost, HID_Handle->InPipe);if ((HID_Handle->DataReady == 0U) && (XferSize != 0U)){(void)USBH_HID_FifoWrite(&HID_Handle->fifo, HID_Handle->pData, HID_Handle->length);HID_Handle->DataReady = 1U;USBH_HID_EventCallback(phost,i);#if (USBH_USE_OS == 1U)phost->os_msg = (uint32_t)USBH_URB_EVENT;#if (osCMSIS < 0x20000U)(void)osMessagePut(phost->os_event, phost->os_msg, 0U);#else(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);#endif#endif}}else{/* IN Endpoint Stalled */if (USBH_LL_GetURBState(phost, HID_Handle->InPipe) == USBH_URB_STALL){/* Issue Clear Feature on interrupt IN endpoint */if (USBH_ClrFeature(phost, HID_Handle->ep_addr) == USBH_OK){/* Change state to issue next IN token */HID_Handle->state = USBH_HID_GET_DATA;}}}break;default:break;}}return status;
}

My_USB_MouseKey.h

下面展示一些 内联代码片


#ifndef __MY_USB_MOUSEKEY_H__
#define __MY_USB_MOUSEKEY_H__		#include "main.h"	 
#include "usbh_def.h"
#include "usart.h"
#include "usbh_hid_mouse.h"
#include "usb_host.h"
#include "usbh_hid.h"
#include "usbh_hid_parser.h"
#ifdef __cplusplusextern "C" {
#endiftypedef enum{EXTRA_NULL=0,LEFT_PRESSED,RIGHT_PRESSED,EXTRA_NULL2,MIDDLE_PRESSED,EXTRA_NULL3,EXTRA_NULL4,EXTRA_NULL5,SIDE_PRESSED,EXTRA_NULL6,EXTRA_NULL7,EXTRA_NULL8,EXTRA_NULL9,EXTRA_NULL10,EXTRA_NULL11,EXTRA_NULL12,EXTRA_PRESSED,
}mouse_status;	typedef enum{MOVEMENT_NULL = 0,UP,DOWN,LEFT,RIGHT,UP_LEFT,UP_RIGHT,DOWN_LEFT,DOWN_RIGHT,
}mouse_movement_status;extern void USBH_HID_ParseHIDDesc(HID_DescTypeDef *desc, uint8_t *buf);
extern ApplicationTypeDef Appli_state;	 
extern  HID_MOUSE_Info_TypeDef    mouse_info;
#ifdef __cplusplus
}
#endif#endif 

这就是我的两个.c和.h文件,其中有一些要说明一下。先说.h文件。

我的.h文件里的#include不用说,都要包含进来,然后是mouse_status,这个枚举是用来最后对标鼠标按键用的枚举;mouse_movement_status是用来对标鼠标移动的枚举。其他的extern主要都是我自己写的函数要用到,所以要extern出来。

然后是.c文件的说明,上面的.c文件里,都是原来的Cubemx里生成的文件,但是要按照博主的方法修改的函数。

第三步,修改struct

我这里对标的是博主的第二步。

修改类结构体,原定义中只有一个数据接口,将这个改成数组形式,就可实现多个接口/类。
下面展示一些 内联代码片

/* USB Host Class structure */
typedef struct 
{const char          *Name;uint8_t              ClassCode;  USBH_StatusTypeDef  (*Init)        (struct _USBH_HandleTypeDef *phost);USBH_StatusTypeDef  (*DeInit)      (struct _USBH_HandleTypeDef *phost);USBH_StatusTypeDef  (*Requests)    (struct _USBH_HandleTypeDef *phost);  USBH_StatusTypeDef  (*BgndProcess) (struct _USBH_HandleTypeDef *phost);USBH_StatusTypeDef  (*SOFProcess) (struct _USBH_HandleTypeDef *phost);  void*                pData[USBH_MAX_NUM_INTERFACES];//原为pData修改为数组形式
} USBH_ClassTypeDef;

第四步,补充USBH_HID_EventCallbcak函数

这里对标博主的第五步。
补充USBH_HID_EventCallbcak函数。此函数被调用于USBH_HID_Process->case HID_POLL,当接收到数据填入fifo后,立即调用USBH_HID_EventCallbcak函数将数据读取到对应的fifo中,防止被下一接口数据覆盖。KeyboardFlag和MouseFlag 用于标记是哪个接口接收了数据。

关于我自己的修改:我这里的修改其实和博主不太一样,因为每次按下键盘或者鼠标都会触发这个函数,所以我把这个函数放在了我自己写的My_USB_MouseKey.h和My_USB_MouseKey.c中。原生的Cubemx的代码我也是直接__WEAK掉了。

Cubemx的代码修改:
在这里插入图片描述

然后自己写了一遍,自己写的如下:
主要实现的函数是USBH_HID_MouseDecode(phost);和USBH_HID_GetASCIICode(k_pinfo);
但是这两个函数是Cubemx生成的,所以可以直接用。自己要写的数据处理函数是mouse_data_process(mouse_info);
keypad_data_process©;
这两个函数。
下面展示一些 内联代码片

void USBH_HID_EventCallback(USBH_HandleTypeDef *phost,uint8_t flag)
{#include "usb_host.h"#include "usbh_hid_mouse.h"char c;HID_KEYBD_Info_TypeDef *k_pinfo;HID_MOUSE_Info_TypeDef *m_pinfo;if(1 == flag){m_pinfo = USBH_HID_GetMouseInfo(phost); if (m_pinfo != NULL){//LED_0_State = 0;USBH_HID_MouseDecode(phost);mouse_data_process(mouse_info);memset(m_pinfo ,0,sizeof(HID_MOUSE_Info_TypeDef ));}}else{k_pinfo = USBH_HID_GetKeybdInfo(phost); /* ?????? */if(k_pinfo == NULL){uint8_t err = 0xff;}if (k_pinfo != NULL){c = USBH_HID_GetASCIICode(k_pinfo); /* ???ASCII? */keypad_data_process(c);memset(k_pinfo ,0,sizeof(HID_KEYBD_Info_TypeDef ));}}
}
// An highlighted block

第五步 修改鼠标和键盘初始化里的pdata

系统自动生成的代码里有两个和键盘鼠标有关的.C文件,一个是usbh_hid_keybd.c,另一个就是usbh_hid_mouse.c。这两个文件夹里各有两个函数要修改。

5.1 键盘文件修改

第一个是关于键盘的,里面的USBH_HID_KeybdInit函数的第一行的phost->pActiveClass->pData要改成phost->pActiveClass->pData[0]。
第二个是USBH_HID_KeybdDecode函数的第一行的phost->pActiveClass->pData要改成phost->pActiveClass->pData[0]。

5.2 鼠标文件修改

第一个是关于鼠标的,里面的USBH_HID_MouseInit函数的第一行的phost->pActiveClass->pData要改成phost->pActiveClass->pData[1]。
第二个是USBH_HID_MouseDecode函数的第一行的phost->pActiveClass->pData要改成phost->pActiveClass->pData[1]。

修改理由:
主要是因为0存储的是键盘的协议,1存储的是鼠标的协议。

第六步 注意Cubemx生成的时候的内存

在Cuebmx的界面里我们设置过一个USB_HOST —>Parameter Settings -----> CMSIS_RTOS ----> USBH_PROCESS_STACK_SIZE
这个参数的设置非常重要,必须大于1500,一般在我们的鼠标键盘没有写什么的时候,大于512就可以跑的起来,但是当像我这样写了一些函数来判断后,内存如果小于1500就跑不完,有的时候就连USB的配置符参数都没有读取完成,或者就直接出现系统卡死现象。这个函数就是用来控制整个USB线程能不能正常跑完,不卡死系统的参数。

还有一个参数就是USB_HOST —>Parameter Settings -----> Host_Configuration ----->USBH_MAX_DATA_BUFFER
这个参数也很重要,一般没有写什么在Callback的返回函数里的时候,这个数值要大于1024,小于这个数值就会根本进不了Callback函数,会出现按了键盘鼠标什么都不出现。当写了一些判断函数后,也就是我这里的所有的函数的量后,这个数值要大于1500,越大自然越好。小了后,不管按什么都不会有反应。一般最好要大于2000左右,小了自然就很难找到错误了。

关于参考的博主的第六步和第七步

关于博主的第六步和第七步我都没有去做,第一是我发现做了以后,整个USB就跑不动了,不知道哪里出了问题,第二就是我觉得博主的第七步说的很对,根本不需要跑demo了,直接所有的都在USBH_HID_EventCallback里已经处理了。

关于运行后还有的一些错误

如果你按我的方法这样修改后,KEIL5一定还是会报一些错误,因为原生的Cubemx代码里还是有一些是static定义的,所以如果还有错误,你可以看下错误的地方,把host_hid.c里只要是自己__weak了的函数里的错误的全删除掉,因为只要__weak了,而且你重写了,就不会再跑__WEAK的函数。还可能有一些错误是识别不到函数,就要#include 一下啊,或者有些变量比如pdata或者下面改进的部分会出现的问题,加了Z在鼠标解码的结构体里,就要自己改下加一下。

关于我自己做的一些改进

我根据自己的硬件设备,做了一些读取USB键盘和鼠标的改进,因为原生的Cubemx生成的鼠标还有一些问题,现在的设备的鼠标的数据和Cubemx生成代码的解码还是有一些出入,这里说明一下这些有出入的地方和我自己的改进部分。

键盘的改进

我上面的代码里不是有实现keypad_data_process©;这个函数么,我这个函数就是输出一下按键到UART1这种调试接口,用电脑显示一下,我的函数如下:
下面展示一些 内联代码片

void keypad_data_process(uint8_t data)
{printf("%c",data);
}	

鼠标的改进

因为原生的Cubmx生成的代码在鼠标上和现在的键盘鼠标一体的设备的出入比较大,所以在原生的Cubemx生成的代码上,还要做一些修改。

第一步

USBH_StatusTypeDef USBH_HID_MouseDecode(USBH_HandleTypeDef *phost)函数要修改。
修改后的函数如下,大家可以对比下Cubemx生成的代码,看下区别。
在这里插入图片描述

第二步

我这里的是比原生的代码多出来的部分,我这里其实是增加了下面的这个prop_z,这个你们也可以按照我的方法自己加一下,主要是打开usbh_hid_mouse.c文件,差不多150行左右,复制prop_y,粘贴在prop_y下面,并且把mouse_report_data + 2改成mouse_report_data + 3就行了,其他的不变。
在这里插入图片描述

第三步

这个.h里要加一个z,只有z才包含鼠标的上下移动的数据。
在这里插入图片描述

这样就好了,这里我要说明下为啥要加这个。
首先,我发现mouse_info这个变量,也就是用HID_MOUSE_Info_TypeDef定义的是这样的,原生的代码只有x,y,buttons[3],这三个变量,但是我测试下来发现,x里面只有鼠标按键的左键、右键、中建、边按键、第二个边键的信息。y里面只有鼠标的左右移动的信息,所以还缺失了鼠标上下移动的信息。所以在原生的代码里,还要加上这条,才能实现鼠标的上下移动的代码。关于buttons[3],这个就没啥用了,但是每次发送回来的数据里button[3]都是一样的数据,所以可以当做是一种验证码,如果哪次发送回来的buttons【3】数据不一样,就说明这次是错误数据。

关于鼠标的解析

因为经过不断的测试,我的鼠标的y和z两个函数和上下左右的关联都有,所以必须要一起处理,单独处理的话,就会很容易解析出错,明明是左移数据,就可能会解析成左和下都有,所以花乐2个小时又不断测试修改,最后成了下面的这些函数。

我自己的鼠标的解析函数如下:

下面展示一些 内联代码片

int InterfaceIndex[USBH_MAX_NUM_INTERFACES] ={0};
uint8_t Pressed[5]={0};void CLear_MousePressed()
{for(int i =0;i<5;i++){Pressed[i]=0;}
}void Show_MousePressed_Status(uint8_t status)
{if((status & LEFT_PRESSED) != 0){Pressed[0] = 1;printf("左键");}if((status & RIGHT_PRESSED) != 0){Pressed[1] = 1;printf("右键");}if((status & MIDDLE_PRESSED) != 0){Pressed[2] = 1;printf("中建");}if((status & SIDE_PRESSED) != 0){Pressed[3] = 1;printf("边按键");}if((status & EXTRA_PRESSED) != 0){Pressed[4] = 1;printf("边2按键");}
}uint8_t Movement_Measure(uint8_t status1,uint8_t status2)
{if(status1 == 0){if(status2 != 0){if(status2>255/2){return UP;}else{return DOWN;}}else{return 0;}}else{if(status2 != 0){if((status1>255/2) && (status2<16)){return LEFT;//0,253,15}else{if((status1>255/2) && (status2>255/2)){return UP_LEFT;//0,255,239}else{if((status1<255/2) && (status2>255/2)){return UP_RIGHT;//0,3,240}else{if((status1>255/2) && (status2<255/2)){return DOWN_LEFT;//0,254,31}else{return DOWN_RIGHT;//0,2,32}}}}}else{if(status1<255/2 ){return RIGHT;}}}return 0;
}void Show_MouseMoved_Status(uint8_t status1,uint8_t status2)
{switch(Movement_Measure(status1,status2)){case LEFT:{printf("左\r\n");break;}case RIGHT:{printf("右\r\n");break;}case UP:{printf("上\r\n");break;}case DOWN:{printf("下\r\n");break;}case UP_LEFT:{printf("左上\r\n");break;}case UP_RIGHT:{printf("右上\r\n");break;}case DOWN_LEFT:{printf("左下\r\n");break;}case DOWN_RIGHT:{printf("右下\r\n");break;}default:break;}
}void keypad_data_process(uint8_t data)
{printf("%c",data);
}	void mouse_data_process(HID_MOUSE_Info_TypeDef data)
{if((data.buttons[0] == 0x00) &&(data.buttons[1] == 0x01)&&(data.buttons[2] == 0x00)){if(data.x !=0){//鼠标按键Show_MousePressed_Status(data.x);}if((data.y != 0x00)||(data.z != 0x00)){//鼠标移动Show_MouseMoved_Status(data.y,data.z);}}CLear_MousePressed();
}	

关于鼠标的知识

参考这位博主的文章,这位博主的解析是正确的。
链接: Linux之解析鼠标input事件数据

根据博主的解释,对应我们STM32的HID_MOUSE_Info_TypeDef的数据结构可知,x是鼠标按键,而且x是uint8_t的8位数据。
x的第一位是左键按下
x的第二位是右键按下
x的第三位是中键按下
x的第四位是边键按下
x的第5位是边2键按下

博主文章中的,data 数组的第1个字节:表示鼠标的水平位移;
也就是对应我们的y。

博主文章中的,data 数组的第2个字节:表示鼠标的垂直位移;
也就是对应我们自己新加的z。

关于后续

因为我这个项目只需要用到这几个按键,和移动,所以我也就测试到这里了,如果还有需要鼠标滚轮的事件的,可以参考
链接: Linux之解析鼠标input事件数据
这位博主的继续修改代码,读取滚轮的数据。

相关文章:

关于怎么用Cubemx生成的USBHID设备实现读取一体的鼠标键盘设备(改进版)

主要最近做了一个要用STM32实现读取鼠标键盘一体的那种USB设备&#xff0c;STM32的界面上要和电脑一样的能通过这个USB接口实现鼠标移动&#xff0c;键盘的按键。然后我就很自然的去参考了正点原子的例程&#xff0c;可是找了一圈&#xff0c;发现正点原子好像用的库函数&#…...

Soildworks学习笔记(二)

放样凸台基体&#xff1a; 自动生成连接两个物体两个面的基体&#xff1a; 2.旋转切除&#xff1a; 3.剪切实体&#xff1a; 4.转换实体引用&#xff1a; 将实体的轮廓线转换至当前草图使其成为当前草图的图元,主要用于在同一平面或另一个坐标中制作草图实体或其尺寸的副本。 …...

Linux配置uwsgi环境

Linux配置uwsgi环境 1.进入虚拟环境 source /envs/django_-shop-system/bin/activate2.安装uwsgi pip install uwsgi3.基于uwsgi运行项目 – 基于配置文件 在项目目录下创建配置文件 #socket 0.0.0.0:8005 http 0.0.0.0:8005 # http120.55.47.111:8005 chdir/opt/www/djang…...

Nagios的安装和使用

*实验* *nagios安装和使用* Nagios 是一个监视系统运行状态和网络信息的监视系统。Nagios 能监视所指定的本地或远程主机以及服务&#xff0c;同时提供异常通知功能等. Nagios 可运行在 Linux/Unix 平台之上&#xff0c;同时提供一个可选的基于浏览器的 WEB 界面以方便系统管…...

Numba 的 CUDA 示例(4/4):原子和互斥

本教程为 Numba CUDA 示例 第 4 部分。 本系列第 4 部分总结了使用 Python 从头开始学习 CUDA 编程的旅程 介绍 在本系列的前三部分&#xff08;第 1 部分&#xff0c;第 2 部分&#xff0c;第 3 部分&#xff09;中&#xff0c;我们介绍了 CUDA 开发的大部分基础知识&#xf…...

【机器学习】机器学习引领AI:重塑人类社会的新纪元

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀机器学习引领AI &#x1f4d2;1. 引言&#x1f4d5;2. 人工智能&#xff08;AI&#xff09;&#x1f308;人工智能的发展&#x1f31e;应用领…...

【制作面包game】

编写一个制作面包的游戏代码涉及到游戏设计、编程和用户界面设计等多个方面。这里我可以提供一个简化版本的Python代码示例&#xff0c;用于创建一个基本的面包制作游戏。这个游戏将会有一个简单的用户界面&#xff0c;玩家可以通过输入命令来制作面包。 游戏的基本流程如下&a…...

Django更改超级用户密码

Django更改超级用户密码 1、打开shell 在工程文件目录下敲入&#xff1a; python manage.py shell再在python交互界面输入&#xff1a; from django.contrib.auth.models import User user User.objects.get(username root) user.set_password(123456) user.save()其中ro…...

ROS基础学习-ROS通信机制进阶

ROS通信机制进阶 目录 0.简介1.常用API1.1 节点初始化函数1.1.1 C++1.1.2 Python1.2 话题与服务相关函数1.2.1 对象获取相关1.2.1.1 C++1.2.1.2 Python1.2.2 订阅对象相关1.2.2.1 C++1.2.2.2 Python1.2.3 服务对象相关函数1.2.3.1 C++1.2.3.2 Python1.2.4 客户端对象相关1.2.4.…...

【Vue3】shallowReactive() and shallowReadonly()

历史小剧场 所谓历史&#xff0c;就是过去的事&#xff0c;它的残酷之处在于&#xff1a;无论你哀嚎&#xff0c;悲伤&#xff0c;痛苦&#xff0c;落寞&#xff0c;追悔&#xff0c;它都无法改变。 一具有名的尸体躺在无数无名的尸体上&#xff0c;这就是所谓的霸业。---- 《明…...

【javaEE初阶】

&#x1f308;&#x1f308;&#x1f308;关于java ⚡⚡⚡java的由来 我们这篇文章主要是来介绍javaEE&#xff0c;一般称为java企业版&#xff0c;实际上java的历史可以追溯到上个世纪90年代&#xff0c;当时主要的语言主流的还是C语言和C&#xff0c;但是在那个时期嵌入式初…...

深度学习 - 梯度下降优化方法

梯度下降的基本概念 梯度下降&#xff08;Gradient Descent&#xff09;是一种用于优化机器学习模型参数的算法&#xff0c;其目的是最小化损失函数&#xff0c;从而提高模型的预测精度。梯度下降的核心思想是通过迭代地调整参数&#xff0c;沿着损失函数下降的方向前进&#…...

Steam下载游戏很慢?一个设置解决!

博主今天重装系统后&#xff0c;用steam下载发现巨慢 500MB&#xff0c;都要下载半小时。 平时下载软件&#xff0c;一般1分钟就搞定了&#xff0c;于是大致就知道&#xff0c;设置应该出问题了 于是修改了&#xff0c;如下设置之后&#xff0c;速度翻了10倍。 如下&#x…...

51单片机采用定时器T1的方式1的中断计数方式,外接开关K4按4次后,8只LED闪烁不停

1、功能描述 采用定时器T1的方式1的中断计数方式&#xff0c;外接开关K4按4次后&#xff0c;8只LED闪烁不停 2、实验原理 定时器原理:8051的定时器可以用于计数外部事件或执行内部定时操作。在本程序中&#xff0c;定时器1被设置为模式2&#xff0c;即8位自动重装载定时器模式…...

windows系统 flutter 开发环境配置

1、管理员运行powershell&#xff0c;安装&#xff1a;Chocolatey 工具&#xff0c;粘贴复制运行下列脚本: Chocolatey 官方安装文档 Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManage…...

【线性代数】SVDPCA

用最直观的方式告诉你&#xff1a;什么是主成分分析PCA_哔哩哔哩_bilibili 奇异值分解singular value decomposition&#xff0c;SVD principal component analysis,PCA 降维操作 pca就是降维后使得信息损失最小 投影在坐标轴上的点越分散&#xff0c;信息保留越多 pca的实现…...

1.Vue2使用ElementUI-初识及环境搭建

目录 1.下载nodejs v16.x 2.设置淘宝镜像源 3.安装脚手架 4.创建一个项目 5.项目修改 代码地址&#xff1a;source-code: 源码笔记 1.下载nodejs v16.x 下载地址&#xff1a;Node.js — Download Node.js 2.设置淘宝镜像源 npm config set registry https://registry.…...

OS复习笔记ch7-3

承接上文我们讲完了页式管理和段式管理&#xff0c;接下来让我们深入讲解一下快表和二级页表 快表 快表和计算机组成原理讲的Cache原理如出一辙。为了减少访存的次数&#xff0c;OS在访问页面的时候创建了快表&#xff08;Translation Lookaside Buffer &#xff0c;简称TLB&…...

MFC 教程-回车时窗口退出问题

【问题描述】 MFC窗口默认时&#xff0c;按回车窗口会退出 【原因分析】 默认调用OnOK() 【解决办法】 重写虚函PreTranslateMessage BOOL CTESTMFCDlg::PreTranslateMessage(MSG* pMsg) {// TODO: 在此添加专用代码和/或调用基类// 修改回车键的操作反应 if (pMsg->…...

CTFHUB-SQL注入-字符型注入

目录 查询数据库名 查询数据库中的表名 查询表中数据 总结 此题目和上一题相似&#xff0c;一个是整数型注入&#xff0c;一个是字符型注入。字符型注入就是注入字符串参数&#xff0c;判断回显是否存在注入漏洞。因为上一题使用手工注入查看题目 flag &#xff0c;这里就不…...

Docker配置Redis集群以及主从扩容与缩容

基础镜像拉取 docker run -p 6379:6379 -d redis:6.0.8 配置文件以及数据卷挂载 # 开启密码验证&#xff08;可选&#xff09; requirepass 1234 # 允许redis外地连接&#xff0c;需要注释掉绑定的IP # bind 127.0.0.1 # 关闭保护模式&#xff08;可选&#xff09; protected-m…...

【计算机网络】 传输层

一、传输层提供的服务 1.1 传输层的功能 1.1.1 传输层的功能如下&#xff1a; 传输层提供应用进程之间的逻辑通信&#xff08;即端到端的通信&#xff09;。与网络层的区别是&#xff1a;网络层提供的是主机之间的逻辑通信。 1.1.2 复用和分用 传输层要还要对收到的报文进行…...

山东大学软件学院项目实训-创新实训-基于大模型的旅游平台(二十七)- 微服务(7)

11.1 : 同步调用的问题 11.2 异步通讯的优缺点 11.3 MQ MQ就是事件驱动架构中的Broker 安装MQ docker run \-e RABBITMQ_DEFAULT_USERxxxx \-e RABBITMQ_DEFAULT_PASSxxxxx \--name mq \--hostname mq1 \-p 15672:15672 \-p 5672:5672 \-d \rabbitmq:3-management 浏览器访问1…...

Java Web应用,IPv6问题解决

在Java Web程序中&#xff0c;如果使用Tomcat并遇到了IPv6相关的问题&#xff0c;可以通过以下几种方式来解决&#xff1a; 1. 配置Tomcat以使用IPv4 默认情况下&#xff0c;Java可能会优先使用IPv6。如果你希望Tomcat使用IPv4&#xff0c;最简单的方法是通过设置系统属性来强…...

MyBatis二级缓存开启条件

MyBatis缓存为俩层体系。分为一级缓存和二级缓存。 一级缓存&#xff1a; 一级缓存默认开启&#xff0c;一级缓存的作用域是SqlSession级别的&#xff0c;这意味着当你更换SqlSession之后就不能再利用原来的SqlSession的一级缓存了。不同的SqlSession之间的一级缓存是隔离的。…...

golang 不用sleep如何实现实现每隔指定时间执行一次for循环?

今天介绍的是在go语言里面不用time.Sleep&#xff0c; 使用for range 定时器管道 来实现按照我们指定的时间间隔来执行for循环, 即&#xff1a; for range ticker.C { } 这样就实现了for每隔指定时间执行一次&#xff0c;除非管道被关闭&#xff0c;否则for而且会一直柱塞当前线…...

【el-tooltips改造】Vue实现文本溢出才显示el-tooltip,否则不显示el-tooltips

实现原理&#xff1a; 使用disabled属性控制el-tooltip的content显示与隐藏&#xff1b; 目标&#xff1a; 1行省略、多行省略、可缩放页面内的文本省略都有效。 实现方式&#xff1a; 1、自定义全局指令&#xff0c;tooltipAutoShow.js代码如下&#xff08;参考的el-table中的…...

【Python数据类型的奥秘】:构建程序基石,驾驭信息之海

文章目录 &#x1f680;Python数据类型&#x1f308;1. 基本概念⭐2. 转化&#x1f44a;3. 数值运算&#x1f4a5;4. 数值运算扩展(math库常用函数) &#x1f680;Python数据类型 &#x1f308;1. 基本概念 整数&#xff08;int&#xff09;&#xff1a;整数是没有小数部分的数…...

vue使用html2canvas截图下载时,存在svg或者img或者特殊字体时截图不全的解决办法

使用html2canvas进行div截图时&#xff0c;存在svg和img的解决办法 写在前面&#xff1a;vue使用html2canvas截图时&#xff0c;存在svg或者img或者特殊字体时截图时空白&#xff0c;或者不全解决办法如下第一步&#xff0c;svg或者img先转base64&#xff08;如果是特殊字体&am…...

机器学习----奥卡姆剃刀定律

奥卡姆剃刀定律&#xff08;Occam’s Razor&#xff09;是一条哲学原则&#xff0c;通常表述为“如无必要&#xff0c;勿增实体”&#xff08;Entities should not be multiplied beyond necessity&#xff09;或“在其他条件相同的情况下&#xff0c;最简单的解释往往是最好的…...

vs做asp网站流程/搜索引擎哪个最好用

本文为芬兰坦佩雷大学&#xff08;作者&#xff1a;Murat Pojon&#xff09;的硕士论文&#xff0c;共39页。 本文研究了机器学习算法在预测学生是否成功方面的应用。本文的重点是比较机器学习方法和特征工程技术在多大程度上提高了预测性能&#xff0c;采用了三种不同的机器学…...

wordpress仿微信底部菜单css/宁波优化关键词首页排名

Linux 入侵检测小结 0x00 审计命令 在linux中有5个用于审计的命令&#xff1a; last&#xff1a;这个命令可用于查看我们系统的成功登录、关机、重启等情况&#xff1b;这个命令就是将/var/log/wtmp文件格式化输出。lastb&#xff1a;这个命令用于查看登录失败的情况&#xff…...

网站使用培训方案/seo竞争对手分析

2019独角兽企业重金招聘Python工程师标准>>> 经过前面的安装、配置、启动&#xff0c;马上就可以看到FastDFS的效果了&#xff0c;我们先上传一个文件&#xff0c;然后再通过http下载。 相关阅读&#xff1a; FastDFS安装使用实战一&#xff08;安装篇&#xff09; …...

劵妈妈这种网站怎么做/软件开发公司简介

解决方案1: 给scroll-view加上white-space: nowrap; &#xff0c;给scroll-view的子元素box加上display:inline-block 就像这样&#xff1a; .scroll-box { white-space: nowrap; } .scroll-box .box{ display:inline-block } 解决方案2:flex scroll-view里包个view&#x…...

网站续费方案/优化网站的目的

标题&#xff1a;日志统计 小明维护着一个程序员论坛。现在他收集了一份"点赞"日志&#xff0c;日志共有N行。其中每一行的格式是&#xff1a; ts id 表示在ts时刻编号id的帖子收到一个"赞"。 现在小明想统计有哪些帖子曾经是"热帖"。如果一…...

枣庄网站制作公司/中国万网登录入口

继续前面一篇随笔《淘宝API开发系列---淘宝API的测试及使用》&#xff0c;来继续介绍淘宝API的具体代码开发部分&#xff0c;上篇主要是介绍淘宝SDK开发的一些流程及必备的信息&#xff0c;以及掌握如何学会利用API文档、淘宝API测试工具来获取我们所需的数据&#xff0c;其中我…...