從一篇開(kāi)始,我們將分享STEP BY STEP從0開(kāi)始基于DWC2實(shí)現(xiàn)主機(jī)驅(qū)動(dòng),本篇先介紹初始化,連接與斷開(kāi)連接的實(shí)現(xiàn)。
以下文件參考【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)”.
實(shí)際不支持主機(jī)協(xié)商協(xié)議,算不上真正的支持OTG,確切的說(shuō)法應(yīng)該是”支持USB主機(jī)或者USB設(shè)備”。

由于我們的模式是OTG_MODE=2,所以要么工作在HOST要么工作在DEVICE,不能動(dòng)態(tài)OTG協(xié)商,所以我們的SOC也沒(méi)有引出ID引腳。
GUSBCFG(偏移0x0C)寄存器的ForceDevMode(bit30)和ForceHstMode(bit29)分別置1,即強(qiáng)制配置為設(shè)備或者主機(jī)模式。


需要注意的是, 配置對(duì)應(yīng)的位之后至少要等25mS完成生效。如果使能了仿真加速模式(如何使能由具體的SOC提供)則只需要等待500uS。所以這里編程時(shí)需要注意。
一個(gè)典型的BUG或者不健壯的驅(qū)動(dòng)編寫(xiě)就是等待時(shí)間不夠,或者未確認(rèn)進(jìn)入HOST模式,就進(jìn)行后續(xù)初始化,此時(shí)操作HOST相關(guān)寄存器會(huì)產(chǎn)生ModeMis事件(GINTSTS.ModeMis bit1),并且這些操作行為是不正常的會(huì)導(dǎo)致功能異常。順帶提一句嵌入式開(kāi)發(fā)中一個(gè)比較常見(jiàn)的BUG或者導(dǎo)致系統(tǒng)不健壯的行為就是做一件事情不進(jìn)行確認(rèn)! 進(jìn)行軟復(fù)位,配置,狀態(tài)切換等等最好都按照規(guī)格書(shū)進(jìn)行狀態(tài)確認(rèn)。這種問(wèn)題往往存在隨機(jī)性,很可能難以測(cè)試出來(lái)。
那么如何確認(rèn)是否進(jìn)入了HOST模式呢,GINTSTS寄存器的bit0,CurMod為1則表示進(jìn)入了HOST模式,所以也可以不固定為等25mS,而是查詢(xún)等待GINTSTS.CurMod置1.
所以配置為主機(jī)模式的代碼如下,這里還配置了接口是使用ULPI還是UTMI。
? /* 全局配置? ? ?* 配置為主機(jī)模式? ? ?* 選擇ULPI還是UTMI接口? ? ?* force host之后必須等待25ms以上,等待轉(zhuǎn)為host? ? ?* [REF1] P346 在修改了GUSBCFG寄存器PHY相關(guān)的配置后需要進(jìn)行軟復(fù)位,所以以下部分放在復(fù)位前? ? ?* 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為主機(jī)模式 */? ? 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,或者直接查詢(xún)等待進(jìn)入HOST模式 */? ? while((USB_OTG_READ_REG(CFG_GINTSTS_ADDR) & CURMODSTS_MASK) == 0);
作為驅(qū)動(dòng)開(kāi)發(fā)一定要每個(gè)細(xì)節(jié)都掌握,我們實(shí)際還應(yīng)該測(cè)試下到底需要多久才轉(zhuǎn)為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,或者直接查詢(xún)等待進(jìn)入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即進(jìn)入了HOST,所以可以看出手冊(cè)一般都會(huì)留有比較大的裕度的。作為嵌入式開(kāi)發(fā)尤其是驅(qū)動(dòng)開(kāi)發(fā)就要了解每一個(gè)細(xì)節(jié),如果要求快啟動(dòng),那么這里就是一個(gè)優(yōu)化點(diǎn),也就是沒(méi)有必要等25mS,可以查詢(xún)進(jìn)入HOST之后即可,那么25-8mS就可以節(jié)省17mS的啟動(dòng)時(shí)間,這是相當(dāng)可觀的。

