STM32 USB數據接收與數據發送程序流程分析


既然學習了USB,那就必須的搞懂USB設備與USB主機數據是怎么通訊的。這里主要講設備端,因為我們的代碼是做USB設備用的。

我們需要必須要定義了USB中斷。起始在STM32的中斷向量表中給USB兩個中斷,我們可以在stm32f10x.h中找到這兩個中斷:

USB_HP_CAN1_TX_IRQn = 19, /*!< USB Device High Priority or CAN1 TX Interrupts */
USB_LP_CAN1_RX0_IRQn = 20, /*!< USB Device Low Priority or CAN1 RX0 Interrupts */

這兩個中斷是USB與CAN復用的中斷,在做USB用時,表示USB設備的高優先級與低優先級中斷。在我的工程中,我選擇用低優先級的USB中斷。代碼如下:

void USB_LP_CAN1_RX0_IRQHandler(void)
{
USB_Istr();
}

中斷服務程序很簡單,就是在發生中斷的時候調用USB_istr()函數。USB_istr()這個函數我們之前說過的,在usb_istr.c中定義的。這個函數處理ISTR中斷狀態寄存器中定義的中斷,包括:CTR正確傳輸中斷、RESET復位中斷,DOVR分組緩沖溢出中斷、ERR錯誤中斷、WAKEUP中斷、SUSP掛起中斷、SOF幀首中斷、ESOF期望幀首中斷。這里重點是CTR中斷,在USB在正確發送或正確接收數據后,USB模塊自動回將ISTR寄存器的該位置1,觸發中斷CTR中斷。在USB_istr()中CTR的處理代碼如下:

#if (IMR_MSK & ISTR_CTR) //正確傳輸中斷CTR標志
if (wIstr & ISTR_CTR & wInterrupt_Mask)//讀出的中斷標志是CRT中斷標志,且CRT中斷使能了
{
CTR_LP(); //調用正確傳輸中斷服務程序
#ifdef CTR_CALLBACK
CTR_Callback(); //當定義了CTR_CALLBACK,則調用CTR_Callback,像鈎子函數一樣,在發生CRT中斷時做點什么
#endif
}

首先要解釋下 #if (IMR_MSK & ISTR_CTR) 這句話。
#define IMR_MSK (CNTR_CTRM | CNTR_WKUPM | CNTR_SUSPM | CNTR_ERRM | CNTR_SOFM \ | CNTR_ESOFM | CNTR_RESETM )
這是IMR_MSK的定義,表示包含所有中斷的掩碼,IMR_MSK & ISTR_CTR表示:如果ISTR_CTR是規定的中斷類別,則編譯#if與#endif之間的代碼。很明顯這里符合。然后,判斷下從CNTR寄存器中讀出來的中斷值是CRT中斷,且該中斷已經在CNTR中使能了。接着調用CTR_LP()函數處理,如果定義了CTR_CALLBACK,則調用CTR_Callback()函數,該函數是個鈎子函數,讓用戶在正確接收到數據后能夠做些什么,比如說亮下燈或通過串口打印些消息。
這里需要着分析下CTR_LP()這個函數在usb_int.c中定義。代碼如下:

/*******************************************************************************
* Function Name : CTR_LP.
* Description : 低優先級的端點正確傳輸中斷服務程序
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void CTR_LP(void)
{
__IO uint16_t wEPVal = 0;
while (((wIstr = _GetISTR()) & ISTR_CTR) != 0) //讀取中斷狀態寄存器的值,看是否是CRT(正確傳輸中斷)
{
EPindex = (uint8_t)(wIstr & ISTR_EP_ID); //獲取產生中斷的端點號,
if (EPindex == 0) //如果端點0
{
SaveRState = _GetENDPOINT(ENDP0); //讀取端點0的狀態寄存器
SaveTState = SaveRState & EPTX_STAT; //保存端點0發送狀態
SaveRState &= EPRX_STAT; //保存端點0接收狀態

_SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK);//設置端點0對主機以NAK方式響應所有的接收和發送請求
if ((wIstr & ISTR_DIR) == 0) //如果是IN令牌
{
_ClearEP_CTR_TX(ENDP0); //清除端點0正確發送標志位
In0_Process(); //處理IN令牌包

/* before terminate set Tx & Rx status */

_SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//在傳輸之前設置端點0接收發送狀態位
return;
}
else //OUT令牌
{
wEPVal = _GetENDPOINT(ENDP0); //獲取端點0的端點寄存器的值

if ((wEPVal &EP_SETUP) != 0) //SETUP分組傳輸完成標志位
{
_ClearEP_CTR_RX(ENDP0); //清除端點0的接收標志位
Setup0_Process(); //端點0建立階段的數據處理

_SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//設置端點0階接收發送標志位
return;
}

else if ((wEPVal & EP_CTR_RX) != 0) //正確接收標志位
{
_ClearEP_CTR_RX(ENDP0); //清除端點0正確標志位
Out0_Process(); //處理OUT令牌包

_SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);//設置端點0的接收發送狀態
return;
}
}
}/* if(EPindex == 0) */
else //如果非0端點
{
wEPVal = _GetENDPOINT(EPindex); //獲取該端點的端點寄存器的值
if ((wEPVal & EP_CTR_RX) != 0) //正確接收標志
{
_ClearEP_CTR_RX(EPindex); //清除端點正確接收標志

(*pEpInt_OUT[EPindex-1])(); //調用注冊過的端點OUT處理函數

} /* if((wEPVal & EP_CTR_RX) */

if ((wEPVal & EP_CTR_TX) != 0) //正確發送標志
{
_ClearEP_CTR_TX(EPindex); //清除正確發送標志

(*pEpInt_IN[EPindex-1])(); //調用注冊過的端點IN處理函數
} /* if((wEPVal & EP_CTR_TX) != 0) */

}/* if(EPindex == 0) else */

}/* while(...) */
}

 
這個函數首先會判斷是否真的CTR中斷,如果是,執行while()中的代碼,用EPindex來保存產生中斷的端點號。EPindex為0表示是端點0產生的中斷,說明此時USB還處於枚舉階段。EPindex不為0,表示枚舉已經成功了,USB處於正常工作狀態。
在枚舉階段,SaveRState保存端點0寄存器的值,接着SaveTState = SaveRState & EPTX_STAT;和SaveRState &=  EPRX_STAT;這兩句,SaveTState保存當前發送端點0的狀態, SaveRState 保存當前接收端點的狀態。接着設置接收端點0為NAk狀態,發送端點0也設置成NAK狀態,也就是說當主機發送任何數據,從機只以NAK回應,從機也只能發送NAK數據,即不允許在數據處理階段進行數據通訊。然后判斷是輸入還是輸出。如果是輸入(注意這里的輸入是相對於主機來說的)則清除端點寄存器的EP_CTR_TX標志位,並且調用IN令牌包處理函數In0_Process()(在usb_core.c中定義)。如果是輸出(注意這里的輸出是相對於主機來說的),則還要判斷接收到是SETUP包還是OUT令牌包,如果是SETUP包,清除端點0寄存器的EP_SETUP位,並且調動SETUP處理函數Setup0_Process(),同時還要回復原來的接發端點的狀態,准備處理下一次的中斷處理。如果是OUT令牌包,清除端點0寄存器的EP_CRT_RX位,調用OUT處理函數Out0_Process(),同時還要回復原來接法端口的狀態,准備處理下一次的中斷處理。
在工作階段或者說是非枚舉階段,首先要判斷下是EP_CTR_RX還EP_CTR_TX標志,如果是EP_CTR_RX正確接收標志,則清除該標志,調用對應端點的OUT處理函數(*pEpInt_OUT[EPindex-1])()(在usb_istr中有注冊過),如果是EP_CTR_TX標志,則清除該標志,調用對應端點的IN處理函數(*pEpInt_IN[EPindex-1])()(在usb_istr中有注冊過)。
在usb_istr.c中非別注冊了7個端點輸入函數和端點輸出函數。如下:

/*定義指向指針的函數指針數組,函數指針分別指向7個端點輸入服務程序*/
void (*pEpInt_IN[7])(void) =
{
EP1_IN_Callback,
EP2_IN_Callback,
EP3_IN_Callback,
EP4_IN_Callback,
EP5_IN_Callback,
EP6_IN_Callback,
EP7_IN_Callback,
};

/*定義指向指針的函數指針數組,函數指針分別指向7個端點輸出服務程序*/
void (*pEpInt_OUT[7])(void) =
{
EP1_OUT_Callback,
EP2_OUT_Callback,
EP3_OUT_Callback,
EP4_OUT_Callback,
EP5_OUT_Callback,
EP6_OUT_Callback,
EP7_OUT_Callback,
};

而這些函數的定義在usb_endp.c中,我們拿EP1_OUT_Callback()函數分析。

/*******************************************************************************
* Function Name : EP1_OUT_Callback.
* Description : 端點1輸出回調函數
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void EP1_OUT_Callback(void)
{
PMAToUserBufferCopy(USB_Receive_Buffer, ENDP1_RXADDR, REPORT_COUNT); //PMA緩沖區接收到的數據拷貝到用戶自定義緩沖區USB_Receive_Buffer中
SetEPRxStatus(ENDP1, EP_RX_VALID); //設置端點的接收狀態為有效,因為端點接收到數據后會端點狀態自動設置成停止狀態
USB_Received_Flag=1; //設置接收到數據標志位
}

這個函數的工作很簡單,首先因為數輸出端點,是接收數據的,而USB模塊接收到的數據又是暫存在PAM雙緩沖區中,所以要線把數據從PMA中讀取出來,放到用戶自己緩沖區中。接着設置端點接收狀態有效,因為當接收數據后,端點就會被關閉。最后置位接收帶數據標志。
 
以上就是USB設備的接收的流程。接下去講講發送流程。發送比接收簡單多了看看下面的代碼就知道了。

/**
* @brief 通過USB發送數據
* @param data 數據存儲首地址
* @param dataNum 發送的數據字節數
* @retval 發送的字節數
*/
uint32_t USB_SendData(uint8_t *data,uint32_t dataNum)
{
//將數據通過USB發送出去
UserToPMABufferCopy(data, ENDP2_TXADDR, dataNum);//拷貝數據到PMA中
SetEPTxCount(ENDP2, REPORT_COUNT); //從端點2發送64字節數據
SetEPTxValid(ENDP2); //使能端點2的發送狀態
return dataNum;
}

把要發送的數據拷貝到PMA中,之后設置端點計數,使能下端點,數據就發送出去了。
 
 
總結下:
數據發送:UserToPMABufferCopy--->SetEPTxCount--->SetEPTxValid
數據接收:USB_LP_CAN1_RX0_IRQHandler--->USB_Istr---->CTR_LP--->EPx_OUT_Callback


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM