關於stm32的USB學習筆記之USB_HW.c


#include <stm32f10x_lib.h>
#include <stm32f10x_map.h>
#include "usbreg.h"
#include "usbuser.h"
#include "usbcore.h"
#include "usb_hw.h"
#define	_DEBUG_
#include "debug.h"

#define	USB_EP_NUM	4

/*端點緩沖區的開始地址
 *因為每個緩沖塊都需要一個端點描術表
 *而所有的端點描述表放在,USB緩沖區的首部
 *此地址是相對於USB緩沖區的地址,我認為加上Offset更好些
 *這里使用2個端點
 *端點0與端點1
 *此時EP_BUF_ADDR指向緩沖區的內容
 */
#define	EP_BUF_ADDR	(sizeof(EP_BUF_DSCR)*USB_EP_NUM)

/*USB緩沖區首地址包括緩沖區描述表,絕對地址*/
EP_BUF_DSCR	* pBUF_DSCR = (EP_BUF_DSCR *) USB_PMA_ADDR;

/*端點空閑緩沖區地址 
 *用於指示目前為止USB緩沖區中還沒有分配的空閑地址的首地址*/
WORD	FreeBufAddr;

	
/*功能:用於初始化USB的時鍾等部分
 *參數:無
 *返回值:無
 */
void USB_Init(void)
{
	printf("進入USB_Init,進行初始化\r\n");
	//使能USB時鍾
	RCC->APB1ENR |= (1<<23);

	//使能USB中斷
	/*因為USB低優先級中斷的中斷號為20,而NVIC——IPRX
	 *寄存器用四位來存儲中斷優先級,所以20/4=5 ,
	 *然后使能第20位中斷*/
	NVIC->IPR[5] |=0x10;
	NVIC->ISER[0]|=(1<<20);
}
/*功能:用於復位USB模塊	  
 *參數:無
 *返回值:無
 */
/*現在以我的水平還搞不懂雙緩沖為何物,所以先不搞^-^*/
/*一些資料:
  *USB低優先級中斷(通道20):可由所有USB事件觸發(正確傳輸,USB復位等).
  *USB高優先級中斷(通道19):僅能由同步和雙緩沖批量傳輸事件觸發,目的是保證最大的傳輸速率.
  *USB喚醒中斷(通道42):由USB掛起模式的喚醒事件觸發.  OTG_FS_WKUP喚醒
  *
  *復位要執行的內容可以參見rm0008 21.4.2節
  */
void USB_Reset(void)
{
	PrintS("USB_Reset\r\n");
	/*復位了嘛,那所有以前產生的中斷都沒有用了,清了完事!*/
	ISTR=0;

	/*通過設置CNTR來控制stm32的USB模塊的工作方式
	 *所有的USB事件中斷都是在低優先級中斷(通道20)上處理的
	 *好吧就先使能這么多吧,先跑起來再說!
	 */
	CNTR= 	CNTR_CTRM		|	// 使能正確傳輸中斷
		 	CNTR_RESETM 	|	//使能復位中斷
			CNTR_SUSPM		|	//使能掛起中斷
			CNTR_WKUPM		;	//使能喚醒中斷
	
	FreeBufAddr	=	EP_BUF_ADDR; //此時FreeBuff指向第一個緩沖區首地址(不包括描述符表),相對地址

	BTABLE	= 0x00;				 //設置緩沖區描述表的位置仍是相對地址
	
	/*為端點0設置緩沖區及各種控制位*/
	pBUF_DSCR->ADDR_TX	=	FreeBufAddr;
	FreeBufAddr+=8;		//端點0設置為8個字節,一般控制數據為8個字節
	pBUF_DSCR->ADDR_RX	=	FreeBufAddr;
	FreeBufAddr+=8;
	/*在count_Rx字段中10~14bit用來表示緩沖區字節的快數
	 *而15bit用來表示塊的大小
	 *0---2byte
	 *1---1byte
	 *我們這里使用了8個字節,bit15為0,所以應該((8<<10)>>1)即8<<9;
	 *至於count_Rx我們在發送時再來賦值
	 */
	pBUF_DSCR->COUNT_RX= 8 << 9;	
	/*設置端點0為控制端點,接收緩沖區有效
	 *低四位代表端點地址
	 */
	EPxREG(0) = EP_CONTROL | EP_RX_VALID;

	/*使能USB模塊,並設置USB地址為0,以響應枚舉*/
	DADDR = DADDR_EF | 0;
}

