在使用ARM的HAL库实现USB接口通信,常常因为有多个库文件和函数的多层调用而觉得使用起来很复杂,而事实上通过合理的配置后,HAL库函数的使用非常简单,本文对利用STM32CubeMX生成的USB_CDC工程的库函数进行分析,从而通过简单的修改实现USB接口的通信。
自顶而下:
-----------------------------------------------------------------------------
设备接收数据流图:
设备发送数据流图:
-----------------------------------------------------------------------------
文件usbd_cdc_if.c,函数如下:
函数描述:USB输出端点接收的数据通过此函数发送给CDC接口。
注意事项:此函数将阻止USB端点上的所有OUT数据包接收,直到退出此函数。 如果在CDC接口上的传输完成之前(即使用DMA控制器)退出此函数,将导致接收更多数据,而先前的数据仍未发送。
参数说明:参数Buf: 待接收数据的缓冲区;
参数Len: 接收的数据数(以字节为单位)
返回: USBD_OK(如果所有操作均正常),否则返回USBD_FAIL
-----------------------------------------------------------------------------
static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{
USBD_CDC_SetRxBuffer(&hUsbDeviceHS, &Buf[0]);//设置接收buf
USBD_CDC_ReceivePacket(&hUsbDeviceHS);//接收数据包
return (USBD_OK);
}
-----------------------------------------------------------------------------------
文件usbd_cdc.c,函数如下:
函数描述:准备接收输出端点数据。
参数说明:参数pdev: USB设备结构体句柄;
返回: USBD_OK(如果所有操作均正常),否则返回USBD_FAIL
-----------------------------------------------------------------------------
uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev)
{
(void)USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,
CDC_DATA_HS_OUT_PACKET_SIZE);
return (uint8_t)USBD_OK;
}
------------------------------------------------------------------------------------
文件usbd_conf.c,函数如下:
函数描述:准备一个端点接收数据。
参数说明:参数pdev: USB设备结构体句柄;
参数ep_addr: 端点号;
参数pbuf: 指向要接收的数据的指针;
参数size: 数据大小;
返回: USBD_OK(如果所有操作均正常),否则返回USBD_FAIL
------------------------------------------------------------------------------------
USBD_StatusTypeDef USBD_LL_PrepareReceive(USBD_HandleTypeDef *pdev, uint8_t ep_addr, uint8_t *pbuf, uint32_t size)
{
hal_status = HAL_PCD_EP_Receive(pdev->pData, ep_addr, pbuf, size);
return usb_status;
}
------------------------------------------------------------------------------------
文件stm32f4xx_hal_pcd.c,函数如下:
函数描述:接收大量数据。
参数说明:参数pdev: PCD结构体句柄;
参数ep_addr: 端点号;
参数pbuf: 指向要接收的数据的指针;
参数len: 接收数据长度;
返回: HAL_OK(如果所有操作均正常),否则返回HAL_FAIL
-----------------------------------------------------------------------------------
HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pBuf, uint32_t len)
{
/*设置和启动 Xfer */
ep->xfer_buff = pBuf;
ep->xfer_len = len;
ep->xfer_count = 0U;
ep->is_in = 0U;
ep->num = ep_addr & EP_ADDR_MSK;
if (hpcd->Init.dma_enable == 1U)//当DMA使能时,给DMA接收BUF的首地址。
{
ep->dma_addr = (uint32_t)pBuf;
}
if ((ep_addr & EP_ADDR_MSK) == 0U)//端点是EP0时,用USB_EP0StartXfer传输数据
{
(void)USB_EP0StartXfer(hpcd->Instance, ep, (uint8_t)hpcd->Init.dma_enable);
}
Else //端点不是EP0时,用USB_EPStartXfer传输数据
{
(void)USB_EPStartXfer(hpcd->Instance, ep, (uint8_t)hpcd->Init.dma_enable);
}
return HAL_OK;
}
------------------------------------------------------------------------------------
文件stm32f4xx_ll_usb.c(底层函数,重点分析)
函数描述:建立和启动一次EP的数据传输,该函数是USB收发中最底层的寄存器操作函数,数据的组包和解包均在此函数中进行。
参数说明: 参数USBx: USB_OTG核心寄存器定义结构体句柄;
参数ep: USB_OTG端点寄存器定义结构体句柄;
参数dma: DMA使能,1有效,0关闭;
返回: HAL_OK(如果所有操作均正常),否则返回HAL_FAIL
-------------------------------------------------------------------------------------
HAL_StatusTypeDef USB_EPStartXfer(USB_OTG_GlobalTypeDef *USBx, USB_OTG_EPTypeDef *ep, uint8_t dma)
{ //定义变量
uint32_t USBx_BASE = (uint32_t)USBx;
uint32_t epnum = (uint32_t)ep->num;
uint16_t pktcnt;
/* ***************************数据发送************************** */
/* 数据发送:输入端点 */
if (ep->is_in == 1U) //判断端点方向:1-输入;0输出
{
/* ********************配置传输大小和包数****************** */
if (ep->xfer_len == 0U) //当传输长度为0
{ //对DIEPTSIZ—IN端点的Txfer大小进行赋值,具体看协议。
USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_PKTCNT);
USBx_INEP(epnum)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_PKTCNT & (1U << 19));
USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_XFRSIZ);
}
else //当传输长度不为0
{ //对DIEPTSIZ—IN端点的Txfer大小进行赋值,具体看协议。
/* 配置传输大小和包数,例如:
*xfersize = N * maxpacket+ short_packet pktcnt = N + (short_packet exist ? 1 : 0)
*/
USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_XFRSIZ);
USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_PKTCNT);
USBx_INEP(epnum)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_PKTCNT & (((ep->xfer_len + ep->maxpacket - 1U) / ep->maxpacket) << 19));
USBx_INEP(epnum)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_XFRSIZ & ep->xfer_len);
if (ep->type == EP_TYPE_ISOC) //判断端点的类型为同步端点
{ //对DIEPTSIZ—IN端点的Txfer大小进行赋值,具体看协议。
USBx_INEP(epnum)->DIEPTSIZ &= ~(USB_OTG_DIEPTSIZ_MULCNT);
USBx_INEP(epnum)->DIEPTSIZ |= (USB_OTG_DIEPTSIZ_MULCNT & (1U << 29));
}
}
/* ********************配置DMA****************** */
if (dma == 1U) //判断DMA使能
{
if ((uint32_t)ep->dma_addr != 0U)
{ //配置IN端点DMA地址寄存器
USBx_INEP(epnum)->DIEPDMA = (uint32_t)(ep->dma_addr);
}
if (ep->type == EP_TYPE_ISOC) //判断端点的类型为同步端点
{
if ((USBx_DEVICE->DSTS & (1U << 8)) == 0U)//判断USB设备状态寄存器
{ //配置设备IN端点控制寄存器—设置为奇数帧
USBx_INEP(epnum)->DIEPCTL |= USB_OTG_DIEPCTL_SODDFRM;
}
else
{//配置设备IN端点控制寄存器—设置为数据 PID
USBx_INEP(epnum)->DIEPCTL |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM;
}
}
/* EP使能,输入数据FIFO */
//配置设备IN端点控制寄存器—设置为清除NAK和端点使能
USBx_INEP(epnum)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA);
}
else //DMA关闭
{
/* EP使能,输入数据FIFO */
//配置设备IN端点控制寄存器—设置为清除NAK和端点使能
USBx_INEP(epnum)->DIEPCTL |= (USB_OTG_DIEPCTL_CNAK | USB_OTG_DIEPCTL_EPENA);
if (ep->type != EP_TYPE_ISOC) //判断端点的类型不为同步端点
{
/*为此EP启用Tx FIFO空中断 */
if (ep->xfer_len > 0U)//且数据长度大于0
{ //开USB设备对应的发送数据的端点的中断
USBx_DEVICE->DIEPEMPMSK |= 1UL << (ep->num & EP_ADDR_MSK);
}
}
else //判断端点的类型为同步端点
{ //判断USB设备状态寄存器
if ((USBx_DEVICE->DSTS & (1U << 8)) == 0U)
{//配置设备IN端点控制寄存器—设置为奇数帧
USBx_INEP(epnum)->DIEPCTL |= USB_OTG_DIEPCTL_SODDFRM;
}
else
{//配置设备IN端点控制寄存器—设置为数据 PID
USBx_INEP(epnum)->DIEPCTL |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM;
}
/* ********************写数据****************** */
(void)USB_WritePacket(USBx, ep->xfer_buff, ep->num, (uint16_t)ep->xfer_len, dma);
}
}
}
/* ***************************数据接收************************** */
/* 数据接收:输出端点 */
else /* OUT 端点*/
{ /* ********************配置传输大小和包数****************** */
/* 设置传输大小和包数,如下:
* pktcnt = N; xfersize = N * maxpacket
*/
USBx_OUTEP(epnum)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
USBx_OUTEP(epnum)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);
if (ep->xfer_len == 0U)
{
USBx_OUTEP(epnum)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
USBx_OUTEP(epnum)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U << 19));
}
else
{
pktcnt = (uint16_t)((ep->xfer_len + ep->maxpacket - 1U) / ep->maxpacket);
USBx_OUTEP(epnum)->DOEPTSIZ |= USB_OTG_DOEPTSIZ_PKTCNT & ((uint32_t)pktcnt << 19);
USBx_OUTEP(epnum)->DOEPTSIZ |= USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket * pktcnt);
}
/* ********************配置DMA****************** */
if (dma == 1U)
{
if ((uint32_t)ep->xfer_buff != 0U)
{//设置设备OUT端点DMA地址
USBx_OUTEP(epnum)->DOEPDMA = (uint32_t)(ep->xfer_buff);
}
}
if (ep->type == EP_TYPE_ISOC)
{
if ((USBx_DEVICE->DSTS & (1U << 8)) == 0U)
{
USBx_OUTEP(epnum)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
}
else
{
USBx_OUTEP(epnum)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
}
}
/* EP 使能*/
USBx_OUTEP(epnum)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
}
return HAL_OK;
}
-------------------------------------------------------------------------------------
在HAL库USB接口CDC模式的函数中,收发功能函数到底层寄存器的操作中间嵌套了很多层。在实际使用过程中,我们并不需要关心中间的过程,仅需调用usbd_cdc_if.c文件中的相关函数就能实现数据的收发。
static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
uint8_t CDC_Transmit_HS(uint8_t* Buf, uint16_t Len)
例如:对接收函数稍作改变,就能实现将收到的数据通过USB发送回去,同时通过串口将数据打印。
static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 11 */
int i;
uint8_t my_RxBuf[100];
uint32_t my_RxLength;
memcpy(my_RxBuf,Buf,*Len);
my_RxLength=*Len;
CDC_Transmit_HS(my_RxBuf, my_RxLength);
for(i=0;i
结果如下:左为USB发送和接收,右为串口打印。