參考【REF2】的P67 ?3.2 Host Connection
流程如下

所以要先使能中斷
使能全局中斷:GAHBCFG寄存器(偏移0x08)的bit0 GlblIntrMsk置1

然后使能對(duì)應(yīng)的中斷類(lèi)型:GINTMSK寄存器(偏移0x18)的
ConIDStsChngMsk(bit28)置位,即ID狀態(tài)改變中斷
PrtIntMsk(bit24)置位,即端口相關(guān)中斷
相關(guān)代碼如下
? ? /* 1. Program GINTMSK.PrtInt to unmask.? ? ?* 使能主機(jī)端口狀態(tài)改變總中斷? ? ?* 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
流程如下

如果設(shè)備直接拔出的斷連:產(chǎn)生GINTSTS.DisconnInt和GINTSTS.ConIDStsChng兩個(gè)中斷
設(shè)備不拔出的斷連:只有一個(gè)GINTSTS.DisconnInt中斷.
中斷使能,在上面連接配置相關(guān)中斷使能的基礎(chǔ)上,配置GINTMSK寄存器(偏移0x18)的DisconnIntMsk(bit29)置位使能斷開(kāi)中斷。代碼見(jiàn)前面連接事件部分。
參考【REF2】的 P66 3.1 Host Initialization
這里先到1234步,詳細(xì)過(guò)程參考2.8小節(jié)。

先暫時(shí)只打印出對(duì)應(yīng)的事件方便調(diào)試,后面需要實(shí)現(xiàn)程序框架的事件處理。
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));? ? ? ? }? ? ? ? /* 標(biāo)志位都是W1C,所以寫(xiě)讀出來(lái)的值即清除所有中斷標(biāo)志 */? ? ? ? USB_OTG_WRITE_REG(CFG_HPRT_ADDR,hprt);? ? }? ? if((intsts & DISCONNINT_MASK) != 0)? ? {? ? ? ? /* 斷開(kāi)連接,CONLDSTSCHNG_MASK也產(chǎn)生則是直接拔出,否則是未拔出斷連 */? ? ? ? USBH_INFO_LOG(("disconnect\r\n"));? ? }? ? if((intsts & CONLDSTSCHNG_MASK) != 0)? ? {? ? ? ? /* 直接拔出設(shè)備的斷開(kāi)連接 */? ? ? ? USBH_INFO_LOG(("Connector ID Status Change\r\n"));? ? }? ? USB_OTG_WRITE_REG(CFG_GINTSTS_ADDR,intsts);? ? return 0;}
測(cè)試時(shí)可以使用一個(gè)設(shè)備,也可以使用一個(gè)1.5K電阻上拉到3.3V,然后分別接DP和DM,接和斷開(kāi)看是否監(jiān)測(cè)到連接和斷開(kāi)。
我這里打印如如下:
未接設(shè)備,程序運(yùn)行打印如下

插入設(shè)備打印如下

拔出設(shè)備打印如下

看到使能中斷后立即就進(jìn)入了
Connector ID Status Change中斷,然后進(jìn)入HOST后又進(jìn)入了一次Connector ID Status Change中斷,如下所示,第二次進(jìn)入可以通過(guò)intsts:54000020變?yōu)?/span>intsts:5400002b確認(rèn)。但是此時(shí)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

復(fù)位后打印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)));

