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   简介

一种还算凑合的通信方式。

参考文献:

  1. [r] 基于STM32的USB程序开发笔记(序): http://bbs.ednchina.com/BLOG_ARTICLE_182000.HTM
  2. [r] 基于STM32的USB程序开发笔记(一): http://bbs.ednchina.com/BLOG_ARTICLE_182060.HTM
  3. [u] 基于STM32的USB程序开发笔记(二): http://bbs.ednchina.com/BLOG_ARTICLE_182085.HTM 直接罗列代码
  4. [u] 基于STM32的USB程序开发笔记(三): http://bbs.ednchina.com/BLOG_ARTICLE_182520.HTM 还是代码
  5. [u] 基于STM32的USB程序开发笔记(四): http://bbs.ednchina.com/BLOG_ARTICLE_182913.HTM 设备枚举上
  6. [u] 基于STM32的USB程序开发笔记(五): http://bbs.ednchina.com/BLOG_ARTICLE_183349.HTM 设备枚举下
  7. [u] 基于STM32的USB程序开发笔记(六): http://bbs.ednchina.com/BLOG_ARTICLE_183470.HTM XP下USB驱动开发
  8. [u] 基于STM32的USB程序开发笔记(七): http://bbs.ednchina.com/BLOG_ARTICLE_183523.HTM XP下USB驱动开发
  9. [i] 基于STM32的USB程序开发笔记.pdf: 2502410字节,就是如上一系列文章整理的
  10. [u] STM32的USB例程修改步骤: http://blog.chinaunix.net/uid-605899-id-3125746.html
  11. [u] 开始学习USB-从STM32的USB-DEMO开始: http://blog.21ic.com/user1/4852/archives/2008/48074.html
  12. [u] stm32 usb学习: http://wenku.baidu.com/view/2f405e105f0e7cd18425365d
  13. [r] 发一个stm32f4的usb虚拟串口程序: http://www.amobbs.com/thread-5514289-1-1.html 一个1.13MB的源码下载,stm32f4 discovery
  14. [u] [CoIDE]_USB_CDC_Example.rar: https://www.dropbox.com/s/tix34ol8k02i88r/%5BCoIDE%5D_USB_CDC_Example.rar 12.19MB没下载
  15. [u] 嵌入式系统的USB虚拟串口设计: http://www.autooo.net/utf8-classid124-id44408.html
  16. [r] USB-Serial on STM32F4: http://vedder.se/2012/07/usb-serial-on-stm32f4/ 很初级的介绍
  17. [u] STM32F4-Discovery: Help transfering data to PC: https://my.st.com/public/STe2ecommunities/mcu/Lists/STM32F4DISCOVERY/DispForm.aspx?ID=351 一些源码例子的下载地址
  18. [u] ericherman/stm32f-discovery-example: https://github.com/ericherman/stm32f4-discovery-example 使用USB-CDC的例子
  19. [u] serialUSB: https://www.das-labor.org/trac/browser/microcontroller/src-stm32f4xx/serialUSB
  20. [u] STM32F4 USB CDC connection: http://www.coocox.org/forum/topic.php?id=1757&page=2
  21. [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.cUSB_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分析

系统文件列表:

  1. startup_stm32f4xx.s:与我用的基本相同,除了注释
  2. stm32f4_discovery.c:有些不同,详见下面
  3. stm32f4_discovery.h:有些不同,详见下面
  4. stm32f4xx_conf.h:与我用的基本相同,除了注释
  5. stm32f4xx_it.c:对方加了好多东西,要看
  6. stm32f4xx_it.h:只有注释不同
  7. 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支持文件:

  1. usb_bsp.c:是由USB支持库提供的,版本不同,差异很大
  2. usb_conf.h:与系统自带的差异很大,要看
  3. usbd_cdc.c:官方库没有找到对应内容,要看
  4. usbd_cdc.h:官方库没有找到对应内容,要看
  5. usbd_conf.h:与系统自带的差异很大,要看
  6. usbd_desc.c:配置文件,要看
  7. usbd_desc.h:配置文件,要看
  8. usbd_usr.c:与系统自带的差异很大,要看

用户文件列表:

  1. main.c

4.1.1   main.c

需导入诸多头文件:

  1. stm32f4xx.h
  2. usbd_cdc_core.h
  3. usbd_cdc.h
  4. usbd_usr.h
  5. 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 是以字符串描述的设备信息。

一系列不长,甚至仅仅用于返回字符串的函数:

  1. USBD_USR_DeviceDescriptor()
  2. USBD_USR_LangIDStrDescriptor()
  3. USBD_USR_ProductStrDescriptor()
  4. USBD_USR_ManufacturerStrDescriptor()
  5. USBD_USR_SerialStrDescriptor()
  6. USBD_USR_ConfigStrDescriptor()
  7. 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行。主要就两个函数:

  1. USB_OTG_BSP_Init() :从90行开始,根据几种开发板,和HS/FS设置相关引脚的功能
  2. USB_OTG_BSP_EnableInterrupt() :从309行开始,配置各种中断

USE_USB_OTG_FS 用于定义STM32F4探索套件以OTG_FS运行。下面只看对STM32F4 Discovery的初始化。

USB_OTG_BSP_Init() 的内容:

  1. 将PA8、PA9、PA11、PA12定义为100MHz,无上拉下拉,GPIO_AF_OTG_FS
  2. 将PA10定义为100MHz、上拉,GPIO_OType_OD、GPIO_AF_OTG_FS
  3. 启用外设时钟:
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);
  4. RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS,ENABLE);
  5. USB_OTG_HS的初始化从132行开始到243行,用到的线更多,先不看了
  6. 250行,启用PWR时钟 RCC_APB1PeriphResetCmd(RCC_APB1Periph_PWR,ENABLE);
  7. 最后用了很多行分别配置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

  1. USB_OTG_FS_CORE
  2. RX_FIFO_FS_SIZE=128
  3. TX0_FIFO_FS_SIZE=32
  4. TX1_FIFO_FS_SIZE=128
  5. TX2_FIFO_FS_SIZE=32
  6. TX3_FIFO_FS_SIZE=0
  7. USB_DEVICE_MODE
  8. __ALIGN_BEGIN :对应的是空,但不为空时对应特定编译器的 __align(4) 用于对齐
  9. __ALIGN_END :对应的是空
  10. __packed __attribute__ ((__packed__)) :对GCC的宏

