关于stm32的USB学习笔记之usbcore.c

树莓派4B USB失灵修复:供电策略与内核参数调优 USB设备识别失败是嵌入式Linux系统中常见问题,本质源于主机控制器电源管理策略与硬件供电能力的不匹配。其原理涉及USB PHY层供电门控、内核autosuspend机制及XHCI协议栈超时控制三重协同;技术价值在于突破默认节能策略,实现高可靠性外设接入;广泛应用于树莓派开发、嵌入式调试、家庭服务器等需长期稳定连接键鼠/串口设备的场景。本文聚焦Ubuntu 22.04下树莓派4B的USB失灵问题,通过禁用usbcore.autosuspend、配置dwc_otg.ignore_disconnect及固化u 阅读详情
#include <stm32f10x_lib.h>
#include "usbreg.h"
#include "usbcore.h"
#include "usbuser.h"
#include "usbcfg.h"
#include "usb.h"
#include "usb_hw.h"
#include "usbdesc.h"
#include "hid.h"
#include "hiduser.h"


#define	_DEBUG_
#include "debug.h"

#pragma diag_suppress 111,1441

//用来指示USB设备的状态
WORD	USB_DeviceStatus;
//用来存储设备的地址
BYTE	USB_DeviceAddress;
//用来存储USB当前使用的设备的配置
BYTE	USB_Configuration;
//此配置使用端点
DWORD USB_EndPointMask;
//用于标志此端点是否已经被停止0~15依次代表15个端点
DWORD USB_EndPointHalt;
//此配置使用的接口数
BYTE  USB_NumInterfaces;
//每个接口可用的当前接口可替换值
BYTE  USB_AltSetting[USB_IF_NUM];


/*用于临时存储控制传输时的数据包*/
USB_SETUP_PACKET SetupPacket;

/*用于向主机发送数据的EP0的数据结构*/
USB_EP_DATA EP0Data;
/*EP0Buf用于向USB发送数据时用的缓冲区*/
BYTE	EP0Buf[USB_MAX_PACKET0];

/*功能:复位USB的一些数据标志
 *参数:无
 *返回值:无
 */
void	USB_ResetCore(void)
{
	//默认为总线供电,因为我们的USB现在不都是插在电脑上才能工作的么&^_^
	USB_DeviceStatus 	=	USB_POWER;	
	//在枚举之初地址当然是0
	USB_DeviceAddress	=	0;
	//配置描述符的标识从1开始,这里也先置为0
	USB_Configuration	=	0;
	//目前使用的是端点0
	USB_EndPointMask	= 	0x00010001;
	//没有停止的端点
	USB_EndPointHalt	=	0x00000000;
}

/*功能:建立阶段,读取建立数据包
 *参数:无
 *返回值:无
 */
void USB_SetupStage(void)
{
	 USB_ReadEP(0x00,(BYTE*)&SetupPacket);
}
/*功能:建立阶段,In握手包
 *参数:无
 *返回值:无
 */
void USB_StatusInStage(void)
{
	 USB_WriteEP(0x80,NULL,0);
}
/*功能:建立阶段,Out握手包
 *参数:无
 *返回值:无
 */
void USB_StatusOutStage(void)
{
	 USB_ReadEP(0x00,EP0Buf);
}
/*功能:数据In阶段
 *参数:无
 *返回值:无
 */
void USB_DataInStage(void)
{
	DWORD	cnt;
	//先计算引次要发送多少数据
	if(EP0Data.Count > USB_MAX_PACKET0)
		cnt	= USB_MAX_PACKET0;
	else
		cnt	= EP0Data.Count;
	//这里写端点却主机读,则将Dir位置1
   	cnt	= USB_WriteEP(0x80,EP0Data.pData,cnt);
	EP0Data.pData	+=	cnt;
	EP0Data.Count	-=	cnt;
}
/*功能:数据Out阶段
 *参数:无
 *返回值:无
 */
void USB_DataOutStage(void)
{
	 DWORD	cnt;

	 cnt	=	USB_ReadEP(0x00,EP0Data.pData);
	 EP0Data.pData+=cnt;
	 EP0Data.Count-=cnt;
}
/*功能:获取USB设备的状态
 *参数:无 
 *返回值:TRUE	--->成功
 *		 FALSE	--->错误
 */
