說明:使用的是STM32F103ZET6
硬體原理圖

在開始枚舉裝置的一些初始化
void bsp_USBInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_USB_PULL_UP, ENABLE);
USB_CABLE_DISABLE();
GPIO_InitStructure.GPIO_Pin = PIN_USB_PULL_UP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);
USB_Init();
}
現在開始分析真正的初始化
第一步:初始化,總線複位及向預設位址 0發送 GET_DESCRIPTOR指令包,請求裝置描述
1)Index[4 - 5]:表示 USB插入總線複位;
2)Index[7 -8]:表示主機向預設位址發送GET_DESCRIPTOR指令包,詳細信
息也抓出來了,如(圖二)所示
3)Index[15 - 17]:表示裝置向主機發送裝置描述資料 Index[16]
4)Index[18 - 19]:表示主機完成 GET_DESCRIPTOR指令後,給裝置發送一個
空應答
現在具體的分析103的usb的執行過程按順序向下執行
***************(1)**************
DEVICE_INFO*pInformation;
DEVICE_PROP*pProperty;
DEVICE_PROP Device_Property=
{
Joystick_init,
Joystick_Reset,
Joystick_Status_In,
Joystick_Status_Out,
Joystick_Data_Setup,
Joystick_NoData_Setup,
Joystick_Get_Interface_Setting,
Joystick_GetDeviceDescriptor,
Joystick_GetConfigDescriptor,
Joystick_GetStringDescriptor,
0,
0x40
};
USER_STANDARD_REQUESTS User_Standard_Requests=
{
Joystick_GetConfiguration,
Joystick_SetConfiguration,
Joystick_GetInterface,
Joystick_SetInterface,
Joystick_GetStatus,
Joystick_ClearFeature,
Joystick_SetEndPointFeature,
Joystick_SetDeviceFeature,
Joystick_SetDeviceAddress
};
//USB核心将主機發送過來的用于實作USB裝置的設定包儲存在裝置資訊結構表中
typedef struct _DEVICE_INFO
{
uint8_tUSBbmRequestType;
uint8_tUSBbRequest;
uint16_t_uint8_tUSBwValues;
uint16_t_uint8_tUSBwIndexs;
uint16_t_uint8_tUSBwLengths;
uint8_tControlState;
uint8_t Current_Feature;
uint8_tCurrent_Configuration;
uint8_tCurrent_Interface;
uint8_tCurrent_AlternateSetting;
ENDPOINT_INFO Ctrl_Info;
}DEVICE_INFO;
usb_init.c檔案裡面的
void USB_Init(void)
{
pInformation =&Device_Info;
pInformation->ControlState =2;
pProperty =&Device_Property;
pUser_Standard_Requests =&User_Standard_Requests;
pProperty->Init();
}
***************(2)**************通過函數指針指向這個初始化函數pProperty 在usb_prop.c檔案裡面
void Joystick_init(void)
{
Get_SerialNum(); //得到串行号
pInformation->Current_Configuration=0; //
PowerOn(); //将USB上電 連接配接裝置
USB_SIL_Init(); //主要是CNTR寄存器的初始化
bDeviceState =UNCONNECTED; //裝置狀态标志 目前狀态未連接配接
}
hw_config.c檔案裡面這個和标準的不一樣有改動,擷取裝置版本号,将其存入到版本号字元串。
voidGet_SerialNum(void) //得到串行号
{
uint32_tDevice_Serial0, Device_Serial1, Device_Serial2;
Device_Serial0 = *(__IO uint32_t*)(0x1FFFF7E8);
Device_Serial1 = *(__IO uint32_t*)(0x1FFFF7EC);
Device_Serial2 = *(__IO uint32_t*)(0x1FFFF7F0);
Device_Serial0 += Device_Serial2;
if(Device_Serial0 != 0)
{
IntToUnicode (Device_Serial0,&Joystick_StringSerial[2] , 8);
IntToUnicode (Device_Serial1,&Joystick_StringSerial[18], 4);
}
}
usb_pwr.c檔案裡面在這個檔案裡面隻是使能了複位,挂起,喚醒中斷,在PowerOn函數使能了複位中斷以後,将進入到USB的複位中斷裡面去。
然後再執行函數USB_SIL_Init将所有的USB中斷都打開。在D+被接通上拉以後,裝置就能被主機檢測到。
RESULT PowerOn(void)
{
#ifndef STM32F10X_CL
uint16_t wRegVal;
USB_Cable_Config(ENABLE); //将USB上電連接配接
//對USB子產品強制複位,類似于USB總線上的複位信号。USB子產品将一直保持在複位狀态下
//直到軟體清除此位。如果USB複位中斷被使能,将産生一個複位中斷。
wRegVal =CNTR_FRES; //強制複位
_SetCNTR(wRegVal);
wInterrupt_Mask = 0;
_SetCNTR(wInterrupt_Mask); //清除複位信号
_SetISTR(0);
//複位中斷屏蔽位 挂起中斷屏蔽位喚醒中斷屏蔽位使能
wInterrupt_Mask = CNTR_RESETM | CNTR_SUSPM |CNTR_WKUPM;
_SetCNTR(wInterrupt_Mask);
#endif
return USB_SUCCESS;
}
usb_istr.c檔案裡面,下面隻寫了進入到複位中斷函數,進入到USB連接配接狀态
void USB_Istr(void)
{
wIstr =_GetISTR();
#if (IMR_MSK &ISTR_RESET) //USB複位請求中斷
if (wIstr & ISTR_RESET&wInterrupt_Mask)
{
_SetISTR((uint16_t)CLR_RESET); //清楚複位中斷标志
Device_Property.Reset(); //進入到複位中斷
#ifdef RESET_CALLBACK
RESET_Callback();
#endif
}
#end
}
usb_prop.c檔案裡面,實作對端點的設定。
void Joystick_Reset(void)
{
pInformation->Current_Configuration =0;
pInformation->Current_Interface =0;
pInformation->Current_Feature =Joystick_ConfigDescriptor[7]; //供電模式選擇
#ifdefSTM32F10X_CL
OTG_DEV_EP_Init(EP1_IN, OTG_DEV_EP_TYPE_INT, 4);
#else
SetBTABLE(BTABLE_ADDRESS); //分組緩沖區描述表位址設定
SetEPType(ENDP0,EP_CONTROL); //初始化為控制端點類型
SetEPTxStatus(ENDP0,EP_TX_STALL); //端點以STALL分組響應所有的發送請求。
//也就是端點狀态設定成發送無效,也就是主機的IN令牌包來的時候,回送一個STALL。
SetEPRxAddr(ENDP0,ENDP0_RXADDR); //設定端點0描述符的接受位址,
SetEPTxAddr(ENDP0,ENDP0_TXADDR); //設定端點0描述符的發送位址
Clear_Status_Out(ENDP0);
//僅用于控制端點 如果STATUS_OUT位被清除,OUT分組可以包含任意長度的資料
SetEPRxCount(ENDP0,Device_Property.MaxPacketSize);
//設定端點0的接受位元組寄存器的最大值是64
SetEPRxValid(ENDP0); //設定接受端點有效
SetEPType(ENDP1,EP_INTERRUPT); //初始化為中斷端點類型
SetEPTxAddr(ENDP1,ENDP1_TXADDR); //設定發送資料的位址
SetEPTxCount(ENDP1,4); //設定發送的長度
SetEPRxStatus(ENDP1,EP_RX_DIS); //設定接受端點關閉
SetEPTxStatus(ENDP1,EP_TX_NAK); //設定發送端點端點非應答
SetDeviceAddress(0); //設定裝置用預設位址相應
#endif
bDeviceState =ATTACHED; //目前狀态連接配接
}
usb_sil.c的檔案裡面,主要是使能了如下這些中斷
CNTR_CTRM 正确傳輸(CTR)中斷使能 CNTR_WKUPM喚醒中斷使能
CNTR_SUSPM 挂起(SUSP)中斷使能 CNTR_ERRM 出錯中斷使能
CNTR_SOFM 幀首中斷使能 CNTR_ESOFM 期望幀首中斷使能CNTR_RESETM設定此位将向PC主機發送喚醒請求。根據USB協定,如果此位在1ms到15ms内保持有效,主機将對USB子產品實行喚醒操作。
uint32_t USB_SIL_Init(void)
{
#ifndef STM32F10X_CL
_SetISTR(0); //清除中斷标志
wInterrupt_Mask = IMR_MSK;
//這組寄存器用于定義USB子產品的工作模式,中斷的處理,裝置的位址和讀取目前幀的編号
_SetCNTR(wInterrupt_Mask); //設定相應的控制寄存器
#else
OTG_DEV_Init();
#endif
return 0;
}
***************(3)**************
1.擷取裝置描述符
usb_int.c的檔案裡面
低優先級中斷 在控制中斷 批量傳輸下使用(在單緩沖模式下使用)
當一次正确的OUT,SETUP,IN資料傳輸完成後,硬體會自動設定此位為NAK狀态,使應用程式有足夠的時間處理完目前傳輸的資料後,響應下一個資料分組
void CTR_LP(void)
{
__IO uint16_t wEPVal = 0;
while (((wIstr =_GetISTR()) & ISTR_CTR) != 0)
{
EPindex = (uint8_t)(wIstr &ISTR_EP_ID); //讀出端點ID
if (EPindex==0) //如果是端點0
{
SaveRState =_GetENDPOINT(ENDP0); //讀取端點0寄存器USB_EP0R
SaveTState = SaveRState &EPTX_STAT; //儲存發送狀态位
SaveRState &= EPRX_STAT; //儲存接受狀态位
_SetEPRxTxStatus(ENDP0,EP_RX_NAK,EP_TX_NAK); //端點以NAK分組響應所有的發送和接受請求(解釋在上面)
if ((wIstr & ISTR_DIR) ==0) //IN令牌,資料被取走
{
_ClearEP_CTR_TX(ENDP0); //清除正确發送标志位
In0_Process(); //處理INT事件
_SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);
return;
}
else
{
wEPVal =_GetENDPOINT(ENDP0); //得到端點0寄存器的資料
if ((wEPVal &EP_SETUP) !=0) //SETUP分組傳輸完成标志
{
_ClearEP_CTR_RX(ENDP0);
Setup0_Process(); //處理SETUP事件
//程式會進入到這個函數裡面
_SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);
return;
}
else if ((wEPVal & EP_CTR_RX) != 0)
{
_ClearEP_CTR_RX(ENDP0);
Out0_Process(); //處理OUT事件
_SetEPRxTxStatus(ENDP0,SaveRState,SaveTState);
return;
}
}
}
else //如果是除端點0以外的端點
{
wEPVal =_GetENDPOINT(EPindex); //得到相應端點寄存器值
if ((wEPVal & EP_CTR_RX) !=0) //檢測正确接收标志 PC-USB OUTint
{
_ClearEP_CTR_RX(EPindex); //清除相應的标志
(*pEpInt_OUT[EPindex-1])(); //調用OUT int服務功能
}
if ((wEPVal & EP_CTR_TX) !=0) //檢測正确發送标志 USB-PC IN int
{
_ClearEP_CTR_TX(EPindex); //清除相應的标志
(*pEpInt_IN[EPindex-1])(); //調用IN int服務功能
}
}
}
}
usb_coer.c的檔案裡面,主要是得到主機發來的标準請求指令
uint8_t Setup0_Process(void)
{
union
{
uint8_t*b;
uint16_t*w;
} pBuf;
#ifdef STM32F10X_CL
USB_OTG_EP *ep;
uint16_t offset = 0;
ep = PCD_GetOutEP(ENDP0);
pBuf.b = ep->xfer_buff;
#else
uint16_t offset = 1;
//得到接受緩沖區位址寄存器位址
pBuf.b = PMAAddr + (uint8_t*)(_GetEPRxAddr(ENDP0) * 2);
#endif
if(pInformation->ControlState != PAUSE)
{
pInformation->USBbmRequestType = *pBuf.b++;
pInformation->USBbRequest = *pBuf.b++;
pBuf.w+= offset;
pInformation->USBwValue = ByteSwap(*pBuf.w++);
pBuf.w+= offset;
pInformation->USBwIndex =ByteSwap(*pBuf.w++);
pBuf.w+= offset;
pInformation->USBwLength = *pBuf.w;
}
pInformation->ControlState =SETTING_UP;
if(pInformation->USBwLength == 0)
{
NoData_Setup0();
}
else
{
Data_Setup0(); //由于是有資料的傳輸,所有要進入到這個函數
}
return Post0_Process();
}
usb_core.c的檔案裡面,這裡隻是選取了GETDESCRIPTOR
的程式部分,其他的部分删除了
void Data_Setup0(void)
{
uint8_t *(*CopyRoutine)(uint16_t);
RESULT Result;
uint32_t Request_No =pInformation->USBbRequest;
uint32_tRelated_Endpoint, Reserved;
uint32_t wOffset, Status;
CopyRoutine =NULL;
wOffset = 0;
//看标準請求碼格式就知道了
if (Request_No ==GET_DESCRIPTOR)
{
//pInformation->USBbmRequestType是下面的兩種标準請求或裝置請求
if(Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT))
{
uint8_t wValue1 =pInformation->USBwValue1; //高一位元組得到描述表種類一共有5種
if (wValue1 ==DEVICE_DESCRIPTOR) //裝置描述
{
CopyRoutine = pProperty->GetDeviceDescriptor;
}
else if (wValue1 == CONFIG_DESCRIPTOR)
{
CopyRoutine = pProperty->GetConfigDescriptor;//配置描述
}
else if (wValue1 == STRING_DESCRIPTOR)
{
CopyRoutine = pProperty->GetStringDescriptor;//字元串描述
}
}
}
if(CopyRoutine)
{
pInformation->Ctrl_Info.Usb_wOffset =wOffset; //本子程式的wOffset是0
pInformation->Ctrl_Info.CopyData = CopyRoutine;//使指針pInformation->Ctrl_Info.CopyData指向CopyRoutine
(*CopyRoutine)(0); //第一次執行時Length=0 傳回的是有效資料的長度存儲到pInformation->Ctrl_Info.Usb_wLength
Result =USB_SUCCESS;
}
else
{ //如果标準請求不存在 看類 廠商請求中是否有
Result =(*pProperty->Class_Data_Setup)(pInformation->USBbRequest);
if (Result== USB_NOT_READY)
{
pInformation->ControlState =PAUSE;
return;
}
}
if(pInformation->Ctrl_Info.Usb_wLength ==0xFFFF) //如果字元的長度是0xffff
{
pInformation->ControlState =PAUSE;
return;
}
if ((Result == USB_UNSUPPORT) ||(pInformation->Ctrl_Info.Usb_wLength == 0))
{
pInformation->ControlState =STALLED;
return;
}
if(ValBit(pInformation->USBbmRequestType,7)) //D7表示資料傳輸方向1:裝置向主機
{
__IOuint32_t wLength = pInformation->USBwLength;
//設定使其為USB主機設定的長度 本程式HID 滑鼠 pProperty->MaxPacketSize是0x40
if(pInformation->Ctrl_Info.Usb_wLength>wLength)
//字元的長度大于主機要求的長度
{
pInformation->Ctrl_Info.Usb_wLength =wLength;
//将其設定為主機要求的
}
else if(pInformation->Ctrl_Info.Usb_wLength<pInformation->USBwLength) //字元的長度小于主機要求的
{
if (pInformation->Ctrl_Info.Usb_wLength<pProperty->MaxPacketSize) //如果字元的長度長度小于每包資料最大位元組數
{
Data_Mul_MaxPacketSize = FALSE;
}
else if ((pInformation->Ctrl_Info.Usb_wLength %pProperty->MaxPacketSize) == 0) //如果是其整數倍
{
Data_Mul_MaxPacketSize = TRUE;
}
}
pInformation->Ctrl_Info.PacketSize =pProperty->MaxPacketSize;
DataStageIn();
}
else //主機向裝置
{
pInformation->ControlState = OUT_DATA;
vSetEPRxStatus(EP_RX_VALID);
}
return;
}
usb_coer.c的檔案裡面
voidDataStageIn(void)
{
ENDPOINT_INFO *pEPinfo =&pInformation->Ctrl_Info; //端點資訊儲存在指針變量中
uint32_t save_wLength =pEPinfo->Usb_wLength; //得到字元的長度
uint32_t ControlState =pInformation->ControlState; //得到目前的狀态
uint8_t*DataBuffer;
uint32_t Length;
if((save_wLength == 0) &&(ControlState == LAST_IN_DATA))//如果字元長度為0 且控制狀态是最後輸入的資料
{
if(Data_Mul_MaxPacketSize ==TRUE) //如果字元的長度是資料包的整數倍
{
Send0LengthData();
ControlState = LAST_IN_DATA;
Data_Mul_MaxPacketSize =FALSE; //這一次發送0位元組狀态轉為最後輸入階段
}
else //字元的長度比資料包要小
{ //資料已經發送完
ControlState = WAIT_STATUS_OUT;
#ifdefSTM32F10X_CL
PCD_EP_Read (ENDP0, 0, 0);
#endif
#ifndefSTM32F10X_CL
vSetEPTxStatus(EP_TX_STALL); //設定端點的發送狀态停止
#endif
}
gotoExpect_Status_Out;
}
Length =pEPinfo->PacketSize; //得到資料包大小 64位元組
ControlState = (save_wLength<= Length) ? LAST_IN_DATA :IN_DATA;//比較大小得到是LAST_IN_DATA還是IN_DATA 18位元組<64位元組 ControlState =LAST_IN_DATA
if (Length> save_wLength)
{
Length =save_wLength;
}
DataBuffer =(*pEPinfo->CopyData)(Length);//DataBuffer指向要複制資料的位址這個位址是随Usb_wOffset變化的
#ifdef STM32F10X_CL
PCD_EP_Write (ENDP0, DataBuffer, Length);
#else
//GetEPTxAddr(ENDP0) 得到發送緩沖區相應端點的位址
//将DataBuffer中的資料複制到相應的發送緩沖區中
UserToPMABufferCopy(DataBuffer,GetEPTxAddr(ENDP0), Length);
#endif
SetEPTxCount(ENDP0, Length); //設定相應的端點要發送的位元組數
pEPinfo->Usb_wLength -= Length;//等于0
pEPinfo->Usb_wOffset += Length;//偏移到18
vSetEPTxStatus(EP_TX_VALID); //使能發送端點 隻要主機的IN令牌包一來SIE就會将描述符傳回給主機
USB_StatusOut();
//設定接收端點有效這個實際上使接受也有效,
Expect_Status_Out:
pInformation->ControlState =ControlState; //儲存控制狀态
}
***************(4)**************
uint8_tIn0_Process(void)
{
uint32_t ControlState =pInformation->ControlState;
if ((ControlState == IN_DATA)|| (ControlState ==LAST_IN_DATA))//進入到這裡
{
DataStageIn();//第一次取裝置描述符隻取一次目前的狀态變為WAIT_STATUS_IN 表明裝置等待狀态過程主機輸出0位元組
ControlState = pInformation->ControlState;
}
else if (ControlState ==WAIT_STATUS_IN) //設定位址狀态階段進入這個程式
{
if((pInformation->USBbRequest == SET_ADDRESS)&&
(Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT)))
{
SetDeviceAddress(pInformation->USBwValue0); //設定使用新的位址
pUser_Standard_Requests->User_SetDeviceAddress();
}
(*pProperty->Process_Status_IN)();
ControlState=STALLED; //變為這個狀态
}
else
{
ControlState= STALLED;
}
pInformation->ControlState =ControlState;
return Post0_Process();
}
uint8_tOut0_Process(void)
{
uint32_t ControlState =pInformation->ControlState;
if ((ControlState == IN_DATA)|| (ControlState == LAST_IN_DATA))
{
//主機在完成傳輸前終止傳輸
ControlState= STALLED;
}
else if ((ControlState == OUT_DATA) ||(ControlState == LAST_OUT_DATA))
{
DataStageOut();
ControlState= pInformation->ControlState;
}
else if (ControlState ==WAIT_STATUS_OUT) //進入到這個裡面
{
(*pProperty->Process_Status_OUT)();//這個函數其實什麼也沒做
#ifndef STM32F10X_CL
ControlState =STALLED; //狀态變成了終止發送和接受
#endif
}
else
{
ControlState= STALLED;
}
pInformation->ControlState =ControlState;
return Post0_Process();
}
***************(5)**************
擷取裝置描述符以後,主機再一次的複位裝置,裝置又進入初始狀态。開始枚舉的第二步設定位址。
void NoData_Setup0(void)
{
RESULT Result = USB_UNSUPPORT;
uint32_t RequestNo =pInformation->USBbRequest;
uint32_t ControlState;
if(Type_Recipient == (STANDARD_REQUEST |DEVICE_RECIPIENT)) //裝置請求
{
else if (RequestNo ==SET_ADDRESS) /設定位址
{
if ((pInformation->USBwValue0 > 127)|| (pInformation->USBwValue1 != 0)
|| (pInformation->USBwIndex != 0)
|| (pInformation->Current_Configuration != 0))
{
ControlState = STALLED;
goto exit_NoData_Setup0;
}
else
{
Result = USB_SUCCESS;
#ifdef STM32F10X_CL
SetDeviceAddress(pInformation->USBwValue0);
#endif
}
}
ControlState =WAIT_STATUS_IN;
USB_StatusIn();//準備好發送0位元組的狀态資料包SetEPTxCount(ENDP0, 0);
//vSetEPTxStatus(EP_TX_VALID);建立階段後直接的進入狀态階段
exit_NoData_Setup0:
pInformation->ControlState =ControlState;
return;
}
uint8_tIn0_Process(void)
{
uint32_t ControlState =pInformation->ControlState;
if ((ControlState == IN_DATA)|| (ControlState == LAST_IN_DATA)) //控制狀态
{
DataStageIn();//第一次取裝置描述符隻取一次 目前的狀态變為WAIT_STATUS_IN 表明裝置等待狀态過程主機輸出0位元組
ControlState= pInformation->ControlState;
}
else if (ControlState ==WAIT_STATUS_IN) //設定位址狀态階段進入這個程式
{
if((pInformation->USBbRequest == SET_ADDRESS)&&
(Type_Recipient == (STANDARD_REQUEST | DEVICE_RECIPIENT)))
{
SetDeviceAddress(pInformation->USBwValue0); //設定使用新的位址
pUser_Standard_Requests->User_SetDeviceAddress();
}
(*pProperty->Process_Status_IN)();
ControlState=STALLED; //終止發送和接受
}
else
{
ControlState= STALLED;
}
pInformation->ControlState =ControlState;
return Post0_Process();
}
uint8_t Post0_Process(void)
{
#ifdef STM32F10X_CL
USB_OTG_EP *ep;
#endif
SetEPRxCount(ENDP0,Device_Property.MaxPacketSize); //設定端點0 要接受的位元組數
if(pInformation->ControlState ==STALLED) //這種狀态下隻接受SETUP指令包
{
vSetEPRxStatus(EP_RX_STALL); //終止端點0接受
vSetEPTxStatus(EP_TX_STALL); //終止端點0發送
}
return(pInformation->ControlState ==PAUSE);
}
***************(6)*************
從新位址擷取裝置描述符