我第一次使用CH32V307芯片,以下內(nèi)容內(nèi)容難免瑕疵,所以僅供參考,希望幫助有類似困惑的開發(fā)者。
系統(tǒng)啟動后在運行第一行printf時導(dǎo)致死機并進入HardFault,原因是heap內(nèi)存不足,C庫分配printf緩存失敗并導(dǎo)致后續(xù)故障。
我在sbrk函數(shù)中添加了一段日志輸出:
__attribute__((used))?void?*_sbrk(ptrdiff_t?incr) { ????extern?char?_end[]; ????extern?char?_heap_end[]; ????static?char?*curbrk?=?_end; ????if?((curbrk?+?incr?<?_end)?||?(curbrk?+?incr?>?_heap_end)){ ????????const?char?msg[]?=?"sbrk?overflow\r\n"; ????????_write(0,?(char*)msg,?sizeof(msg)); ????????return?NULL?-?1; ????} ????curbrk?+=?incr; ????return?curbrk?-?incr; }
通過這段消息我才定位到是堆內(nèi)存溢出導(dǎo)致的故障。
進一步分析堆內(nèi)存為什么會溢出,通過檢查_end指針和_heap_end指針,發(fā)現(xiàn)有效的堆空間已經(jīng)不足1000字節(jié)。進一步分析鏈接腳本,發(fā)現(xiàn)官方提供的鏈接腳本沒有保證heap空間的最小值,當(dāng)用戶占用的RAM要接近最大值時,heap空間的長度沒有保證。
????.bss?: ????{ ????????.?=?ALIGN(4); ????????PROVIDE(?_sbss?=?.); ????????*(.sbss*) ????????*(.gnu.linkonce.sb.*) ????????*(.bss*) ????????*(.gnu.linkonce.b.*)???????? ????????*(COMMON*) ????????.?=?ALIGN(4); ????????PROVIDE(?_ebss?=?.); ????}?>RAM?AT>FLASH ????PROVIDE(?_end?=?_ebss);?????/*?heap的起始地址夾在bss段和stack段之間?*/ ????PROVIDE(?end?=?.?);?????????/*?這中間的剩余空間大小不好控制?*/ ????.stack?ORIGIN(RAM)?+?LENGTH(RAM)?-?__stack_size?: ????{ ????????PROVIDE(?_heap_end?=?.?);???? ????????.?=?ALIGN(4); ????????PROVIDE(_susrstack?=?.?); ????????.?=?.?+?__stack_size; ????????PROVIDE(?_eusrstack?=?.); ????????__freertos_irq_stack_top?=?.; ????}?>RAM
調(diào)整一下鏈接腳本:
ENTRY(?_start?) __stack_size?=?2048; __heap_size??=?2048;????????/*?顯式聲明heap空間大小?*/ SECTIONS { ????/*?other?section?*/ ????.bss?: ????{ ????????.?=?ALIGN(4); ????????PROVIDE(?_sbss?=?.); ????????*(.sbss*) ????????*(.gnu.linkonce.sb.*) ????????*(.bss*) ????????*(.gnu.linkonce.b.*) ????????*(COMMON*) ????????.?=?ALIGN(4); ????????PROVIDE(?_ebss?=?.);???/*?這句可以調(diào)整到下面?*/ ????}?>RAM ????.heap?: ????{ ????????.?=?ALIGN(4); ????????PROVIDE?(?end?=?.?); ????????PROVIDE?(?_end?=?.?); ????????.?=?.?+?__heap_size; ????????.?=?ALIGN(4); ????????PROVIDE(?_heap_end?=?.?); ????}?>RAM ????.stack?ORIGIN(RAM)?+?LENGTH(RAM)?-?__stack_size?: ????{ ????????.?=?ALIGN(4); ????????PROVIDE(_susrstack?=?.?); ????????.?=?.?+?__stack_size; ????????PROVIDE(?_eusrstack?=?.); ????????__freertos_irq_stack_top?=?.; ????}?>RAM }
以上這個鏈接腳本將固定heap空間為__heap_size設(shè)置的大小,heap段和stack段之間剩余的空間將可被stack使用,可避免棧溢出。如果想將剩余空間分配到heap,可將PROVIDE( _heap_end = . );移動到stack段的起始位置。
在我的開發(fā)環(huán)境中,默認情況下使用printf會使用1500字節(jié)左右的heap空間,如果你希望printf減小heap的使用,可將標(biāo)準(zhǔn)輸出的行緩沖模式改為0緩沖模式,在第一次使用printf函數(shù)前執(zhí)行setvbuf函數(shù)。
int?main(void) { ????setvbuf(stdout,?NULL,?0,?_IONBF);???????/*?零緩沖模式?*/ ????NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); ????SystemCoreClockUpdate(); ????Delay_Init(); ????USART_Printf_Init(115200); ????printf("SystemClk:%d\r\n",SystemCoreClock); ????printf("ChipID:%08x\r\n",?DBGMCU_GetCHIPID()?); ????while(1); }
這樣可大幅減少heap空間的使用。
當(dāng)出現(xiàn)故障時我們可以在HardFault函數(shù)中輸出必要的信息來協(xié)助分析問題:
#include? void?HardFault_Handler(void)?__attribute__((interrupt("WCH-Interrupt-fast"))); extern?int?_write(int?fd,?char?*buf,?int?size); static?int?fault_printf(const?char?*format,?...) { ????#define?FAULT_MSG_LEN?(32) ????static?char?buf[FAULT_MSG_LEN]; ????int?len; ????va_list?args; ????va_start(args,?format); ????len?=?vsnprintf(buf,?FAULT_MSG_LEN,?format,?args); ????va_end(args); ????if(len?>?FAULT_MSG_LEN) ????????len?=?FAULT_MSG_LEN; ????_write(0,?buf,?len); ????return?len; } void?HardFault_Handler(void) { ????fault_printf("HardFault:\r\n"); ????fault_printf("MSTATUS:?%#x\r\n",?__get_MSTATUS()); ????fault_printf("MCAUSE?:?%#x\r\n",?__get_MCAUSE()); ????fault_printf("MEPC???:?%#x\r\n",?__get_MEPC()); ????if((__get_MCAUSE()?&?0x80000000)?==?0){ ????????uint16_t?exc_id?=?__get_MCAUSE()?&?7; ????????const?char*?cause[8]?=?{ ????????????"Code?unaligned", ????????????"Code?can't?access", ????????????"Code?error", ????????????"Break?point", ????????????"Load?MEM?unaligned", ????????????"Load?MEM?error", ????????????"Store?MEM?unaligned", ????????????"Store?MEM?error", ????????}; ????????if(exc_id?<?8){ ????????????fault_printf("%s\r\n",?cause[exc_id]); ????????} ????} ????while?(1) ????{ ????} }
在HardFault使用了一個自定義的print,這是為了避免使用C庫的printf時出現(xiàn)內(nèi)存分配失敗后導(dǎo)致的一系列連續(xù)故障。在HardFault中出現(xiàn)新的HardFault故障會把事情搞得更加復(fù)雜。
最后,我們來調(diào)整一下MounRiver軟件的設(shè)置,這樣可以讓我們能夠清楚直觀的看到Flash和RAM分別用了多少。
在工程設(shè)置中定位到C/C++build - Setting中,在鏈接選項中添加一個額外的鏈接選項 -Wl,--print-memory-usage
這樣比直接顯示各個字段的大小要更加直觀。
上圖顯示RAM用了93%,當(dāng)RAM占用接近最大時,需要及時調(diào)整代碼中各個模塊的內(nèi)存分配。