前言
最近学习了USB2.0的一些知识,这里就当笔记,回顾记录一下,参考的资料是B站的刘凯老师STM32 培训教程和龙虎老师的USB应用精粹分析,如果有哪些错误的理解,也希望大佬们指点一二,谢谢!
前面也说了USB的一些基本概念,但是作为软件开发学习的时候看这些还是空洞,我还是更喜欢看代码,从代码里知道整个工作流程,然后从这些工作流程反推一些USB的知识,我后面是以ST的USB官方例程来看这些,下面就是我个人的一些理解,简单讲一下USB的初始化过程。
是这个版本的代码
无论学习单片机哪个外设的开发,寄存器的学习是一定要的,
1.USB_ISTR寄存器,
他是USB的中断状态寄存器,当事务完成一次传输的时候, CTR位置都会被硬件置位。
事务传输针对的端点号就在EP_ID[3:0]中,
如果是方向呢?从机到主机?还是主机到从机?他是在DIR位上,
如果DIR为1则代表一个OUT事务完成,DIR为0则代表一个IN事务完成
注意:CTR DIR EP_ID 都是只读的。我们要做的就是读取这三个位上的信息做不同处理
2.USB_EPnR寄存器
他是端点寄存器,当USB_ISTR寄存器DIR为0的时候则代表一个IN事务完成,此时CTR_TX就会被硬件置位,如果是DIR为1的时候,代表一个OUT事务完成,此时CTR_RX就会被硬件置位,那如果是SETUP事件呢,那么相应CTR_RX和SETUP位都会被置位,那么我们通过判断SETUP位,就能指代发生的是OUT事件还是SETUP事件。
3.USB_CNTR寄存器
代码环节
简单介绍了几个寄存器,我们就进入代码环节了.
1.第一个就是 usb_regs.h 头文件
为了方便我们使用USB控制器,官方定义了大量的宏定义来便捷我们操作底层寄存器,我其实我们以后用到别的芯片时候可以把他们复制过来,改写一下就好了。我简单介绍一下吧,_SetCNTR函数就是用来设置 USB_CNTR寄存器,_GetCNTR函数就是用来获取CNTR寄存器的值,_pEPTxAddr函数用来设置端点对应发送包缓冲区的起始地址,_pEPRxCount函数用来设置接收包缓冲区的字节数
2.看看USB控制器如何被初始化
首当其冲肯定就是main.c,前面两个设置中断优先级和设置USB控制时钟,
最重要的就是USB_Init();
1.USB_Init()被定义在usb_init.c中![6be95087e39e43f69c4f483c7d6ba481.png](https://img-blog.csdnimg.cn/direct/6be95087e39e43f69c4f483c7d6ba481.png)
首先注意在这里面定义了几个全局变量,都是很重要的
第一个就是 EPindex 用来保存当前的端点号,指出当前传输完成的事务是针对哪个端点,
对应我们上面USB_ISTR寄存器中的EP_ID[3:0],实际工作中我们会把其数据读出来放在EPindex中
DEVICE_INFO,DEVICE_PROP,USB_STANDARD_REQUESTS 这三个结构体都在usb_core.h
2.usb_core.h
(1)DEVICE_INFO结构体就是用来保存当前设备的相关信息
前五个字段表示接收到的请求,也就是主机通过控制管道往设备发送SETUP事务中的数据包信息
而剩下的那些uint8_t类型的变量表示当前设备的控制管道状态 特征 配置 接口信息 就是设备接收到主机请求的执行结果 (其实这里说的基本都是端点0 的枚举时刻 )
最后那个结构体ENDPOINT_INFO ,他有几个成员,分别就是以下作用
获取描述符的时候
wLength表示设备有多少个数据需要发送
wOffset表示要发送的描述符数组的偏移量
PacketSize表示每次发送数据包的字节大小
函数指针CopyData指向数据发送或接收的函数
(2)DEVICE_PORP表示设备属性结构体
前十个都是函数指针,函数指针就是某个函数的入口地址,这些函数具体是我们实现的,也就是回调函数
(3) USB_STANDARD_REQUESTS结构体是用来响应主机的标准请求
这个结构体里也都是函数指针,用户代码和标准请求管理之间的接口
中断掩码变量 wInterrupt_Mask 默认为0被用来设置USB_CNTR寄存器
来看看USB_Init()做了啥事吧
(1)首先就是把代表设备信息的全局变量赋值给 pInformation 指针 也就是后面我们操控设备信息都可以直接用指针了。
(2)接下来我们设置控制状态为 2 就是USB_core.h 中的输入数据状态
这个结构体 _CONTROL_STATE里面(控制传输分为初始设置时期,数据时期这个可选,状态时期,这三种时期,因为在进行一个个事务传输的时候,USB控制器只知道一个个事务是不知道所处在的状态的,比如控制传输初始建立时期后出现了一个IN事务,那么他到底是数据时期还是状态时期呢,这时候这个结构体就起作用了 )
(3)就是将Device_Property结构体的地址赋值给pProperty指针
Device_Property这个结构体里都是函数,pProperty指针里都是函数指针,为了解耦,都是回调函数了
(4)就是将User_Standard_Requests结构体的地址赋值给pUser_Standard_Requests指针
(5)最后pProperty指针调用了初始化函数 进行初始化 执行的是void Joystick_init(void)
Get_SerialNum();//更新设备序列号里的字符串描述符
pInformation->Current_Configuration = 0; //表示当前设备未配置
PowerOn 函数
(1)拉低控制三极管开关电路的引脚为PD9 让上拉电阻与D+信号线相连,不过这里硬件直接相连了。
(2)设置USB_CNTR寄存器的FRES位,置一,强制复位,同时清除PDWN位,退出断电模式
(3)紧接着又全部清除了USB_ISTR寄存器中所有的中断请求位置,
然后仅仅使能了CNTR寄存器里USB复位,挂起,唤醒这三个中断屏蔽位
USB_SIL_Init();
又全部清除一遍ISTR寄存器
然后使能了 IMR_MSK 对应的 正确传输(CTR) 出错(ERR) 唤醒(WKUP)挂起(SUSP)
复位(RESET) 帧首(SOF) 期望帧首(ESOF) 这些中断掩码位(固件库里是有响应这些中断的中断服务函数)
最后 设备状态bDeviceState设置为未连接UNCONNECTED ,枚举以后才是已连接
学习做的一些笔记写的可能有点乱