STM32 USB Device应用中的PMA包缓冲

from : http://www.51hei.com/bbs/dpj-40953-1.html

STM32系列MCU大多具有USB外设,其中一部分具有USB FS模块,作为DEVICE使用。另外一部分具备OTG模块,可以实现HOST/DEVICE双重角色的功能。这里聊聊关于STM32F1/F3/L1系列的USB FS 模块中的包数据缓冲话题,即Packet Buffer Memory。STM32F1/F3/L1这三个系列的USB FS模块基本具有相同的结构,这里不妨以STM32F1系列的非互联型芯片的USB FS 模块为例来介绍下Packet Buffer Memory Area,后面简称PMA.

这个PMA的作用就是USB设备模块用来实现MCU与主机进行数据通信的一个专门的数据缓冲区,我们称之为USB硬件缓冲区。说得具体点就是USB模块把来自主机的数据接收进来后先放到PMA,然后再被拷贝到用户数据缓存区;或者MCU要发送到主机的数据,先从用户数据缓存区拷贝进PMA,再通过USB模块负责发送给主机。

不少人在利用ST官方提供的参考工程库文件来开发自己的USB应用时,围绕PMA的配置和使用的地方有时会出错卡壳,或者说即使做完了,即使关于PMA可能还有些疑惑。这里一起聊聊PMA话题。不妨先看看STM32F1系列的非互联型芯片的USB FS 模块功能结构图。

上图中的红色方框部分就是Packet Buffer Memory,该系列芯片的的最大容量为512字节,在USB模块内部按半字访问,即256个半字。该存储区域既可以被USB内核通过Packer buffer Interface【包缓冲接口】寻址访问,亦可以被CPU通过APB1总线访问,某时刻具体谁去访问它由图中的Arbiter【仲裁器】决定,APB1总线具有更高优先级。要注意的是,USB内核访问该区域是16位数据宽度对齐,而APB1总线访问它是以32位数据宽度对齐。另外,USB模块中的端点相关寄存器的访问跟PMA一样,也可以分别被USB内核或APB1总线访问,同样存在两种访问对齐的问题。

好,继续聊聊PMA的具体内部结构和用法。我们知道,【如果不知道就假设知道吧,其实无数的理论都是从假设或约定开始的:-)】每个双向端点对应2个Packet Buffer,分别用作发送数据包的缓冲和接收数据包的缓冲【双缓冲端点可视为两个单向端点轮流跟HOST固定地做IN或OUT方向的单向通信,轮换使用2个同向缓冲区】。这些Packet buffer的大小和位置在PMA内可以配置,具体由缓冲描述表【buffer description table】来指定,该描述表也放在PMA里面。它的起始地址由USB_BTABLE寄存器指定。即整个PMA放置的内容就是缓冲描述表和该表所指定的各端点相应的接收或发送缓冲区。

现在问题来了,既然缓冲描述表负责指定使用到的各端点的包缓冲地址及大小,那缓冲描述表自身在PMA中所占存储空间的大小怎么定的呢?

下面就是PMA的内容框架结构示意图。PMA内存放着USB应用中用到的各端点包缓冲【PACKET BUFFER】和指定这些PACKET BUFFER的地址和大小的缓冲描述表【buffer description table】两部分内容。

从上面PMA的内部框架图可以比较直观地看出缓冲描述表是由各端点两个方向的缓冲区起始地址及相关数据长度之内容所占据,每个双向端点占4个连续的半字,在PMA内部按照地址从低往高的顺序依次存放各端点的发送缓冲区的地址、待发送数据的长度、接收缓冲区的地址、接收缓冲区的长度。这4个连续的半字组成缓冲描述表的一个表项[ENTRY]。各表项又按照端点序号依次从小往大的朝地址递增方向连续存放。

前面说过缓冲描述表在PMA中的起始位置的偏移量由USB_BTABLE寄存器决定。如无特别需要,一般把它设置为零,即从PMA的硬件起始地址开始存放包缓冲描述表,确切地说,从PMA的起始地址开始沿着端点序号由小到大的顺序依次存放或预留各端点表项,直到程序代码中指定的最大端点号为止。每个表项占4个半字,即8个字节。

比方说,如果你用到端点0,1,3 共三个端点,那缓冲描述表里除了安排端点0,1,3的表项外,其中2号端点的表项空间【8个字节】依然会预留在表项1与表项3之间的存储空间。也就是说这种情况下,缓冲描述表自身还是占4*8=32字节的空间,其中表项2的8字节空间可以说是浪费了【其实也可以用,只是使用起来不太方便】。

