目录
USBFS包含了一个内部的全速USB PHY,不需要外部PHY芯片。
注:GD32F450的USB FS寄存器地址分布和含义与STM32F407一样,只是各自的命名方式不同。另外,在使用中代码也可能会有少许不同,即GD32F450的USB代码不一定可以在STM32F407上跑,反之以然。
1. 定义USB FS的结构
#define USBFS_REG_BASE 0x50000000L /*!< base address of USBFS registers */
typedef struct
{
uint32_t GOTGCS; //0x00
uint32_t GOTGINTF; //0x04
uint32_t GAHBCS; //0x08
uint32_t GUSBCS; //0x0C
uint32_t GRSTCTL; //0x10
uint32_t GINTF; //0x14
uint32_t GINTEN; //0x18
uint32_t GRSTATR; //0x1C
uint32_t GRSTATP; //0x20
uint32_t GRFLEN; //0x24
uint32_t HNPTFLEN_DIEP0TFLEN; //0x28
uint32_t HNPTFQSTAT; //0x2C
uint32_t recv1[2]; //0x30, 0x34
uint32_t GCCFG; //0x38
uint32_t CID; //0x3C
uint32_t recv2[48]; //0x40 - 0xFF
uint32_t HPTFLEN; //0x100
uint32_t DIEPxTFLEN[3]; //0x104
}USBFSGlobal_t;
typedef struct
{
uint32_t HCTL; //0x00
uint32_t HFT; //0x04
uint32_t HFINFR; //0x08
uint32_t resv1; //0x0C
uint32_t HPTFQSTAT; //0x10
uint32_t HACHINT; //0x14
uint32_t HACHINTEN; //0x18
uint32_t resv2[9]; //0x1C - 0x3F
uint32_t HPCS; //0x40
uint32_t resv3[47]; //0x44 - 0xFF
struct //0x100
{
uint32_t CTL; //0x00
uint32_t resv1; //0x04 - 0x07
uint32_t INTF; //0x08
uint32_t INTEN; //0x0C
uint32_t LEN;
}Channel[8];
}USBFSHost_t;
typedef struct
{
uint32_t DCFG; //0x00
uint32_t DCTL; //0x04
uint32_t DSTAT; //0x08
uint32_t recv1; //0x0C
uint32_t DIEPINTEN; //0x10
uint32_t DOEPINTEN; //0x14
uint32_t DAEPINT; //0x18
uint32_t DAEPINTEN; //0x1C
uint32_t recv2[2]; //0x20 - 0x27
uint32_t DVBUSDT; //0x28
uint32_t DVBUSPT; //0x2C
uint32_t recv3; //0x30
uint32_t DIEPFEINTEN; //0x34
uint32_t recv4[50]; //0x38 - 0xFF
struct //0x100
{
uint32_t CTL; //0x00
uint32_t recv1; //0x04
uint32_t INTF; //0x08
uint32_t recv2; //0x0C
uint32_t LEN; //0x10
uint32_t recv3; //0x14
uint32_t TFSTAT; //0x18
uint32_t recv4; //0x1C, offset = 0x20
}DIEPx[4];
uint32_t recv5[96]; //0x180 - 0x2FF
struct //0x300
{
uint32_t CTL; //0x00
uint32_t recv1; //0x04
uint32_t INTF; //0x08
uint32_t recv2; //0x0C
uint32_t LEN; //0x10
uint32_t recv3[3]; //0x14 - 0x1F
}DOEPx[4];
}USBFSDev_t;
//#define USBFSCore ((USBFSGlobal_t *)USBFS_REG_BASE)
//#define USBFSDev ((USBFSDev_t *)(USBFS_REG_BASE + 0x800))
//#define USBFSPwrClk ((uint32_t *)(USBFS_REG_BASE + 0xE00))
typedef struct
{
volatile USBFSGlobal_t *Global;
volatile USBFSHost_t *Host;
volatile USBFSDev_t *Dev;
uint32_t *PwrClk;
uint8_t maxEPNum;
uint32_t *FIFO[4];
}USBFS_t;
volatile USBFS_t USBFS =
{
((USBFSGlobal_t *)USBFS_REG_BASE),
((USBFSHost_t *)(USBFS_REG_BASE + 0x400)),
((USBFSDev_t *)(USBFS_REG_BASE + 0x800)),
((uint32_t *)(USBFS_REG_BASE + 0xE00)),
4,
{
((uint32_t *)(USBFS_REG_BASE + 0x1000)),
((uint32_t *)(USBFS_REG_BASE + 0x2000)),
((uint32_t *)(USBFS_REG_BASE + 0x3000)),
((uint32_t *)(USBFS_REG_BASE + 0x4000))
},
};
2. IO初始化
DP、DM对应GPIOA11和GPIOA12.
//DM: GPIOA11, DP: GPIOA12
GPIO_AFSEL1(GPIOA) &= ~(uint32_t)((uint32_t)0xFF << (11 % 8) * 4);
GPIO_AFSEL1(GPIOA) |= (((uint32_t)0xAA << (11 % 8) * 4));
GPIO_CTL(GPIOA) &= ~(uint32_t)(((uint32_t)0xF << 11 * 2));
GPIO_CTL(GPIOA) |= (uint32_t)(((uint32_t)0xA << 11 * 2)); //GPIO_MODE_AF
GPIO_PUD(GPIOA) &= ~(uint32_t)(((uint32_t)0xF << 11 * 2));
//GPIO_PUD(GPIOA) |= (uint32_t)(((uint32_t)0x5 << 11 * 2)); //GPIO_PUPD_PULLUP
GPIO_OMODE(GPIOA) &= ~(uint32_t)((uint32_t)0x3 << 11); //GPIO_OTYPE_PP
GPIO_OSPD(GPIOA) &= ~(uint32_t)((uint32_t)0xF << 11 * 2);
GPIO_OSPD(GPIOA) |= (uint32_t)(((uint32_t)0xF << 11 * 2)); //GPIO_OSPEED_200MHZ
3. 初始化频率
USB的频率源有2种方式:IRC48M(内部48MHz RC 振荡器时钟)和PLL48M。
3.1 IRC48M
IRC48M晶振对应USB应用精度不够高,所以还需要CK_CTC校准精度。
通过设置RCU_ADDCTL的位16为1使能IRC48M,然后通过判断RCU_ADDCTL的位17为1表示IRC48M已经稳定。
{
uint32_t timeout;
RCU_ADDCTL |= ((uint32_t)1 << 16); //Enable IRC48M
timeout = 30000;
do
{
if((RCU_ADDCTL & ((uint32_t)1 << 17)) > 1)
break;
timeout--;
delayms(1);
}while(timeout > 0);
RCU_ADDCTL |= ((uint32_t)1 << 0); //Select IRC48M
}
USB初始化完成后再设置CTC校准
RCU_ADDAPB1EN |= ((uint32_t)1 << 27); //Enable CTC
ctcConfig();
while ((CTC_STAT & ((uint32_t)1 << 0)) == 0) //1 means CK OK
{
}
其中CTC校准函数ctcConfig参考例程拷贝
void ctcConfig(void)
{
/* configure CTC reference signal source prescaler */
ctc_refsource_prescaler_config(CTC_REFSOURCE_PSC_OFF);
/* select reference signal source */
ctc_refsource_signal_select(CTC_REFSOURCE_USBSOF);
/* select reference signal source polarity */
ctc_refsource_polarity_config(CTC_REFSOURCE_POLARITY_RISING);
/* configure hardware automatically trim mode */
ctc_hardware_trim_mode_config(CTC_HARDWARE_TRIM_MODE_ENABLE);
/* configure CTC counter reload value, Fclock/Fref-1 */
ctc_counter_reload_value_config(0xBB7F);
/* configure clock trim base limit value, Fclock/Fref*0.0012/2 */
ctc_clock_limit_value_config(0x1D);
/* CTC counter enable */
ctc_counter_enable();
}
3.2 PLL48M
PLL48M有2个源:CK_PLLQ时钟或CK_PLLSAIP时钟。建议使用CK_PLLQ时钟。
RCU_ADDCTL &= ~((uint32_t)1 << 1); //Select PLLQ
RCU_ADDCTL &= ~((uint32_t)1 << 0); //Select PLL48M
对于GD32F450 200M时钟时,现在的例程中CK_PLLQ时钟不准,会导致USB工作不稳定。可以选择120M或168M。
附PLL的计算公式,对于USB应用要是PLLQ为48M:
CK_PLLVCOSRC = CK_PLLSRC / PLLPSC
CK_PLLVCO = CK_PLLVCOSRC × PLLN
CK_PLLP = CK_PLLVCO / PLLP
CK_PLLQ = CK_PLLVCO / PLLQ
CK_PLLSRC即外部晶体的频率,这里是25M。
/* Configure the main PLL, PSC = 25, PLL_N = 400, PLL_P = 2, PLL_Q = 9 */
RCU_PLL = (25U | (400U << 6U) | (((2U >> 1U) - 1U) << 16U) |
(RCU_PLLSRC_HXTAL) | (9U << 24U));
即 CK_PLLVCOSRC = 1M,CK_PLLVCO = 400M,CK_PLLP = 200M,PLLQ = 400 / 9 = 44.4444M, 和48M有误差。要想200M的PLLP和48M的PLLQ同时成立,则PLLVCO最小要设置到1200M,而PLLVCO的范围是100MHz 到 500MHz 之间,所以当200M频率需求时,无法使用PLL48M模式,可以采用第一种IRC48M的设置。
3.3 使能USB FS
RCU_AHB2EN |= ((uint32_t)1 << 7); //Enable
4. USB中断初始化
USB FS有2个中断:USBFS_WKUP_IRQHandler(只有在使用USB睡眠功能时才需要)和USBFS_IRQHandler。
4.1 设置中断优先级
SCB->AIRCR = NVIC_AIRCR_VECTKEY_MASK | ((uint32_t)0x500);
设置中断优先级的组,即中断用2个位作为组优先级,用2个位作为子优先级。
4.2 设置优先级
USBFS_WKUP_IRQHandler设置为组0,组内编号0,即Wakeup的中断有限级最高,而USBFS_IRQn设置为组3,组内编号0,即中断优先级是组3中最高,但是比Wakeup低2级。
nvic_irq_enable((uint8_t)USBFS_IRQn, 3U, 0U);
#ifdef USB_LOW_POWER
RCU_APB1EN |= ((uint32_t)1 << 28); //Enable PMU
/* USB wakeup EXTI line configuration */
exti_interrupt_flag_clear(EXTI_18);
exti_init(EXTI_18, EXTI_INTERRUPT, EXTI_TRIG_RISING);
exti_interrupt_enable(EXTI_18);
nvic_irq_enable((uint8_t)USBFS_WKUP_IRQn, 0U, 0U);
#endif
5. 初始化USB
5.1 选择内部PHY
USBFS.Global->GUSBCS |= ((uint32_t)1 << 6); //Select embedded PHY
这里有个问题,datasheet中没有这个位的说明,代码里面设置这个位为1.
5.2 复位
USB内核软件复位, 复位AHB和USB时钟域电路,以及大多数的寄存器。
//Reset Core
USBFS.Global->GRSTCTL |= ((uint32_t)1 << 0);
while ((USBFS.Global->GRSTCTL & ((uint32_t)1 << 0)) > 0) // wait for the core to be soft reset
{
}
delayus(3);
5.3 配置USB内核
//Active the transceiver and enable VBUS sensing
//bit16: Power On
//bit18: VBUS A-device comparer enable
//bit19: VBUS B-device comparer enable
USBFS.Global->GCCFG |= ((uint32_t)1 << 16) | ((uint32_t)1 << 18) | ((uint32_t)1 << 19);
#ifndef USB_IGNORE_VBUS
USBFS.Global->GCCFG |= ((uint32_t)1 << 21);
#endif
USBFS.Global->GCCFG |= ((uint32_t)1 << 20); //Enable SOF
delayms(20);
位16表示USB内核上电,位18和位19是用于OTG识别设备类型。
位21表示是否忽略VBUS检测,为1时则表示忽略VBUS引脚电压,这时可以将该Pin作为其他使用。
位20表示SOF输出使能。
5.4 软断开USB连接
#ifdef USB_DEVICE_MODE
USBFS.Dev->DCTL |= ((uint32_t)1 << 1); //Soft Disconnect
#endif
5.5 设置USB模式
USBFS.Global->GUSBCS &= ~(((uint32_t)1 << 29) | ((uint32_t)1 << 30));
#if (defined(USB_DEVICE_MODE))
USBFS.Global->GUSBCS |= ((uint32_t)1 << 30);
#elif (defined(USB_HOST_MODE))
USBFS.Global->GUSBCS |= ((uint32_t)1 << 29);
#endif
OTG不需要设置,而Device和Host模式下需要将USB强制设置为对应的模式。
5.6 Device模式初始化
5.6.1 清除时钟停止使能
USBFS.PwrClk = 0;
5.6.2 配置周期性帧尾时间
定义周期性帧时间的帧尾标志触发的时间点,默认80%
USBFS.Dev->DCFG &= ~((uint32_t)0x03 << 11);
5.6.3 设置USB速度
USB FS只能设置全速
USBFS.Dev->DCFG |= ((uint32_t)0x03 << 0);
5.6.4 设置接收FIFO的长度
所有的OUT端点通过共享 Rx FIFO接收包数据。
USBFS.Global->GRFLEN = USB_FS_FIFO_RX_LEN;
接收FIFO总是在USB FIFO中起始位置开始的,所以不需要设置RX FIFO的起始地址。 注意这个长度是32位计数,比如如果设置RX FIFO的长度为64字节,这个寄存器的值为64/4 = 16。STM32F407的文档建议这个长度为40个字,即160字节。
5.6.5 设置发送FIFO的长度
发送FIFO有4个(设备模式下),即每个IN端点使用一个FIFO。同样的,这些长度都是32位计数的。
※端点0的TX FIFO设置
端点0的TX FIFO在Global的设置中,紧接着RX FIFO。
USBFS.Global->HNPTFLEN_DIEP0TFLEN = ((uint32_t)USB_FS_FIFO_TX0_LEN << 16) | USB_FS_FIFO_RX_LEN;
※其他3个端点的TX FIFO设置
USBFS.Global->DIEPxTFLEN[0] = ((uint32_t)USB_FS_FIFO_TX1_LEN << 16)
| (USB_FS_FIFO_RX_LEN + USB_FS_FIFO_TX0_LEN);
USBFS.Global->DIEPxTFLEN[1] = ((uint32_t)USB_FS_FIFO_TX2_LEN << 16)
| (USB_FS_FIFO_RX_LEN + USB_FS_FIFO_TX0_LEN + USB_FS_FIFO_TX1_LEN);
USBFS.Global->DIEPxTFLEN[2] = ((uint32_t)USB_FS_FIFO_TX3_LEN << 16)
| (USB_FS_FIFO_RX_LEN + USB_FS_FIFO_TX0_LEN + USB_FS_FIFO_TX1_LEN + USB_FS_FIFO_TX2_LEN);
仔细说明一下FIFO的结构:
USBFS的FIFO总大小为1.25K字节。上面的初始化就是通过USBFS_GRFLEN 和 USBFS_DIEPxTFLEN (x=0…3)配置 FIFO 的大小和起始偏移地址。FIFO的地址和大小如下图:
整个FIFO大小为1.25K字节 = 1280字节 = 0x500,上图中End是0x13F,是因为这里是以字(4字节)计算的,所以0x500 / 4 = 0x140。
整个FIFO可以分为5个空间,即1个Rx FIFO空间和4个Tx FIFO空间,所有的端点公用Rx FIFO空间,4个端点各自有对应的Tx FIFO空间。
Rx FIFO空间的起始地址固定为0x000,它的大小由USBFS_GRFLEN设定,注意这里设置的是字节数,而且长度必须是4的倍数。
USBFS_DIEPxTFLEN中设定的是4个Tx FIFO的起始地址和长度,高16位是起始地址,而低16位为FIFO长度,注意也是字节且长度必须是4个倍数。
上面是配置FIFO的空间分布,而实际读写FIFO时FIFO的范围地址是映射为固定的,并不是上面设置长度时的位置,如下图,比如端点0的地址0x1000-0x1FFF。写端点0时数据写入0x1000这个地址即可。
而且在读写FIFO时必须是4字节的倍数读写。
5.6.6 刷新所有TX FIFO
void usbFlushTXFIFO(uint8_t fifoNum)
{
USBFS.Global->GRSTCTL = ((uint32_t)fifoNum << 6U) | ((uint32_t)1 << 5);
while((USBFS.Global->GRSTCTL & ((uint32_t)1 << 5)) > 0)
{
}
delayus(3);
}
刷新所有的FIFO参数为0x10即可。
5.6.7 刷新RX FIFO
void usbFlushRXFIFO(void)
{
USBFS.Global->GRSTCTL = ((uint32_t)1 << 4);
while((USBFS.Global->GRSTCTL & ((uint32_t)1 << 4)) > 0)
{
}
delayus(3);
}
5.6.8 清除所有的中断标志
//Clear all pending device interrupts
USBFS.Dev->DIEPINTEN = 0U;
USBFS.Dev->DOEPINTEN = 0U;
USBFS.Dev->DAEPINT = 0xFFFFFFFFU;
USBFS.Dev->DAEPINTEN = 0U;
6. 初始化端点
6.1 清零所有IN端点
for(i = 0; i < USBFS.maxEPNum; i++)
{
if((USBFS.Dev->DIEPx[i].CTL & ((uint32_t)1 << 31)) > 0) //bit31: DP Enable
{
USBFS.Dev->DIEPx[i].CTL |= ((uint32_t)1 << 30 | (uint32_t)1 << 27); //bit30: DP Disable, bit27: Set NAK
}
else
{
USBFS.Dev->DIEPx[i].CTL = 0;
}
USBFS.Dev->DIEPx[i].LEN = 0; //set IN endpoint transfer length to 0
USBFS.Dev->DIEPx[i].INTF = 0xFF; //clear all pending IN endpoint interrupts
}
6.2 清零所有OUT端点
if((USBFS.Dev->DOEPx[i].CTL & ((uint32_t)1 << 31)) > 0) //bit31: DP Enable
{
USBFS.Dev->DOEPx[i].CTL |= ((uint32_t)1 << 30 | (uint32_t)1 << 27); //bit30: DP Disable, bit27: Set NAK
}
else
{
USBFS.Dev->DOEPx[i].CTL = 0;
}
USBFS.Dev->DOEPx[i].LEN = 0; //set OUT endpoint transfer length to 0
USBFS.Dev->DOEPx[i].INTF = 0xFF; //clear all pending OUT endpoint interrupts
6.3 初始化EP
每个端点设置方向、包最大大小和端点编号(这个可以通过配置描述符约束依次定义),其中端点0是默认64字节大小,双向。
if(i < sizeof(epDir))
{
//bit0-10: maximum packet length
//bit18-19: endpoint type
//bit22-25: FIFO number
if(epDir[i] == USB_EP_DIR_IN || epDir[i] == USB_EP_DIR_BOTH)
{
USBFS.Dev->DIEPx[i].CTL &= ~(((uint32_t)0x7FF << 0) | ((uint32_t)0x3 << 18) | ((uint32_t)0xF << 22));
USBFS.Dev->DIEPx[i].CTL |= ((uint32_t)epInSize[i] << 0) | ((uint32_t)epType[i] << 18) | ((uint32_t)i << 22);
value |= (uint32_t)1 << i;
}
if (epDir[i] == USB_EP_DIR_OUT || epDir[i] == USB_EP_DIR_BOTH)
{
USBFS.Dev->DOEPx[i].CTL &= ~(((uint32_t)0x7FF << 0) | ((uint32_t)0x3 << 18) | ((uint32_t)0xF << 22));
USBFS.Dev->DOEPx[i].CTL |= ((uint32_t)epInSize[i] << 0) | ((uint32_t)epType[i] << 18) | ((uint32_t)i << 22);
value |= (uint32_t)1 << (i + 16);
}
}
最后将对应的USB端点中断使能
USBFS.Dev->DAEPINTEN |= value;
7. USB中断使能
7.1 端点Tx FIFO下溢中断使能
USBFS.Dev->DIEPINTEN |= ((uint32_t)1 << 4); //endpoint Tx FIFO underrun interrupt enable bit
7.2 清除所有的中断
USBFS.Global->GOTGINTF = 0xFFFFFFFF;
USBFS.Global->GINTF = 0xBFFFFFFF;
7.3 使能中断
//bit31: wakeup interrupt enable
//bit11: USB suspend interrupt enable
USBFS.Global->GINTEN = ((uint32_t)1 << 31) | ((uint32_t)1 << 11);
#ifdef USB_USB_FIFO
USBFS.Global->GINTEN |= (uint32_t)1 << 4; //bit4: receive FIFO non-empty interrupt enable
#endif
USBFS.Global->GINTEN |=
((uint32_t)1 << 12) | //bit12: USB reset interrupt enable
((uint32_t)1 << 13) | //bit13: enumeration finish enable
((uint32_t)1 << 18) | //bit18: IN endpoints interrupt enable
((uint32_t)1 << 19) | //bit19: OUT endpoints interrupt enable
((uint32_t)1 << 3) | //bit3: start of frame interrupt enable
((uint32_t)1 << 21) | //bit21: isochronous OUT transfer not complete interrupt enable
((uint32_t)1 << 20); //bit20: isochronous IN transfer not complete interrupt enable
#ifdef USB_IGNORE_VBUS
//bit30: session interrupt enable
//bit2: OTG interrupt enable
USBFS.Global->GINTEN |= ((uint32_t)1 << 30) | ((uint32_t)1 << 2);
#endif
7.4 使能USB中断总开关
USBFS.Global->GAHBCS |= ((uint32_t)1 << 0); //bit0: global interrupt enable
8. 连接USB
#ifndef USB_OTG_MODE
USBFS.Dev->DCTL &= ~((uint32_t)1 << 1);
delayms(3);
#endif
到了这一步USB的USBFS_IRQHandler中断应该可以进入了。
9. USBFS_IRQHandler中断处理
9.1 判断是否为设备中断
if((USBFS.Global->GINTF & ((uint32_t)1 << 0)) > 0)
return;
如果是主机模式中断,则直接返回
9.2 判断该中断是否使能
intr = USBFS.Global->GINTF & USBFS.Global->GINTEN;
if(intr == 0)
return;
9.3 端点OUT中断
if ((intr & ((uint32_t)1 << 19)) > 0) //bit19: OUT endpoint interrupt flag
{
}
9.4 端点IN中断
if ((intr & ((uint32_t)1 << 18)) > 0) //bit18: IN endpoint interrupt flag
{
}
9.5 Suspend中断
if ((intr & ((uint32_t)1 << 11)) > 0) //bit11: USB suspend
{
}
9.6 唤醒中断
if ((intr & ((uint32_t)1 << 31)) > 0) //bit31: wakeup interrupt flag
{
}
9.7 SOF中断
if ((intr & ((uint32_t)1 << 3)) > 0) //bit3: start of frame flag
{
}
9.8 接收缓冲非空中断
if ((intr & ((uint32_t)1 << 4)) > 0) //bit4: rx FIFO non-empty interrupt flag
{
}
9.9 复位中断
if ((intr & ((uint32_t)1 << 12)) > 0) //bit12: USB reset
{
}
9.10 枚举完成中断
if ((intr & ((uint32_t)1 << 13)) > 0) //bit13: enumeration finished
{
}
9.11 同步IN传输未完成中断
if ((intr & ((uint32_t)1 << 20)) > 0) //bit20: isochronous IN transfer not complete interrupt flag
{
}
9.12 同步OUT端点未完成传输
if ((intr & ((uint32_t)1 << 21)) > 0) //bit21: isochronous OUT transfer not complete interrupt flag
{
}
9.13 会话中断
if ((intr & ((uint32_t)1 << 2)) > 0) //bit2: OTG interrupt flag
{
}