-第 2 页-
0、 友情提示
《零死角玩转 STM32》系列教程由初级篇、中级篇、高级篇、系统篇、
四个部分组成,根据野火 STM32 开发板旧版教程升级而来,且经过重新深入编
写,重新排版,更适合初学者,步步为营,从入门到精通,从裸奔到系统,让
您零死角玩转 STM32。M3 的世界,与野火同行,乐意惬无边。
另外,野火团队历时一年精心打造的《STM32 库开发实战指南》将于今
年 10 月份由机械工业出版社出版,该书的排版更适于纸质书本阅读以及更有利
于查阅资料。内容上会给你带来更多的惊喜。是一本学习 STM32 必备的工具
书。敬请期待!
-第 3 页-
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
-第 4 页-
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:该文件中包含系统配置的函数。
-第 5 页-
野火 STM32 开发板中 USB 硬件原理图
6.2 USB 简介
USB 模块为 PC 主机和微控制器所实现的功能之间提供了符合 USB
的
通信连接。PC 主机和微控制器之间的数据传输是通过共享一专用的数据缓冲区
来完成的,该数据缓冲区能被 USB 外设直接访问。这块专用数据缓冲区的大小
由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可
使用 512 字节缓冲区,最多可用于 16 个单向或 8 个双向端点。
USB 模块同 PC 主机通信,根据 USB 规范实现令牌分组的
,数据发送/
接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括 CRC
的生成和校验。每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地
址、大小和需要传输的字节数。当 USB 模块识别出一个有效的功能/端点的令
牌分组时,(如果需要传输数据并且端点已配置)随之发生相关的数据传输。
USB 模块通过一个内部的 16 位寄存器实现端口与专用缓冲区的数据交换。
在所有的数据传输完成后,如果需要,则根据传输的方向,发送或接收适当的
握手分组。在数据传输结束时,USB 模块将触发与端点相关的中断,通过读状
态寄存器和/或者利用不同的中断处理程序,微控制器可以确定:
● 哪个端点需要得到服务
● 产生如位填充、格式、CRC、协议、缺失 ACK、缓冲区溢出/缓冲区未
满等错误时,正在进行的是哪种类型的传输。
-第 6 页-
有关更多 USB 的介绍请参考《STM32 参考手册中文》。USB 是一个很复
杂的设备,要想全面的学习 USB,光靠做完这个实验和看《STM32 参考手册中
文》是远远不够的,还要大量阅读 ST 的官方 USB 文档。还有网上有本关于
USB 方面的书《圈圈教你学 USB》很不错,大家可以去买来研究研究。总之,
一句话,USB 耐学,^_^。
6.3 友情提示
USB 是一个比较复杂的
,单单 USB 就可以出一本书,在这里野火不
可能为大家讲解地非常详细,但这个文档可以为大家扫清代码阅读的障碍。要
想更深入的学习 USB,仍需大家的努力,网上有本叫《圈圈教你学
USB》的书就将得非常好,有兴趣的朋友可以买来看看,虽然有电子版,但大
伙还是买本书支持下圈圈,毕竟圈圈写书也不容易呀^_^……
6.4 实验讲解
在配置好我们需要用到的库文件之后,我们就从 main 函数开始分析,关
于如何添加库文件请参考前面的教程,这里不再详述。
1. /*
2. * 函数名:main
3. * 描述 :无
4. * 输入 :无
5. * 输出 :无
6. */
7. int main(void)
8. {
9. /* 配置系统时钟为 72M */
10. SystemInit();
11.
12. /* 串口 1 配置 15200-8-N-1 */
13. USART1_Config();
14.
15. /* SD 卡中断配置,优先级最高 */
16. NVIC_Configuration();
17.
18. /* 等待 SD 卡底层初始化成功 */
19. while ( SD_USER_Init() != SD_OK );
20.
21.
22. /* 获取 SD 卡容量信息,并在串口打印出来 */
23. Get_Medium_Characteristics();
24.
25. /* 配置 USB 时钟为 48M */
26. Set_USBClock();
27.
28. /* 配置 USB 中断 */
29. USB_Interrupts_Config();
-第 7 页-
30.
31. /* 配置 USB D+ 线为全速模式 */
32. USB_Cable_Config (ENABLE);
33.
34. /* USB 系统初始化 */
35. USB_Init();
36.
37. /* 等待 USB 中断到来,在中断函数中进行数据的传输 */
38. while (1)
39. {
40. }
41. }
系统库函数 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 中实现。
1. /*
2. * 函数名:Set_USBClock
3. * 描述 :配置 USB 时钟(48M)
4. * 输入 :无
5. * 输出 :无
-第 8 页-
6. */
7. void Set_USBClock(void)
8. {
9. /* USBCLK = PLLCLK */
10. RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);
11.
12. /* Enable USB clock */
13. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);
14. }
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 中实现:
1. /*
2. * 函数名:USB_Cable_Config
3. * 描述 :Software Connection/Disconnection of USB Cable
4. * 输入 :-NewState: new state
5. * 输出 :无
6. */
7. void USB_Cable_Config (FunctionalState NewState)
8. {
9. GPIO_InitTypeDef GPIO_InitStructure;
10. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
11.
12. /* PE3 输出 0 时 D+ 接上拉电阻工作于全速模式 */
13. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
14. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
15. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; /* 开漏输出 */
16. GPIO_Init(GPIOE, &GPIO_InitStructure);
17.
18. if (NewState != DISABLE)
19. {
20. GPIO_ResetBits(GPIOE, GPIO_Pin_3); /* USB 全速模式 */
21. }
22. else
23. {
24. GPIO_SetBits(GPIOE, GPIO_Pin_3); /* 普通模式 */
25. }
26. }
在硬件设计上我们通过 I/O 控制一个三极管的通断来决定 D+这跟数据线是
否上拉:
-第 9 页-
USB_Init();用于系统初始化,USB_Init();在 usb_init.c 中实现:
1. /********************************************************
2. * Function Name : USB_Init
3. * Description : USB system initialization
4. * Input : None.
5. * Output : None.
6. * Return : None.
7. *********************************************************/
8. void USB_Init(void)
9. {
10. pInformation = &Device_Info;
11. pInformation->ControlState = 2;
12. pProperty = &Device_Property;
13. pUser_Standard_Requests = &User_Standard_Requests;
14. /* Initialize devices one by one */
15. pProperty->Init();
16. }
如果我们在调试程序时,不成功的话,一般都是这个函数出了问题,而一
般的问题又会是硬件的问题,我当初调试的时候就郁闷了很久。
最后让程序在一个 while (1) { } 无限循环总等待 USB 中断的到来,然后进
行中断服务程序的处理。USB 中断函数 void USB_Istr(void)在 usb_istr.c 中实现:
1. /**********************************************************
2. * Function Name : USB_Istr
3. * Description : ISTR events interrupt service routine
4. * Input : None.
5. * Output : None.
6. * Return : None.
7. **********************************************************/
8. void USB_Istr(void)
-第 10 页-
9. {
10. wIstr = _GetISTR();
11.
12. #if (IMR_MSK & ISTR_RESET)
13. if (wIstr & ISTR_RESET & wInterrupt_Mask)
14. {
15. _SetISTR((u16)CLR_RESET);
16. Device_Property.Reset();
17. #ifdef RESET_CALLBACK
18. RESET_Callback();
19. #endif
20. }
21. #endif
22. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
23. #if (IMR_MSK & ISTR_DOVR)
24. if (wIstr & ISTR_DOVR & wInterrupt_Mask)
25. {
26. _SetISTR((u16)CLR_DOVR);
27. #ifdef DOVR_CALLBACK
28. DOVR_Callback();
29. #endif
30. }
31. #endif
32. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
33. #if (IMR_MSK & ISTR_ERR)
34. if (wIstr & ISTR_ERR & wInterrupt_Mask)
35. {
36. _SetISTR((u16)CLR_ERR);
37. #ifdef ERR_CALLBACK
38. ERR_Callback();
39. #endif
40. }
41. #endif
42. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
43. #if (IMR_MSK & ISTR_WKUP)
44. if (wIstr & ISTR_WKUP & wInterrupt_Mask)
45. {
46. _SetISTR((u16)CLR_WKUP);
47. Resume(RESUME_EXTERNAL);
48. #ifdef WKUP_CALLBACK
49. WKUP_Callback();
50. #endif
51. }
52. #endif
53. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
54. #if (IMR_MSK & ISTR_SUSP)
55. if (wIstr & ISTR_SUSP & wInterrupt_Mask)
56. {
57.
58. /* check if SUSPEND is possible */
59. if (fSuspendEnabled)
60. {
61. Suspend();
62. }
63. else
64. {
65. /* if not possible then resume after xx ms */
66. Resume(RESUME_LATER);
67. }
68. /* clear of the ISTR bit must be done after setting of CNTR_FSUSP */
69. _SetISTR((u16)CLR_SUSP);
70. #ifdef SUSP_CALLBACK
71. SUSP_Callback();
72. #endif
73. }
74. #endif
75. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
76. #if (IMR_MSK & ISTR_SOF)
77. if (wIstr & ISTR_SOF & wInterrupt_Mask)
78. {
79. _SetISTR((u16)CLR_SOF);
80. bIntPackSOF++;
81.
82. #ifdef SOF_CALLBACK
83. SOF_Callback();
84. #endif
85. }
-第 11 页-
86. #endif
87. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*/
88. #if (IMR_MSK & ISTR_ESOF)
89. if (wIstr & ISTR_ESOF & wInterrupt_Mask)
90. {
91. _SetISTR((u16)CLR_ESOF);
92. /* resume handling timing is made with ESOFs */
93. Resume(RESUME_ESOF); /* request without change of the machine state */
94.
95. #ifdef ESOF_CALLBACK
96. ESOF_Callback();
97. #endif
98. }
99. #endif
100. /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
*/
101. #if (IMR_MSK & ISTR_CTR)
102. if (wIstr & ISTR_CTR & wInterrupt_Mask)
103. {
104. /* servicing of the endpoint correct transfer interrupt */
105. /* clear of the CTR flag into the sub */
106. CTR_LP();
107. #ifdef CTR_CALLBACK
108. CTR_Callback();
109. #endif
110. }
111. #endif
112. } /* USB_Istr */
实验到了这里,我们已经可以通过 USB 来操作我们开发板上的 MicroSD 卡
了。有关更多实验的细节请大家阅读源码。
6.5 实验现象
将野火 STM32 开发板供电(DC5V),插上 JLINK,插上 MicroSD 卡,插上方
口的 USB 线,将编译好的程序下载到开发板,如果程序运行成功,则可在电脑
上看到开发板上的 U 盘(我用的是 1G 的卡),如下所示:
-第 12 页-
打开 U 盘,可看到里面的文件。还可以进行新建、复制、粘贴、格式化等
操作。与操作我们的普通 U 盘没什么区别。
-第 13 页-
友情提示
6、UsbDevice(模拟U盘)
6.1 实验描述及工程文件清单
6.2 USB简介
6.3友情提示
6.4实验讲解
6.5实验现象