/*功能:復位一個端點
 *參數:	端點號
 *				EPNum:bit3~bit0	----> 端點號
 * 				EPNum:bit7		----> 端點方向
 *
 *返回值:無
 */
 /*其實就是將下一個要發送的數據包變成DATA0*/
 void	EP_Reset(DWORD	EPNum)
 {
 	DWORD	num,var;
   	PrintS("EP_Reset\r\n");
	/*獲得端點號,低四位為端點號*/
	num = EPNum & 0x0F;
	var = EPxREG(num);
	/*如果bit7為1則將此端點的發送toggle置為0,
	 *否則將此端點的接收toggle置為0
	 *因為數據總是從data0數據包開始發送的
	 */
	 if(EPNum & 0x80)
	 	EPxREG(num) = var & (EP_MASK | EP_DTOG_TX);/*輸入端點*/
	 else
	 	EPxREG(num) = var & (EP_MASK | EP_DTOG_RX);/*輸出端點*/
	 
 }
 /*功能:連接或斷開USB功能
  *參數:true -->連接USB
  *      false-->關閉USB
  *返回值:無
  */
void USB_Connect(BOOL turnon)
{
	/*需要注意一點的事,所有的USB寄存器盡量用=而不要用與或非
	 *在編程手冊上有明確表明,這樣可能會導至出一些問題*/
	printf("進入連接USB程序\r\n");
	 /*將USB強制復位*/
	CNTR = CNTR_FRES;
//	printf("test1\r\n");
	/*因為剛連接所以應該跟才啟動一樣,將所有沒有處理的中斷給清理掉*/
	ISTR=0;
//	printf("test2\r\n");
	if(turnon)
	{
//		printf("test3\r\n");
		/*使能GPIOA,然后將PA.8置低,使USB
		 *的D+加1.5K上接電阻,使USB集線器識別了高速設備
		 *這樣才能讓USB識別
		 */
		  RCC->APB2ENR |= (1 << 2);                 	 /* enable clock for GPIOA */
		  GPIOA->CRH &= ~(0x0f << 0 * 4);                /* clear port PA8 */
		  GPIOA->CRH |=  (0x03 << 0 * 4);                /* PA6 General purpose output open-drain, max speed 50 MHz */
		  GPIOA->BRR  =  (   1 << 8    );                /* reset PA8  (set to low) */	 	
		/*經過調試發現,這個語句的本意是:復位USB模塊
		 *然后在此使能CNTR_RESETM即復位中斷標志
		 *至於端點0的初始化留在USB低優先級中進行處理
		 *當然,我們也只開了低優先級中斷^_^!*/
		  CNTR = CNTR_RESETM;	 /*此處只使能了復位中斷,*/	 
	}
	else
		CNTR = CNTR_FRES | CNTR_PDWN ;/*復位並關閉*/

}
/*功能:設置端點狀態
 *參數:EPnum --->端點號
 *     stat  --->要設置的狀態值
 *返回值:無
 */
 void USB_ConfigEP (USB_ENDPOINT_DESCRIPTOR * pEPD)
 {
 	DWORD num,val;
	
	//取得端點號
	num = pEPD->bEndpointAddress & 0xf;

	val = pEPD->wMaxPacketSize;
	//如果是IN端點
	if(pEPD->bEndpointAddress & USB_ENDPOINT_DIRECTION_MASK)
	{
		//此處我只想說pBUF_DSCR是指針,剩下的就是語法問題了
		(pBUF_DSCR + num)->ADDR_TX = FreeBufAddr;
		/*取2的倍數,因為緩沖區都是字對齊的,注意此處如果大於1023會出現浪費現象
		 *因為USB_COUNTn_TX只能接收bit0~bit9
		 */
		val = (val + 1)& ~1;
	}
	//輸出端點
	else
	{
		(pBUF_DSCR + num)->ADDR_RX = FreeBufAddr;
		/*因為USB_COUNTn_RX中存儲只用bit10~bit14,如果BLSIZE=0(即塊大小為2字節),那么只能是0~62個字節
		 *所以如果大於62,則應將塊大小設置為BLSIZE=1(即32個字節)
		 */
		if(val > 62	)
		{
			//塊大小為32,則大小應該為32的倍數
			val = (val +31)&~31;
			/*關於此計算公式,參見rm0008,21,5.3節
			 *(val >> 5)<<10 == val <<5
			 */
			(pBUF_DSCR + num)->COUNT_RX = ((val << 5)-1) | 0x8000;
		}
		else
		{
			val = (val + 1) & ~1;
			(pBUF_DSCR + num)->COUNT_RX = val << 9;
		}
	}
	//修正空閑區域的起始地址
	FreeBufAddr += val ;

	switch(pEPD->bmAttributes & USB_ENDPOINT_TYPE_MASK)
	{
		//控制端點
		case USB_ENDPOINT_TYPE_CONTROL:
			val = EP_CONTROL;
			break;
		//同步端點
		case USB_ENDPOINT_TYPE_ISOCHRONOUS:
			val = EP_ISOCHRONOUS;
			break;
		//塊傳輸
		case USB_ENDPOINT_TYPE_INTERRUPT:
			val = EP_INTERRUPT;
			break;
		default:
			printf("出錯了,未識別的端點類型\r\n");
			break;
	}
	val |= num;
	//設置端點寄存器
	EPxREG(num) = val;
 }
