您的位置 首页 5G

STM32 USB 问题汇总

以下是笔者将ST的Custom_HID例程修改为自定义USB设备例程时总结出来的,因为笔者也是刚刚学USB开发不久,某些方面理解错误在所难免,请各位…

以下是笔者将ST的Custom_HID例程修正为“自界说USB设备”例程时总结出来的,由于笔者也是刚刚学USB开发不久,某些方面了解过错在所难免,请各位大虾纠正。

一、usb_desc.c文件

依据你程序运用的通讯方法修正。usb_desc.h文件中界说要依据usb_desc.c文件中的数组的巨细;ConfigDescriptor[SIZ_CONFIG_DESC]下添加需求处理的端点;依据需求添加或删去陈述描述符(首要用于HID)和CDC接口描述符(首要用于完结USB转串口)等。详细方法能够下载个“电脑圈圈”运用D12编写的比如。

二、Usb_conf.h文件:

1、修正需求处理那些中止

CNTR_CTRM 处理数据正确传输后操控,比如说呼应主机

CNTR_DOVRM /* DMA OVeR/underrun Mask */

CNTR_ERRM /* ERRor Mask */

CNTR_WKUPM 0 /* WaKe UP Mask */

CNTR_SUSPM /* SUSPend Mask */

CNTR_RESETM 首要处理USB复位后进行一些初始化使命

CNTR_SOFM /* Start Of Frame Mask */

CNTR_ESOFM /* Expected Start Of Frame Mask */

如:

usb_conf.h中的#define IMR_MSK (CNTR_CTRM | CNTR_SOFM | CNTR_RESETM )是决议USB_CNTR寄存器中的那个USB相关中止发动仍是屏蔽。

2、依据需求添加端点缓存地址,要依据缓存区的地址修正,避免数据堆叠

如下为依据每个缓冲区的巨细为64字节修正:

#define ENDP1_TXADDR (0xC0)

#define ENDP1_RXADDR (0xD0)

#define ENDP2_TXADDR (0x100)

#define ENDP2_RXADDR (0x140)

#define ENDP3_TXADDR (0x180)

#define ENDP3_RXADDR (0x1C0)

3、修正/* CTR service routines */下的EPX_IN_Callback和EPX_OUT_Callback。注释掉需求处理的函数。NOP_Process表明不处理。

三usb_prop.c文件

1、修正void XX_Reset(void)(如:void Joystick_Reset(void))

一般/* Initialize Endpoint 0 */的不用修正,如下为举例阐明端点1的初始化,其他端口原理相同。

SetEPType(ENDP1, EP_INTERRUPT);//设置端点1类型

/*EP_BULK 批量端点

EP_CONTROL 操控端点

EP_ISOCHRNOUS 同步端点

EP_INTERRUPT 中止端点*/

SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置端点1缓冲区基地址

SetEPTxCount(ENDP1, 64);// 装备Tx 缓冲计数器

SetEPRxStatus(ENDP1, EP_RX_DIS);// //设置端点接纳封闭

SetEPTxStatus(ENDP1, EP_TX_NAK);// //设置端点1发送不应对

/*

#define EP_RX_DIS (0x0000) // EndPoint RX DISabled 端点接纳封闭

#define EP_RX_STALL (0x1000) // EndPoint RX STALLed 端点接纳推迟

#define EP_RX_NAK (0x2000) // EndPoint RX NAKed 端点接纳不应对

#define EP_RX_VALID (0x3000) // EndPoint RX VALID端点接纳有用

#define EP_TX_DIS (0x0000) //EndPoint TX DISabled

#define EP_TX_STALL (0x0010) // EndPoint TX STALLed

#define EP_TX_NAK (0x0020) // EndPoint TX NAKed

#define EP_TX_VALID (0x0030) // EndPoint TX VALID */

2、删去不相干的描述符等。

如自界说的USB设备就不需求以下结构体初始化:

ONE_DESCRIPTOR Joystick_Report_Descriptor

ONE_DESCRIPTOR Mouse_Hid_Descriptor

3、修正RESULT XX_Data_Setup(u8 RequestNo)的数据类恳求处理。