所以在使能中斷前先清中斷,此時(shí)就只有使能HOST時(shí)一次Connector ID Status Change中斷了
USB_OTG_WRITE_REG(CFG_GINTSTS_ADDR,USB_OTG_READ_REG(CFG_GINTSTS_ADDR)); /* W1C寫(xiě)1清零 使能中斷前先清中斷,避免后面一使能中斷就進(jìn)入Connector ID Status Change中斷 */? ? /*? ? ?* 后續(xù)1234步驟,參考【REF2】 P66 3.1 Host Initialization? ? ?*/? ? /* 1. Program GINTMSK.PrtInt to unmask.? ? ?* 使能主機(jī)端口狀態(tài)改變總中斷? ? ?* 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);
再來(lái)測(cè)試,此時(shí)就只有一次進(jìn)入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

但是這里有個(gè)問(wèn)題,還是有ModeMis標(biāo)志
看我們的代碼懷疑是reset導(dǎo)致,intsts的CurMod清除了。
所以加個(gè)reset前后的intsts打印看一下

確實(shí)如我們所猜測(cè),復(fù)位前后intsts54000029變?yōu)榱?/span>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

所以復(fù)位后還是需要等待它重新變?yōu)?/span>host,因?yàn)?/span>FORCEHSTMODE不會(huì)被復(fù)位。
usbh_core_reset();? ? /* 復(fù)位之后INTSTS的bit0會(huì)被清零,變?yōu)榱朔荋OST模式,必須重新等待進(jìn)入HOST模式,因?yàn)镕orceHstMode不會(huì)被清零 */? ? 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)));
此時(shí)測(cè)試,就正常了。
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

注意先使用電阻上拉來(lái)測(cè)試,
開(kāi)始就使用轉(zhuǎn)接頭接設(shè)備進(jìn)行測(cè)試,可能轉(zhuǎn)接頭線序不對(duì)導(dǎo)致問(wèn)題,浪費(fèi)調(diào)試時(shí)間。
比如我手里的一個(gè)轉(zhuǎn)接頭,接線如下,接的GND,ID,DM,DP,但是沒(méi)接VBUS,那么此時(shí)接上沒(méi)有自供電的設(shè)備,比如鼠標(biāo)等設(shè)備就不能工作。

修改后如下,接GND,DM,DP,VBUS,ID未用(我們Forece)不接。

再整理下初始化過(guò)程如下,注意紅色框部分要特別注意。

以上僅實(shí)現(xiàn)了連接也斷開(kāi)連接的檢測(cè),后面我們還需要完善驅(qū)動(dòng)架構(gòu),采用基于狀態(tài)機(jī)和事件的處理方式實(shí)現(xiàn)各種狀態(tài)的轉(zhuǎn)換。并提供給用戶必要的接口,比如注冊(cè)事件事件回調(diào)等。
下面一些需要注意的地方總結(jié)下:
1.注意測(cè)試斷開(kāi)連接時(shí),如果使用轉(zhuǎn)接頭,注意確認(rèn)轉(zhuǎn)接頭的線序。最好是先使用1.5K電阻上拉到3.3V,然后接DP然后斷開(kāi)測(cè)試全速/高速連接與斷開(kāi),接DM然后斷開(kāi)測(cè)試低速連接與斷開(kāi)。
2.ForceHstMode之后要等待其確認(rèn)進(jìn)入HOST模式才能繼續(xù)操作。為了優(yōu)化速率可以查詢(xún)等待查詢(xún)到進(jìn)入HOST即可,而不比按照規(guī)格書(shū)等25mS以上。
3.CSftRst會(huì)導(dǎo)致INTSTS的狀態(tài)清除,即bit0的CurMod狀態(tài)清除又變回了默認(rèn)的DEVICE模式,所以此時(shí)需要重新等待進(jìn)入HOST模式,否則繼續(xù)操作HOST相關(guān)寄存器會(huì)進(jìn)入MisMatch。因?yàn)?/span>ForceHstMode是不會(huì)被CSftRst清除的,所以硬件會(huì)自動(dòng)重新進(jìn)入HOST,IBTSTS的bit0會(huì)重新置位。
4.使能中斷前,先清除中斷標(biāo)志,避免已開(kāi)始就進(jìn)入不需要的中斷。