STM32 USB应用笔记
STM32 USB应用笔记
USB
作者: |
gashero |
日期: |
2013-02-06 |
目录
- 1 简介
- 2 STM32的USB简介
- 3 USB实现类
- 3.1 USB-CDC
- 4 代码分析
- 4.1 stm32f4-discovery-usb-cdc-example分析
- 4.1.1 main.c
- 4.1.2 usbd_desc.h
- 4.1.3 usbd_desc.c
- 4.1.4 stm32f4xx_it.c
- 4.1.5 usb_bsp.c
- 4.1.6 usb_conf.h
- 4.1.7 usbd_conf.h
- 4.1.8 usbd_usr.c
- 4.1.9 usbd_cdc.h
- 4.1.10 usbd_cdc.c
- 4.1.11 项目文件
- 4.2 stsw_stm32081.zip中STM32_USB-FS-Device_Lib_V4.0.0的USB库
- 4.2.1 usb_def.h
- 4.2.2 usb_type.h
- 4.2.3 usb_lib.h
- 4.2.4 usb_core.h/.c
- 4.2.5 usb_init.h/.c
- 4.2.6 usb_int.h/.c
- 4.2.7 usb_mem.h/.c
- 4.2.8 usb_regs.h/.c
- 4.2.9 usb_sil.h/.c
- 4.3 stsw-stm32081.zip中STM32_USB-FS-Device_Lib_V4.0.0的VirtualComport_Loopback
- 4.3.1 main.c
- 4.3.2 platform_config.h
- 4.3.3 hw_config.h/.c
- 4.3.4 stm32_it.h/.c
- 4.3.5 usb_conf.h
- 4.3.6 usb_desc.h/.c
- 4.3.7 usb_istr.h/.c
- 4.3.8 usb_prop.h/.c
- 4.3.9 usb_pwr.h/.c
- 4.3.10 usb_endp.c
- 5 应用注记
- 5.1 使用CDC类与上位机的通信
- 5.1.1 尝试CTR中断的通信
- 5.2 使用USB-FS-Device的VirtualComPort_Loopback例子,作为与电脑的USB串口通信
- 5.3 休眠处理
1 简介
一种还算凑合的通信方式。
参考文献:
- [r] 基于STM32的USB程序开发笔记(序): http://bbs.ednchina.com/BLOG_ARTICLE_182000.HTM
- [r] 基于STM32的USB程序开发笔记(一): http://bbs.ednchina.com/BLOG_ARTICLE_182060.HTM
- [u] 基于STM32的USB程序开发笔记(二): http://bbs.ednchina.com/BLOG_ARTICLE_182085.HTM 直接罗列代码
- [u] 基于STM32的USB程序开发笔记(三): http://bbs.ednchina.com/BLOG_ARTICLE_182520.HTM 还是代码
- [u] 基于STM32的USB程序开发笔记(四): http://bbs.ednchina.com/BLOG_ARTICLE_182913.HTM 设备枚举上
- [u] 基于STM32的USB程序开发笔记(五): http://bbs.ednchina.com/BLOG_ARTICLE_183349.HTM 设备枚举下
- [u] 基于STM32的USB程序开发笔记(六): http://bbs.ednchina.com/BLOG_ARTICLE_183470.HTM XP下USB驱动开发
- [u] 基于STM32的USB程序开发笔记(七): http://bbs.ednchina.com/BLOG_ARTICLE_183523.HTM XP下USB驱动开发
- [i] 基于STM32的USB程序开发笔记.pdf: 2502410字节,就是如上一系列文章整理的
- [u] STM32的USB例程修改步骤: http://blog.chinaunix.net/uid-605899-id-3125746.html
- [u] 开始学习USB-从STM32的USB-DEMO开始: http://blog.21ic.com/user1/4852/archives/2008/48074.html
- [u] stm32 usb学习: http://wenku.baidu.com/view/2f405e105f0e7cd18425365d
- [r] 发一个stm32f4的usb虚拟串口程序: http://www.amobbs.com/thread-5514289-1-1.html 一个1.13MB的源码下载,stm32f4 discovery
- [u] [CoIDE]_USB_CDC_Example.rar: https://www.dropbox.com/s/tix34ol8k02i88r/%5BCoIDE%5D_USB_CDC_Example.rar 12.19MB没下载
- [u] 嵌入式系统的USB虚拟串口设计: http://www.autooo.net/utf8-classid124-id44408.html
- [r] USB-Serial on STM32F4: http://vedder.se/2012/07/usb-serial-on-stm32f4/ 很初级的介绍
- [u] STM32F4-Discovery: Help transfering data to PC: https://my.st.com/public/STe2ecommunities/mcu/Lists/STM32F4DISCOVERY/DispForm.aspx?ID=351 一些源码例子的下载地址
- [u] ericherman/stm32f-discovery-example: https://github.com/ericherman/stm32f4-discovery-example 使用USB-CDC的例子
- [u] serialUSB: https://www.das-labor.org/trac/browser/microcontroller/src-stm32f4xx/serialUSB
- [u] STM32F4 USB CDC connection: http://www.coocox.org/forum/topic.php?id=1757&page=2
- [u] STM32实现USB Video Class开始干活了: http://www.amobbs.com/thread-5262477-1-1.html
2 STM32的USB简介
基本资料是STM32的参考手册、USB2.0规范、USB外设库。
设备(device)只是被动触发的,主机(host)掌握主动权,包括发送什么数据,什么时候发送,读还是写。设备只是配合完成设备枚举、数据方向和大小,之类的。
两个中断向量:
/* 处理USB高优先级或CAN TX中断 */
void USB_HP_CAN_TX_IRQHandler(void) {
USB_HPI();
}
/* 处理USB低优先级或CAN RX0中断 */
void USB_LP_CAN_RX0_IRQHandler(void) {
USB_LPI();
}
USB_HPI() 和 USB_LPI() 即转向 usb_core.h/.c 进行处理。中断传输、控制传输、批量传输(bulk)由 USB_LPI() 响应,批量传输也可以由 USB_HPI() 响应,同步传输只由 USB_HPI() 处理。
这样只需要关注 usb_core.c 的 USB_LPI() 和 USB_HPI() 了。
USB_LPI() 函数的定义: @page 7-10
3 USB实现类
3.1 USB-CDC
CDC协议是通用的USB实现,在很多操作系统都不需要驱动就支持。所以有人实现了基于USB-CDC的串口,倒是个好思路。
例子代码的下载: http://dl.dropbox.com/u/56124886/stm32f4-discovery/stm32f4-discovery-usb-cdc-example.zip
貌似我也应该使用BSP了,方便些。
官方库的 STM32_USB-Host-Device_Lib_V2.1.0/Libraries/STM32_USB_Device_Library/Class/cdc 提供了CDC的支持了。
4 代码分析
4.1 stm32f4-discovery-usb-cdc-example分析
系统文件列表:
- startup_stm32f4xx.s:与我用的基本相同,除了注释
- stm32f4_discovery.c:有些不同,详见下面
- stm32f4_discovery.h:有些不同,详见下面
- stm32f4xx_conf.h:与我用的基本相同,除了注释
- stm32f4xx_it.c:对方加了好多东西,要看
- stm32f4xx_it.h:只有注释不同
- system_stm32f4xx.c:版本有所不同,但用我的更靠谱些
对于stm32f4_discovery.c/.h文件,他们在该项目里,但不在 stm32f4_dsp_stdperiph_lib.zip 。存在于 stm32f4discovery_fw.zip 中。不过函数名有些差别。例如该项目的 STM32F4_Discovery_LEDInit() 对应 stm32f4discovery_fw.zip 中的 STM_EVAL_LEDInit() 。以下的区别也是如此。
USB支持文件:
- usb_bsp.c:是由USB支持库提供的,版本不同,差异很大
- usb_conf.h:与系统自带的差异很大,要看
- usbd_cdc.c:官方库没有找到对应内容,要看
- usbd_cdc.h:官方库没有找到对应内容,要看
- usbd_conf.h:与系统自带的差异很大,要看
- usbd_desc.c:配置文件,要看
- usbd_desc.h:配置文件,要看
- usbd_usr.c:与系统自带的差异很大,要看
用户文件列表:
- main.c
4.1.1 main.c
需导入诸多头文件:
- stm32f4xx.h
- usbd_cdc_core.h
- usbd_cdc.h
- usbd_usr.h
- usbd_desc.h
初始化一堆LED,使用了BSP:
STM32F4_Discovery_LEDInit(LED3);
STM32F4_Discovery_LEDInit(LED4);
STM32F4_Discovery_LEDInit(LED5);
STM32F4_Discovery_LEDInit(LED6);
STM32F4_Discovery_PBInit(BUTTON_USER,BUTTON_MODE_EXTI);
STM32F4_Discovery_LEDOn(LED3);
Delay(0xffff);
USB的初始化,有可能的话,尽量使用OTG_HS(480Mbps):
//外部声明
__ALIGN_BEGIN USB_OTG_CORE_HANDLE USB_OTG_dev __ALIGN_END;
//main()函数中
USBD_Init(&USB_OTG_dev,
#ifdef USB_USB_OTG_HS
USB_OTG_HS_CORE_ID,
#else
USB_OTG_FS_CORE_ID,
#endif
&USR_desc,
&USBD_CDC_cb,
&USR_cb);
注释说,魔术发生在 usbd_cdc.c 文件,其他应该看的还有 usbd_desc.h 。
最后就是每0x100000个周期让灯闪耀一次。
这里调用的 USBD_Init() 函数,定义于 Libraries/STM32_USB_Device_Library/Core/src/usbd_core.c 。
4.1.2 usbd_desc.h
实际上是几个常量定义,加一堆函数声明,实际配置内容并不在这里。常量:
#define USB_DEVICE_DESCRIPTOR_TYPE 0x01
#define USB_CONFIGURATION_DESCRIPTOR_TYPE 0x02
#define USB_STRING_DESCRIPTOR_TYPE 0x03
#define USB_INTERFACE_DESCRIPTOR_TYPE 0x04
#define USB_ENDPOINT_DESCRIPTOR_TYPE 0x05
#define USB_SIZ_DEVICE_DESC 18
#define USB_SIZ_STRING_LANGID 4
4.1.3 usbd_desc.c
317行。常量定义如下:
#define USBD_VID 0x304
#define USBD_PID 0xe457
#define USBD_LANGID_STRING 0x40b
#define USBD_MANUFACTURER_STRING "Roope Kokkoniemi"
#define USBD_PRODUCT_HS_STRING "stm32f4-discovery-usb-cdc-example"
#define USBD_SERIALNUMBER_HS_STRING "00000000050B"
#define USBD_PRODUCT_FS_STRING "stm32f4-discovery-usb-cdc-example"
#define USBD_SERIALNUMBER_FS_STRING "00000000050C"
#define USBD_CONFIGURATION_HS_STRING "usb-cdc-example config"
#define USBD_INTERFACE_HS_STRING "usb-cdc-example Interface"
#define USBD_CONFIGURATION_FS_STRING "usb-cdc-example config"
#define USBD_INTERFACE_FS_STRING "usb-cdc-example Interface"
由此可见实际的VID、PID,以及定义的各种字符串。
89行定义结构体变量 USR_desc
USBD_Device USR_desc= {
USBD_USR_DeviceDescriptor,
USBD_USR_LangIDStrDescriptor,
USBD_USR_ManufacturerStrDescriptor,
USBD_USR_ProductStrDescriptor,
USBD_USR_SerialStrDescriptor,
USBD_USR_ConfigStrDescriptor,
USBD_USR_InterfaceStrDescriptor,
};
具体函数定义都在下面呢。
107行的结构体 USBD_DeviceDesc 定义了USB设备的详细信息。
135行的结构体 USBD_DeviceQualifierDesc 似乎也是定义USB设备的,但是参数来源未知。10个成员。
155行的结构体 USBD_LangIDDesc 是以字符串描述的设备信息。
一系列不长,甚至仅仅用于返回字符串的函数:
- USBD_USR_DeviceDescriptor()
- USBD_USR_LangIDStrDescriptor()
- USBD_USR_ProductStrDescriptor()
- USBD_USR_ManufacturerStrDescriptor()
- USBD_USR_SerialStrDescriptor()
- USBD_USR_ConfigStrDescriptor()
- USBD_USR_InterfaceStrDescriptor()
4.1.4 stm32f4xx_it.c
中断处理的,大部分还是空的,前头有些外部变量定义:
extern USB_OTG_CORE_HANDLE USB_OTG_dev;
extern uint32_t USBD_OTG_ISR_Handler (USB_OTG_CORE_HANDLE *pdev);
extern void DISCOVERY_EXTI_IRQHandler(void);
#ifdef USB_OTG_HS_DEDICATED_EP1_ENABLED
extern uint32_t USBD_OTG_EP1IN_ISR_Handler (USB_OTG_CORE_HANDLE *pdev);
extern uint32_t USBD_OTG_EP1OUT_ISR_Handler (USB_OTG_CORE_HANDLE *pdev);
#endif
OTG_FS_WKUP_IRQHandler() 中断处理函数:
#ifdef USE_USB_OTG_FS
void OTG_FS_WKUP_IRQHandler(void)
{
if(USB_OTG_dev.cfg.low_power)
{
*(uint32_t *)(0xE000ED10) &= 0xFFFFFFF9 ;
SystemInit();
USB_OTG_UngateClock(&USB_OTG_dev);
}
EXTI_ClearITPendingBit(EXTI_Line18);
}
#endif
OTG_HS_WKUP_IRQHandler() 中断处理函数:
#ifdef USE_USB_OTG_HS
void OTG_HS_WKUP_IRQHandler(void)
{
if(USB_OTG_dev.cfg.low_power)
{
*(uint32_t *)(0xE000ED10) &= 0xFFFFFFF9 ;
SystemInit();
USB_OTG_UngateClock(&USB_OTG_dev);
}
EXTI_ClearITPendingBit(EXTI_Line20);
}
#endif
然后是将一些中断处理函数映射出去:
#ifdef USE_USB_OTG_HS
void OTG_HS_IRQHandler(void)
#else
void OTG_FS_IRQHandler(void)
#endif
{
USBD_OTG_ISR_Handler (&USB_OTG_dev);
}
#ifdef USB_OTG_HS_DEDICATED_EP1_ENABLED
void OTG_HS_EP1_IN_IRQHandler(void)
{
USBD_OTG_EP1IN_ISR_Handler (&USB_OTG_dev);
}
void OTG_HS_EP1_OUT_IRQHandler(void)
{
USBD_OTG_EP1OUT_ISR_Handler (&USB_OTG_dev);
}
#endif
按钮的事件处理:
void EXTI0_IRQHandler(void) {
DISCOVERY_EXTI_IRQHandler();
/* Clear the EXTI line pending bit */
EXTI_ClearITPendingBit(USER_BUTTON_EXTI_LINE);
}
可见基本上就是做一下初始化,然后把实际的中断处理都交给外面去做了。
函数 USBD_OTG_ISR_Handler() 、 USBD_OTG_EP1IN_ISR_Handler() 、 USBD_OTG_EP1OUT_ISR_Handler() 定义于 usb_dcd_int.c 文件。
4.1.5 usb_bsp.c
382行。主要就两个函数:
- USB_OTG_BSP_Init() :从90行开始,根据几种开发板,和HS/FS设置相关引脚的功能
- USB_OTG_BSP_EnableInterrupt() :从309行开始,配置各种中断
宏 USE_USB_OTG_FS 用于定义STM32F4探索套件以OTG_FS运行。下面只看对STM32F4 Discovery的初始化。
USB_OTG_BSP_Init() 的内容:
- 将PA8、PA9、PA11、PA12定义为100MHz,无上拉下拉,GPIO_AF_OTG_FS
- 将PA10定义为100MHz、上拉,GPIO_OType_OD、GPIO_AF_OTG_FS
-
启用外设时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE); - RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS,ENABLE);
- USB_OTG_HS的初始化从132行开始到243行,用到的线更多,先不看了
- 250行,启用PWR时钟 RCC_APB1PeriphResetCmd(RCC_APB1Periph_PWR,ENABLE);
- 最后用了很多行分别配置FS和HS模式的USER_BUTTON的功能,貌似用来唤醒USB的
USB_OTG_BSP_EnableInterrupt() 的内容,就是配置各种NVIC,优先级什么的,一共就29行。
4.1.6 usb_conf.h
271行。定义一些宏,以及各种USB RAM FIFO的大小。
定义的宏,先假设我们定义了宏 USE_USB_OTG_FS :
- USB_OTG_FS_CORE
- RX_FIFO_FS_SIZE=128
- TX0_FIFO_FS_SIZE=32
- TX1_FIFO_FS_SIZE=128
- TX2_FIFO_FS_SIZE=32
- TX3_FIFO_FS_SIZE=0
- USB_DEVICE_MODE
- __ALIGN_BEGIN :对应的是空,但不为空时对应特定编译器的 __align(4) 用于对齐
- __ALIGN_END :对应的是空
- __packed __attribute__ ((__packed__)) :对GCC的宏
4.1.7 usbd_conf.h
98行。用于USB-CDC的一些设置。常量定义如下,也是假设定义了 USE_USB_OTG_FS :
- USBD_CFG_MAX_NUM=1
- USBD_ITF_MAX_NUM=1
- USB_MAX_STR_DESC_SIZ=50
- CDC_IN_EP=0x81 :EP1用于data IN
- CDC_OUT_EP=0x01 :EP1用于data OUT
- CDC_CMD_EP=0x82 :EP2用于CDC命令
- CDC_DATA_MAX_PACKET_SIZE=64 :输入输出包最大大小
- CDC_CMD_PACKET_SZE=8 :控制端点包大小
- CDC_IN_FRAME_INTERVAL=5 :两个IN传输间最大帧数量
- APP_RX_DATA_SIZE=2048 :IN缓冲的总大小
- APP_FOPS=cdc_fops
4.1.8 usbd_usr.c
189行。先导入几个头文件:
- usbd_usr.h
- usbd_ioreq.h
- stm32f4_discovery.h
定义结构体变量:
USBD_Usr_cb_TypeDef USR_cb= {
USBD_USR_Init,
USBD_USR_DeviceReset,
USBD_USR_DeviceConfigured,
USBD_USR_DeviceSuspended,
USBD_USR_DeviceResumed,
};
这里引用的5个函数都在下面定义的,但是实际都是操作LED5(红色)的:
- USBD_USR_Init() :初始化LED5
- USBD_USR_DeviceReset() :无内容,会传入 uint8_t speed 可选输出日志
- USBD_USR_DeviceConfigured() :点亮LED5
- USBD_USR_DeviceSuspended() :熄灭LED5
- USBD_USR_DeviceResume() :点亮LED5
4.1.9 usbd_cdc.h
41行。就定义了两个宏:
- DEFAULT_CONFIG=0
- OTHER_CONFIG=1
4.1.10 usbd_cdc.c
218行。具体的USB-CDC实现。
37-40行定义了收发缓冲区:
extern uint8_t APP_Rx_Buffer[];
extern uint32_t APP_Rx_ptr_in;
53-60行定义结构体变量:
CDC_IF_Prop_TypeDef cdc_fops= {
cdc_Init,
cdc_DeInit,
cdc_Ctrl,
cdc_DataTx,
cdc_DataRx,
};
这里定义的5个函数中 cdc_Init() 和 cdc_DeInit() 很简单,就是直接返回 USBD_OK 即可。
cdc_Ctrl() 是根据输入命令Cmd来用switch做处理的,但是虽然列出了所有命令,但是没有做任何处理,最后直接返回了 USBD_OK 。
cdc_DataTx() 用于通过IN端点发送数据,实际内部就是把参数的缓冲区内容复制到 APP_Rx_Buffer 中。最后返回 USBD_OK 。
cdc_DataRx() 用于通过OUT端点接收数据,本例实际就是将接到的数据回发回去而已。该函数会阻塞其他OUT包接收,直到退出该函数。如果在CDC接口完成前退出,会收到更多数据,而之前的却不会发出。
cdc_DataRx() 将参数指定的缓冲区写入内容,当遇到a/A时点亮LED6,当遇到s/S时熄灭LED6,再通过 cdc_DataTx() 函数将内容发出,最后返回 USBD_OK 。
DISCOVERY_EXTI_IRQHandler() 中断处理函数,用于指定缓冲区内容为 "terve" ,然后发送出去。
4.1.11 项目文件
文件名叫 stm32f4-discovery-usb-cdc-example.elf.launch 。
其中多处提到 atollic.hardwaredebug ,不知道是什么IDE的。而且也没有提到哪些文件应该一起编译进去,看来又要我自己想办法了。
4.2 stsw_stm32081.zip中STM32_USB-FS-Device_Lib_V4.0.0的USB库
4.2.1 usb_def.h
一些枚举:
typedef enum _RECIPIENT_TYPE {
DEVICE_RECIPIENT,
INTERFACE_RECIPIENT,
ENDPOINT_RECIPIENT,
OTHER_RECIPIENT,
} RECIPIENT_TYPE;
typedef enum _STANDARD_REQUESTS {
GET_STATUS=0,
CLEAR_FEATURE,
RESERVED1,
SET_FEATURE,
RESERVED2,
SET_ADDRESS,
GET_DESCRIPTOR,
SET_DESCRIPTOR,
GET_CONFIRURATION,
SET_CONFIRURATION,
GET_INTERFACE,
SET_INTERFACE,
TOTAL_sREQUEST,
SYNCH_FRAME=12
} STAND_REQUESTS; //by gashero
typedef enum _DESCRIPTOR_TYPE {
DEVICE_DESCRIPTOR=1,
CONFIG_DESCRIPTOR,
STRING_DESCRIPTOR,
INTERFACE_DESCRIPTOR,
ENDPOINT_DESCRIPTOR,
} DESCRIPTOR_TYPE;
typedef enum _FEATURE_SELECTOR {
ENDPOINT_STALL,
DEVICE_REMOTE_WAKEUP,
} FEATURE_SELECTOR;
一些常量定义:
#define REQUEST_TYPE 0x60
#define STANDARD_REQUEST 0x00
#define CLASS_REQUEST 0x20
#define VENDOR_REQUEST 0x40
#define RECIPIENT 0x1f
4.2.2 usb_type.h
内容特别短,如下:
#include "usb_conf.h"
#ifndef NULL
#define NULL ((void*)0)
#endif
typedef enum {
FALSE=0, TRUE=!FALSE
} bool;
4.2.3 usb_lib.h
只是导入了一堆其他头文件:
#include "hw_config.h"
#include "usb_type.h"
#include "usb_regs.h"
#include "usb_def.h"
#include "usb_core.h"
#include "usb_init.h"
#include "usb_sil.h"
#include "usb_mem.h"
#include "usb_int.h"
4.2.4 usb_core.h/.c
usb_core.h
定义了一些数据结构:
- _CONTROL_STATE 枚举
- OneDescriptor 结构体
- _RESULT 枚举
- _ENDPOINT_INFO 结构体
- _DEVICE 结构体
- _DEVICE_INFO 结构体
- _DEVICE_PROP 结构体
- _USER_STANDARD_REQUEST 结构体
一堆导出函数就不写了,在 usb_core.c 里写。
一些从外部导入的变量:
- DEVICE_PROP Device_Property
- USER_STANDARD_REQUESTS User_Standard_Requests
- DEVICE Device_Table
- DEVICE_INFO Device_Info
- uint16_t SaveRState
- uint16_t SaveTState
usb_core.c
@wait
4.2.5 usb_init.h/.c
@wait
4.2.6 usb_int.h/.c
@wait
4.2.7 usb_mem.h/.c
@wait
4.2.8 usb_regs.h/.c
都很长。
usb_regs.h
定义的数据结构:
- _EP_DBUF_DIR 枚举
- EP_BUF_NUM 枚举
一些地址:
- RegBase:USB外设基址
- PMAAddr:Packet Memory Area基址
- CNTR:寄存器
- ISTR:寄存器
- FNR:寄存器
- DADDR:寄存器
- BTABLE:寄存器
- EP<N>_OUT:端点寄存器
- EP<N>_IN:端点寄存器
- ENDP<N>:端点枚举值
一些成段的声明:
- ISTR中断事件:105-125行
- CNTR寄存器位:130-144行
- FNR寄存器位:149-153行
- DADDR寄存器位:157-158行
- 端点寄存器:163-205行
@wait 看到206行的导出宏,太大了
4.2.9 usb_sil.h/.c
@wait
4.3 stsw-stm32081.zip中STM32_USB-FS-Device_Lib_V4.0.0的VirtualComport_Loopback
分析的是模板,而不是我改出来的。(by gashero)
要改进通信速度,应该从两个方面,一个是usb_endp.c中每秒发送次数,一个是使用CTR中断。
4.3.1 main.c
导入头文件:
#include "hw_config.h"
#include "usb_lib.h"
#include "usb_desc.h"
#include "usb_pwr.h"
一些全局需要使用的变量,从外部导入:
extern __IO uint8_t Receive_Buffer[64];
extern __IO uint32_t Receive_length;
extern __IO uint32_t length;
uint8_t Send_Buffer[64];
uint32_t packet_sent=1;
uint32_t packet_receive=1;
主函数,初始化USB相关的东西,以及按照收到的数据来转发:
int main() {
Set_System();
Set_USBClock();
USB_Interrupts_Config();
USB_Init();
while(1) {
if (bDeviceState==CONFIGURED) {
CDC_Receive_DATA();
if(Receive_length!=0) {
if(packet_sent==1) {
CDC_Send_DATA((unsigned char*)Receive_Buffer,Receive_length);
}
Receive_length=0;
}
}
}
}
标准的断言处理:
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line) {
while(1);
}
#endif
4.3.2 platform_config.h
看来是平台相关配置。
Note
移植过程大量修改这里。
32行到72行,必须声明个开发板什么的,其实无所谓,都删除掉就是了。然后导入 "stm32f10x.h" 。
76行到94行,是声明3个ID,不知干啥用的,反正没改也过去了。
97行到148行,是声明D+上拉电阻控制引脚的。该引脚低电平有效,开启D+的上拉电阻。我是将其全部删掉,然后自己重新定义的:
#define USB_DISCONNECT GPIOB
#define USB_DISCONNECT_PIN GPIO_Pin_1
#define RCC_APB2Periph_GPIO_DISCONNECT RCC_APB2Periph_GPIOB
4.3.3 hw_config.h/.c
hw_config.h :
导入头文件:
#include "platform_config.h"
#include "usb_type.h"
几个导出常量,不太清楚:
#define MASS_MEMORY_START 0x04002000
#define BULK_MAX_PACKET_SIZE 0x00000040
#define LED_ON 0xf0
#define LED_OFF 0xff
然后就是声明10个函数,不写了。
hw_config.c :
导入头文件:
#include "stm32_it.h"
#include "usb_lib.h"
#include "usb_prop.h"
#include "usb_desc.h"
#include "hw_config.h"
#include "usb_pwr.h"
一堆似有变量声明:
ErrorStatus HSEStartUpStatus;
EXTI_InitTypeDef EXTI_InitStructure;
extern __IO uint32_t packet_sent;
extern __IO uint8_t Send_Buffer[VIRTUAL_COM_PORT_DATA_SIZE];
extern __IO uint32_t packet_receive;
extern __IO uint8_t Receive_length;
uint8_t Receive_Buffer[64];
uint32_t Send_length;
static void IntToUnicode(uint32_t value, uint8_t *pbuf, uint8_t len);
extern LINE_CODING linecoding;
各个函数的定义:
- Set_System() :63-141行,配置USB使用的GPIO,以及中断线
- Set_USBClock() :149-162行,开启USB时钟
- Enter_LowPowerMode() :170-174行,进入低功耗模式
- Leave_LowPowerMode() :182-198行,离开低功耗模式,需要重新初始化系统
- USB_Interrupts_Config() :206-254行,中断配置,包括低优先级,高优先级、唤醒
- USB_Cable_Config(NewState) :262-284行,控制D+上拉是否开启
- Get_SerialNum() :293-308行,创建一个序列号字符串描述符
- IntToUnicode(value,*pbuf,len) :317-336行,看来是数字到Unicode的转换,有必要么
- CDC_Send_DATA(*ptrBuffer,Send_length) :345-362行,发送数据,直接调用USB库的3个函数,成功返回1,失败返回0
- CDC_Receive_DATA() :371-377行,获取数据,返回1
4.3.4 stm32_it.h/.c
stm32_it.h 就是声明了一堆的中断处理函数,共11个。
stm32_it.c 定义了11个中断处理函数。
导入头文件:
#include "hw_config.h"
#include "stm32_it.h"
#include "usb_lib.h"
#include "usb_istr.h"
中断处理函数中,9个标准的:
- NMI_Handler() :空的
- HardFault_Handler() :死循环
- MemManage_Handler() :死循环
- BusFault_Handler() :死循环
- UsageFault_Handler() :死循环
- SVC_Handler() :空的
- DebugMon_Handler() :空的
- PendSV_Handler() :空的
- SysTick_Handler() :空的
然后就是两个USB相关的,以HD设备为例:
USB_LP_IRQHandler() :内部直接调用 USB_Istr() 。
USB_FS_WKUP_IRQHandler() :内部直接调用 EXTI_ClearITPendingBit(EXTI_Line18) 。
4.3.5 usb_conf.h
一些声明。
- EP_NUM (4) :使用的端点数量
- BTABLE_ADDRESS (0x00) :缓冲表基址
- ENDP<N>_RXADDR (0x40) :端点N的接收缓冲基址
- ENDP<N>_TXADDR (0x80) :端点N的发送缓冲基址
- IMR_MSK :事件屏蔽位
- EP<N>_IN_Callback :输入端点回调,全是空的
- EP<N>_OUT_Callback :输出端点回调,全是空的
实际的缓冲表部分:
#define BTABLE_ADDRESS (0x00)
#define ENDP0_RXADDR (0x40)
#define ENDP0_TXADDR (0x80)
#define ENDP1_TXADDR (0xc0)
#define ENDP2_TXADDR (0x100)
#define ENDP3_RXADDR (0x110)
IMR_MSK的声明:
#define IMR_MSK (CNTR_CTRM | CNTR_WKUPM | CNTR_SUSPM | CNTR_ERRM | \
CNTR_SOFM | CNTR_ESOFM | CNTR_RESETM)
4.3.6 usb_desc.h/.c
决定了设备显示的名字,和其他字符串描述。收发缓冲区大小也是在这里。
usb_desc.h 一些常量定义和函数声明:
#define USB_DEVICE_DESCRIPTOR_TYPE 0x01
#define USB_CONFIGURATION_DESCRIPTOR_TYPE 0x02
#define USB_STRING_DESCRIPTOR_TYPE 0x03
#define USB_INTERFACE_DESCRIPTOR_TYPE 0x04
#define USB_ENDPOINT_DESCRIPTOR_TYPE 0x05
#define VIRTUAL_COM_PORT_DATA_SIZE 64
#define VIRTUAL_COM_PORT_INT_SIZE 8
#define VIRTUAL_COM_PORT_SIZ_DEVICE_DESC 18
#define VIRTUAL_COM_PORT_SIZ_CONFIG_DESC 67
#define VIRTUAL_COM_PORT_SIZ_STRING_LANGID 4
#define VIRTUAL_COM_PORT_SIZ_STRING_VENDOR 38
#define VIRTUAL_COM_PORT_SIZ_STRING_PRODUCT 50
#define VIRTUAL_COM_PORT_SIZ_STRING_SERIAL 26
#define STANDARD_ENDPOINT_DESC_SIZE 0x09
usb_desc.c 一些结构体的定义。
导入头文件:
#include "usb_lib.h"
#include "usb_desc.h"
Virtual_Com_Port_DeviceDescriptor 数组,元素是uint8_t类型,18个元素。
Virtual_Com_Port_ConfigDescriptor 数组,元素是uint8_t类型,65个元素。
Virtual_Com_Port_StringLangID 数组,元素是uint8_t类型,4个元素。
Virtual_Com_Port_StringVendor 数组,元素是uint8_t类型,具体长度看VIRTUAL_COM_PORT_SIZ_STRING_VENDOR。
Virtual_Com_Port_StringProduct 数组,元素是uint8_t类型,具体长度看VIRTUAL_COM_PORT_SIZ_STRING_PRODUCT。
Virtual_Com_Port_StringSerial 数组,元素是uint8_t类型,具体长度看VIRTUAL_COM_PORT_SIZ_STRING_SERIAL。
4.3.7 usb_istr.h/.c
分发回调函数的声明和定义。
usb_istr.h 一些声明。
导入头文件:
#include "usb_conf.h"
导出函数 USB_Istr() 。直接声明的回调函数 EP<N>_IN_Callback 和 EP<N>_IN_Callback 其中N取1~7。
一些需要宏定义才声明的函数,对应宏的名字就是函数名的全大写:
- CTR_Callback()
- DOVR_Callback()
- ERR_Callback()
- WKUP_Callback()
- SUSP_Callback()
- RESET_Callback()
- SOF_Callback()
- ESOF_Callback()
usb_istr.c 各类回调函数的定义。
导入头文件:
#include "usb_lib.h"
#include "usb_prop.h"
#include "usb_pwr.h"
#include "usb_istr.h"
似有变量声明:
__IO uint16_t wIstr;
__IO uint8_t bIntPackSOF=0; //by gashero
__IO uint32_t esof_counter=0;
__IO uint32_t wCNTR=0;
非控制端点的函数指针 pEpInt_IN 和 pEpInt_OUT 。
USB_Istr() 从77-229行。包括按照各种标识调用各个其他回调函数,相当于一个分派器。从现在看还是针对USB的,而不是虚拟串口的。
4.3.8 usb_prop.h/.c
usb_prop.h 常量定义和14个函数声明。
一个结构体的定义:
typedef struct {
uint32_t bitrate;
uint8_t format;
uint8_t paritytype;
uint8_t datatype;
} LINE_CODING;
常量定义:
#define Virtual_Com_Port_GetConfiguration NOP_Process
//#define Virtual_Com_Port_SetConfiguration NOP_Process
#define Virtual_Com_Port_GetInterface NOP_Process
#define Virtual_Com_Port_SetInterface NOP_Process
#define Virtual_Com_Port_GetStatus NOP_Process
#define Virtual_Com_Port_ClearFeature NOP_Process
#define Virtual_Com_Port_SetEndPointFeature NOP_Process
#define Virtual_Com_Port_SetDeviceFeature NOP_Process
//#define Virtual_Com_Port_SetDeviceAddress NOP_Process
#define SEND_ENCAPSULATED_COMMAND 0x00
#define GET_ENCAPSULATED_RESPONSE 0x01
#define SET_COMM_FEATURE 0x02
#define GET_COMM_FEATURE 0x03
#define CLEAR_COMM_FEATURE 0x04
#define SET_LINE_CODING 0x20
#define GET_LINE_CODING 0x21
#define SET_CONTROL_LINE_STATE 0x22
#define SEND_BREAK 0x23
usb_prop.c
导入头文件:
#include "usb_lib.h"
#include "usb_conf.h"
#include "usb_prop.h"
#include "usb_desc.h"
#include "usb_pwr.h"
#include "hw_config.h"
定义变量:
uint8_t Request=0;
一些结构体的实例化:
- LINE_CODING linecoding :波特率115200,无停止位,无校验位,8位数据
- DEVICE Device_Table :就2个字段,EP_NUM和1
- DEVICE_PROP Device_Property :12个字段的结构体定义,传入串口的一堆操作函数
- USER_STANDARD_REQUESTS User_Standard_Requests :9个字段,也是一堆函数引用
- ONE_DESCRIPTOR Device_Descriptor :2个字段,描述符
- ONE_DESCRIPTOR Config_Descriptor :2个字段,描述符
- ONE_DESCRIPTOR String_Descriptor[4] :4个元素的数组,每个2个字段,字符串描述符
虚拟串口相关的操作函数:
- Virtual_Com_Port_init() :121-137行,串口初始化
- Virtual_Com_Port_Reset() :146-191行,串口复位
- Virtual_Com_Port_SetConfiguration() :200-209行,设置配置
- Virtual_Com_Port_SetDeviceAddress() :218-221行,设置设备地址
- Virtual_Com_Port_Status_In() :230-236行,IN状态
- Virtual_Com_Port_Status_Out() :245-256行,OUT状态,无内容
- Virtual_Com_Port_Data_Setup() :255-286行,数据设置
- Virtual_Com_Port_NoData_Setup() :295-311行,无数据设置
- Virtual_Com_Port_GetDeviceDescriptor() :320-323行,获取设备描述符
- Virtual_Com_Port_GetConfigDescriptor() :332-335行,获取配置描述符
- Virtual_Com_Port_GetStringDescriptor() :344-355行,获取字符串描述符
- Virtual_Com_Port_Get_Interface_Setting() :366-377行,获取接口设置
- Virtual_Com_Port_GetLineCoding() :386-394行,获取行编码
- Virtual_Com_Port_SetLineCoding() :403-411行,设置行编码
4.3.9 usb_pwr.h/.c
usb_pwr.h
两个枚举定义,恢复状态和设备状态:
typedef enum _RESUME_STATE {
RESUME_EXTERNAL,
RESUME_INTERNAL,
RESUME_LATER,
RESUME_WAIT,
RESUME_START,
RESUME_ON,
RESUME_OFF,
RESUME_ESOF,
} RESUME_STATE;
typedef enum _DEVICE_STATE {
UNCONNECTED,
ATTACHED,
POWERED,
SUSPENDED,
ADDRESSED,
CONFIGURED,
} DEVICE_STATE;
然后是5个函数的声明。
两个变量的声明:
extern __IO uint32_t bDeviceState;
extern __IO bool fSuspendEnabled;
usb_pwr.c
导入头文件:
#include "usb_lib.h"
#include "usb_conf.h"
#include "usb_pwr.h"
#include "hw_config.h"
变量和结构体定义:
__IO uint32_t bDeviceState = UNCONNECTED;
__IO bool fSuspendEnabled=TRUE;
__IO uint32_t EP[8];
struct {
__IO RESUME_STATE eState;
__IO uint8_t bESOFcnt;
} ResumeS;
__IO uint32_t remotewakeupon=0;
函数的定义:
- PowerOn() :64-85行,一些基本配置,返回USB_SUCCESS
- PowerOff() :94-108行,基本配置,返回USB_SUCCESS
- Suspend() :117-213行,挂起支持
- Resume_Init() :222-245,恢复的初始化
- Resume() :259-316行,恢复
4.3.10 usb_endp.c
导入头文件:
#include "usb_lib.h"
#include "usb_desc.h"
#include "usb_mem.h"
#include "hw_config.h"
#include "usb_istr.h"
#include "usb_pwr.h"
一个常量定义,发送IN数据包的间隔1帧=1mS:
#define VCOMPORT_IN_FRAME_INTERVAL 5
一些变量定义:
extern __IO uint32_t packet_sent;
extern __IO uint32_t packet_receive;
extern __IO uint8_t Receive_Buffer[64];
uint32_t Receive_length;
然后是一堆看起来是回调函数的。不过刚才在 usb_conf.h 中说了都是空的。具体不清楚,先把定义都写了吧:
void EP1_IN_Callback() {
packet_sent=1;
}
void EP3_OUT_Callback() {
packet_receive=1;
Receive_length=GetEPRxCount(ENDP3);
PMAToUserBufferCopy((unsigned char*)Receive_Buffer, ENDP3_RXADDR, Receive_length);
}
5 应用注记
5.1 使用CDC类与上位机的通信
已经使用USB库实现了,但是现在通信经常丢包,所以研究接下来的办法。
5.1.1 尝试CTR中断的通信
分析发现CTR关键字在 STM32_USB-FS-Device_Lib_V4.0.0 库中,出现在如下文件中:
- inc/usb_int.h:3处
- inc/usb_regs.h:20处
- src/usb_int.c:27处
- src/usb_regs.c:8处
且没有看到具体的中断处理有关语句。
usb_regs.h 中声明的两个函数 ClearEP_CTR_RX(bEpNum) 和 ClearEP_CTR_TX(bEpNum) 看来是用来清除收发两个方向的CTR标识的。
而 src/usb_int.c 中有关CTR的调用全部在两个函数中,即 CTR_HP() 和 CTR_LP() 。貌似是中断处理函数。
在应用代码中,CTR在 usb_conf.h 中出现两处,用于定义要启用CTR_CALLBACK。应该从这里启用CTR回调。另外在 usb_istr.h 中出现两处,没有意义。在 usb_istr.c 中出现六处,启用回调。
所以要启用CTR回调,分为几个步骤:
- 在 usb_conf.h 的75行,启用宏 CTR_CALLBACK
- 不要修改 usb_istr.* 文件
- 在应用中定义 CTR_Callback() 函数,内容自己玩
确定了每个端点都有自己的CTR_TX和CTR_RX位,我需要的是向上位机发送数据,所以要寻找特定端点的CTR_TX位。
usb_regs.h 中定义了端点寄存器的一些值 EP_CTR_RX 和 EP_CTR_TX 。也许就是 GetEPTxStatus() 函数。实际的实现是在 usb_regs.h:337 的一个宏。当该函数返回0x30时,就是可以发送数据了。
USB外设的基址 0x4000,5c00 。
分析下USB CDC应用中的4个端点:
- EP0:USB_EP0R=0x5210,CONTROL端点
- EP1:USB_EP1R=0x0031,BULK端点
- EP2:USB_EP2R=0x0622,INTERRUPT端点
- EP3:USB_EP3R=0x3003,BULK端点
这里几乎看不出东西。那就分析 CDC_Send_DATA() 函数。直接在 hw_config.c 中发现了,是通过EP1发送的。而接收则是EP3。没找到EP2干啥的。
标准做法是收到主机ACK后,通过USB_ISTR寄存器的EP_ID和DIR位识别是哪里产生的事件,然后清除CTR_TX位,然后准备好发送缓冲区。DIR=0时是只有CTR_TX被置位,DIR=1时则CTR_RX被置位,而CTR_TX可能被置位。所以对于只关心CTR_TX的我,可以不看DIR。实际上USB_ISTR中并没有看到任何值,都是0。
5.2 使用USB-FS-Device的VirtualComPort_Loopback例子,作为与电脑的USB串口通信
先从 STM32_USB-FS-Device_Lib_V4.0.0/Projects/VirtualComPort_Loopback 目录打开,其内重要的内容包括inc目录里的头文件和src目录里的C文件。把如下文件拷贝到应用的目录里:
- hw_config.h/.c
- stm32_it.h/.c
- usb_conf.h
- usb_desc.h/.c
- usb_endp.c
- usb_istr.h/.c
- usb_prop.h/.c
- usb_pwr.h/.c
然后都要编译到程序里。
原装的 platform_config.h 太麻烦了,自己写一个简单的:
#ifndef __PLATFORM_CONFIG_H
#define __PLATFORM_CONFIG_H
#ifdef BLUERIDGE13
#include <stm32f10x.h>
#define USB_DISCONNECT GPIOB
#define USB_DISCONNECT_PIN GPIO_Pin_1
#define RCC_APB2Periph_GPIO_DISCONNECT RCC_APB2Periph_GPIOB
#define ID1 (0x1FFFF7E8)
#define ID2 (0x1FFFF7EC)
#define ID3 (0x1FFFF7F0)
#endif
#endif
所以这里的关键内容就是定义芯片的头文件,USB断开的引脚(PB1),所用外设时钟,以及3各ID,不知干啥的。
另外在自己程序的主文件里需要声明几个全局变量以及头文件,方便后续使用:
#include "hw_config.h"
#include "usb_lib.h"
#include "usb_desc.h"
#include "usb_pwr.h"
#include "usb_regs.h"
extern __IO uint8_t Receive_Buffer[64];
extern __IO uint32_t Receive_Length;
extern __IO uint32_t length;
__IO uint32_t packet_sent=1;
__IO uint32_t packet_receive=1;
拥有如上信息就能编译成功了。
stm32_it.c 中有一些没必要的中断声明,反倒耽误我做事了,可以直接过去注释掉,比如 SysTick_Handler() 。
main() 函数里需要加几行初始化内容,然后才能实际的发送内容:
Set_System(); //必须有
Set_USBClock();
USB_Interrupts_Config();
USB_Init();
发送数据到PC的例子,基于SysTick,初始化为 SysTick_Config(9000000); //8Hz 。然后实际的代码:
void SysTick_Handler() {
STM_EVAL_LEDToggle(LED2);
if ((GetEPTxStatus(1) & EP_TX_NAK)=EP_TX_NAK) {
CDC_Send_DATA((uint8_t*)"Hello\r\n",7);
}
}
然后用minicom就可以看到发来的数据了。minicom在数据的发送上是每次一个字符的,务必小心。单片机接收到的也是每次一个字符。而不是在回车时一个完整的。而Python的serial库等,就能一次发送个完整的字符串。
要在单片机上接收上位机发来的信息,使用:
extern __IO uint8_t Receive_Buffer[64];
extern __IO uint32_t Receive_length; //没错,后面的length是全小写的
while(1) {
CDC_Receive_DATA(); //接收信息并更新全局变量
if (Receive_length!=0) { //收到了信息
CDC_Send_DATA((uint8_t*)Receive_Buffer,Receive_length);
Receive_length=0; //必须写
}
}
5.3 休眠处理
stm32提供的USB库会在特定情况下让芯片进入挂起状态来省电。但一旦进入挂起模式,HSE会停止,导致JTAG/SWD调试也停止了,就没法继续调试了。而这个功能,对于大多数时候并没有什么意义。
挂起模式的实现在 usb_pwr.c 的 Suspend() ,被 usb_istr.c 所调用。逻辑是当变量 fSuspendEnabled=TRUE 时就调用。
最简单方便的解决方法是在主程序的启动文件里声明一下该变量:
extern __IO bool fSuspendEnabled;
然后在主程序里将其设置为不进入挂起:
fSuspendEnabled=FALSE;
然后就不会进入该死的挂起了。
这种挂起状态往往是因为USB设置出了问题,USB初始化失败,从而进入了挂起。而正常启动USB设备时不会出现该问题。
推荐阅读