__inline BOOL	USB_GetStatus(void)
{
	DWORD	n,m;
	
	switch(SetupPacket.bmRequestType.BM.Recipient)
	{
		//接收端是设备
		case REQUEST_TO_DEVICE:
			//返回设备状态给他
			EP0Data.pData = (BYTE *)&USB_DeviceStatus;
			//将状态信息发送给主机
			USB_DataInStage();
			break;
		//接收端是接口
		case REQUEST_TO_INTERFACE:
			/*配置描述符的标识从1开始,并且请求的接口号不能大于接口的数目,因为接口数目从0开始
			 *因为我们接口描述符中的接口号是一个字节所以这里wIndex中的数据中人低字节有效
			 */
			if((USB_Configuration !=0)&&(SetupPacket.wIndex.WB.L < USB_NumInterfaces))
			{	
				//清0两个字节,因为根据USB协议此处必须返回0
				*((__packed WORD *)EP0Buf) = 0;	
				EP0Data.pData = EP0Buf;
				//发送给主机
				USB_DataInStage();
			}
			//此接口出现了错误
			else
				return	FALSE;
			break;
		case REQUEST_TO_ENDPOINT:
			//端点号高1位0方向,低4位为端点号
			n	= SetupPacket.wIndex.WB.L & 0x8f;
			//m的高16位代表in端点的标志,低16位代表out端点的标志
			m	= (n&0x80)?(1<<16 )<< (n&0x0f) :(1<<n);
			//如果配置标识不为0,或配置标识为0,但是是端点0时,才算正常
			if((USB_Configuration !=0)||((n&0x0f)==0)&&(USB_EndPointMask & m))
			{
				//查看此端点是否已经停用
				*((__packed WORD *)EP0Buf) = (USB_EndPointHalt &m )?1:0;
				EP0Data.pData = EP0Buf;
				//将数据发送给主机
				USB_DataInStage();
			}
			//说明配置描述符出了问题
			else
				return	FALSE;
			break;
		default:
			return	FALSE;
	}	
  	return TRUE;
}
/*功能:设置/清除USB设备的特征
 *参数:sc
 			0----->清除
			1----->设置
 *返回值:TRUE	--->成功
 *		 FALSE	--->错误
 */
__inline BOOL USB_SetClrFeature(DWORD	sc)
{
	DWORD	n,m;
	switch(SetupPacket.bmRequestType.BM.Recipient)
	{
		//接收端是设备,则清除或设置设备的特性
		case REQUEST_TO_DEVICE:
			if(SetupPacket.wValue.W == USB_FEATURE_REMOTE_WAKEUP)
			{
				if(sc)
				{
					printf("设置设备远程唤醒特性\r\n");
					//设置USB状态为使能远程唤醒
					USB_DeviceStatus |= USB_GETSTATUS_REMOTE_WAKEUP;
					/*stm32硬件本身就支持远程唤醒,这里就不用设置了
					 *当然,软件支不支持在于对中断屏蔽位的设置
					 */
				}	
				else
				{
					printf("清除设备远程唤醒特性\r\n");
					USB_DeviceStatus &= ~USB_GETSTATUS_REMOTE_WAKEUP;
					/*stm32硬件本身就支持远程唤醒,这里就不用设置了
					 *当然,软件支不支持在于对中断屏蔽位的设置
					 */
				}
			}
			//不支持的请求
			else
			{
				printf("不支持的请求\r\n");
				return FALSE;
			}
			break;
		//接收端是接口
		case REQUEST_TO_INTERFACE:
			//接口不支持清除特性
			printf("清楚设置接口特征不被USB2.0支持\r\n");
			return 	FALSE;
		//接收端是端点
		case REQUEST_TO_ENDPOINT:
			//取得端点号
			n = SetupPacket.wIndex.WB.L & 0x8F;
//			printf("请求的端点是:");
//			printhex(n);
//			printf("USB_Configuration:");
//			printhex(USB_Configuration);
//			printf("USB_EndPointMask");
//			printhex(USB_EndPointMask);
//			printf("\r\n");
			//将端点转化成与EndPointMask一样的格式
			m =	(n & 0x80)?((1<<16) << (n&0x0f)) : (1<<n);
		    /*根据USB协议来说,
			 *地址状态下只有端点0才能使用,
			 *配置状态下可用
			 *不知道他这句是不是bug
			 *原文件引用:
			 *This request is valid when the device is in the Address state;
			 *references to interfaces or to endpoints other than endpoint zero 
			 *shall cause the device to respond with a Request Error
			 */
			 /*	此处认为只用配置状态下的非0端点才能清楚特征 ,至于地址状态下没有处理
			  *
			  */
			if((USB_Configuration !=0) && ((n&0x0f)!=0) && (USB_EndPointMask & m))
			{
				//设置/清楚 某个端点stall状态
				if(SetupPacket.wValue.W == USB_FEATURE_ENDPOINT_STALL)
				{
					//设置
					if(sc)
					{
						printf("设置端点远程唤醒特性\r\n");
						//设置请求的端点为stall
						USB_SetStallEP(n);
						//更新USB的状态位
						USB_EndPointHalt |= m;	
					}
					//清楚
					else
					{
						printf("清楚端点远程唤醒特性\r\n");
						USB_ClrStallEP(n);
						USB_EndPointHalt &= ~m;
					}

				}
				//未定义的请求
				else
				{
					printf("不正确的端点请求\r\n");
					return FALSE;
				}
			}
			//不正确的请求时机
			else
			{
				printf("不正确的请求时机\r\n");
				return	FALSE;
			}
			break;
		//未定义的接收端类型
		default:
				printf("未定义的接收端类型\r\n");
				printf("接收端为:");
				printhex(SetupPacket.bmRequestType.BM.Recipient);
				printf("\r\n");
				return	FALSE;
			
	}
	return TRUE;
}
/*功能:用于向主机发送描述符
 *参数:无 
 *返回值:TRUE	--->成功
 *		 FALSE	--->发生了错误
 */