如Custom_HID例程修正为“自界说USB设备”例程时能够将以下代码删去

if ((RequestNo == GET_DESCRIPTOR)

&& (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT))

&& (pInformation->USBwIndex0 == 0))

{

if (pInformation->USBwValue1 == REPORT_DESCRIPTOR)

{

CopyRoutine = Joystick_GetReportDescriptor;

}

else if (pInformation->USBwValue1 == HID_DESCRIPTOR_TYPE)

{

CopyRoutine = Joystick_GetHIDDescriptor;

}

}

4、删去不相干的取得描述符回来函数

如自界说的USB设备就不需求以下函数:

Joystick_GetReportDescriptor

Joystick_GetHIDDescriptor

四、usb_endp.c文件

1、添加之前界说的中止数据处理函数

如:

void EP1_OUT_Callback(void)

{

这些写接纳代码

}

五、数据发送和接纳,举例阐明

1、数据接纳

u8 DataLen;

DataLen = GetEPRxCount(ENDP1);

PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);

SetEPRxValid(ENDP1);

USART1_Send(DataLen);

count_out = 1;

2、数据发送

UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);

SetEPTxCount(ENDP1, 64);

SetEPTxValid(ENDP1);

===========================================================================

汇总2:STM32 USB 程序将BULK EP改成双缓冲机制后,一向狂飚到了1MB/S!来自:http://www.powermcu.com/bbs/viewthread.php?tid=693

前天测验自己编写的USB驱动程序时分发现从主机到STM32的OUT传输(主机到设备)速率居然只需最高33KB/S,实在是晕死了。经过研讨后发现是驱动程序中设置的PIPE MaxTransferSize参数的联系,原先设置64只能33KB/S,后参阅其他USB设备驱动程序的值,设置成了65535,再测验USB OUT的速度,到达了500KB/S,总算处理了驱动程序的瓶颈。不过算下USB 2.0全速的通讯速率是12Mb/S,排除去CRC、令牌、SOF等等开支怎样也应该不止最大500KB/S啊。到网上看了看,基本上应该能到达600KB/S~700KB/S以上,我现在的速度应该还有很大的提高才是。
看看程序,发现
void EP3_OUT_Callback(void)//EP3 OUT的回调函数,当EP3接纳到数据时分中止调用该函数
{
count_out = GetEPRxCount(ENDP3);//取得接纳到的数据长度
PMAToUserBufferCopy(buffer_out, ENDP3_RXADDR, count_out);//将数据从USB EP3 RX的缓冲区复制到用户指定的数组中
SetEPRxValid(ENDP3); //完结复制后置有用状况,然后EP3发送ACK主机能够进行下一个数据包的发送
}
试着将PMAToUserBufferCopy这句注释掉(这样STM32就不处理接纳到的数据了)后再测验速度,惊讶地发现速度居然到达了997KB/S!晚上细心想了想,数据肯定是要运用的,这个数据复制的进程的时刻消费总是少不了的;由于通常状况下USB设备BULK数据接纳的进程便是:接纳到数据,置NAK->将缓冲区数据复制到用户区(用户处理进程)->发ACK告诉主机完结了完好的接纳能够发送下一个->主机发送下一个,依照以上的进程USB接纳一步步的进行,只需STM32不完结数据处理,状况就一向是NAK,主机就会不停地发送该数据包,浪费了带宽,因而就会导致我上面最大速度500KB/S难以再添加的状况!不甘心啊~~
昨天晚上又细心研讨了STM32的技能参阅手册的USB章节内容,里边说到BULK能够选用双缓冲机制(PING-PONG)进行处理,正好能够处理上面的状况。双缓冲机制的原理便是分配2块接纳缓冲,STM32的用户处理和USB接口能够别离替换占用2个缓冲区,当USB端点接纳数据写其间一个缓冲区的时分,用户的应用程序能够一同处理另一个缓冲区,这样缓冲区顺次交流占有者,只需用户处理程序在USB端点接纳的时刻片段内完结处理,就能够彻底不影响USB的通讯速度!
程序部分修正
一、EP3_OUT的设置修正,
//ZYP:修正EP3为BULK双缓冲方法————————-
SetEPType(ENDP3, EP_BULK);
SetEPDoubleBuff(ENDP3);
SetEPDblBuffAddr(ENDP3, ENDP3_BUF0Addr, ENDP3_BUF1Addr);
SetEPDblBuffCount(ENDP3, EP_DBUF_OUT, VIRTUAL_COM_PORT_DATA_SIZE);
ClearDTOG_RX(ENDP3);
ClearDTOG_TX(ENDP3);
ToggleDTOG_TX(ENDP3);
SetEPRxStatus(ENDP3, EP_RX_VALID);
SetEPTxStatus(ENDP3, EP_TX_DIS);
//——————————————————
二、EP3_OUT回调函数的修正
void EP3_OUT_Callback(void)
{
//ZYP:以下是修正成EP3双缓冲OUT后的处理函数
if (GetENDPOINT(ENDP3) & EP_DTOG_TX)//先判别本次接纳到的数据是放在哪块缓冲区的
{
FreeUserBuffer(ENDP3, EP_DBUF_OUT); //先开释用户对缓冲区的占有,这样的话USB的下一个接纳进程能够马上进行,一同用户并行进行下面处理
count_out = GetEPDblBuf0Count(ENDP3);//读取接纳到的字节数
PMAToUserBufferCopy(buffer_out, ENDP3_BUF0Addr, count_out);
}
else
{
FreeUserBuffer(ENDP3, EP_DBUF_OUT);
count_out = GetEPDblBuf1Count(ENDP3);
PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out);
}
}
经过上面的修正,总算处理了STM32在处理接纳数据时导致主机等候的状况,用BUS HOUND软件测验了下