/*功能:設置端點狀態
 *參數:EPnum --->端點號
 *     stat  --->要設置的狀態值
 *返回值:無
 */
 void EP_Status(DWORD EPNum,DWORD stat)
 {
 	DWORD num,var;
	
	/*取得端點號*/
	num = EPNum & 0x0f;
	var = EPxREG(num);
	/*此處用了一點小技巧,因為端點寄存器是寫1反轉,所以想設置相應的值只有使用異或*/
	if(EPNum & 0x80)	//輸入端點
		EPxREG(num)=(var ^ (stat & EP_STAT_TX)) & (EP_MASK | EP_STAT_TX);
	else				//輸出端點
		EPxREG(num)=(var ^ (stat & EP_STAT_RX)) & (EP_MASK | EP_STAT_RX);

 }
/*功能:復位端點
 *參數:EPNum 	bit0~bit3為端點號
 			  	bit7	 為端點方向
 *返回值:無
 */
void USB_ResetEP(DWORD EPNum)
{
	EP_Reset(EPNum);
}
/*功能:設置端點為stall
 *參數:EPNum 	bit0~bit3為端點號
 			  	bit7	 為端點方向
 *返回值:無
 */
void USB_SetStallEP(DWORD	EPNum)
{
	EP_Status(EPNum,EP_TX_STALL | EP_RX_STALL);
}
/*功能:設置端點為enable
 *參數:EPNum 	bit0~bit3為端點號
 			  	bit7	 為端點方向
 *返回值:無
 */
void USB_EnableEP(DWORD EPNum)
{
	EP_Status(EPNum,EP_TX_VALID | EP_RX_VALID);
}

/*功能:設置端點為disable
 *參數:EPNum 	bit0~bit3為端點號
 			  	bit7	 為端點方向
 *返回值:無
 */
void USB_DisableEP(DWORD EPNum)
{
	EP_Status(EPNum,EP_TX_DIS | EP_RX_DIS);
}

/*功能:清除端點的stall狀態
 *參數:EPNum 	bit0~bit3為端點號
 			  	bit7	 為端點方向
 *返回值:無
 */
void USB_ClrStallEP(DWORD	EPNum)
{
	EP_Status(EPNum,EP_TX_VALID | EP_RX_VALID);
}
/*功能:設置USB地址
 *參數:addr要設置的地址
 *返回值:無
 */
void USB_SetAddress(DWORD	addr)
{
	//DADDR的高1位是用來使能USB模塊的
	DADDR = DADDR_EF | addr;
}
/*功能:用於讀端點緩沖區
 *參數:EPnum --->端點號
 *     pData --->用於接收從端點緩沖區中讀到的數據
 *		此函數有些問題,應該加入一個參數顯示緩沖區有多大
 *返回值:讀到的字節數
 */
DWORD USB_ReadEP(DWORD EPnum,BYTE * pData)
{

	DWORD num,cnt,*pv,n;
	
	//得到端點號    
	num = EPnum & 0xf;

	//取得些端點的緩沖區地址
	pv=(DWORD *)(USB_PMA_ADDR + 2* ((pBUF_DSCR + num)->ADDR_RX));
	//COUNT_RX低10位存放的是緩沖區的數據
	cnt=(pBUF_DSCR + num)->COUNT_RX & EP_COUNT_MASK;
	for(n=0;n<(cnt+1)/2;n++)
	{
		/*pakced關鍵字用於單字節對齊,這在USB數據結構中的結構體中尤為重要
		 *因為stm32訪問時使用32位,而USB訪問時使用16位,所以pData為WORD,而
		 *pv為DWORD型指針
		 */
	 	*((__packed WORD *)pData)=*pv++;
		/*這里pData為單字節指針所以,還是加2而不是加4*/
		pData+=2;
	}
	/*OK,現在我們的端點又可以接收數據了,設置為VALID*/
	EP_Status (EPnum,EP_RX_VALID);
   	return cnt;
}

