#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,进行初始化rn"); //使能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_Resetrn"); /*复位了嘛,那所有以前产生的中断都没有用了,清了完事!*/ 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_Resetrn"); /*获得端点号,低四位为端点号*/ 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程序rn"); /*将USB强制复位*/ CNTR = CNTR_FRES; // printf("test1rn"); /*因为刚连接所以应该跟才启动一样,将所有没有处理的中断给清理掉*/ ISTR=0; // printf("test2rn"); if(turnon) { // printf("test3rn"); /*使能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("出错了,未识别的端点类型rn"); 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("进入挂起中断rn"); //强制挂起 CNTR |= CNTR_FSUSP; //进入低功耗模式 CNTR |= CNTR_LPMODE; } /*功能:USB唤醒 *参数:无 *返回值:无 */ void USB_WakeUp(void) { GPIOE->BRR |=1 ; /* Turn On Suspend LED */ printf("进入唤醒中断rn"); //唤醒了,当然得把这一位给清了 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); } } } }
如有错误还请指正!!!