哈哈,这下总算爽了。
PS:上面的FreeUserBuffer(ENDP3, EP_DBUF_OUT); 这句话的上下方位是要害,假如放到函数的后边,则依旧会有主机等候STM32处理数据的状况,速度仍然是500KB/S!
把这句话放在复制函数的前面的话就真实把双缓冲PING-PONG机制用起来了。大致算了下PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out);这句话当count_out为最大值64的时分STM32履行需求302个周期,72MHZ状况下约4.2微秒履行时刻,而USB传输依照12Mb/s的线速度传输64字节的数据至少也得40微秒,因而只需PMAToUserBufferCopy的时刻不超越40微秒,就不会导致缓冲区竞赛的状况。

===============================================================================

汇总3:STM32的USB中止阐明,来自:http://bbs.ednchina.com/BLOG_ARTICLE_238817.HTM

STM32的USB模块能够发生三种中止:USB唤醒中止、USB高优先级中止和USB低优先级中止,在STM32的参阅手册中没有详细阐明这三种中止对应哪些事情,现阐明如下:

1)USB唤醒中止:在中止向量表中的方位是42。这个中止在USB设备从暂停形式唤醒时发生,唤醒事情由USB_ISTR寄存器的WKUP位标识。

2)USB高优先级中止:在中止向量表中的方位是19。这个中止仅由USB同步(Isochronous)形式传输或双缓冲块(Bulk)传输形式下的正确传输事情发生,正确传输事情由USB_ISTR寄存器的CTR位标识。

3)USB低优先级中止:在中止向量表中的方位是20。这个中止由一切其它的USB事情发生,例如正确传输(不包括同步形式和双缓冲块形式)、USB复位等,事情标志位在USB_ISTR寄存器中。

在STM32的USB开发包的比如中包括了上述中止的处理,例如在USB扬声器的比如中,CTR_HP函数处理USB高优先级中止;在一切比如中都有USB_Istr()函数处理USB低优先级中止

===============================================================================

汇总4:怎么运用STM32的USB库支撑操控端点0,来自:http://bbs.ednchina.com/BLOG_ARTICLE_242276.HTM

首要咱们先回忆一下操控端点的传输方法:

操控端点的传输有三个阶段,SETUP阶段、数据阶段和状况阶段;数据阶段又分为数据入(DATA IN)和数据出(DATA OUT),操控端点传输能够没有数据阶段;状况阶段有状况入(STATUS IN)和状况出(STATUS OUT)。

