USB协议简介
简介
USB采用差分串行输入数据,在USB2.0以及之前的版本中,总共包括四个引脚,包括供电端,GOUND,以及数据的D+端和D-端。
USB支持的设备有很多种,常见的包括音频接口,打印机,人机交互设备,下文中的举例主要以人机交互设备(HID)为主
总线信号
信号 | 功能 | 引脚电平 |
---|---|---|
J、K状态 | 用于表示一般逻辑高低 | J: D+=1, D-=0 K:D+=1, D-=0 |
Reset | 设置到未配置状态 | D+=0,D-=0, 持续10ms |
Idle | 数据发送前后状态 | 电平和J一致 |
Sync | 同步信号 | 3次KJ状态切换,后跟随2位时间的K状态 |
Resume | 从Idle中恢复 | |
SOP | 开始状态 | 从Idle切换到k状态 |
EOP | 结束状态 | 持续2位时间D+=0, D-=1,后跟随1位时间的J状态 |
传输包结构
USB传输有清晰的层次结构。一次传输包括多个Transaction,一个Transaction包括多Packet
一个Packet由四部分组成:
SOP | SYNC | Content | EOP |
---|---|---|---|
起始信号 | 同步 | 内容 | 结束 |
包分为四类,命令 (Token) 、Packet 帧首 (Start of Frame) 、Packet 数据 (Data) 、Packet 握手 (Handshake) Packet
其中的SOP、SYNC和EOP的功能在总线信号中已经粗略介绍过
其中的Content分为多个部件,Content的结构一般如下,各个Content包含内容部件会有所不同
PID | 地址 | 帧号 | 数据 | 校验 |
---|---|---|---|---|
包类型 | 目标地址,由8位设备地址和4位端点地址 | 累计发送帧 | CRC |
PID将包分为令牌、数据、握手以及特殊包等。
一个Transaction总是由主机发起,定义四种传输类型:中断传输,等时传输,控制传输和批量传输。注意传输都是由主机发起的,中断传输实际上是周期性向外设发起传输
描述符
当USB设置插入主机时,主机会去尝试获取各个描述符,主要包括设备描述符、配置描述符、接口描述符,相当于设备的初始化过程
设备描述符,主要用于描述厂家,设备版本等
配置描述符,主要用于接口数量和功率设置
接口描述符,主要用于设置接口编号和接口类
端点描述符,主要设置每个端点的地址速度
报告描述符,针对某些设备的设置描述
STM32 USB 库函数
设备描述符
在源文件usbd_desc.c中记录了设备描述符的描述数组
可以通过Cubemx修改字符串的设置
// 各类的描述符和字符串 USBD_DescriptorsTypeDef FS_Desc = { USBD_FS_DeviceDescriptor //设备描述符 , USBD_FS_LangIDStrDescriptor // 字符串的语言ID , USBD_FS_ManufacturerStrDescriptor // 厂商字符串 , USBD_FS_ProductStrDescriptor // 产品字符串 , USBD_FS_SerialStrDescriptor // 序列字符串 , USBD_FS_ConfigStrDescriptor // 设置字符串 , USBD_FS_InterfaceStrDescriptor //接口字符串 };
以USB_HID 为例,STM32库函数中在接口描述符码中定义为0x00,表示设备用途在类描述符中定义而非设备描述符中
在usbd_hid*.c中记录了其他的描述符
USBD_ClassTypeDef USBD_HID = { USBD_HID_Init, //初始化函数指针 USBD_HID_DeInit, //恢复复位函数指针 USBD_HID_Setup, NULL, /*EP0_TxSent*/ NULL, /*EP0_RxReady*/ USBD_HID_DataIn, /*DataIn*/ NULL, /*DataOut*/ NULL, /*SOF */ NULL, NULL, /* 不同速度下的其他各类描述符 */ USBD_HID_GetHSCfgDesc, USBD_HID_GetFSCfgDesc, USBD_HID_GetOtherSpeedCfgDesc, USBD_HID_GetDeviceQualifierDesc, };
报告描述符
USB的HID设备通过报告描述符确定通信的格式,在设备的初始化的过程中完成。
报告描述符由通用Item组成,Item分为两种,在HID中主要使用Short Item
Item在起始的1字节中描述了后方数据的长度,Item的类型,Item各个类型的分类标签
Item类型分为三种,包括Main类,Global类、Local类,不同Item描述的数据作用范围不同,这部分可以由生成工具自动生成
在usbd_hid.c中定义了报告描述符
//自动生成的代码中,没有数据的注释 //在参考中,有报告描述符的详细解释,我简单介绍下操作的逻辑结构 static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]={ //描述用途 //创建集合{ //集合用途 //创建数据集合{ //数据说明 //} //} }
数据说明中说明了每次传输的各个字节对应位的用处,默认第一个数据的前三位为三个按钮的状态,第二三四个数据分别表示xy和滚轮偏移量。
设置完成后,每次USB发送报告只需要安装报告描述符写好的规定的数据传输即可。
使用函数USBD_HID_SendReport
实现发送数据
修改主函数实现如下
#include "main.h" #include "usb_device.h" #include "usbd_hid.h" extern USBD_HandleTypeDef hUsbDeviceFS; void SystemClock_Config(void); int main(void) { int8_t buff[4]={0}; HAL_Init(); SystemClock_Config(); MX_USB_DEVICE_Init(); while(1){ //鼠标左键 buff[0] = 0x01; USBD_HID_SendReport(&hUsbDeviceFS, buff, sizeof(buff)); HAL_Delay(10); buff[0] = 0x00; USBD_HID_SendReport(&hUsbDeviceFS, buff, sizeof(buff)); HAL_Delay(100); //鼠标左移 buff[1] = -100; USBD_HID_SendReport(&hUsbDeviceFS, buff, sizeof(buff)); HAL_Delay(1000); buff[1] = 0; } } // 时钟设置应该按照各个芯片的时钟树设置,本实验使用的是STM32F3DISCOVERY void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USB|RCC_PERIPHCLK_USART1 |RCC_PERIPHCLK_TIM8; PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; PeriphClkInit.USBClockSelection = RCC_USBCLKSOURCE_PLL_DIV1_5; PeriphClkInit.Tim8ClockSelection = RCC_TIM8CLK_HCLK; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); } }