#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);
}
}
}
}
如有錯誤還請指正!!!