总结起来,操控端点有如下三种或许的传输进程(以下括号中的0或1表明DATA0或DATA1传输):
一、 SETUPDATA_IN(0)DATA_IN(1)DATA_IN(0)……STATUS_OUT(1)
二、 SETUPDATA_OUT(0)DATA_OUT(1)DATA_OUT(0)…… STATUS_IN(1)
三、 SETUPSTATUS_IN(1)

这儿做一个约好,把上述进程一界说为“数据入进程”,进程二界说为“数据出进程”,进程三界说为“无数据进程”。一切的USB操控端点的数据传输都能够并且只用这三种传输进程表明。HID的SET_REPORT是数据出进程,HID的GET_REPORT是数据入进程,USB的GET DEVICE DESCRIPTOR是数据入进程,USB的SET CONFIGURATION是无数据进程,等等。


接下来,咱们看看STM32的USB库是怎么处理操控端点0的传输。

依据USB协议,每个SETUP包都由8个字节构成,用户程序能够经过结构体Device_Info(类型DEVICE_INFO)拜访SETUP包的数据,由于在整个的USB处理中都要用到结构体Device_Info的内容,库中界说了一个大局的指针pInformation指向这个结构体,用户能够经过这个指针拜访结构体的内容。

对应SETUP包的8个字节,用户能够用下述方法拜访:
pInformation->USBbmRequestType (字节类型)
pInformation->USBbRequest(字节类型)
pInformation->USBwValue(双字节类型)
pInformation->USBwIndex(双字节类型)
pInformation->USBwLength (双字节类型)
运用pInformation->USBwValue0拜访wValue的低字节,pInformation->USBwValue1拜访wValue的高字节。
运用pInformation->USBwIndex0拜访USBwIndex的低字节,pInformation->USBwIndex1拜访USBwIndex的高字节。
运用pInformation->USBwLength0拜访USBwLength的低字节,pInformation->USBwLength1拜访USBwLength的高字节。

经过剖析SETUP包的8个字节,能够判别出一个SETUP的传输进程是归于数据入进程、数据出进程仍是无数据进程。STM32的USB库中处理了一切的USB协议文本中界说的规范SETUP指令,关于USB协议文本中未界说的指令,USB库依照数据入进程、数据出进程或无数据进程经过回调函数交给用户程序处理。

大局变量Device_Property(DEVICE_PROP类型)封装了一切的回调函数,DEVICE_PROP界说如下:
typedef struct _DEVICE_PROP
{
void (*Init)(void);// 设备初始化回调函数
void (*Reset)(void);// USB复位回调函数

void (*Process_Status_IN)(void);// STATUS_IN阶段处理回调函数
void (*Process_Status_OUT)(void); // STATUS_OUT阶段处理回调函数

RESULT (*Class_Data_Setup)(u8 RequestNo);//数据入/出进程处理回调函数
RESULT (*Class_NoData_Setup)(u8 RequestNo); //无数据进程处理回调函数

RESULT(*Class_Get_Interface_Setting)(u8 Interface, u8 AlternateSetting); // GET_INTERFACE 回调函数

u8* (*GetDeviceDescriptor)(u16 Length); // GET_DEVICE_DESCRIPTION回调函数
u8* (*GetConfigDescriptor)(u16 Length); // GET_CONFIGURATION_DESCRIPTION回调函数
u8* (*GetStringDescriptor)(u16 Length); // GET_STRING_DESCRIPTION回调函数
u8 MaxPacketSize; // 最大包长度
} DEV%&&&&&%E_PROP;

结合SETUP的三种传输进程,用户经过完结不同的回调函数即可完结对各种USB类指令的处理,下面以HID的SET REPORT为例阐明。


在介绍详细完结之前,先介绍一下另一个回调函数CopyRoutine的概念,这个函数的原型是:
u8 *CopyRoutine(u16 length);// 回来一个缓冲区指针

