前段時間我用一個國產(chǎn)MCU實現(xiàn)了雷蛇鍵盤的效果,按鍵支持十鍵無沖,RGB燈支持單控任意一個燈任意一種顏色,但是這個過程還是比較曲折的,原本以為鍵盤功能是最難搞的,低功耗處理是最簡單的,沒想到前面這么順利,最后才翻車了,所以特意出一期記錄一下我踩過的坑。
1 USB遠程睡眠喚醒要注意的幾個點
1、配置描述符(Configuration Descriptor)要打開遠程喚醒(Remote Wakeup)功能。
Configuration Descriptor
Offset | Field | Size | Value | Description | remark |
---|---|---|---|---|---|
0 | bLength | 1 | Number | 以字節(jié)為單位的描述符大小 | bLength以字節(jié)為單位的描述符大小(0x09) |
1 | bDescriptorType | 1 | Constant | 配置描述符類型 | 一般為CONFIGURATION (0x02) |
2 | wTotalLength | 2 | Number | 配置返回的數(shù)據(jù)總長度 | 包括該配置返回的所有描述符(配置、接口、端點、和專用的類型或者專用的廠商描述符)的總長度 |
3 | bNumInterfaces | 1 | Number | 配置支持的接口數(shù)量 | 最小值為0x01 |
4 | bConfigurationValue | 1 | Number | Get Configuration 和Set Configuration請求的配置值 | 必須為0x01或者更高值。取值為0的Set Configuration請求會使設(shè)備進入未配置狀態(tài)(Not Configured state) |
5 | iConfiguration | 1 | Index | 字符串描述符索引 | 若沒有字符串描述符,這個字段的值為0 |
6 | bmAttributes | 1 | Bitmap | 配置特性 | Bit7: USB1.0協(xié)議中表示總線供電(Bus Powered),設(shè)置bit7=1表示由總線供電(Bus Powered),其他協(xié)議該位保留(Reserved),必須設(shè)置為1 Bit6: 自供電(Self-powered),bits6=1時,設(shè)備自供電(Self-powered) Bit5: 遠程喚醒(Remote Wakeup),bit5=1時,設(shè)備支持遠程喚醒 Bit4…0: 未使用,保留,必須為0 |
7 | bMaxPower | 1 | mA | 設(shè)備從總線獲取的最大功耗 | 當(dāng)設(shè)備完全運行時,特定配置的USB設(shè)備從總線取得的最大功耗 |
bmAttributes屬性的Bit5要置1,這樣才能打開遠程喚醒(Remote Wakeup)功能,另外PC端也要在相應(yīng)的USB設(shè)備上打開“允許此設(shè)備喚醒計算機(O)”
。
Configuration Descriptor 部分配置參考示例:
0x09, //bLength(9); 配置描述符
0x02, //bDescriptorType(Configuration);
0x29,0x00, //wTotalLength(41);
0x01, //bNumInterfaces(1);
0x01, //bConfigurationValue(1);
0x00, //iConfiguration(0);
0xA0, //bmAttributes(BUSPower); //支持遠程喚醒
0x32, //MaxPower(100mA);
2、USB的時鐘頻率不能低于48MHz且必須是48的倍數(shù)。
時鐘頻率要設(shè)對,否則會導(dǎo)致通訊異常。這個點原本我是知道的,但沒想到的是MCU在進入休眠之后自動切換到了內(nèi)部時鐘,而且在喚醒之后沒有切換回來,因此導(dǎo)致喚醒之后USB通訊異常。
所以,在上電初始化的時候以及休眠喚醒之后都需要配置好系統(tǒng)時鐘。
3、USB Device喚醒PC需要發(fā)送喚醒序列。
PC在進入睡眠之后會主動發(fā)送SetDeviceFeature
,設(shè)備端收到以后進入掛起狀態(tài)(SUSPend)
并且USB進入低功耗模式,如果設(shè)備需要喚醒PC的話則需要發(fā)送喚醒序列
,先使用RESUME_INTERNAL
喚醒設(shè)備本身,然后進入遠程喚醒狀態(tài)RESUME_START
,遠程喚醒的操作就是把USB控制寄存器的第4位置1,然后等待10ms把USB控制寄存器的第4位置為0,最后進入RESUME_OFF狀態(tài)
,設(shè)備的一次遠程喚醒請求完成。
注:USB總線由SUSPend狀態(tài)
切換回CONFIGURED狀態(tài)
實際上是由Host決定的,Device只能發(fā)送喚醒序列,然后等Host返回ClearFeature
之后才能真正的喚醒USB,回到正常的CONFIGURED狀態(tài)
。
RESUME函數(shù)參考示例:
/*******************************************************************************
* @fn Resume
*
* @brief This is the state machine handling resume operations and
* timing sequence. The control is based on the Resume structure
* variables and on the ESOF interrupt calling this subroutine
* without changing machine state.
*
* @param a state machine value (RESUME_STATE)
* RESUME_ESOF doesn't change ResumeS.eState allowing
* decrementing of the ESOF counter in different states.
*
* @return None.
*/
void Resume(RESUME_STATE eResumeSetVal)
{
uint16_t wCNTR;
if (eResumeSetVal != RESUME_ESOF)
{
ResumeS.eState = eResumeSetVal;
}
switch (ResumeS.eState)
{
case RESUME_EXTERNAL:
if (remotewakeupon ==0)
{
Resume_Init();
ResumeS.eState = RESUME_OFF;
}
else
{
ResumeS.eState = RESUME_ON;
}
break;
case RESUME_INTERNAL:
Resume_Init();
ResumeS.eState = RESUME_START;
remotewakeupon = 1;
break;
case RESUME_LATER:
ResumeS.bESOFcnt = 2;
ResumeS.eState = RESUME_WAIT;
break;
case RESUME_WAIT:
ResumeS.bESOFcnt--;
if (ResumeS.bESOFcnt == 0)
ResumeS.eState = RESUME_START;
break;
case RESUME_START:
wCNTR = _GetCNTR();
wCNTR |= CNTR_RESUME;
_SetCNTR(wCNTR);
ResumeS.eState = RESUME_ON;
ResumeS.bESOFcnt = 10;
break;
case RESUME_ON:
ResumeS.bESOFcnt--;
if (ResumeS.bESOFcnt == 0)
{
wCNTR = _GetCNTR();
wCNTR &= (~CNTR_RESUME);
_SetCNTR(wCNTR);
ResumeS.eState = RESUME_OFF;
remotewakeupon = 0;
}
break;
case RESUME_OFF:
case RESUME_ESOF:
default:
ResumeS.eState = RESUME_OFF;
break;
}
}
2 MCU喚醒之后引起USB異常的幾個點
我在調(diào)試好鍵盤功能之后就開始著手做MCU的休眠,但是在調(diào)試的過程中發(fā)現(xiàn)了一些新的問題。
注:我用的MCU是ch32v203,這個MCU是一款國產(chǎn)IC,應(yīng)該是參考了stm32設(shè)計的,無論是硬件還是軟件都極其相似,因此,如果改用stm32或者其他stm32的替代方案可能也會有類似的問題。
1、不能在USB中斷服務(wù)函數(shù)里面讓MCU進入休眠。
收到PC端傳過來的休眠信號之后,會進USB中斷,然后進入掛起狀態(tài)(SUSPend),我測試的時候圖方便直接在中斷里面讓MCU進入了停機模式,結(jié)果MCU喚不醒了,可能是因為喚醒之后要從睡眠那行代碼繼續(xù)往后跑,但是因為睡眠是在中斷服務(wù)函數(shù)里面的,喚醒之后進不了這個中斷了,也就沒法繼續(xù)往下跑了。
2、睡眠之前要失能窗口看門狗。
這個問題有點莫名其妙,窗口看門狗是掛在APB1時鐘上面的,MCU進入休眠的時候會關(guān)閉APB1時鐘,所以看門狗是不會影響休眠和喚醒的,實際上也是MCU休眠和喚醒的功能也是正常的,休眠之前USB和看門狗也是正常的,但是如果休眠時不先關(guān)閉看門狗時鐘,喚醒之后就會出現(xiàn)USB通訊異常的情況,我一時間也沒搞懂是什么原因,唯一有關(guān)聯(lián)的是USB和看門狗都是掛在APB1下面的,有大神可以解答一下我的疑惑嗎?
3、MCU休眠只能選擇WFE,選擇WFI的話USB無法喚醒MCU。
普通外部中斷喚醒(EXTI0-15)不管用WFI還是WFE都是可以正常使用的,USB中斷(EXTI18)在MCU休眠之前也是可以正常使用,但是一旦MCU通過WFI進入休眠之后,就無法通過USB中斷喚醒了,這個時候哪怕通過其他外部中斷喚醒了MCU,USB也還是無法恢復(fù)正常通訊。
如果是用WFE則沒有這個問題,這就很奇怪了,中斷配置我也檢查過很多次了,并沒有發(fā)現(xiàn)什么問題,最后沒辦法就只能用WFE了。
結(jié)束語
關(guān)于USB遠程睡眠喚醒的坑就講到這,這里其實只是列舉了一部分,因為這只是總結(jié)我遇到的新坑,有些以前踩過的坑這里就沒寫了,我也是第一次做USB的低功耗,沒想到會遇到這么多奇怪的問題。雖然最后問題都解決了,但是有些疑惑還是沒想明白,有大神知道的話還望不吝賜教!