4.1.7   usbd_conf.h

98行。用于USB-CDC的一些设置。常量定义如下,也是假设定义了 USE_USB_OTG_FS

  1. USBD_CFG_MAX_NUM=1
  2. USBD_ITF_MAX_NUM=1
  3. USB_MAX_STR_DESC_SIZ=50
  4. CDC_IN_EP=0x81 :EP1用于data IN
  5. CDC_OUT_EP=0x01 :EP1用于data OUT
  6. CDC_CMD_EP=0x82 :EP2用于CDC命令
  7. CDC_DATA_MAX_PACKET_SIZE=64 :输入输出包最大大小
  8. CDC_CMD_PACKET_SZE=8 :控制端点包大小
  9. CDC_IN_FRAME_INTERVAL=5 :两个IN传输间最大帧数量
  10. APP_RX_DATA_SIZE=2048 :IN缓冲的总大小
  11. APP_FOPS=cdc_fops

4.1.8   usbd_usr.c

189行。先导入几个头文件:

  1. usbd_usr.h
  2. usbd_ioreq.h
  3. 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(红色)的:

  1. USBD_USR_Init() :初始化LED5
  2. USBD_USR_DeviceReset() :无内容,会传入 uint8_t speed 可选输出日志
  3. USBD_USR_DeviceConfigured() :点亮LED5
  4. USBD_USR_DeviceSuspended() :熄灭LED5
  5. USBD_USR_DeviceResume() :点亮LED5

4.1.9   usbd_cdc.h

41行。就定义了两个宏:

  1. DEFAULT_CONFIG=0
  2. 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