__inline BOOL	USB_GetDescriptor()
{
	BYTE 	* pD;
	DWORD	len,n;

	switch (SetupPacket.bmRequestType.BM.Recipient)
	{
		//发给设备的请求
		 case REQUEST_TO_DEVICE:
		 	switch(SetupPacket.wValue.WB.H)
			{
				//获取设备描述符
				case USB_DEVICE_DESCRIPTOR_TYPE:
				  	printf("获取设备描述符\r\n");
					EP0Data.pData = (BYTE *)USB_DeviceDescriptor;
					len	= USB_DEVICE_DESC_SIZE;
					break;
				//获取配置描述符
				case USB_CONFIGURATION_DESCRIPTOR_TYPE:
				  	printf("获取USB配置描述符\r\n");
					pD = (BYTE *)USB_ConfigDescriptor;
				/* 引用USB协议:The range of values used for a
				 * descriptor index is from 0 to one less than 
				 * the number of descriptors of that type implemented by the device
				 * 配置描述符中的标识是从1开始计的,但是,请求中的值是标识值为配置描述符
				 */
				 /*由于可能有几个配置描述符所以可能需要描述一下指针*/
					for(n=0;n!= SetupPacket.wValue.WB.L;n++)
					{
						if(((USB_CONFIGURATION_DESCRIPTOR *)pD)-> bLength != 0)
						{
							pD+=((USB_CONFIGURATION_DESCRIPTOR *)pD)->wTotalLength;
						}
					}
					//如果请求的描述符为空则返回stall
					if(((USB_CONFIGURATION_DESCRIPTOR *)pD)->bLength == 0)
						return	FALSE;
					//设置要传送的指针
					EP0Data.pData = pD;
					//虽然配置描述符中多了一个字节,但是wTotalLength中没有算上,所以这里不用管,直接用就行了
					len = ((USB_CONFIGURATION_DESCRIPTOR*)pD)->wTotalLength;
				  	break;
				//字符串描述符
				case USB_STRING_DESCRIPTOR_TYPE:
				  	printf("获取字符串描述符\r\n");
					EP0Data.pData = (BYTE *)USB_StringDescriptor + SetupPacket.wValue.WB.L;
					len = ((USB_STRING_DESCRIPTOR *)EP0Data.pData)->bLength;
					break;
				default:
					return	FALSE;
			}
			break;
		//请求接收端接口
//		case REQUEST_TO_INTERFACE:
//			printf("请求接收端接口\r\n");
//			switch(SetupPacket.wValue.WB.H)
//			{
//				//Hid 描述符
//				case HID_HID_DESCRIPTOR_TYPE:  
//					printf("请求HID描\r\n");
//					break;
//				case HID_REPORT_DESCRIPTOR_TYPE:
//					printf("请求报告描述符\r\n");
//					//这里只有一个报告描述符,报告描述符的索引是从0开始计,跟数组是一样的,如是不是0则返回Stall
//					if(SetupPacket.wIndex.WB.L != 0)
//						return FALSE;
//					EP0Data.pData = (BYTE *)HID_ReportDescriptor;
//					printf("报告描述符请求的字节:");
//					printhex(EP0Data.Count);
//					printf("报告描述符本来的长度:");
//					printhex(HID_ReportDescSize);
//					printf("\r\n");
//					len = HID_ReportDescSize;
//					break;
//				default:
//					printf("未识别的请求!!!###,类型是:");
//					printhex(SetupPacket.wValue.WB.H);
//					printf("\r\n");
//					break;
//			}
//			break;
		default:
			return	FALSE;
 	}

	printf("获取描述符成功\r\n");
	//将数据发送给主机
	if(EP0Data.Count > len)
		EP0Data.Count = len;
	USB_DataInStage();
	return 	TRUE;
}
/*功能:设置配置请求
 *参数:无
 *返回值:TRUE 	--- >成功
 *		 FALSE	--- >失败
 */