USB库经过这个函数取得用户的数据缓冲区地址,然后能够在数据出进程中把收到的数据复制到用户缓冲区,或在数据入进程中把用户缓冲区的数据复制到USB发送缓冲区。每个数据出进程或许有若干次DATA_OUT传输,USB库每完结一次这样的传输都会调用一次回调函数CopyRoutine,参数length是本次传输所收到的数据字节数目,CopyRoutine有必要回来一个缓冲区指针,这个缓冲区有必要能够包容length字节的数据,CopyRoutine回来到USB库之后,USB库将把收到的数据复制到用户指定的缓冲区。相同每个数据入进程也或许有若干次DATA_IN传输,每次需求向主机传输数据时,USB库都会调用一次回调函数CopyRoutine,参数length是本次传输所要发送的数据字节数目,CopyRoutine有必要回来一个缓冲区指针,这个缓冲区中有必要包括要求的数据字节,USB库将把用户缓冲区的数据复制到USB缓冲区并择机发送出去。

当以length=0调用CopyRoutine时,CopyRoutine需求回来用户缓冲区的长度,由于CopyRoutine的回来类型是一个指针,所以需求经过类型的强制转化回来缓冲区长度。这个功用是为了处理用户缓冲区的长度与主机SETUP数据恳求长度不符的状况,而不至于形成用户缓冲区的溢出。


介绍完上述若干概念和回调函数,再看SET_REPORT的完结就很简单了。

SET_REPORT是一个数据出进程,因而需求完结一个Class_Data_Setup回调函数,示例如下:

RESULT HID_Data_Setup(u8 RequestNo)
{
u8 *(*CopyRoutine)(u16 length);
CopyRoutine = NULL;
if (pInformation->USBbmRequestType == CLASS_REQUEST|INTERFACE_RECIPIENT
&& RequestNo == SET_REPORT)
CopyRoutine = My_Data_Request;

if (CopyRoutine == NULL)
return USB_UNSUPPORT;

pInformation->Ctrl_Info.CopyData = CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset = 0;
pInformation->Usb_wLength = (*CopyRoutine)(0);

return USB_SUCCESS;
} // End of HID_Data_Setup()

u8 My_Buffer[10];
u8 *My_Data_Request(u16 length)
{
if (length == 0)
return (u8*)10;// 假定你的REPORT长度和Buffer长度为10

return My_Buffer;
}


上面介绍的CopyRoutine用于把屡次传输的数据包合并到一个完好的缓冲区中,因而只需到STATUS阶段才能够辅导一次SETUP传输是否完毕,所以用户程序需求在回调函数Process_Status_IN中处理从SET_REPORT接纳到的数据。由于一切的回调函数都是USB中止处理的一部分,所以更好的方法是在Process_Status_IN中设置一个符号,然后在用户主程序中判别这个符号并做处理。


留意,STM32的USB库规划成以回调函数处理用户指令恳求,包括类指令恳求,是为了能够明晰区域分库程序和用户程序,使这两者不会混在一同,这样的优点是十分显着的,当USB库需求更新晋级时,只需替换掉相应的程序模块,而不用修正用户现已完结的程序。

以上的介绍都能够在STM32 USB库的阐明手册中找到。


上述暗示代码是以My_Buffer长度为10字节为例,而USB库的默许包长度为16字节,因而My_Data_Request并没有多包的处理。

关于多包的缓冲区处理的暗示代码可所以这样的:

u8 *My_Data_Request(u16 length)
{
if (length == 0)
return (u8*)100;// 假定你的REPORT长度和Buffer长度为100

return &My_Buffer[pInformation->Ctrl_Info.Usb_wOffset];
}

这儿有一个库中运用的变量pInformation->Ctrl_Info.Usb_wOffset,这个变量回在传输每个数据包时分由库中的程序按数据包长度添加,如最大包长为16字节时,第一次调用My_Data_Request时Usb_wOffset=0,第2次调用My_Data_Request时Usb_wOffset=16,第三次调用My_Data_Request时Usb_wOffset=32,依此类推。这样就能够运用Usb_wOffset作为My_Buffer的下标从My_Data_Request回来。


关于发问“怎么传递length?在上面没有看到这个参数的传递进程”的答复:

参数length是用于检测缓冲区长度是否满足,假如你有满足长的缓冲区,能够不用检测,上述示例中运用了一个固定的缓冲区,所以不用运用参数length检测缓冲区长度。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/yingyong/5g/275370.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部