定义了一些数据结构:

  1. _CONTROL_STATE 枚举
  2. OneDescriptor 结构体
  3. _RESULT 枚举
  4. _ENDPOINT_INFO 结构体
  5. _DEVICE 结构体
  6. _DEVICE_INFO 结构体
  7. _DEVICE_PROP 结构体
  8. _USER_STANDARD_REQUEST 结构体

一堆导出函数就不写了,在 usb_core.c 里写。

一些从外部导入的变量:

  1. DEVICE_PROP Device_Property
  2. USER_STANDARD_REQUESTS User_Standard_Requests
  3. DEVICE Device_Table
  4. DEVICE_INFO Device_Info
  5. uint16_t SaveRState
  6. 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

定义的数据结构:

  1. _EP_DBUF_DIR 枚举
  2. EP_BUF_NUM 枚举

一些地址:

  1. RegBase:USB外设基址
  2. PMAAddr:Packet Memory Area基址
  3. CNTR:寄存器
  4. ISTR:寄存器
  5. FNR:寄存器
  6. DADDR:寄存器
  7. BTABLE:寄存器
  8. EP<N>_OUT:端点寄存器
  9. EP<N>_IN:端点寄存器
  10. ENDP<N>:端点枚举值

一些成段的声明:

  1. ISTR中断事件:105-125行
  2. CNTR寄存器位:130-144行
  3. FNR寄存器位:149-153行
  4. DADDR寄存器位:157-158行
  5. 端点寄存器: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;

各个函数的定义:

  1. Set_System() :63-141行,配置USB使用的GPIO,以及中断线
  2. Set_USBClock() :149-162行,开启USB时钟
  3. Enter_LowPowerMode() :170-174行,进入低功耗模式
  4. Leave_LowPowerMode() :182-198行,离开低功耗模式,需要重新初始化系统
  5. USB_Interrupts_Config() :206-254行,中断配置,包括低优先级,高优先级、唤醒
  6. USB_Cable_Config(NewState) :262-284行,控制D+上拉是否开启
  7. Get_SerialNum() :293-308行,创建一个序列号字符串描述符
  8. IntToUnicode(value,*pbuf,len) :317-336行,看来是数字到Unicode的转换,有必要么
  9. CDC_Send_DATA(*ptrBuffer,Send_length) :345-362行,发送数据,直接调用USB库的3个函数,成功返回1,失败返回0
  10. 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个标准的:

  1. NMI_Handler() :空的
  2. HardFault_Handler() :死循环
  3. MemManage_Handler() :死循环
  4. BusFault_Handler() :死循环
  5. UsageFault_Handler() :死循环
  6. SVC_Handler() :空的
  7. DebugMon_Handler() :空的
  8. PendSV_Handler() :空的
  9. SysTick_Handler() :空的

然后就是两个USB相关的,以HD设备为例:

USB_LP_IRQHandler() :内部直接调用 USB_Istr()

USB_FS_WKUP_IRQHandler() :内部直接调用 EXTI_ClearITPendingBit(EXTI_Line18)

4.3.5   usb_conf.h

一些声明。

  1. EP_NUM (4) :使用的端点数量
  2. BTABLE_ADDRESS (0x00) :缓冲表基址
  3. ENDP<N>_RXADDR (0x40) :端点N的接收缓冲基址
  4. ENDP<N>_TXADDR (0x80) :端点N的发送缓冲基址
  5. IMR_MSK :事件屏蔽位
  6. EP<N>_IN_Callback :输入端点回调,全是空的
  7. 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_CallbackEP<N>_IN_Callback 其中N取1~7。

一些需要宏定义才声明的函数,对应宏的名字就是函数名的全大写:

  1. CTR_Callback()
  2. DOVR_Callback()
  3. ERR_Callback()
  4. WKUP_Callback()
  5. SUSP_Callback()
  6. RESET_Callback()
  7. SOF_Callback()
  8. 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_INpEpInt_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;

