背景
硬件:STM32H743IIT6芯片,正点原子--阿波罗开发板
软件:STM32H7标准例程-- USB鼠标键盘(Host)实验
USB学习资料:https://download.csdn.net/download/weixin_50969532/88521442
USB主机从机例程:https://download.csdn.net/download/weixin_50969532/88521457
USB鼠标键盘(Host)例程只实现了对单一的鼠标控制,或者单一的键盘控制,而没有实现目前常用的无线USB键鼠一体。键鼠一体设备是有两个接口的设备,需要主机同步开发对两个接口的处理。通过这项实践也能对主机控制接口有更详细的认知。
一、USB基础知识
USB:通用串行总线,是由主机发起通信、从机被动接收的主从机通信机制。
管道:主从机通信的通道,可以理解为两个海岸之间船只来往的航线。要想船只走得通,必须开放这条航线;要想进行通信,必须打开对应的管道。
端点:主从机传输的最终对象,可以理解为船只来往的码头,航线的两头即端点。要想船只来往,必须开放码头;要想进行通信,必须开放对应的端点。
接口:几个端点的集合,实现某一具体功能。比如这2个码头用来运输食品,这就是一个食品接口;另外4个码头用来运输石油,这是一个石油接口。键鼠一体有两个接口,一个接口是键盘,一个接口是鼠标。
二、USB主机库文件简介
主机库的文件分为USB内核文件和类文件:
USB内核文件像心脏,负责处理最核心的事务,比如处理设备连接状态机、开关管道和端点。
类文件像四肢,帮助内核实现某一具体的功能。鼠标键盘都属于HID类(人机交互类)。
例程中写好了usbh_conf.c文件,连接了USB库文件和底层HAL库。
三、HID多接口主机的实现
鼠标Host例程已经实现了单一接口的主机,主要通过以下改动实现两个接口:
1、修改接口数量、端点数量等配置。
/*根据自身设备属性修改端点最大数量、接口最大数量*/
#define USBH_MAX_NUM_ENDPOINTS 2
#define USBH_MAX_NUM_INTERFACES 2
/*根据设备接口描述符定义接口编号*/
#define USBH_KEYBOARD_INDEX 0
#define USBH_MOUSE_INDEX 1
2、修改类结构体,原定义中只有一个数据接口,将这个改成数组形式,就可实现多个接口/类。
/* 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;
3、修改Interface的Init函数
/*定义全局变量*/
uint8_t InterfaceIndex[USBH_MAX_NUM_INTERFACES];
/**
* @brief USBH_HID_InterfaceInit
* The function init the HID class.
* @param phost: Host handle
* @retval USBH Status
*/
static USBH_StatusTypeDef USBH_HID_InterfaceInit (USBH_HandleTypeDef *phost)
{
uint8_t max_ep,i=0;
uint8_t num = 0;
uint8_t interface;
USBH_StatusTypeDef status = USBH_FAIL ;
HID_HandleTypeDef *HID_Handle;
/*note:此处修改为适配两个接口*/
for(i=0;i<USBH_MAX_NUM_INTERFACES;i++)
{
interface = USBH_FindInterface(phost, phost->pActiveClass->ClassCode, HID_BOOT_CODE, i+1); //note:寻找匹配描述符protocol的接口,键盘为1,鼠标为2
InterfaceIndex[i] = interface;//note:记录接口编号,以在用户函数中使用
if(interface == 0xFF) /* No Valid Interface */
{
status = USBH_FAIL;
USBH_DbgLog ("Cannot Find the interface for %s class.", phost->pActiveClass->Name);
}
else
{
USBH_SelectInterface (phost, interface);
phost->pActiveClass->pData[i] = (HID_HandleTypeDef *)USBH_malloc (sizeof(HID_HandleTypeDef));
HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];
HID_Handle->state = 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 = HID_INIT;
HID_Handle->ctl_state = 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;//note:主机查询的周期间隔,可根据实际运行情况调大,否则会频繁进入接收数据却来不及处理
}
/* Check fo 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=0 ;num < max_ep; num++) //note:此处与源码相比增加了num=0,否则最外侧for循环运行时第二次不进入此处
{
if(phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress & 0x80)
{
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 */
USBH_OpenPipe (phost,
HID_Handle->InPipe,
HID_Handle->InEp,
phost->device.address,
phost->device.speed,
USB_EP_TYPE_INTR,
HID_Handle->length);
USBH_LL_SetToggle (phost, HID_Handle->InPipe, 0);
}
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 */
USBH_OpenPipe (phost,
HID_Handle->OutPipe,
HID_Handle->OutEp,
phost->device.address,
phost->device.speed,
USB_EP_TYPE_INTR,
HID_Handle->length);
USBH_LL_SetToggle (phost, HID_Handle->OutPipe, 0);
}
}
status = USBH_OK;
}
}
return status;
}
4、将涉及到phost->pActiveClass->pData的函数内均改为phost->pActiveClass->pData[i],加入for循环。如 USBH_HID_InterfaceDeInit函数,USBH_HID_ClassRequest函数,USBH_HID_Process函数,USBH_HID_SOFProcess函数
/**
* @brief USBH_HID_InterfaceDeInit
* The function DeInit the Pipes used for the HID class.
* @param phost: Host handle
* @retval USBH Status
*/
USBH_StatusTypeDef USBH_HID_InterfaceDeInit (USBH_HandleTypeDef *phost )
{
uint8_t i=0;
for(i=0;i<USBH_MAX_NUM_INTERFACES;i++)
{
HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];
if(HID_Handle->InPipe != 0x00)
{
USBH_ClosePipe (phost, HID_Handle->InPipe);
USBH_FreePipe (phost, HID_Handle->InPipe);
HID_Handle->InPipe = 0; /* Reset the pipe as Free */
}
if(HID_Handle->OutPipe != 0x00)
{
USBH_ClosePipe(phost, HID_Handle->OutPipe);
USBH_FreePipe (phost, HID_Handle->OutPipe);
HID_Handle->OutPipe = 0; /* Reset the pipe as Free */
}
if(phost->pActiveClass->pData[i])
{
USBH_free (phost->pActiveClass->pData[i]);
}
}
return USBH_OK;
}
5、补充USBH_HID_EventCallbcak函数。此函数被调用于USBH_HID_Process->case HID_POLL,当接收到数据填入fifo后,立即调用USBH_HID_EventCallbcak函数将数据读取到对应的pinfo中,防止被下一接口数据覆盖。KeyboardFlag和MouseFlag 用于标记是哪个接口接收了数据。
USBH_HID_Process(...)
{
//省略部分代码.....
case HID_POLL:
if(USBH_LL_GetURBState(phost , HID_Handle->InPipe) == USBH_URB_DONE)
{
if(HID_Handle->DataReady == 0)
{
fifo_write(&HID_Handle->fifo, HID_Handle->pData, HID_Handle->length);
HID_Handle->DataReady = 1;
if(0==i)
{
KeyboardFlag = 1;
}
else
{
MouseFlag = 1;
}
USBH_HID_EventCallback(phost);
#if (USBH_USE_OS == 1)
osMessagePut ( phost->os_event, USBH_URB_EVENT, 0);
#endif
}
}
//省略部分代码.....
}
/*定义全局变量*/
uint8_t KeyboardFlag = 0;
uint8_t MouseFlag= 0;
HID_KEYBD_Info_TypeDef *k_pinfo;
HID_MOUSE_Info_TypeDef *m_pinfo;
/**
* @brief The function is a callback about HID Data events
* @param phost: Selected device
* @retval None
*/
void USBH_HID_EventCallback(USBH_HandleTypeDef *phost)
{
if(1 == KeyboardFlag)
{
KeyboardFlag = 0;
k_pinfo = USBH_HID_GetKeyInfo(phost);
}
else if(1 == MouseFlag)
{
MouseFlag= 0;
m_pinfo = USBH_HID_GetMouseInfo(phost);
}
}
6、修改该类的所有文件内的phost->Control.setup.b.wIndex.w
赋值语句修改为phost->Control.setup.b.wIndex.w = phost->device.current_interface;
.
7、修改main文件中的应用函数,获取键盘鼠标信息已经放在USBH_HID_EventCallbcak函数,不需要在USB_Demo中再次获取,只需要判断pinfo是否有效,处理后及时清空。
//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码
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");
}
}
}
}
以上是在学习USB中摸索出来的多接口实现,如有问题,请赐教~欢迎交流