/*功能:用於寫端點緩沖區
 *參數:EPNum --->端點號
 *     pData --->指向要發送的數據緩沖區
 *	   cnt	 --->要寫入的字節數
 *返回值:寫入的字節數
 */
 DWORD USB_WriteEP(DWORD EPNum , BYTE * pData,DWORD cnt)
 {
 	DWORD num,*pv,n;

	num = EPNum & 0x0f;

	pv=(DWORD *)(USB_PMA_ADDR + 2*((pBUF_DSCR+num)->ADDR_TX));
	/*此處應該判斷要寫入的數據是否超量了,可能會產一個隱藏bug*/
   	for(n=0;n<(cnt + 1)/2;n++)
	{
		*pv++=*((__packed WORD*)pData);
		pData+=2;
	}
	//OK,現在USB發送緩沖區中已經有東西了,可以響應主機了
	(pBUF_DSCR+num)->COUNT_TX=cnt;
	EP_Status(EPNum,EP_TX_VALID);
	return 	cnt;
 }

/*功能:USB掛起
 *參數:無
 *返回值:無
 */
void USB_Suspend(void)
{
    GPIOE->BSRR |=1 ;            /* Turn Off Suspend LED */
	printf("進入掛起中斷\r\n");
	//強制掛起
	CNTR |= CNTR_FSUSP;	
	//進入低功耗模式
	CNTR |= CNTR_LPMODE;
}

/*功能:USB喚醒
 *參數:無
 *返回值:無
 */
void USB_WakeUp(void)
{
 	GPIOE->BRR |=1 ;            /* Turn On Suspend LED */
	printf("進入喚醒中斷\r\n");
	//喚醒了,當然得把這一位給清了
	CNTR &=  ~CNTR_FSUSP;	
	//USB的喚醒事件會復位此位,我們這里不用管
	//CNTR &= ~CNTR_LPMODE;
}
/*功能:USB低優先級中斷服務程序
 *參數:無
 *返回值:無
 */
 void USB_LP_CAN_RX0_IRQHandler(void)
 {
 	DWORD	istr;
	DWORD 	num,var;

	istr=ISTR;	//取得中斷標志位
	/*USB復位中斷的處理*/
	if(istr & ISTR_RESET)
	{
		//復位設備
		USB_Reset();
		//復位與USB協議有關的數據
		USB_ResetCore();
		ISTR = ~ISTR_RESET;	/*已經處理完復位中斷了,清楚復位中斷標志*/
	}
	/*USB掛起中斷*/
/*	if(istr & 
********************************************************************
*/

  if (istr & ISTR_SUSP) {
  	USB_Suspend();
    ISTR = ~ISTR_SUSP;
  }

  /* USB Wakeup */
  if (istr & ISTR_WKUP) {
	USB_WakeUp();
    ISTR = ~ISTR_WKUP;
  }

//******************************************************************
	/*端點接中發送中斷處理*/
	while((istr = ISTR) & ISTR_CTR) 
	{
		//清楚中斷標志位
		ISTR = ~ISTR_CTR;
		//取得發生中斷的端點號
		num=istr & ISTR_EP_ID;
		//取得端點寄存器的值		
		var = EPxREG(num);
		//正確的接收傳輸
		if(var & EP_CTR_RX )
		{
			//清0正確接收標志位
		//	printf("端點號為:");
		//	printhex((u8)num);
			EPxREG(num) = var & ~EP_CTR_RX & EP_MASK;
			//調用相應的端點進行處理
			if(USB_P_EP[num])
			{
				//如果是控制傳輸,則使有USG_EVT_SETUP
				if(var & EP_SETUP)
					USB_P_EP[num](USB_EVT_SETUP);
				else
					//否則就是普通的輸出包
					USB_P_EP[num](USB_EVT_OUT);
			}
		}
		//產生的是發送成功中斷
		if(var & EP_CTR_TX)
		{
			//清楚中斷標志位
			EPxREG(num) = var & ~EP_CTR_TX & EP_MASK;
			//調用對應的中斷函數進行處理
			if(USB_P_EP[num])
			{
				//USB發送成功
				USB_P_EP[num](USB_EVT_IN);	
			}
		}

	}

 }
如有錯誤還請指正!!!


免責聲明!

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



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