当然,我这里只是举个例子。一般来讲,比方中的端点安排完全可以调整为 0,1,2,那3号端点就用不着了。这样的话缓冲描述表自身所占的PMA空间为 3*8=24 字节,节省出8个字节可以给后面的包收发缓冲区之用。

我们已经知道,PMA里面的各端点的包缓冲地址及大小是由缓冲描述表指定的,而缓冲描述表同时又跟这些包缓冲共同占据整个PMA。那么,如果缓冲描述表自身在什么位置、占多少空间不清的话,而去给端点指定缓冲地址及大小难免有点抓瞎的味道。有人在做STM32 USB DEVICE开发应用时,正是在对缓冲描述表本身位置及自身用到多少PMA存储空间不清楚的情况下而去给用到的端点指派缓冲地址及大小,经常出现调试或通信异常。

经过上面的介绍,了解了缓冲描述表的结构和存储模式后,对于不同端点需求的情况下,缓冲描述表自身占多少空间就不难算出了,知道了起始地址,结尾地址自然就知道了。下面一起来看看大家常见的一个STM32 USB DEVICE 应用实例。

显然,这里用到了0,1,2三个端点。具体是0号双向端点,1号IN端点,2号OUT端点。配置代码里有5个数据,其中第一行的数据0x00告知缓冲描述表的起始位置位于PMA的起始位置,另外4个数据用来明确给出应用中用到的各端点数据缓冲区起始地址【或发或收】。顺便提下,USB硬件不会把本端点的数据溢出到相邻端点的缓冲区。从这几句代码可以看出使用到的各端点数据缓冲区的起始地址及大致范围,但似乎并不能明确看出前面提到的缓冲描述表所占的地址空间及尾地址。尤其ENDP0_TXADDR的起始地址0X18怎么定出来的呢?是否还可以变动?

结合上面关于缓冲描述表的介绍,一起来分析下。这里用到3个端点,而且是三个编号连续的端点,对应3个表项,那么该缓冲描述表自身所占空间长度为 3*8=24,16进制是0x18; 因为BTABLE_ADDRESS=0,所以缓冲描述表在PMA里存放位置就是0x00-0x17这段存储空间,从0x18开始以上的空间就可用作包缓冲区了。为了充分利用PMA空间,所以0号端点的发送缓冲区起始地址ENDP0_TXADDR就从0x18开始了。其它缓冲区的地址位置和大小也是在结合各自端点传输特性和相邻包缓冲大小的基础上拟定,避免相邻缓冲区的交叉重叠。

那这个起始地址0X18是否可以变动呢?如果在端点安排不变的前提下,这个0X18是否可以变动,答案是肯定的。只要不放进缓冲描述表地址段里面就好,完全可以弄成0x20,0x28,0x48等不小于0x17的偶数,因为USB模块对PMA存储区的访问是双字节对齐。当然,如果你把0x18调整后,其它相邻的包缓冲地址往往需要做相应的调整。

那假如在上面图示的基础上,因为项目功能需求调整,减少1个端点,比如把2号OUT端点取消,那上面的PMA地址配置需要怎样调整呢?【其它相关问题这里就不延伸了】

如果取消OUT 2端点的话,上面有关PMA地址的配置基本可以不动,屏蔽掉关于OUT2端点接收缓冲地址定义的就行。当然,这里的端点数由之前的3个变少到2个,缓冲描述表自身的长度实际上变少为 2*8=16 字节了,也就是说从0x10开始以上的空间都可以用来做包缓冲区,如果包缓冲区继续保持在0x18开始的地方也无妨。

假如就在上面图示基础上,因为功能的需求调整,增加1个单向端点,比如增加3号IN端点,那上面的PMA地址配置需要怎样调整呢?

这里的端点数由之前的3个变成4个,缓冲描述表自身的长度就变多为 4*8=32字节了,即0x20以下的空间都给缓冲描述表占了,换句话说,只有从0x20开始以上的空间才能用作包缓冲区。那端点0发送缓冲地址【ENP0_TXADDR】如果还保持0x18的话肯定不行,这里不调整的话可能就乱大了。经常有人在以ST官方参考库的基础上增加端点后,只是添加新增端点的缓冲区地址和大小及相应描述配置等,却没有顾及到这个包缓冲的起始地址的问题而遇到调试障碍。具体到这里,ENP0_TXADDR缓冲区的地址不得小于0x20,其它缓冲地址往往需要适当调整移动。当然,如果觉得剩余PMA空间够用的话,你也完全可以在既不影响其它端点缓冲大小又不影响自身缓冲需要的前提下,大刀阔斧的把ENP0_TXADDR缓冲起始地址换到一个全新的地址,不一定非得围着0x18或0x20转来转去,比方换到0x138。

......Have a rest.....

