6、UsbDevice(模拟U盘)
6.1 实验描述及工程文件清单
实验描述: 这是一个USB Mass Storage 实验,用USB线连接PC机与开发板,在电脑上就可以像操作普通U盘那样来操作开发板中的MicroSD卡,并在超级终端中打印出相应的调试信息。在这里野火用的MicroSD卡的容量是1G。
硬件连接:
PE3-USB-MODE (PE3为普通I/O)
PA11-USBDM(D-)
PA12-USBDP(D+)
用到的库文件:
startup/start_stm32f10x_hd.c
CMSIS/core_cm3.c
CMSIS/system_stm32f10x.c
FWlib/stm32f10x_gpio.c
FWlib/stm32f10x_rcc.c
FWlib/stm32f10x_usart.c
FWlib/misc.c
FWlib/stm32f10x_dma.c
FWlib/stm32f10x_sdio.c
FWlib/stm32f10x_flash.c
用户编写的文件:
USER/main.c
USER/stm32f10x_it.c
USER/usart1.c
USER/sdcard.c
USER/usb_istr.c
USER/usb_prop.c
USER/usb_pwr.c
USER/hw_config.c
USER/memory.c
USB文件:
usb_core.c
usb_init.c
usb_mem.c
usb_regs.c
usb_bot.c
usb_scsi.c
usb_data.c
usb_desc.c
usb_endp.c
其中USER文件夹下红色标注的5个c文件也是USB库文件,只因为我们需要修改它们才没有把它们放在USBLIB目录下。
stm32f10x_it.c:该文件中包含USB中断服务程序,由于USB中断有很多情况,这里的中断服务程序只是调用usb_Istr.c文件中的USB_Istr函数,由USB_Istr函数再做轮询处理。
usb_istr.c:该文件中只有一个函数,即USB中断的USB_Istr函数,该函数对各类引起USB中断的事件作轮询处理。
usb_prop.c:该文件用于实现相关设备的USB协议,例如初始化、SETUP包、IN包、OUT包等等。
usb_pwr.c:该文件中包含处理上电、调电、挂起和恢复事件的函数。
memory.c:该文件中包含USB读写SD卡的函数。
hw_config.c:该文件中包含系统配置的函数。
野火STM32开发板中USB硬件原理图:
6.2 USB简介
USB模块为PC主机和微控制器所实现的功能之间提供了符合USB规范的通信连接。PC主机和微控制器之间的数据传输是通过共享一专用的数据缓冲区来完成的,该数据缓冲区能被USB外设直接访问。这块专用数据缓冲区的大小由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可使用512字节缓冲区,最多可用于16个单向或8个双向端点。
USB模块同PC主机通信,根据USB规范实现令牌分组的检测,数据发送/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括CRC的生成和校验。每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地址、大小和需要传输的字节数。当USB模块识别出一个有效的功能/端点的令牌分组时,(如果需要传输数据并且端点已配置)随之发生相关的数据传输。
USB模块通过一个内部的16位寄存器实现端口与专用缓冲区的数据交换。在所有的数据传输完成后,如果需要,则根据传输的方向,发送或接收适当的握手分组。在数据传输结束时,USB模块将触发与端点相关的中断,通过读状态寄存器和/或者利用不同的中断处理程序,微控制器可以确定:
● 哪个端点需要得到服务
● 产生如位填充、格式、CRC、协议、缺失ACK、缓冲区溢出/缓冲区未满等错误时,正在进行的是哪种类型的传输。
有关更多USB的介绍请参考《STM32参考手册中文》。USB是一个很复杂的设备,要想全面的学习USB,光靠做完这个实验和看《STM32参考手册中文》是远远不够的,还要大量阅读ST的官方USB文档。还有网上有本关于USB方面的书《圈圈教你学USB》很不错,大家可以去买来研究研究。总之,一句话,USB耐学,^_^。
6.3友情提示
USB是一个比较复杂的知识点,单单USB就可以出一本书,在这里野火不可能为大家讲解地非常详细,但这个文档可以为大家扫清代码阅读的障碍。要想更深入的学习USB,仍需大家的努力,网上有本叫《圈圈教你学
USB》的书就将得非常好,有兴趣的朋友可以买来看看,虽然有电子版,但大
伙还是买本书支持下圈圈,毕竟圈圈写书也不容易呀^_^……
6.4实验讲解
在配置好我们需要用到的库文件之后,我们就从main函数开始分析,关于如何添加库文件请参考前面的教程,这里不再详述。
/*
* 函数名:main
* 描述 :无
* 输入 :无
* 输出 :无
*/
int main(void)
{
/* 配置系统时钟为72M */
SystemInit();
/* 串口1配置 15200-8-N-1 */
USART1_Config();
/* SD卡中断配置,优先级最高 */
NVIC_Configuration();
/* 等待SD卡底层初始化成功 */
while ( SD_USER_Init() != SD_OK );
/* 获取SD卡容量信息,并在串口打印出来 */
Get_Medium_Characteristics();
/* 配置USB时钟为48M */
Set_USBClock();
/* 配置USB中断 */
USB_Interrupts_Config();
/* 配置USB D+ 线为全速模式 */
USB_Cable_Config (ENABLE);
/* USB系统初始化 */
USB_Init();
/* 等待USB中断到来,在中断函数中进行数据的传输 */
while (1)
{
}
}
系统库函数SystemInit();将系统时钟配置为72MHZ, USART1_Config();初始化等下要用到的串口1,用于打印MicroSD卡的容量信息。有关这两个函数的详细介绍请参考前面的教程,这里不再详述。
NVIC_Configuration();用于配置MicroSD卡的中断优先级,本实验中配置为最高优先。
while ( SD_USER_Init() != SD_OK );用于等待MicroSD卡底层硬件初始化成功,SD_USER_Init()在sdcard.c中实现。只有这个初始化成功了,接下来才能通过USB的方式来访问,所以才采用while ( );的写法,如果初始化不成功的话则一直等待,直到初始化成功为止。SD_USER_Init()在main.c中实现。
Get_Medium_Characteristics();用来获取MicroSD卡的容量信息,并通过串口1在超级终端中打印出来。Get_Medium_Characteristics();也在main.c中实现。
Set_USBClock();将USB的时钟设置为48MHZ,其实USB的时钟必须设置为48MHZ,如下图所示,截图来自《STM32参考手册中文》第47页。Set_USBClock();在hw_config.c中实现。
/*
* 函数名:Set_USBClock
* 描述 :配置USB时钟(48M)
* 输入 :无
* 输出 :无
*/
void Set_USBClock(void)
{
/* USBCLK = PLLCLK */
RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);
/* Enable USB clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);
}
USB_Interrupts_Config();用于配置USB的中断优先级,本实验中USB的中断优先级次于MicroSD卡的中断优先级。USB_Interrupts_Config();在hw_config.c中实现。
USB_Cable_Config (ENABLE); 用于配置USB D+ 这跟数据线为全速模式,即D+这根数据线接一个上拉电阻。当D-数据线接上拉电阻时则工作于低速模式。USB_Cable_Config (); 在hw_config.c中实现:
/*
* 函数名:USB_Cable_Config
* 描述 :Software Connection/Disconnection of USB Cable
* 输入 :-NewState: new state
* 输出 :无
*/
void USB_Cable_Config (FunctionalState NewState)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
/* PE3 输出 0 时 D+ 接上拉电阻工作于全速模式 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; /* 开漏输出 */
GPIO_Init(GPIOE, &GPIO_InitStructure);
if (NewState != DISABLE)
{
GPIO_ResetBits(GPIOE, GPIO_Pin_3); /* USB全速模式 */
}
else
{
GPIO_SetBits(GPIOE, GPIO_Pin_3); /* 普通模式 */
}
}
在硬件设计上我们通过I/O控制一个三极管的通断来决定D+这跟数据线是否上拉:
USB_Init();用于系统初始化,USB_Init();在usb_init.c中实现:
/********************************************************
* Function Name : USB_Init
* Description : USB system initialization
* Input : None.
* Output : None.
* Return : None.
*********************************************************/
void USB_Init(void)
{
pInformation = &Device_Info;
pInformation->ControlState = 2;
pProperty = &Device_Property;
pUser_Standard_Requests = &User_Standard_Requests;
/* Initialize devices one by one */
pProperty->Init();
}
如果我们在调试程序时,不成功的话,一般都是这个函数出了问题,而一般的问题又会是硬件的问题,我当初调试的时候就郁闷了很久。
最后让程序在一个while (1) { } 无限循环总等待USB中断的到来,然后进行中断服务程序的处理。USB中断函数void USB_Istr(void)在usb_istr.c中实现:
/**********************************************************
* Function Name : USB_Istr
* Description : ISTR events interrupt service routine
* Input : None.
* Output : None.
* Return : None.
**********************************************************/
void USB_Istr(void)
{
wIstr = _GetISTR();
#if (IMR_MSK & ISTR_RESET)
if (wIstr & ISTR_RESET & wInterrupt_Mask)
{
_SetISTR((u16)CLR_RESET);
Device_Property.Reset();
#ifdef RESET_CALLBACK
RESET_Callback();
#endif
}
#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_DOVR)
if (wIstr & ISTR_DOVR & wInterrupt_Mask)
{
_SetISTR((u16)CLR_DOVR);
#ifdef DOVR_CALLBACK
DOVR_Callback();
#endif
}
#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_ERR)
if (wIstr & ISTR_ERR & wInterrupt_Mask)
{
_SetISTR((u16)CLR_ERR);
#ifdef ERR_CALLBACK
ERR_Callback();
#endif
}
#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_WKUP)
if (wIstr & ISTR_WKUP & wInterrupt_Mask)
{
_SetISTR((u16)CLR_WKUP);
Resume(RESUME_EXTERNAL);
#ifdef WKUP_CALLBACK
WKUP_Callback();
#endif
}
#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_SUSP)
if (wIstr & ISTR_SUSP & wInterrupt_Mask)
{
/* check if SUSPEND is possible */
if (fSuspendEnabled)
{
Suspend();
}
else
{
/* if not possible then resume after xx ms */
Resume(RESUME_LATER);
}
/* clear of the ISTR bit must be done after setting of CNTR_FSUSP */
_SetISTR((u16)CLR_SUSP);
#ifdef SUSP_CALLBACK
SUSP_Callback();
#endif
}
#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_SOF)
if (wIstr & ISTR_SOF & wInterrupt_Mask)
{
_SetISTR((u16)CLR_SOF);
bIntPackSOF++;
#ifdef SOF_CALLBACK
SOF_Callback();
#endif
}
#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_ESOF)
if (wIstr & ISTR_ESOF & wInterrupt_Mask)
{
_SetISTR((u16)CLR_ESOF);
/* resume handling timing is made with ESOFs */
Resume(RESUME_ESOF); /* request without change of the machine state */
#ifdef ESOF_CALLBACK
ESOF_Callback();
#endif
}
#endif
/*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
#if (IMR_MSK & ISTR_CTR)
if (wIstr & ISTR_CTR & wInterrupt_Mask)
{
/* servicing of the endpoint correct transfer interrupt */
/* clear of the CTR flag into the sub */
CTR_LP();
#ifdef CTR_CALLBACK
CTR_Callback();
#endif
}
#endif
} /* USB_Istr */
实验到了这里,我们已经可以通过USB来操作我们开发板上的MicroSD卡了。有关更多实验的细节请大家阅读源码。
6.5实验现象
将野火STM32开发板供电(DC5V),插上JLINK,插上MicroSD卡,插上方口的USB线,将编译好的程序下载到开发板,如果程序运行成功,则可在电脑上看到开发板上的U盘(我用的是1G的卡),如下所示:
打开U盘,可看到里面的文件。还可以进行新建、复制、粘贴、格式化等操作。与操作我们的普通U盘没什么区别。