大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是改動i.MXRT1xxx裡IOMUXC_GPR暫存器保留位可能會造成系統異常。
痞子衡的嵌入式技術交流群裡有一位非常活躍的朋友(網名:文,痞子衡已經指定他為副群主)近日向痞子衡反映了一個在i.MXRT1062應用程式裡動態調整FlexRAM導致WDOG模組工作異常的問題,經過一番排查,痞子衡發現了i.MXRT晶片系統設計裡的一個小祕密,這個祕密警示我們在MCU裡應儘量遵循謹慎的外設暫存器賦值法,這個暫存器謹慎賦值法是什麼,痞子衡先賣個關子,文末會揭祕。痞子衡今天就將這個問題解決過程還原一下,希望對大家有所啟發:
一、重配FlexRAM影響WDOG的表象問題
痞子衡先交待一下問題背景,這個網友是在i.MXRT1062板子上做的測試,使用的是 \SDK_EVK-MIMXRT1060\boards\evkmimxrt1060\driver_examples\wdog\iar 例程(XiP),他對工程啟動檔案和主函式改動如下:
int main(void)
{
wdog_config_t config;
BOARD_ConfigMPU();
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
PRINTF("\r\n******** System Start ********\r\n");
// 使能WDOG模組,設定Timeout時間,不啟用中斷
WDOG_GetDefaultConfig(&config);
// Timeout value is (0xF + 1)/2 = 8 sec.
config.timeoutValue = 0xFU;
WDOG_Init(DEMO_WDOG_BASE, &config);
PRINTF("--- wdog Init done---\r\n");
while (1)
{
// 故意不喂狗,讓WDOG超時復位系統
//WDOG_Refresh(DEMO_WDOG_BASE);
PRINTF(" \r\nWDOG has be refreshed!");
/* Delay. */
delay(SystemCoreClock);
}
}
他在啟動檔案 startup_MIMXRT1062.s 裡將預設128KB ITCM、128KB DTCM、256KB OCRAM的FlexRAM分配調整成了256KB DTCM、256KB OCRAM(關於FlexRAM基本知識詳見痞子衡舊文 《百變星君FlexRAM》),這種FlexRAM動態調整方式僅適用XiP工程。最終執行結果裡看,應用程式似乎僅執行了一次,沒有像預想得那樣重複啟動執行。
如果在 startup_MIMXRT1062.s 裡將重配FlexRAM程式碼去掉,這個WDOG例程是可以正常工作的,串列埠助手裡可以看到迴圈列印,所以這很容易讓人推斷出FlexRAM重配功能導致WDOG模組工作異常了。
二、找到程式異常的根本原因
由於這個WDOG例程並不是完全功能異常,至少首次列印是有的,說明重配FlexRAM並沒有對程式堆疊運存等造成實質影響,啟動檔案裡那段重配FlexRAM程式碼本身沒有邏輯問題。而列印輸出在WDOG超時時間到了之後就沒有了,看起來WDOG模組應該是正常產生了軟復位。為了最小化程式碼去定位問題,痞子衡將這個網友WDOG例程主函式修改如下,去掉WDOG相關程式碼,直接用 NVIC_SystemReset() 代替。執行後發現,仍然僅有一次列印,這個實驗的意義是那段重配FlexRAM程式碼會導致軟復位後程式沒法再次執行,而跟具體WDOG模組無關。
int main(void)
{
BOARD_ConfigMPU();
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
PRINTF("\r\n******** System Start ********\r\n");
while (1)
{
NVIC_SystemReset();
}
}
我們現在將焦點放回到重配FlexRAM那段彙編程式碼本身,程式碼很簡單,就是將i.MXRT晶片內部的IOMUXC_GPR->GPR17(基址0x400ac044)和IOMUXC_GPR->GPR16(基址0x400ac040)分別整體賦值為0x5555aaaa和0x00000007,單純從暫存器有效功能位定義上來看,這樣操作是沒問題的。
LDR R0,=0x400AC044
LDR R1,=0x5555aaaa
STR R1,[R0]
LDR R0,=0x400AC040
LDR R1,=0x00000007
STR R1,[R0]
翻看手冊裡關於IOMUXC_GPR->GPR17和IOMUXC_GPR->GPR16暫存器的位定義,發現IOMUXC_GPR->GPR16暫存器中有很多bit是保留位,並且其中bit21保留位預設值是1,與其他保留位預設值0不一樣。顯然 IOMUXC_GPR->GPR16 = 0x00000007 這樣的賦值語句會將其bit21誤清零,並且IOMUXC_GPR暫存器在軟復位後也不會改變其值 (參見《SystemReset不復位的GPR暫存器小結》一文)。
難道問題是由IOMUXC_GPR->GPR16[21]保留位被誤清零導致的?死馬當活馬醫吧,我們修改一下重配FlexRAM程式碼如下(兩種方式都行),將IOMUXC_GPR->GPR16[21]保持為預設1。執行後發現,異常問題解決了,串列埠助手裡可以看到迴圈列印。現在我們知道了IOMUXC_GPR暫存器即使是保留位也不要輕易當使用者標誌位使用,更不要輕易改變其預設值,因為SoC佔用了這些位,具體用途未詳述。可以推測IOMUXC_GPR->GPR16[21]位跟系統啟動有關,並且其值的設定是在軟復位後才生效的。
#ifdef FLEXRAM_CFG_STANDARD
LDR R0,=0x400AC044
MOV32 R1,0x5555aaaa
STR R1,[R0]
LDR R0,=0x400AC040
LDR R1,[R0]
ORR R1,R1,#4
STR R1,[R0]
#else
LDR R0,=0x400AC044
LDR R1,=0x5555aaaa
STR R1,[R0]
LDR R0,=0x400AC040
LDR R1,=0x00200007
STR R1,[R0]
#endif
三、MCU外設暫存器謹慎賦值法
現在痞子衡揭祕文章開頭賣的關子,到底什麼是謹慎的外設暫存器賦值法。其實可以從晶片標頭檔案定義裡去學,假設我們有一個模組叫PERIPH,模組內部有一個名為REG的暫存器,這個暫存器中有功能位FUNC(單bit或者多bit),晶片標頭檔案中通常定義如下:
typedef struct {
__IO uint32_t REG;
} PERIPH_Type;
#define PERIPH_REG_FUNC_MASK (0x4U) // 或者 (0xCU)
#define PERIPH_REG_FUNC_SHIFT (2U)
#define PERIPH_REG_FUNC(x) (((uint32_t)(((uint32_t)(x)) << PERIPH_REG_FUNC_SHIFT)) & PERIPH_REG_FUNC_MASK)
#define PERIPH_BASE (0x400AC000u)
#define PERIPH ((PERIPH_Type *)PERIPH_BASE)
謹慎暫存器賦值法的核心要義就是每次操作都只涉及一種功能位,並且不要影響其他功能位的值,就像下面程式碼所示。切忌出現 PERIPH->REG = value1 | value2 | ... 這樣的一次性多個不同功能位一起賦值的操作。
謹慎暫存器賦值法既可以避免模組設計裡不同功能位賦值有先後順序的限制問題,也可以防止誤改某些保留位預設值的異常情況發生。當然這也是有小小代價的,那就是會增加了一些程式碼長度。
// 如果PERIPH->REG[FUNC]是單bit
PERIPH->REG |= PERIPH_REG_FUNC_MASK;
PERIPH->REG &= ~PERIPH_REG_FUNC_MASK;
// 如果PERIPH->REG[FUNC]是多bit
PERIPH->REG = (PERIPH->REG & (~PERIPH_REG_FUNC_MASK)) | PERIPH_REG_FUNC(value);
至此,改動i.MXRT1xxx裡IOMUXC_GPR暫存器保留位可能會造成系統異常痞子衡便介紹完畢了,掌聲在哪裡~~~
歡迎訂閱
文章會同時釋出到我的 部落格園主頁、CSDN主頁、知乎主頁、微信公眾號 平臺上。
微信搜尋"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。