还是接着上面的实例继续聊,看看根据上面的初始化配置,相关缓冲描述表的表项内容在PMA中是如何存放的。

显然,整个缓冲描述表用到3个端点,共占空间 3*8=24个字节。

因为OUT 1端点和IN 2端点没有用到,但他们在表项中的椭圆形圆圈圈出来的灰色部分空间还预留出来了,一共8个字节夹在中间基本算是浪费了。如果把端点 OUT 2 换到 OUT 1,那整个缓冲描述表只用到2个端点,所需空间就变为 16字节了。多余出来的8字节就可以方便的留给包缓冲用。另外,表格中的有些数据可能需要结合参考手册的USB寄存器部分看看。那个XXXX/YYYY表示初始化时不定的,准备做数据传输时才确定。地址0x40006000是PMA的起始地址。

 

小结下,缓冲描述表的起始地址和所占空间都是可以明确指定和计算出来的,包缓冲地址的安排往往跟它有关联。只有明确知道了缓冲描述表的起始地址和自身所需存储空间长度后,才能放心地用剩余的PMA给用到的端点分别指定缓冲地址及大小。

缓冲描述表里的表项是根据端点号在PMA里按序填充的,合理安排端点可以减少缓冲描述表的PMA空间使用量,而留下更多空间给端点包缓冲区。

最后,注意每次传输实际收到的数据包长度不会超过各端点接收缓冲区容量的限制,长了会被截掉。

<think>好的,我现在需要回答用户关于STM32的MX_USB_DEVICE_Init函数在没有连接USB设备时是否会卡死在初始化阶段的问题。首先,我得回忆一下STM32USB初始化流程。根据引用中的信息,特别是引用[3]提到的问题,USB的配置需要注意起始地址,否则可能导致缓冲溢出。这可能影响初始化的成功,但用户的问题是关于未连接设备时的卡死情况。 接下来,我需要查看STM32CubeMX生成的USB初始化代码。通常,MX_USB_DEVICE_Init函数会初始化USB外设,设置描述符、端点等。根据USB协议,设备初始化通常不依赖于主机是否连接,因为USB设备在初始化时作为从机(slave)存在,等待主机的枚举。因此,即使没有物理连接,初始化过程应该完成,不会卡死。 不过,用户提到的卡死可能与其他因素有关,比如错误的时钟配置或DMA设置。引用[2]提到USB作为虚拟串口的配置需要正确选择设备模式,如果配置错误可能导致初始化失败。此外,引用[3]强调PMA(Packet Memory Area)的正确配置,如果端点缓冲区的地址设置错误,可能导致硬件异常,进而卡死程序。 因此,MX_USB_DEVICE_Init函数本身在没有USB连接时应该正常返回,不会阻塞。但如果存在硬件配置错误,如时钟未启用、PMA地址冲突,或者中断处理不当,可能导致初始化失败或进入错误状态。需要检查这些配置是否正确,以及是否有未处理的错误中断导致程序卡死。 最后,用户可能需要验证他们的工程配置,特别是USB相关设置和PMA分配,确保符合参考示例。同时,建议使用调试器跟踪初始化过程,查看是否在某个步骤出现异常。</think>关于$MX\_USB\_DEVICE\_Init$函数在未检测到USB设备时的行为分析如下: 1. **初始化流程特性** $MX\_USB\_DEVICE\_Init$函数主要完成USB外设的寄存器配置、描述符加载和中断使能。此阶段仅涉及硬件初始化,不依赖物理连接状态。初始化完成后,USB外设会等待主机发送总线复位信号以触发枚举过程[^3]。 2. **未连接时的典型表现** - 初始化函数会正常执行并返回,**不会主动阻塞或卡死** - USB核心会自动检测VBUS电压判断连接状态 - 若程序后续含等待枚举完成的循环(如`while(CDC_Transmit_HS() != USBD_OK)`),则可能在此处阻塞 3. **可能导致卡死的异常情况** - **PMA地址配置错误**:若端点缓冲区与描述符区域地址重叠,会导致硬件异常(常见于手动修改PMA配置时) - **时钟配置错误**:未正确配置USB时钟源或分频系数 - **中断冲突**:未正确处理USB中断或中断优先级设置不当 4. **调试建议** ```c /* 典型初始化代码片段 */ void MX_USB_DEVICE_Init(void) { USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS); USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC); USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS); USBD_Start(&hUsbDeviceFS); // 此处仅启动外设,不检测连接状态 } ``` 建议通过调试器观察: - 是否执行到`USBD_Start()`后的返回 - 检查`hUsbDeviceFS.dev_state`状态值 - 监测VBUS检测引脚电平状态
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值