欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

STM32 USB应用笔记

程序员文章站 2022-03-30 19:29:48
...

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设备时不会出现该问题。