同上一篇MPU6050一样,我还是写一篇关于USB的帖子,在圈圈等玩USB的大神面前,我掌握的USB知识实在是九牛一毛,所以这篇帖子加上了入门级的修饰语。写这篇帖子主要是为了那些想快速开发USB的人,至于想深入了解USB协议,可以先学完我这个再去看看别的高级教程可能会好点,虽然我强调自己掌握的USB知识不多,但是对于一般的应用已经足够,我这里主要是学会怎么去用USB做自己的东西,而且我觉得刚学完一个东西就来教下别人实在是一个一举两得的做法,因为我更清楚学习时的心理和一些小问题,而且我也可以进一步总结。我有个观点不知道其他大神中不中听,好像圈圈大神就搞了USB几年达到了精通的水平,可能一些几个月达到了很懂的水平,但是这个过程实在是很漫长,对于很多人也没有必要对USB进行彻底的研究,只要会用就好,就像你可能不懂车的高级原理,但是你会开车就好了,^_^。其他大神不同意的话,写个高级的USB教程让我们学习下吧。这里其实也是个人学习笔记。
记得一个月前,我想做无线鼠标,开始在网上查找资料,从那个时候开始学习USB,找了圈圈的书将前面足足看了3、4遍,还只是瞎子摸象,不过瞎子摸象很正常啦,学什么东西一开始哪里可能能够弄清全局。看了3、4编,实在是很煎熬,一直没有什么进展,没有搞出点成果。就开始上网找资料,让瞎子摸象来得更彻底一点。
如果学过I2C的话,可能对USB理解会更透彻点,两者在某些方面还是有共通之处的,某种意义上来说,STM32的USB跟硬件I2C有点像,89C52的USB介于软件I2C和硬件I2C之间,89C52有了USB芯片的协助后,很多时序不用自己模拟,但是编程使用芯片搞USB这过程就跟软件I2C一样的蛋疼。首先STM32是有USB的两个数据端口的,首先看上面的这张图,是野火ISO板子USB原理图部分,一个VCC,一个GND连接到USB设备,充电的时候就是这两个起作用,供电的作用。D+和D-就是差分数据线,ID我们一般没有用到,是用来设备和主机之间的识别用到,有时候两台机器可以做设备和主机就靠这根线来区别。主要是三极管这部分,由电路图可以知道PD3口要给低电平上拉电阻才能接到D+,应该这里在D-还要有一个三极管和上拉电路,不然这里默认了所接的设备一定是全速设备和高速设备,不是低速设备,不知道接低速设备有没有事。USB运行的前提是要上拉电阻接通才行喔,这里不直接接上拉的原因就是可以自己控制,你可以再程序中控制这里来断开USB连接然后再接上去,选择较多,当然也有直接接上去的。除了STM32,89c52和STC12等单片机也可以搞USB,圈圈就是用89C52来搞的,但是89C52是没有像STM32那样子内置USB外设的,那么就需要加多1块USB芯片,还有相应的外围电路,但是STM32就不用,好方便的说。”主机在检测到设备接入后,会执行设备识别,这个过程比较麻烦,它的枚举过程包含了设备的一些相关信息与通信方式。 “这个过程我觉得如果不是要真正研究USB的话,也没有必要看,看了搞不好神经错乱。加上ST官方库的USB例程后,STM32搞USB难度降低了几个等级。ST官方有个JoyStickMouse的例程,就是通过摇杆来控制鼠标。我们接下来是用按键来控制鼠标。
官方的源文件地址:http://pan.baidu.com/s/1eQuyL8I
原子的触控USB鼠标实验就是其中一个很好的教程,很通俗,很符合初学者心理,可惜讲解得太简短了,不懂触摸屏的可以直接忽略它的触摸屏部分,
教程地址:http://pan.baidu.com/s/1jGuW9EI
工程地址:http://pan.baidu.com/s/1bnD1IZd
USB2.0协议中文版: http://pan.baidu.com/s/1bnndwYV
关于 STM32F102/103 的USB模块和USB库函数:http://pan.baidu.com/s/1kT7LLhd这是官方对库的说明:http://pan.baidu.com/s/1ntmdbsP
USB英文文档:http://pan.baidu.com/s/1nt6vLs1
USB描述符: http://pan.baidu.com/s/1kTkLlf5
圈圈教你玩USB----PDF: http://pan.baidu.com/s/1ntoKSwL
基于STM32 的USB程序开发笔记(看不懂,不过好像很牛逼):http://pan.baidu.com/s/1kTkLlKb
USB源代码分析(很详细的)http://pan.baidu.com/s/1o6mbbyq
USB鼠标工程(网上找的)圈圈USB资料合集:
首先说明的是你在过程里面看到很多的类似usmart,如果这些都是个人调试程序用的,如果你想进一步了解就看下我这个贴子,不想就直接忽略好了。http://www.amobbs.com/thread-5582408-1-1.html
记住,在网上找到一些USB例程无法直接使用在你的板子上面的原因之一就是硬件的原因,所以才需要稍微了解下原理才能根据自己的板子改装。本帖子会包括:STM32鼠标移植,STM32键盘移植,89C52鼠标移植,89C52键盘移植,可能后续还会有其他的USB作品的移植。
这里顺便说下几个文件,大致了解下就好啦
上传一个USB的讲解文档,写得不错。这里面首先是讲解了USB的一些基本知识,如果全部懂了的话,那么USB已经算是学得很好了。鼠标改键盘也有,还有关于具体库函数的详解,看完觉得很赞!!!下面有些内容引用自这个文件!!
文件地址: http://pan.baidu.com/s/1dDw2Hep
usb_desc.c: 提供了设备、端点、接口、字符串、群组、制造商描述符(本来想在这里讲解下描述符的,但是描述符在这里的作用不大明显,到USB键盘那块的时候再讲解吧)
问题八:在标准的 USB 请求命令中,经常会看到 Descriptor,这是什么来的呢?
回答八:Descriptor 即描述符,是一个完整的数据结构,可以通过 C 语言等编程实现,并存
储在 USB 设备中,用于描述一个 USB 设备的所有属性,USB 主机是通过一系列命令来要
求设备发送这些信息的。它的作用就是通过如问答节中的命令***作来给主机传递信息,从
而让主机知道设备具有什么功能、属于哪一类设备、要占用多少带宽、使用哪类传输方式及
数据量的大小,只有主机确定了这些信息之后,设备才能真正开始工作,所以描述符也是十
分重要的部分,要好好掌握。标准的描述符有 5 种,USB 为这些描述符定义了编号:
1——设备描述符
2——配置描述符
3——字符描述符
4——接口描述符
5——端点描述符
上面的描述符之间有一定的关系,一个设备只有一个设备描述符,而一个设备描述符可以包
含多个配置描述符,而一个配置描述符可以包含多个接口描述符,一个接口使用了几个端点,
就有几个端点描述符。这间描述符是用一定的字段构成的,详细说明见上面文档的10页。
简单理解里面就是USB设备的一些描述,像档案。
usb_prop.c: 提供了 Device_Property(性能),Device_Table &USER_STANDARD_REQUEST(请求)结构描述,这 3 个东西定义于 usb_core.c,摇杆鼠标的一些处理过程
hw_config.c: 提供了实际硬件需要的操作函数,Joystick_Send()通过函数UserToPMABufferCopy 和 SetEPTxValid 将坐标值发给了 USB 端口。
STM32f10x_it.c:里面有中断处理函数,千万别忽略里面的内容。
usb_core.c:USB 总线数据处理的核心文件,标准协议。
usb_init.c,usb_int.c:用于端点数据输入输入中断处理
usb_mem.c:用于缓冲区操作
usb_regs.c:用于寄存器操作
2.接下来要在main里面配置USB
详细步骤如下:
1. 官方例函数Set_System(); 用途: 配置好系统时钟等,按键IO口,以及上面我们提到的接通上拉电阻的那个管脚,USB管脚等等,
这个函数根据作用分为以下几个部分:
系统时钟配置部分(可以省略)
开启上拉时钟管脚的时钟,然后配置该管脚,这个管脚( USB_DISCONNECT_PIN )应该找到它定义的地方,将它改为你板子上面对应的管脚,可以用Ctrl+F找,我们找到它所在的地方如下图,因为这里涉及条件编译,所以你需要找到你对应编译部分的全部改掉。我需要将它改成PD3.
如果你的板子上面没有对应的管脚,只有类似下面的一个跳帽,就需要接上跳帽,接上去之后不要管这部分了,跟你都已经没有关系了。
![](https://i-blog.csdnimg.cn/blog_migrate/1202aec55bc46cc9a215a8de1c7954e4.jpeg)
- /* Enable the USB disconnect GPIO clock */
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_DISCONNECT, ENABLE);
- /* USB_DISCONNECT used as USB pull-up */
- GPIO_InitStructure.GPIO_Pin = USB_DISCONNECT_PIN;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
- GPIO_Init(USB_DISCONNECT, &GPIO_InitStructure);
- /*Set PA11,12 as IN - USB_DM,DP*/
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
-
- /*SET PA11,12 for USB: USB_DM,DP*/
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_14);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_14);
- /*Enable Joystick GPIOs clock*/
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOF, ENABLE);
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOE, ENABLE);
- /*Configure the JoyStick IOs as input floating*/
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_4 |
- GPIO_Pin_9 | GPIO_Pin_10;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; /*PullDown is mandatory for Joystick pins*/
- GPIO_Init(GPIOF, &GPIO_InitStructure);
- /*Configure the JoyStick IOs as input floating*/
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_5;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
- GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; /*PullDown is mandatory for Joystick pins*/
- GPIO_Init(GPIOB, &GPIO_InitStructure);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
- GPIO_Init(GPIOF, &GPIO_InitStructure);
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
- GPIO_Init(GPIOE, &GPIO_InitStructure);
- EXTI_ClearITPendingBit(EXTI_Line18);
- EXTI_InitStructure.EXTI_Line = EXTI_Line18;
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStructure);
上面几个步骤直接简化USB中断向量的设置,所以我在我的程序屏蔽了这个官方函数,直接自己写个,然后放在配置的最前面。
- EXTI_InitTypeDef EXTI_InitStructure;
- void USB_IT_Config(void)
- {
- EXTI_ClearITPendingBit(EXTI_Line18);
- EXTI_InitStructure.EXTI_Line = EXTI_Line18;
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
- EXTI_InitStructure.EXTI_LineCmd = ENABLE;
- EXTI_Init(&EXTI_InitStructure);
- }
3. 官方例 函数 Set_USBClock() 作用:开启USB时钟, 更准确的解释是: 配置USB 时钟,也就是从72M 的主频得到48M的USB 时钟(1.5 分频)
4. 官方例 函数 USB_Init(); 作用:USB硬件初始化, 更准确的解释是: 用于初始化USB,最主要的就是调用了Joystick_init函数,开启了USB部分的电源等
这里面有个很重要的东西要说下
- void USB_Init(void)
- {
- pInformation = &Device_Info;
- pInformation->ControlState = 2;
- pProperty = &Device_Property;
- pUser_Standard_Requests = &User_Standard_Requests;
- /* Initialize devices one by one */
- pProperty->Init();
- }
- typedef struct _DEVICE_PROP
- {
- void (*Init)(void); /* Initialize the device */
- void (*Reset)(void); /* Reset routine of this device */
- /* Device dependent process after the status stage */
- void (*Process_Status_IN)(void);
- void (*Process_Status_OUT)(void);
- /* Procedure of process on setup stage of a class specified request with data stage */
- /* All class specified requests with data stage are processed in Class_Data_Setup
- Class_Data_Setup()
- responses to check all special requests and fills ENDPOINT_INFO
- according to the request
- If IN tokens are expected, then wLength & wOffset will be filled
- with the total transferring bytes and the starting position
- If OUT tokens are expected, then rLength & rOffset will be filled
- with the total expected bytes and the starting position in the buffer
- If the request is valid, Class_Data_Setup returns SUCCESS, else UNSUPPORT
- CAUTION:
- Since GET_CONFIGURATION & GET_INTERFACE are highly related to
- the individual classes, they will be checked and processed here.
- */
- RESULT (*Class_Data_Setup)(uint8_t RequestNo);
- /* Procedure of process on setup stage of a class specified request without data stage */
- /* All class specified requests without data stage are processed in Class_NoData_Setup
- Class_NoData_Setup
- responses to check all special requests and perform the request
- CAUTION:
- Since SET_CONFIGURATION & SET_INTERFACE are highly related to
- the individual classes, they will be checked and processed here.
- */
- RESULT (*Class_NoData_Setup)(uint8_t RequestNo);
- /*Class_Get_Interface_Setting
- This function is used by the file usb_core.c to test if the selected Interface
- and Alternate Setting (uint8_t Interface, uint8_t AlternateSetting) are supported by
- the application.
- This function is writing by user. It should return "SUCCESS" if the Interface
- and Alternate Setting are supported by the application or "UNSUPPORT" if they
- are not supported. */
- RESULT (*Class_Get_Interface_Setting)(uint8_t Interface, uint8_t AlternateSetting);
- uint8_t* (*GetDeviceDescriptor)(uint16_t Length);
- uint8_t* (*GetConfigDescriptor)(uint16_t Length);
- uint8_t* (*GetStringDescriptor)(uint16_t Length);
- /* This field is not used in current library version. It is kept only for
- compatibility with previous versions */
- void* RxEP_buffer;
-
- uint8_t MaxPacketSize;
- }DEVICE_PROP;
- DEVICE_PROP Device_Property =
- {
- Joystick_init,
- Joystick_Reset,
- Joystick_Status_In,
- Joystick_Status_Out,
- Joystick_Data_Setup,
- Joystick_NoData_Setup,
- Joystick_Get_Interface_Setting,
- Joystick_GetDeviceDescriptor,
- Joystick_GetConfigDescriptor,
- Joystick_GetStringDescriptor,
- 0,
- 0x40 /*MAX PACKET SIZE*/
- };
- /*******************************************************************************
- * Function Name : USB_Cable_Config.
- * Description : Software Connection/Disconnection of USB Cable.
- * Input : NewState: new state.
- * Output : None.
- * Return : None
- *******************************************************************************/
- void USB_Cable_Config (FunctionalState NewState)
- {
- #if defined(STM32L1XX_MD) || defined(STM32L1XX_HD)|| defined(STM32L1XX_MD_PLUS)
- if (NewState != DISABLE)
- {
- STM32L15_USB_CONNECT;
- }
- else
- {
- STM32L15_USB_DISCONNECT;
- }
-
- #else
- if (NewState != DISABLE)
- {
- GPIO_ResetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);
- }
- else
- {
- GPIO_SetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);
- }
- #endif /* STM32L1XX_MD */
- }
要让你的电脑识别这是个鼠标的话,还需要配置中端函数,官方函数写在stm32_it.c这个文件里面,这个也是重点,我找了很久才知道在中断函数里面还有这样子的东西,相关代码如下:
- /******************************************************************************/
- /* STM32 Peripherals Interrupt Handlers */
- /*******************************************************************************
- * Function Name : USB_IRQHandler
- * Description : This function handles USB Low Priority interrupts
- * requests.
- * Input : None
- * Output : None
- * Return : None
- *******************************************************************************/
- #if defined(STM32L1XX_MD) || defined(STM32L1XX_HD)|| defined(STM32L1XX_MD_PLUS) || defined(STM32F37X)
- void USB_LP_IRQHandler(void)
- #else
- void USB_LP_CAN1_RX0_IRQHandler(void)
- #endif
- {
- USB_Istr();
- }
- #if defined(STM32L1XX_MD) || defined(STM32L1XX_HD)|| defined(STM32L1XX_MD_PLUS)
- /*******************************************************************************
- * Function Name : USB_FS_WKUP_IRQHandler
- * Description : This function handles USB WakeUp interrupt request.
- * Input : None
- * Output : None
- * Return : None
- *******************************************************************************/
- void USB_FS_WKUP_IRQHandler(void)
- {
- EXTI_ClearITPendingBit(EXTI_Line18);
- }
- #endif
- /*******************************************************************************
- * Function Name : EXTI_IRQHandler
- * Description : This function handles External lines interrupt request.
- * Input : None
- * Output : None
- * Return : None
- *******************************************************************************/
- #if defined(STM32L1XX_MD) || defined(STM32L1XX_HD)|| defined(STM32L1XX_MD_PLUS)
- void EXTI0_IRQHandler(void)
- #elif defined (STM32F37X)
- void EXTI2_TS_IRQHandler(void)
- #else
- void EXTI9_5_IRQHandler(void)
- #endif
- {
- if (EXTI_GetITStatus(KEY_BUTTON_EXTI_LINE) != RESET)
- {
- /* Check if the remote wakeup feature is enabled (it could be disabled
- by the host through ClearFeature request) */
- if (pInformation->Current_Feature & 0x20)
- {
- pInformation->Current_Feature &= ~0x20;
- /* Exit low power mode and re-configure clocks */
- Resume(RESUME_INTERNAL);
- }
-
- /* Clear the EXTI line pending bit */
- EXTI_ClearITPendingBit(KEY_BUTTON_EXTI_LINE);
- }
- }
- /*******************************************************************************
- * Function Name : USBWakeUp_IRQHandler
- * Description : This function handles USB WakeUp interrupt request.
- * Input : None
- * Output : None
- * Return : None
- *******************************************************************************/
- void USBWakeUp_IRQHandler(void)
- {
- EXTI_ClearITPendingBit(EXTI_Line18);
- }
- /*******************************************************************************
- * Function Name : USBWakeUp_IRQHandler
- * Description : This function handles USB WakeUp interrupt request.
- * Input : None
- * Output : None
- * Return : None
- *******************************************************************************/
- void USBWakeUp_IRQHandler(void)
- {
- EXTI_ClearITPendingBit(EXTI_Line18);
- }
- /*******************************************************************************
- * Function Name : USB_IRQHandler
- * Description : This function handles USB Low Priority interrupts
- * requests.
- * Input : None
- * Output : None
- * Return : None
- *******************************************************************************/
- void USB_LP_CAN1_RX0_IRQHandler(void)
- {
- USB_Istr();
- }
http://www.openedv.com/posts/list/33515.htm?privmsg=21890&&sysid=20#192355
3.真正控制鼠标
实际要控制鼠标还要看死循环里面的这些函数
- if (bDeviceState == CONFIGURED)
- {
- if ((JoyState() != 0) && (PrevXferComplete))
- {
- Joystick_Send(JoyState());
- }
- }
请教在设计USB键盘的时候怎么才能达到一直按下的效果: http://www.amobbs.com/thread-5581955-1-1.html
- void Joystick_Send(uint8_t Keys)
- {
- uint8_t Mouse_Buffer[4] = {0, 0, 0, 0};
- int8_t X = 0, Y = 0;
-
- switch (Keys)
- {
- case JOY_LEFT:
- X -= CURSOR_STEP;
- break;
- case JOY_RIGHT:
- X += CURSOR_STEP;
- break;
- case JOY_UP:
- Y -= CURSOR_STEP;
- break;
- case JOY_DOWN:
- Y += CURSOR_STEP;
- break;
- default:
- return;
- }
- /* prepare buffer to send */
- Mouse_Buffer[1] = X;
- Mouse_Buffer[2] = Y;
-
- /* Reset the control token to inform upper layer that a transfer is ongoing */
- PrevXferComplete = 0;
-
- /* Copy mouse position info in ENDP1 Tx Packet Memory Area*/
- USB_SIL_Write(EP1_IN, Mouse_Buffer, 4);
-
- /* Enable endpoint for transmission */
- SetEPTxValid(ENDP1);
- }
工程地址: http://pan.baidu.com/s/1jGMaXU2
http://v.youku.com/v_show/id_XNzE5MTM3NDUy.html
被山寨的作品的帖子: http://www.amobbs.com/thread-5531209-1-1.html 成本:
目前成本:100块
元件清单:STM32最小系统+MPU6050+几根杜邦线+USB线
成本最低可降至:40块。
最低成本元件清单:2元STC12单片机+25元USB模块(可能可以降至更低,自己做的话)+MPU6050(最低10块),可能企业生产的话会便宜很多。
涉及的软硬件知识:
1.USB通讯协议,用来模拟键盘用,这个最坑
2.I2C通讯协议,用来获取MPU6050数据用
3.MPU6050的使用
4.STM32单片机的使用
5.串口方面的东西,用来调试MPU6050输出数据
项目细节介绍:
1.下面这两句是精华,可以看一个文档,就知道这里面的奥秘,从获取的各轴加速度大概算出MPU6050的姿态。高中物理知识啊!! http://pan.baidu.com/s/1jGzajLs
- Roll = (((atan2(temp1[2],temp1[0])*180)/3.1416)-90); //x轴角度
- Pitch = (((atan2(temp1[2],temp1[1])*180)/3.1416)-90); //y轴角度
2.MPU6050可以看这个我发的帖子: http://www.amobbs.com/thread-5581033-1-1.html
放上工程文件: http://pan.baidu.com/s/1bn3Yg3t , 板子是野火ISO。
怎么将上面的鼠标改成键盘呢?鼠标和键盘在程序上的区别是什么?总体上来说,两者都是HID设备,很大部分都是一样,所以程序很大部分一样。不一样就是鼠标是鼠标,键盘是键盘,细节的东西不一样。可以参见两篇帖子,一篇是上面的飞车
一篇是圈圈的:
http://www.amobbs.com/thread-1387906-1-1.html
有人将其内容整理成文档,地址如下:
http://pan.baidu.com/s/1i3j3uEL
不过一开始读他们的帖子感觉还是很难懂。设备描述符可以结合上面的那个USB基础知识去看,多少页?都说是10页那里了。^__^这些比较繁琐,而且都有这么多资料了,大就慢慢看吧
按下某个键就相当于在按照相应的格式发送相应键的值过去,这些键的值就在这个文档里面,HID键盘代码: http://pan.baidu.com/s/1kThxxNh
from: https://www.amobbs.com/thread-5583091-1-1.html?_dsign=b0f4c123