从一篇开始,我们将分享STEP BY STEP从0开始基于DWC2实现主机驱动,本篇先介绍初始化,连接与断开连接的实现。
以下文件参考【REF1】和【REF2】指的
【REF1】指DWC_otg_databook.pdf
【REF2】指DesignWare Cores USB 2.0 Hi-Speed On-The Go (OTG) Programming Guide.pdf
GHWCFG2寄存器的bit[2:0]OtgMode,可以查看IP配置的模式.该寄存器的偏移地址是0x48.
我们这里查看到的模式是2,即不支持HNP和SRP的OTG模式,
“3'b010: Non-HNP and Non-SRP Capable OTG (Host and Device)”.
实际不支持主机协商协议,算不上真正的支持OTG,确切的说法应该是”支持USB主机或者USB设备”。
由于我们的模式是OTG_MODE=2,所以要么工作在HOST要么工作在DEVICE,不能动态OTG协商,所以我们的SOC也没有引出ID引脚。
GUSBCFG(偏移0x0C)寄存器的ForceDevMode(bit30)和ForceHstMode(bit29)分别置1,即强制配置为设备或者主机模式。
需要注意的是, 配置对应的位之后至少要等25mS完成生效。如果使能了仿真加速模式(如何使能由具体的SOC提供)则只需要等待500uS。所以这里编程时需要注意。
一个典型的BUG或者不健壮的驱动编写就是等待时间不够,或者未确认进入HOST模式,就进行后续初始化,此时操作HOST相关寄存器会产生ModeMis事件(GINTSTS.ModeMis bit1),并且这些操作行为是不正常的会导致功能异常。顺带提一句嵌入式开发中一个比较常见的BUG或者导致系统不健壮的行为就是做一件事情不进行确认! 进行软复位,配置,状态切换等等最好都按照规格书进行状态确认。这种问题往往存在随机性,很可能难以测试出来。
那么如何确认是否进入了HOST模式呢,GINTSTS寄存器的bit0,CurMod为1则表示进入了HOST模式,所以也可以不固定为等25mS,而是查询等待GINTSTS.CurMod置1.
所以配置为主机模式的代码如下,这里还配置了接口是使用ULPI还是UTMI。
/* 全局配置
* 配置为主机模式
* 选择ULPI还是UTMI接口
* force host之后必须等待25ms以上,等待转为host
* [REF1] P346 在修改了GUSBCFG寄存器PHY相关的配置后需要进行软复位,所以以下部分放在复位前
* Typically software reset is used during software development
* and also when you dynamically change the PHY selection
* bits in the USB configuration registers listed above.
*/
tmp = USB_OTG_READ_REG(CFG_GUSBCFG_ADDR);
tmp |= FORCEHSTMODE_MASK; /* Force为主机模式 */
tmp &= ~PHYSEL_MASK; /* bit6-1'b0: USB 2.0 high-speed UTMI+ or ULPI PHY */
tmp |= ULPI_UTMI_SEL_MASK; /* bit4-ULPI_UTMI_SEL_RANGE 1:ULPI */
tmp &= ~PHYIF_MASK; /* bit3-PHYIF_RANGE 8bit */
tmp &= ~ULPI_UTMI_SEL_MASK; /* bit4-ULPI_UTMI_SEL_RANGE 0:UTMI+ */
tmp |= PHYIF_MASK; /* bit3-PHYIF_RANGE phy 16bitx */
USB_OTG_WRITE_REG(CFG_GUSBCFG_ADDR,tmp);
//timer_delay_ms(25); /* 可以固定等待25mS,或者直接查询等待进入HOST模式 */
while((USB_OTG_READ_REG(CFG_GINTSTS_ADDR) & CURMODSTS_MASK) == 0);
作为驱动开发一定要每个细节都掌握,我们实际还应该测试下到底需要多久才转为HOST模式。
USB_OTG_WRITE_REG(CFG_GUSBCFG_ADDR,tmp);
USBH_INFO_LOG(("force host:%d\r\n",timer_get_time()));
//timer_delay_ms(25); /* 可以固定等待25mS,或者直接查询等待进入HOST模式 */
while((USB_OTG_READ_REG(CFG_GINTSTS_ADDR) & CURMODSTS_MASK) == 0);
USBH_INFO_LOG(("enter host:%d\r\n",timer_get_time()));
打印如下,这里单位是uS,所以看到14095uS-6256uS不到8mS即进入了HOST,所以可以看出手册一般都会留有比较大的裕度的。作为嵌入式开发尤其是驱动开发就要了解每一个细节,如果要求快启动,那么这里就是一个优化点,也就是没有必要等25mS,可以查询进入HOST之后即可,那么25-8mS就可以节省17mS的启动时间,这是相当可观的。
参考【REF2】的P67 3.2 Host Connection
流程如下
所以要先使能中断
使能全局中断:GAHBCFG寄存器(偏移0x08)的bit0 GlblIntrMsk置1
然后使能对应的中断类型:GINTMSK寄存器(偏移0x18)的
ConIDStsChngMsk(bit28)置位,即ID状态改变中断
PrtIntMsk(bit24)置位,即端口相关中断
相关代码如下
/* 1. Program GINTMSK.PrtInt to unmask.
* 使能主机端口状态改变总中断
* GINTMSK.PrtIntMsk(bit[24])=1 unmask
*/
tmp = USB_OTG_READ_REG(CFG_GINTMSK_ADDR);
tmp |= PRTINTMSK_MASK;
tmp |= DISCONNINTMSK_MASK;
tmp |= CONLDSTSCHNGMSK_MASK;
USB_OTG_WRITE_REG(CFG_GINTMSK_ADDR,tmp);
参考【REF2】的P67 3.3 Host Disconnection
流程如下
如果设备直接拔出的断连:产生GINTSTS.DisconnInt和GINTSTS.ConIDStsChng两个中断
设备不拔出的断连:只有一个GINTSTS.DisconnInt中断.
中断使能,在上面连接配置相关中断使能的基础上,配置GINTMSK寄存器(偏移0x18)的DisconnIntMsk(bit29)置位使能断开中断。代码见前面连接事件部分。
参考【REF2】的 P66 3.1 Host Initialization
这里先到1234步,详细过程参考2.8小节。
先暂时只打印出对应的事件方便调试,后面需要实现程序框架的事件处理。
static uint32_t usbh_isr_handler(uint32_t vector, uint32_t arg)
{
(void)vector;
(void)arg;
uint32_t intsts;
uint32_t hprt;
intsts = USB_OTG_READ_REG(CFG_GINTSTS_ADDR) & USB_OTG_READ_REG(CFG_GINTMSK_ADDR);
hprt = USB_OTG_READ_REG(CFG_HPRT_ADDR);
USBH_INFO_LOG(("intsts:%#x,prtint:%#x\r\n",USB_OTG_READ_REG(CFG_GINTSTS_ADDR),hprt));
if((intsts & PRTINT_MASK) != 0)
{
if((hprt & PRTCONNDET_MASK) != 0)
{
int speed;
speed = (hprt&PRTSPD_MASK)>>PRTSPD_OFFSET;
if(speed == 0)
{
s_subh_dev.speed = USBH_SPEED_HIGH;
}
else if(speed == 1)
{
s_subh_dev.speed = USBH_SPEED_FULL;
}
else
{
s_subh_dev.speed = USBH_SPEED_LOW;
}
USBH_INFO_LOG(("connect:%d\r\n",speed));
}
/* 标志位都是W1C,所以写读出来的值即清除所有中断标志 */
USB_OTG_WRITE_REG(CFG_HPRT_ADDR,hprt);
}
if((intsts & DISCONNINT_MASK) != 0)
{
/* 断开连接,CONLDSTSCHNG_MASK也产生则是直接拔出,否则是未拔出断连 */
USBH_INFO_LOG(("disconnect\r\n"));
}
if((intsts & CONLDSTSCHNG_MASK) != 0)
{
/* 直接拔出设备的断开连接 */
USBH_INFO_LOG(("Connector ID Status Change\r\n"));
}
USB_OTG_WRITE_REG(CFG_GINTSTS_ADDR,intsts);
return 0;
}
测试时可以使用一个设备,也可以使用一个1.5K电阻上拉到3.3V,然后分别接DP和DM,接和断开看是否监测到连接和断开。
我这里打印如如下:
未接设备,程序运行打印如下
插入设备打印如下
拔出设备打印如下
看到使能中断后立即就进入了
Connector ID Status Change中断,然后进入HOST后又进入了一次Connector ID Status Change中断,如下所示,第二次进入可以通过intsts:54000020变为intsts:5400002b确认。但是此时intsts:5400002b的bit1 ModeMis也置位了。
core init:
force host:6241
enter host:14080
init intsts:54000020,prtint:0
intsts:54000020,prtint:0
Connector ID Status Change
intsts:5400002b,prtint:1000
Connector ID Status Change
复位后打印intsts初始值,也可以看到是init intsts:54000020
ConIDStsChng是置位的
usbh_core_reset();
USBH_INFO_LOG(("init intsts:%#x,prtint:%#x\r\n",USB_OTG_READ_REG(CFG_GINTSTS_ADDR),USB_OTG_READ_REG(CFG_HPRT_ADDR)));
所以在使能中断前先清中断,此时就只有使能HOST时一次Connector ID Status Change中断了
USB_OTG_WRITE_REG(CFG_GINTSTS_ADDR,USB_OTG_READ_REG(CFG_GINTSTS_ADDR)); /* W1C写1清零 使能中断前先清中断,避免后面一使能中断就进入Connector ID Status Change中断 */
/*
* 后续1234步骤,参考【REF2】 P66 3.1 Host Initialization
*/
/* 1. Program GINTMSK.PrtInt to unmask.
* 使能主机端口状态改变总中断
* GINTMSK.PrtIntMsk(bit[24])=1 unmask
*/
tmp = USB_OTG_READ_REG(CFG_GINTMSK_ADDR);
tmp |= PRTINTMSK_MASK;
tmp |= DISCONNINTMSK_MASK;
tmp |= CONLDSTSCHNGMSK_MASK;
USB_OTG_WRITE_REG(CFG_GINTMSK_ADDR,tmp);
再来测试,此时就只有一次进入Connector ID Status Change中断了。
core init:
force host:6243
enter host:14082
init intsts:54000020,prtint:0
intsts:1400002b,prtint:1000
Connector ID Status Change
但是这里有个问题,还是有ModeMis标志
看我们的代码怀疑是reset导致,intsts的CurMod清除了。
所以加个reset前后的intsts打印看一下
确实如我们所猜测,复位前后intsts54000029变为了54000020,
core init:
force host:6235
enter host:14073
before reset intsts:54000029,prtint:0
after reset intsts:54000020,prtint:0
intsts:1400002b,prtint:1000
Connector ID Status Change
所以复位后还是需要等待它重新变为host,因为FORCEHSTMODE不会被复位。
usbh_core_reset();
/* 复位之后INTSTS的bit0会被清零,变为了非HOST模式,必须重新等待进入HOST模式,因为ForceHstMode不会被清零 */
USBH_INFO_LOG(("wait host:%d\r\n",timer_get_time()));
while((USB_OTG_READ_REG(CFG_GINTSTS_ADDR) & CURMODSTS_MASK) == 0);
USBH_INFO_LOG(("enter host:%d\r\n",timer_get_time()));
USBH_INFO_LOG(("after reset intsts:%#x,prtint:%#x\r\n",USB_OTG_READ_REG(CFG_GINTSTS_ADDR),USB_OTG_READ_REG(CFG_HPRT_ADDR)));
此时测试,就正常了。
Reset Cause:0
Boot reason:POR
core init:
force host:6250
enter host:14089
before reset intsts:54000029,prtint:0
wait host:14636
enter host:24410
after reset intsts:54000029,prtint:0
注意先使用电阻上拉来测试,
开始就使用转接头接设备进行测试,可能转接头线序不对导致问题,浪费调试时间。
比如我手里的一个转接头,接线如下,接的GND,ID,DM,DP,但是没接VBUS,那么此时接上没有自供电的设备,比如鼠标等设备就不能工作。
修改后如下,接GND,DM,DP,VBUS,ID未用(我们Forece)不接。
再整理下初始化过程如下,注意红色框部分要特别注意。
以上仅实现了连接也断开连接的检测,后面我们还需要完善驱动架构,采用基于状态机和事件的处理方式实现各种状态的转换。并提供给用户必要的接口,比如注册事件事件回调等。
下面一些需要注意的地方总结下:
1.注意测试断开连接时,如果使用转接头,注意确认转接头的线序。最好是先使用1.5K电阻上拉到3.3V,然后接DP然后断开测试全速/高速连接与断开,接DM然后断开测试低速连接与断开。
2.ForceHstMode之后要等待其确认进入HOST模式才能继续操作。为了优化速率可以查询等待查询到进入HOST即可,而不比按照规格书等25mS以上。
3.CSftRst会导致INTSTS的状态清除,即bit0的CurMod状态清除又变回了默认的DEVICE模式,所以此时需要重新等待进入HOST模式,否则继续操作HOST相关寄存器会进入MisMatch。因为ForceHstMode是不会被CSftRst清除的,所以硬件会自动重新进入HOST,IBTSTS的bit0会重新置位。
4.使能中断前,先清除中断标志,避免已开始就进入不需要的中断。