__inline BOOL USB_SetConfiguration(void)
{
	USB_COMMON_DESCRIPTOR * pD;
	DWORD	alt,n,m;
	printf("设置配置\r\n");
	//配置值不能为0
	if(SetupPacket.wValue.WB.L)
	{
		pD=(USB_COMMON_DESCRIPTOR *)USB_ConfigDescriptor;
		//循环到配置描述符中的最后一个端点描述符
		while(pD->bLength)
		{
			 switch(pD->bDescriptorType)
			 {
			 	//此时是配置描述符
			 	case USB_CONFIGURATION_DESCRIPTOR_TYPE:	
					//如果此配置描述符是主机要设置的配置描述符
					if(((USB_CONFIGURATION_DESCRIPTOR * )pD)->bConfigurationValue == SetupPacket.wValue.WB.L)
					{
						//保存起此时,设备所用的配置值
						USB_Configuration = SetupPacket.wValue.WB.L;
						//保存起此时,设备所用的配置的接口数目
						USB_NumInterfaces = ((USB_CONFIGURATION_DESCRIPTOR*)pD)->bNumInterfaces;
						//清0每个接口可替换设置值
						for(n=0;n<USB_IF_NUM;n++)
						{
							USB_AltSetting[n]=0;	
						}	
						//我们将所有的以前可能使用的端点禁用,如果需要,在端点描述符中再使能就行了
						//这样如果更改了配置就保证了,不会使用到以前配置使用,而本次配置没有使用的端点
						//可能是为了防止干扰,猜的^-^!
						for(n=1;n<16;n++)
						{
							if(USB_EndPointMask & (1<<n))
								USB_DisableEP(n);
							if(USB_EndPointMask & ((1<<16)<<n))
								USB_DisableEP(n|0x80);
						}
						//还没有枚举到端点因此这里只使用的是端点0
						USB_EndPointMask = 0x00010001;
						//刚配置,当然没有停止的端点啊,全新的&_^
						USB_EndPointHalt = 0x00000000;
						/*在复位时,我们默认是总线供电,现在开始正式运行以前要因为主机会根据我们的配
						 *置描述符进行调整,一旦配置描述符返回后,配置描述符就生效了,所以这里必须来依据实际
						 *来重新设置
						 */
						if(((USB_CONFIGURATION_DESCRIPTOR *)pD)->bmAttributes & USB_CONFIG_SELF_POWERED)
							USB_DeviceStatus |= USB_GETSTATUS_SELF_POWERED;
						else
							USB_DeviceStatus &= ~USB_GETSTATUS_SELF_POWERED;
					}
					//否则略过,下一个配置描述符
					else
					{
						(BYTE *)pD += ((USB_CONFIGURATION_DESCRIPTOR *)pD)->wTotalLength;
						continue;		
					}
					break;
				//下面是接口描述符
				case USB_INTERFACE_DESCRIPTOR_TYPE:
					alt = ((USB_INTERFACE_DESCRIPTOR *)pD)->bAlternateSetting;
					break;
				case USB_ENDPOINT_DESCRIPTOR_TYPE:
					//我们仅仅需要处理多个子接口中的第一个就行了,(如果有子接口的话,没有的话刚好也是0)
					if(alt == 0)
					{
						//得到此端点
						n = ((USB_ENDPOINT_DESCRIPTOR *)pD)->bEndpointAddress & 0x8f;
						//将端点号转化成bit以存储在USB_EndPointMask中去代表我们使用了此端点
						m = (n&0x80)?((1<<16)<<(n&0x0f)):(1<<n);
						USB_EndPointMask |= m;
						//配置端点
						USB_ConfigEP((USB_ENDPOINT_DESCRIPTOR  *)pD);
						//使能端点
						USB_EnableEP(n);
					    //复位端点,使端点处于确定的端点
						USB_ResetEP(n);
					}
					break;
			 }
		(BYTE *)pD += pD->bLength;
		}	
	}
	//如果请求的配置为0,则我们将所有的配置恢复到地址状态时的配置
	else
	{
		USB_Configuration = 0;
		for(n=1;n<16;n++)
		{
			//如果上一个配置使用了此端点,则disable it
			if(USB_EndPointMask & (1<<n)) //OUT端点
				 USB_DisableEP(n);
			if(USB_EndPointMask & (1<<16)<<n)  //IN端点
				 USB_DisableEP(n | 0x80);
		}	
		//清除使用的全部端点,(当然0端点还是除外的)
		USB_EndPointMask = 0x00010001;
		USB_EndPointHalt = 0x00000000;
	}
	//我们根据USB_Configuration的值来查看我们到底时否找到了所需要的值
	if(USB_Configuration == SetupPacket.wValue.WB.L)
	{	
		printf("设置配置成功\r\n");
		return	TRUE;
	}
	else
	{
		printf("设置配置失败\r\n");
		return	FALSE;
	}
}

