前段時(shí)間我用一個(gè)國產(chǎn)MCU實(shí)現(xiàn)了雷蛇鍵盤的效果,按鍵支持十鍵無沖,RGB燈支持單控任意一個(gè)燈任意一種顏色,但是這個(gè)過程還是比較曲折的,原本以為鍵盤功能是最難搞的,低功耗處理是最簡單的,沒想到前面這么順利,最后才翻車了,所以特意出一期記錄一下我踩過的坑。
1 USB遠(yuǎn)程睡眠喚醒要注意的幾個(gè)點(diǎn)
1、配置描述符(Configuration Descriptor)要打開遠(yuǎn)程喚醒(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ù)總長度 | 包括該配置返回的所有描述符(配置、接口、端點(diǎn)、和專用的類型或者專用的廠商描述符)的總長度 |
3 | bNumInterfaces | 1 | Number | 配置支持的接口數(shù)量 | 最小值為0x01 |
4 | bConfigurationValue | 1 | Number | Get Configuration 和Set Configuration請求的配置值 | 必須為0x01或者更高值。取值為0的Set Configuration請求會(huì)使設(shè)備進(jìn)入未配置狀態(tài)(Not Configured state) |
5 | iConfiguration | 1 | Index | 字符串描述符索引 | 若沒有字符串描述符,這個(gè)字段的值為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í),設(shè)備自供電(Self-powered) Bit5: 遠(yuǎn)程喚醒(Remote Wakeup),bit5=1時(shí),設(shè)備支持遠(yuǎn)程喚醒 Bit4…0: 未使用,保留,必須為0 |
7 | bMaxPower | 1 | mA | 設(shè)備從總線獲取的最大功耗 | 當(dāng)設(shè)備完全運(yùn)行時(shí),特定配置的USB設(shè)備從總線取得的最大功耗 |
bmAttributes屬性的Bit5要置1,這樣才能打開遠(yuǎn)程喚醒(Remote Wakeup)功能,另外PC端也要在相應(yīng)的USB設(shè)備上打開“允許此設(shè)備喚醒計(jì)算機(jī)(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); //支持遠(yuǎn)程喚醒
0x32, //MaxPower(100mA);
2、USB的時(shí)鐘頻率不能低于48MHz且必須是48的倍數(shù)。
時(shí)鐘頻率要設(shè)對,否則會(huì)導(dǎo)致通訊異常。這個(gè)點(diǎn)原本我是知道的,但沒想到的是MCU在進(jìn)入休眠之后自動(dòng)切換到了內(nèi)部時(shí)鐘,而且在喚醒之后沒有切換回來,因此導(dǎo)致喚醒之后USB通訊異常。
所以,在上電初始化的時(shí)候以及休眠喚醒之后都需要配置好系統(tǒng)時(shí)鐘。
3、USB Device喚醒PC需要發(fā)送喚醒序列。
PC在進(jìn)入睡眠之后會(huì)主動(dòng)發(fā)送SetDeviceFeature
,設(shè)備端收到以后進(jìn)入掛起狀態(tài)(SUSPend)
并且USB進(jìn)入低功耗模式,如果設(shè)備需要喚醒PC的話則需要發(fā)送喚醒序列
,先使用RESUME_INTERNAL
喚醒設(shè)備本身,然后進(jìn)入遠(yuǎn)程喚醒狀態(tài)RESUME_START
,遠(yuǎn)程喚醒的操作就是把USB控制寄存器的第4位置1,然后等待10ms把USB控制寄存器的第4位置為0,最后進(jìn)入RESUME_OFF狀態(tài)
,設(shè)備的一次遠(yuǎn)程喚醒請求完成。
注:USB總線由SUSPend狀態(tài)
切換回CONFIGURED狀態(tài)
實(shí)際上是由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異常的幾個(gè)點(diǎn)
我在調(diào)試好鍵盤功能之后就開始著手做MCU的休眠,但是在調(diào)試的過程中發(fā)現(xiàn)了一些新的問題。
注:我用的MCU是ch32v203,這個(gè)MCU是一款國產(chǎn)IC,應(yīng)該是參考了stm32設(shè)計(jì)的,無論是硬件還是軟件都極其相似,因此,如果改用stm32或者其他stm32的替代方案可能也會(huì)有類似的問題。
1、不能在USB中斷服務(wù)函數(shù)里面讓MCU進(jìn)入休眠。
收到PC端傳過來的休眠信號之后,會(huì)進(jìn)USB中斷,然后進(jìn)入掛起狀態(tài)(SUSPend),我測試的時(shí)候圖方便直接在中斷里面讓MCU進(jìn)入了停機(jī)模式,結(jié)果MCU喚不醒了,可能是因?yàn)閱拘阎笠獜乃吣切写a繼續(xù)往后跑,但是因?yàn)樗呤窃谥袛喾?wù)函數(shù)里面的,喚醒之后進(jìn)不了這個(gè)中斷了,也就沒法繼續(xù)往下跑了。
2、睡眠之前要失能窗口看門狗。
這個(gè)問題有點(diǎn)莫名其妙,窗口看門狗是掛在APB1時(shí)鐘上面的,MCU進(jìn)入休眠的時(shí)候會(huì)關(guān)閉APB1時(shí)鐘,所以看門狗是不會(huì)影響休眠和喚醒的,實(shí)際上也是MCU休眠和喚醒的功能也是正常的,休眠之前USB和看門狗也是正常的,但是如果休眠時(shí)不先關(guān)閉看門狗時(shí)鐘,喚醒之后就會(huì)出現(xiàn)USB通訊異常的情況,我一時(shí)間也沒搞懂是什么原因,唯一有關(guān)聯(lián)的是USB和看門狗都是掛在APB1下面的,有大神可以解答一下我的疑惑嗎?
3、MCU休眠只能選擇WFE,選擇WFI的話USB無法喚醒MCU。
普通外部中斷喚醒(EXTI0-15)不管用WFI還是WFE都是可以正常使用的,USB中斷(EXTI18)在MCU休眠之前也是可以正常使用,但是一旦MCU通過WFI進(jìn)入休眠之后,就無法通過USB中斷喚醒了,這個(gè)時(shí)候哪怕通過其他外部中斷喚醒了MCU,USB也還是無法恢復(fù)正常通訊。
如果是用WFE則沒有這個(gè)問題,這就很奇怪了,中斷配置我也檢查過很多次了,并沒有發(fā)現(xiàn)什么問題,最后沒辦法就只能用WFE了。
結(jié)束語
關(guān)于USB遠(yuǎn)程睡眠喚醒的坑就講到這,這里其實(shí)只是列舉了一部分,因?yàn)檫@只是總結(jié)我遇到的新坑,有些以前踩過的坑這里就沒寫了,我也是第一次做USB的低功耗,沒想到會(huì)遇到這么多奇怪的問題。雖然最后問題都解決了,但是有些疑惑還是沒想明白,有大神知道的話還望不吝賜教!