STM32 USB应用笔记
?
USB
作者: |
gashero |
日期: |
2013-02-06 |
目录
1???简介
一种还算凑合的通信方式。
参考文献:
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分析
系统文件列表:
对于stm32f4_discovery.c/.h文件,他们在该项目里,但不在 stm32f4_dsp_stdperiph_lib.zip 。存在于 stm32f4discovery_fw.zip 中。不过函数名有些差别。例如该项目的 STM32F4_Discovery_LEDInit() 对应 stm32f4discovery_fw.zip 中的 STM_EVAL_LEDInit() 。以下的区别也是如此。
USB支持文件:
用户文件列表:
4.1.1???main.c
需导入诸多头文件:
初始化一堆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 是以字符串描述的设备信息。
一系列不长,甚至仅仅用于返回字符串的函数:
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行。主要就两个函数:
宏 USE_USB_OTG_FS 用于定义STM32F4探索套件以OTG_FS运行。下面只看对STM32F4 Discovery的初始化。
USB_OTG_BSP_Init() 的内容:
USB_OTG_BSP_EnableInterrupt() 的内容,就是配置各种NVIC,优先级什么的,一共就29行。
4.1.6???usb_conf.h
271行。定义一些宏,以及各种USB RAM FIFO的大小。
定义的宏,先假设我们定义了宏 USE_USB_OTG_FS :
4.1.7???usbd_conf.h
98行。用于USB-CDC的一些设置。常量定义如下,也是假设定义了 USE_USB_OTG_FS :
4.1.8???usbd_usr.c
189行。先导入几个头文件:
定义结构体变量:
USBD_Usr_cb_TypeDef USR_cb= {
? ? USBD_USR_Init,
? ? USBD_USR_DeviceReset,
? ? USBD_USR_DeviceConfigured,
? ? USBD_USR_DeviceSuspended,
? ? USBD_USR_DeviceResumed,
};
这里引用的5个函数都在下面定义的,但是实际都是操作LED5(红色)的:
4.1.9???usbd_cdc.h
41行。就定义了两个宏:
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
定义了一些数据结构:
一堆导出函数就不写了,在 usb_core.c 里写。
一些从外部导入的变量:
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
定义的数据结构:
一些地址:
一些成段的声明:
@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;
各个函数的定义:
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个标准的:
然后就是两个USB相关的,以HD设备为例:
USB_LP_IRQHandler() :内部直接调用 USB_Istr() 。
USB_FS_WKUP_IRQHandler() :内部直接调用 EXTI_ClearITPendingBit(EXTI_Line18) 。
4.3.5???usb_conf.h
一些声明。
实际的缓冲表部分:
#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。
一些需要宏定义才声明的函数,对应宏的名字就是函数名的全大写:
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;
一些结构体的实例化:
虚拟串口相关的操作函数:
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;
函数的定义:
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 库中,出现在如下文件中:
且没有看到具体的中断处理有关语句。
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回调,分为几个步骤:
确定了每个端点都有自己的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个端点:
这里几乎看不出东西。那就分析 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文件。把如下文件拷贝到应用的目录里:
然后都要编译到程序里。
原装的 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设备时不会出现该问题。
原文:http://gashero.iteye.com/blog/2300218