/*功能:用于处理端点0的输入和输出请求函数
 *参数: USB_EVT_SETUP 枚举时上位机发送的数据包
 *      USB_EVT_OUT	  普通传输时,上位机发送的数据包
 *		USB_EVT_IN	  普通传输时,上位机请求发送数据
 *返回值:无
 */
void USB_EndPoint0 (DWORD event)
{
//	printf("进入端点0处理函数\r\n");
	switch (event)
	{
		//控制传输
		case USB_EVT_SETUP:
		//	printf("进入建立阶段\r\n");
			//首先读取主机发来的请求
			USB_SetupStage();
		   	//设置EP0的数据结构
			EP0Data.Count = SetupPacket.wLength;
			//识别是何请求
//			printf("请求是");
//			printhex(SetupPacket.bRequest);
//			printf("    被请求对象:");
//			printhex(SetupPacket.bmRequestType.B);
//			printf("\r\n");
			//bRequestType.bit5~bit6代表请求类型
			switch(SetupPacket.bmRequestType.BM.Type)
			{
				//标准请求
				case REQUEST_STANDARD:
					switch(SetupPacket.bRequest)
					{
					//获取USB状态
					case USB_REQUEST_GET_STATUS:
						 //如果状态失败则停止0x81端点
					 	if(!USB_GetStatus())
						{
							printf("获取状态失败\r\n");
							goto	stall_i;
						}
						break;
					case USB_REQUEST_CLEAR_FEATURE:
						//如果清除特征失败则停止0x81端点
						if(!USB_SetClrFeature(0))
						{
							printf("清楚特征失败\r\n");
							goto stall_i;	
						}
						//成功了握一下手OK
						USB_StatusInStage();	
						break;
					case USB_REQUEST_SET_FEATURE:
						//如果状态失败则停止0x81端点
						if(!USB_SetClrFeature(1))
						{
							printf("设置特征失败\r\n");
							goto stall_i;	
						}	
						//成功了握一下手OK
						USB_StatusInStage();
						break;
					case USB_REQUEST_SET_ADDRESS:
						switch(SetupPacket.bmRequestType.BM.Recipient)
						{
							//只支持向设备设置地址
							/*USB协议指出必须在改地址前完成检测并对此信息包进行响应
							 *即USB的设置地址阶段必须在主机接收到0长度数据包后才能进行
							 *当主机接收到状态阶段的0数据包时将会产生接收中断
							 *所以放到了USB_EVT_IN处*/
							case REQUEST_TO_DEVICE:
								printf("设置地址\r\n");
								USB_DeviceAddress = 0x80 | SetupPacket.wValue.WB.L;
								USB_StatusInStage();
						   		break;
							default:
								printf("未识别的接收端,接收端是");
								printhex(SetupPacket.bmRequestType.BM.Recipient);
								printf("\r\n");
								goto	stall_i;
						}
						break;
					//获取设置描述符
					case USB_REQUEST_GET_DESCRIPTOR:
						if(!USB_GetDescriptor())
							goto	stall_i;
						break;
					//设置配置请求
					case USB_REQUEST_SET_CONFIGURATION:
						switch(SetupPacket.bmRequestType.BM.Recipient)
						{
							//对设备
						 	case REQUEST_TO_DEVICE:
								if(!USB_SetConfiguration())
								{
									//如果失败则对主机返回stall
									goto stall_i;
								}
								//完成了请求,握个手OK
								USB_StatusInStage();
								break;
							//不支持对其他类型请求
							default:
								goto stall_i;
						}
						break;
					case USB_REQUEST_GET_INTERFACE:
						printf("请求接口\r\n");
						break;
					default:
						printf("未识别的请求,请求代码是");
						printhex(SetupPacket.bRequest);
						printf("\r\n");
						break;
					}
				break;
				//类请求
				case REQUEST_CLASS:
					printf("类请求\r\n");
					switch (SetupPacket.bmRequestType.BM.Recipient)
					{
						//对接口请求
						case REQUEST_TO_INTERFACE:
							if(SetupPacket.wIndex.WB.L == USB_HID_IF_NUM)
							{
								switch(SetupPacket.bRequest)
								{
									case HID_REQUEST_GET_REPORT:
										printf("获取报告描述符\r\n");
										break;
									case HID_REQUEST_SET_REPORT:
										printf("设置报告描述符\r\n");
										break;
									case HID_REQUEST_GET_IDLE:
										printf("获取空闲率\r\n");
										break;
									case HID_REQUEST_SET_IDLE:
										printf("设置空闲\r\n");
										break;
								    case HID_REQUEST_GET_PROTOCOL:
										printf("查询报告协议是否在活动\r\n");
										break;				
									case HID_REQUEST_SET_PROTOCOL:
										printf("SetReprot\r\n");
										break;	
									default:
										printf("不支持的HID请求\r\n");
										break;
						   		}
							}
							break;
					}
					break;
				//产商请求
				case REQUEST_VENDOR:
				//未定义请求
				default:
		stall_i:
					USB_SetStallEP(0x80);
					break;
			}
			break;
		//接收成功
		case USB_EVT_OUT:
			printf("USB_EVT_OUT\r\n");
			if(SetupPacket.bmRequestType.BM.Dir ==0)
			{
				printf(" 请求是:");
				printhex(SetupPacket.bRequest);
				printf("\r\n");
			}
			//返回握手
			else
			{
				USB_StatusOutStage();
			}
			break;
		//发送成功
		case USB_EVT_IN:
		//	printf("USB_EVT_IN\r\n");
			if(SetupPacket.bmRequestType.BM.Dir == 1)
				USB_DataInStage();
			/*USB协议指出必须在改地址前完成检测并对此信息包进行响应
			 *即USB的设置地址阶段必须在主机接收到0长度数据包后才能进行
			 *当主机接收到状态阶段的0数据包时将会产生接收中断
			 *所以放到了USB_EVT_IN处*/
			 //原keil库没有加if判断,怀疑为bug,这里判断是否是由于设置地址的状态阶段成功而触发的中断
			 else// if(SetupPacket.bRequest == USB_REQUEST_SET_ADDRESS)
			 {
				if(USB_DeviceAddress & 0x80)
				{
					USB_DeviceAddress &= 0x7f;
					USB_SetAddress(USB_DeviceAddress);
				}		 	
			 }
			break;
	}

}