一些结构体的实例化:

  1. LINE_CODING linecoding :波特率115200,无停止位,无校验位,8位数据
  2. DEVICE Device_Table :就2个字段,EP_NUM和1
  3. DEVICE_PROP Device_Property :12个字段的结构体定义,传入串口的一堆操作函数
  4. USER_STANDARD_REQUESTS User_Standard_Requests :9个字段,也是一堆函数引用
  5. ONE_DESCRIPTOR Device_Descriptor :2个字段,描述符
  6. ONE_DESCRIPTOR Config_Descriptor :2个字段,描述符
  7. ONE_DESCRIPTOR String_Descriptor[4] :4个元素的数组,每个2个字段,字符串描述符

虚拟串口相关的操作函数:

  1. Virtual_Com_Port_init() :121-137行,串口初始化
  2. Virtual_Com_Port_Reset() :146-191行,串口复位
  3. Virtual_Com_Port_SetConfiguration() :200-209行,设置配置
  4. Virtual_Com_Port_SetDeviceAddress() :218-221行,设置设备地址
  5. Virtual_Com_Port_Status_In() :230-236行,IN状态
  6. Virtual_Com_Port_Status_Out() :245-256行,OUT状态,无内容
  7. Virtual_Com_Port_Data_Setup() :255-286行,数据设置
  8. Virtual_Com_Port_NoData_Setup() :295-311行,无数据设置
  9. Virtual_Com_Port_GetDeviceDescriptor() :320-323行,获取设备描述符
  10. Virtual_Com_Port_GetConfigDescriptor() :332-335行,获取配置描述符
  11. Virtual_Com_Port_GetStringDescriptor() :344-355行,获取字符串描述符
  12. Virtual_Com_Port_Get_Interface_Setting() :366-377行,获取接口设置
  13. Virtual_Com_Port_GetLineCoding() :386-394行,获取行编码
  14. 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;

函数的定义:

  1. PowerOn() :64-85行,一些基本配置,返回USB_SUCCESS
  2. PowerOff() :94-108行,基本配置,返回USB_SUCCESS
  3. Suspend() :117-213行,挂起支持
  4. Resume_Init() :222-245,恢复的初始化
  5. 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 库中,出现在如下文件中:

  1. inc/usb_int.h:3处
  2. inc/usb_regs.h:20处
  3. src/usb_int.c:27处
  4. 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回调,分为几个步骤:

  1. usb_conf.h 的75行,启用宏 CTR_CALLBACK
  2. 不要修改 usb_istr.* 文件
  3. 在应用中定义 CTR_Callback() 函数,内容自己玩

确定了每个端点都有自己的CTR_TX和CTR_RX位,我需要的是向上位机发送数据,所以要寻找特定端点的CTR_TX位。

usb_regs.h 中定义了端点寄存器的一些值 EP_CTR_RXEP_CTR_TX 。也许就是 GetEPTxStatus() 函数。实际的实现是在 usb_regs.h:337 的一个宏。当该函数返回0x30时,就是可以发送数据了。

USB外设的基址 0x4000,5c00 。

分析下USB CDC应用中的4个端点:

  1. EP0:USB_EP0R=0x5210,CONTROL端点
  2. EP1:USB_EP1R=0x0031,BULK端点
  3. EP2:USB_EP2R=0x0622,INTERRUPT端点
  4. 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文件。把如下文件拷贝到应用的目录里:

  1. hw_config.h/.c
  2. stm32_it.h/.c
  3. usb_conf.h
  4. usb_desc.h/.c
  5. usb_endp.c
  6. usb_istr.h/.c
  7. usb_prop.h/.c
  8. 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.cSuspend() ,被 usb_istr.c 所调用。逻辑是当变量 fSuspendEnabled=TRUE 时就调用。

最简单方便的解决方法是在主程序的启动文件里声明一下该变量:

extern __IO bool fSuspendEnabled;

然后在主程序里将其设置为不进入挂起:

fSuspendEnabled=FALSE;

然后就不会进入该死的挂起了。

 

这种挂起状态往往是因为USB设置出了问题,USB初始化失败,从而进入了挂起。而正常启动USB设备时不会出现该问题。

你可能感兴趣的:(STM32 USB应用笔记)