从ATmega32u4到USB HID:打造免驱键盘鼠标的完整实战指南 USB HID协议是计算机与键盘、鼠标等外设通信的标准接口,它允许设备无需安装驱动即可被系统识别。在嵌入式开发中,实现USB HID的传统方案往往需要外部转接芯片,而ATmega32u4凭借内置USB收发器,让开发者直接用单片机原生引脚模拟标准输入设备。本文从HID枚举原理出发,深入解析设备描述符和报告描述符的结构,并展示如何基于Arduino或TinyUSB构建键盘、鼠标及复合HID设备。结合安卓OTG实战,验证了免驱即插即用的效果,同时涵盖PCB设计中晶振匹配、去耦电容、ESD保护等硬件优化细节,为打造 阅读详情

相关推荐

90元工业深度相机逆向驱动实战:USB协议+OpenCV深度图采集

深度相机是计算机视觉中获取三维空间信息的核心硬件,其原理基于主动红外结构光或双目立体匹配,通过硬件加速实现毫米级精度的实时深度计算。技术价值在于绕过闭源SDK、直接操控USB底层协议,显著降低嵌入式视觉系统开发成本与周期。典型应用场景包括手势交互、前景分割、点云重建及工业缺陷检测等。本文以乐视Astra Pro为例,详解如何利用libusb逆向解析厂商自定义控制接口,结合OpenCV实现稳定640×480@30fps深度图采集与实时处理,覆盖Windows/Linux/树莓派多平台部署要点。

weixin_30745553的博客 357

关于STM32H7的USB初始化失败

在使用STM32H7的USB时,初始化会失败,如果不开启优化,是没有问题的,只要开启就会时常出现USB初始化失败, // 使用CubeMX生成的void MX_USB_DEVICE_Init(void);/** * Init USB device Library, add supported class and start the library * @retval None */ void MX_USB_DEVICE_Init(void) { /* Init Device Library,

weixin_45045183的博客 5189

Arduino USB Host Shield 实战:从设备变主机,轻松外接键盘手柄U盘

从单片机通信的基础概念讲起,Arduino 原生 USB 口只能作为从机设备,通过串口转 USB 芯片实现与电脑通信。要突破这一限制,需要引入 USB 主机协议栈。USB Host Shield 以 MAX3421E 为核心,通过 SPI 接口分担底层协议处理,让 Arduino 具备 USB 主机能力。这项技术让嵌入式系统能够直接管理键盘、鼠标、游戏手柄等标准 HID 设备,以及 U 盘等存储设备,广泛应用于离线数据采集、机器人控制、互动装置等场景。掌握 USB 枚举流程、SPI 通信和电源管理,即可稳定

weixin_34221332的博客 313

STMCUBEIDE+USB3320 卡在USB_CoreReset

解决办法__HAL_RCC_SYSCFG_CLK_ENABLE();();

zerothpower的博客 886

STM32单片机USB 学习资料STM32F10x_USB_培训资料USB程序开发笔记等文档资料.zip

STM32单片机USB 学习资料STM32F10x_USB_培训资料USB程序开发笔记等文档资料: 022525_STM32F10x_USB_学习培训资料.pdf CD00158241.pdf STM32 USB Mass Storage学习资料.pdf STM32的USB详解.pdf STSW-STM32121.zip USB培训_Part1_协议.pdf USB培训_Part2_USB_IP及其库的使用.pdf USB培训_Part3_USB_OTG_IP及其库的使用.pdf 基于STM32的USB程序开发笔记.pdf

Windows平台MuJoCo仿真环境配置与Qt集成实战

本文详细介绍了在Windows平台上配置MuJoCo物理仿真环境并与Qt框架集成的完整实战流程。内容涵盖从环境准备、库文件配置、项目创建到核心代码编写的每一步,重点解决了Windows下常见的编译链接错误和路径问题,帮助开发者在Windows系统中高效搭建机器人仿真与可视化控制开发环境。

weixin_29221753的博客 172

usb/core文件之usb_core.c

/for (i = 0;i < len;i++)/***/for (i = 0;/***/#else#endif#endif#endif#else#endif#endif#endif#endif。

qq_33772147的博客 423

C++常见面试题总结

C++常见面试题总结

xu_fu_yong的专栏 8万+

Linux USB协议栈框架与驱动开发:从URB到枚举故障排查

在嵌入式开发和设备驱动领域,理解操作系统如何与外部硬件交互是定位问题的关键。USB作为最通用的外设接口,其协议栈的层级关系直接影响驱动编写与调试效率。Linux内核将USB体系划分为Host Controller Driver、USB Core与设备驱动三层,通过URB(USB Request Block)机制完成数据交换,设备枚举过程则依赖于控制传输与描述符解析。掌握这一框架,不仅能帮助开发者快速实现自定义HID、批量传输或虚拟串口功能,还能在遇到设备无法识别、数据传输异常时,借助usbmon、dmesg

weixin_29189377的博客 208

电磁炉线圈加热仿真全流程:用Ansys Maxwell分析涡流效应与热损耗

本文详细介绍了使用Ansys Maxwell进行电磁炉线圈加热仿真的全流程。通过构建2D轴对称涡流场模型,深入分析了高频下的涡流效应、趋肤效应与热损耗分布,为优化线圈设计、提升加热效率与均匀性提供了量化依据和实用指南。

sss66的博客 966

基于DFT的scanshift sdc模板(DFT)

摘要:本文详细介绍了DFT测试模式下的SDC约束方法。

murphyrong的博客 887

USB枚举背后的秘密:从设备描述符到驱动加载的完整流程拆解

本文深入解析了USB设备枚举的完整流程,从物理连接到驱动加载。详细拆解了设备描述符中VID、PID、bcdUSB等关键字段的含义,并结合USB 2.0规范,通过Wireshark抓包实例分析了主机与设备间的协议对话。同时,对比了Windows与Linux系统在驱动匹配和加载逻辑上的差异,为开发者诊断兼容性问题、优化设备设计提供了实用指南。

desk3的博客 711

从零构建:用Arduino和MCP2515模块搭建你的第一个CAN Bus网络

本文详细介绍了如何使用Arduino和MCP2515模块从零搭建CAN Bus网络,实现温湿度数据的传输与显示。涵盖硬件连接、软件配置、发送端与接收端代码实现,以及系统调试和进阶应用,帮助初学者快速掌握CAN总线通信技术。

d6e7f8的博客 833

WSL2烧录STM32实战:打通Windows开发与Linux嵌入式工具链

嵌入式开发中,USB调试设备(如ST-Link)在Windows与Linux环境间的兼容性问题长期困扰工程师。其本质是硬件访问权限、内核驱动支持与用户态工具链的协同失效。WSL2凭借原生Linux内核、USB/IP透传机制及完整udev子系统,成为唯一能绕过Windows USB驱动栈、直接运行st-flash和OpenOCD等开源调试工具的轻量级方案。它不仅解决STM32固件烧录‘找不到设备’‘权限拒绝’等高频故障,更支撑CMake自动化构建、VS Code源码级调试及CI/CD流水线统一,为现代C++、

weixin_33802505的博客 565

pytorch转onnx转paddlepaddle

1.pt转onnx 可查看我之前的文章:https://blog.csdn.net/oyjwin/article/details/117778583?spm=1001.2014.3001.5501

oyjwin的博客 1461

R语言广义加型模型(GAM)的运用例子及实现教程

一旦拟合了广义加型模型,我们可以查看模型摘要和诊断,以评估模型的质量和拟合情况。# 查看模型摘要响应变量:mpg(汽车的燃油效率)拟合函数:mpg ~ s(hp) + s(wt)

Mrrunsen的博客 3446

Clion调试STM32时ST-Link报错的5层根因与实战修复

ST-Link是STM32嵌入式开发中最常用的JTAG/SWD调试接口,其本质是USB协议设备与OpenOCD、GDB协同工作的软硬结合体。理解其底层通信原理(USB枚举→驱动加载→固件协议→OpenOCD服务→IDE集成)是解决‘设备未识别’‘GDB连接超时’‘烧录失败’等高频问题的关键。技术价值在于规避玄学重启,实现可验证、可复现、可自动化的调试链路诊断。典型应用场景包括Clion替代Keil/IAR的工程迁移、高校STM32教学实训、中小硬件团队CI/CD嵌入式调试自动化。本文聚焦ST-Link V2

weixin_34112181的博客 452

Buildroot RK3568系统中更改GPU驱动为:panfrost

1:Kernel中开启Panfrost 2:更新mesa 到mesa-24.0.2 3:查看/proc/interrupts 中的panfrost 中断 4:LInux启动日志 5:查看GPU负载

OpenWrt/WLAN/驱动/Android/嵌入式开发总结 2078

USB Host与Device本质区别:硬件信号、供电、协议与驱动五维解析

USB是一种严格主从架构的串行总线协议,其核心在于Host(主机)与Device(设备)的角色分工。Host负责供电、发起通信、枚举设备并调度数据传输;Device仅能响应请求、依赖Host供电且无主动通信权。这种单向控制机制决定了USB外设兼容性、OTG切换可行性及嵌入式调试成败。理解D+D-上拉/下拉电阻、VBUS供电能力、令牌包调度逻辑、内核URB与gadget模型差异,是解决Android USB串口异常、打印机识别失败、Linux枚举中断等高频问题的技术基础。本文聚焦真实工程场景,覆盖从物理层电平

weixin_34216614的博客 221

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

WSL2烧录STM32实战:OpenOCD+ST-Link全链路解析

嵌入式开发中,STM32烧录本质是调试适配器(如ST-Link)通过SWD/JTAG协议与目标芯片建立可靠通信的过程。其底层依赖USB设备透传、Linux内核驱动(stlink模块)、OpenOCD协议栈及ARM工具链协同工作。在Windows平台,驱动签名、USB权限与服务隔离常导致‘No device found’等兼容性问题;而WSL2凭借原生Linux内核支持、udev设备管理与完整POSIX环境,可无缝运行st-flash、OpenOCD和arm-none-eabi-gcc等关键工具,实现从编译、

weixin_33924220的博客 545

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

GD32H759+RT-Thread实现工业级USB CDC ACM虚拟串口

USB CDC ACM是一种广泛使用的USB设备类,用于实现虚拟串口通信,其核心原理基于USB协议栈中的控制传输与批量传输机制。在嵌入式实时系统中,CDC ACM的技术价值不仅在于替代物理串口,更在于提供标准化、免驱、跨平台(Windows/Linux/RTOS)的可靠数据通道。实际工程中,其稳定运行高度依赖硬件时钟精度、专用USB RAM地址对齐、PHY供电管理及USB描述符合规性等底层约束。尤其在GD32H759这类高性能工控MCU上,需协同处理双PHY配置、48MHz独立时钟域锁定、USB专用SRAM

weixin_33778544的博客 148

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

u-boot USB启动全链路解析:从控制器初始化到fatload

USB主机模式是嵌入式系统在裸机环境下实现外设存储加载的基础能力,其本质是在无操作系统支持时,由Bootloader重建精简版USB协议栈,完成控制器初始化、PHY供电配置、设备枚举、MSC类识别及FAT文件系统解析。该过程高度依赖SoC硬件特性,涉及时钟/复位/寄存器/信号完整性等底层协同,技术价值在于支撑量产阶段的固件更新与安全启动。典型应用场景包括i.MX6ULL、STM32H7等平台的U盘启动调试与产线烧录。本文聚焦u-boot中USB Host全流程机制,深入剖析控制器初始化与fatload执行链

weixin_34401479的博客 338

扫地机器人双脑架构:Linux与FreeRTOS/STM32的职责隔离设计

嵌入式实时控制系统中,功能安全要求关键执行动作(如急停、电机使能)必须具备确定性响应能力——这源于ISO 26262对ASIL-B级系统‘可证明失效覆盖’与‘无单点故障’的核心约束。Linux作为通用操作系统,其内核调度不确定性、内存管理开销及软中断抖动,天然无法满足毫秒级硬实时需求;而FreeRTOS+STM32构成的MCU控制层,凭借裸机级中断响应(<3μs)、静态内存分配与硬件直连传感链路,成为实现故障域隔离与时间域隔离的唯一可靠载体。该架构广泛应用于智能服务机器人、AGV及车规级辅助设备,本质是将‘

weixin_33796177的博客 331
上一篇: 关于stm32的USB学习笔记之USB_HW.c
下一篇: C语言编程好习惯(持续更新)
findstr
博客等级 码龄19年 63粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值