C_and_CPP 板


LINE

這個就是目前開發板很常見的 sd card booting, 以前的開發版並不是很常見到這樣的功 能。終於來到這裡了, 想了好久的點子能把它實現真是一件美妙的事情。現在很多開發板 都提供了從 sd 開機的功能, 例如樹莓派, 比起直接操作 flash 簡單很多, 但要自己實 作這功能, 就沒那麼容易, 從 fig 0 的金字塔圖就可以看出需要很多知識, 這是集合了 之前努力所開的果實。 我在很久之前就有這想法了, 但直到在《20150117 成功大學 UniDEMO 期末聯合 DEMO 計 畫 ( https://goo.gl/NbZqar )》看到 mp3 player 那組的作品後, 我才對於 sd card 的讀取有點想法, 這是我最難克服的部份, 沒想到成大的學生在專題就把 sd card 搞定 了, 真是厲害。直到 20161119 我才完成, 從想法到實現, 應該超過兩年。 為什麼有這樣的想法, 是因為 flash 壽命有限, 如果因為 flash 寫爛了, 這塊開發板就 不能用了豈不可惜, 透過 sd card boot 就可以避免這樣的問題, 我再也不會去改寫 flash 的 boot code 了, 讓開發板生命可以持續更久。 《前一篇 - 在 sd card 上使用 fat 檔案系統 ( https://goo.gl/kIEk4H )》在 linux 下的測試只能把 elf 有關程式碼的部份印出, 而不能真的載入, 這是因為需要載入到 0x21000000, 而這個位址不見得在 os 環境下可以正確要到, 我曾試過 mmap, 不見得可 以得到所要的位址。 讀取 elf program header 的程式碼不算太難, 但得先對 elf 有略懂的理解才行, 我不 會說明這部份, 因為《一步步嵌入式操作系:ARM程的方法与 ( https://goo.gl/m961sn ) 》chapter 8 裡頭有介紹, 我不可能寫得比他還好了。這本書我已經提過很多次了, 如果 你沒有, 應該想辦法搞到它。 ( https://goo.gl/JqhMcR ) fig 0 金字塔學習門檻 延續《前一篇 - 在 sd card 上使用 fat 檔案系統 ( https://goo.gl/kIEk4H )》的努 力, 我「只要」把 elf 的內容複製到某個位址就可以了, spi_sdcard.c L970 - L1013 就是在做這樣的努力。 elf_pheader.p_vaddr 就是把 elf 內容複製到的位址, 然後再透過找出 elf entry point 就可以執行這個 elf 了。 那這個程式困難在哪裡? 一樣是在微小的記憶體, 這個 myur_168M.elf 有 68848, 128k + 64k ccm 總共只有 192k 的記憶體, 要怎麼載入這個 elf 執行檔而還要執行它呢? 我原本的想法是把 elf 載入到 ccm, 然後再從 ccm 位址 parse 這個 elf 並載入到 0x21000000 的位址, 但不管我怎麼縮, 這個 elf 就是無法在 64k 之內。這時候只好靠 進階程式技術了, 以分段讀取的方式, 把所需要的 elf 部份載入到記憶體。 《一步步嵌入式操作系:ARM程的方法与 ( https://goo.gl/m961sn )》chapter 8 的程式 碼是將 elf 整個讀到記憶體, 再去 parse elf 並複製到載入位址上, 這樣的方式程式比 較好寫。 而分段讀取的方式就不需要花費 64k ccm, 我把 ccm 的 64 k 拿來當 stack 了。這也是 為什麼這個程式要先讀 elf header, 再來才是 program header, 然後運用 f_seek 讀出 需要的部份。fatfs 幫了大忙, 讓我可以呼叫這些 api 完成這功能。否則要自己讀取 fat, 計算檔案在那個 cluster, 還要考慮小小的記憶體, 這不是簡單幾天就可以完成 的, 有經驗的程式員馬上就可以聯想到其中的大工程。 所以很簡單的 copy elf program body 的部份就變成 spi_sdcard.c L917 - 1010 這麼 複雜了。 spi_sdcard.c 917 #if 1 918 { 919 u8 *elf_code = 0; 920 921 FIL fil; /* File object */ 922 char buf[BUF_SIZE]; /* Line buffer */ 923 FRESULT fr; /* FatFs return code */ 924 //fr = f_open(&fil, "1.txt", FA_READ); 925 //const char *fn = "2:/MYUR_1~1.ELF"; 926 //const char *fn = "0:/myur_168M.elf"; 927 const char *fn = "0:/myur_168M-data-bss.elf"; 928 fr = f_open(&fil, fn, FA_READ); 929 if (fr) 930 { 931 //printf("open %s fail\n", fn); 932 return (int)fr; 933 } 934 935 TCHAR* pos=0; 936 DWORD fsize = f_size (&fil); 937 //printf("fsize: %d\n", fsize); 938 int r_len = 0; 939 while (1) 940 { 941 f_read(&fil, buf, BUF_SIZE, &r_len); 942 //printf("r_len: %d\n", r_len); 943 fsize -= BUF_SIZE; 944 #if 0 945 if (fsize < BUF_SIZE) 946 print_packet(buf, fsize); 947 else 948 print_packet(buf, BUF_SIZE); 949 #endif 950 Elf32Ehdr elf_header = *((Elf32Ehdr*)buf); 951 #if 0 952 printf("sizeof Elf32Ehdr: %d\n", sizeof(Elf32Ehdr)); 953 printf("sizeof Elf32Phdr: %d\n", sizeof(Elf32Phdr)); 954 printf("elf_header.e_phoff: %d\n", elf_header.e_phoff); 955 #endif 956 u32 entry = elf_header.e_entry; 957 if (elf_header.e_phoff > BUF_SIZE) 958 { 959 //printf("error elf_header.e_phoff > BUF_SIZE!!\n"); 960 break; 961 } 962 Elf32Phdr elf_pheader = *((Elf32Phdr*)((u8 *)buf + elf_header.e_phoff)); // program header 963 //printf("elf_header.e_phnum: %d\n", elf_header.e_phnum); 964 for (int i=0 ; i < elf_header.e_phnum; ++i) 965 { 966 int ret; 967 //printf("p_vaddr: %#x offset: %#x size: %d\n", elf_pheader.p_vaddr, elf_pheader.p_offset, elf_pheader.p_filesz); 968 ret = f_lseek(&fil, elf_pheader.p_offset); 969 970 u32 read_times = 1; 971 if (elf_pheader.p_filesz > BUF_SIZE) 972 { 973 #if 0 974 printf("elf_pheader.p_filesz: %d\n", elf_pheader.p_filesz); 975 printf("BUF_SIZE: %d\n", BUF_SIZE); 976 printf("elf_pheader.p_filesz > BUF_SIZE need to read %d times.\n", read_times); 977 #endif 978 read_times = elf_pheader.p_filesz/BUF_SIZE; 979 if ( elf_pheader.p_filesz % bUF_SIZE != 0) 980 ++read_times; 981 } 982 ur_puts(USART2, "elf_pheader.p_filesz: "); 983 print_u32(elf_pheader.p_filesz); 984 ur_puts(USART2, "\r\n"); 985 986 ur_puts(USART2, "read_times: "); 987 print_u32(read_times); 988 ur_puts(USART2, "\r\n"); 989 990 for (int i=0 ; i < read_times; ++i) 991 { 992 u32 read_len = BUF_SIZE; 993 if (i == (read_times-1)) 994 { 995 read_len = elf_pheader.p_filesz - i * BUF_SIZE; 996 } 997 f_read(&fil, buf, read_len, &r_len); 998 #if 0 999 ur_puts(USART2, "read_len: "); 1000 print_u32(read_len); 1001 ur_puts(USART2, "\r\n"); 1002 #endif 1003 1004 ur_puts(USART2, "r_len: "); 1005 print_u32(r_len); 1006 ur_puts(USART2, "\r\n"); 1007 s32_memcpy(elf_pheader.p_vaddr + BUF_SIZE * i, buf, r_len); 1008 ur_puts(USART2, "elf_pheader.p_vaddr + read_len * i: "); 1009 print_u32(elf_pheader.p_vaddr + BUF_SIZE * i); 1010 ur_puts(USART2, "\r\n"); 1011 //printf("yy r_len: %d\n", r_len); 1012 //print_packet(buf, r_len); 1013 } 1014 (*(void(*)())entry)(); 1015 ur_puts(USART2, "back to loader\r\n"); 1016 while(1); 那被載入的 elf 執行檔應該怎麼設計呢? 我把 128k 分成 2 個 64k, 從 sd card 載入 的就從 0x20010000 往上的 64 k, 這很容易, 透過 linker script (參考 stm32.sd.ld) 就可以搞定。 0x20010000 + 64k 就是 sd card 上 elf 所能使用的位址空間, 程式一旦超過這個大 小, 就會出亂子。 stm32.sd.ld 11 MEMORY 12 { 13 FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 1024K 14 SRAM (xrw) : ORIGIN = 0x20010000, LENGTH = 64K 16 } 17 18 ENTRY(mymain) 19 20 SECTIONS 21 { 22 .text : 23 { 24 KEEP(*(.isr_vector .isr_vector.*)) 25 *(.text .text.*) 26 *(.rodata .rodata*) 27 _etext = .; 28 } > SRAM 29 .data : AT (_etext) 30 { 31 _data = .; 32 *(.data .data.*) 33 _edata = .; 34 } > SRAM 35 .bss(NOLOAD) : 36 { 37 _bss = .; 38 *(.bss .bss.*) 39 *(COMMON) 40 . = ALIGN(4); 41 _ebss = .; 42 } > SRAM 43 .stackarea(NOLOAD) : 44 { 45 . = ALIGN(8); 46 *(.stackarea .stackares.*) 47 . = ALIGN(8); 48 } > SRAM 49 50 . = ALIGN(4); 51 _end = .; 52 } 被載入的 elf 的 stack 就先用 loader 的吧。 再來的問題是該怎麼執行這個 elf 程式呢? 有幾個方法: function call goto 弄成一個 process 柿子挑軟的吃, 我沒那麼大的野心, 弄成一個 process 太複雜了, 就用 function call 來搞定就好。最簡單的當然是 goto, 直接跳去那裡執行就好了, 也不用返回 loader 了 。 程式員如果有操作程式碼的能力, 這是一個強力的技術, 什麼是操作程式碼的能力? 就是 我要讓程式怎麼跑, 程式就怎麼跑。coroutine, setjmp/longjmp, exception handle, stack unwind, debugger 就是有這樣能力的技術或是程式。 這個功能也需要這樣的能力, 我要從目前的 loader 移動到被載入的 elf, 然後還要跳回 來, 把那個 elf 當然一個新的 function, 去執行它就好了。 spi_sdcard.c L1014 那個恐怖的轉型就是在做這件事情, 取出 elf 的 entry point address, 轉成 function pointer, 然後執行他, 比想像中的還要簡單吧! 這比 c++ 的 exception handle 簡單 100 倍以上 (我絕對沒有誇張)。 list 1 是我第一次嘗試, 當然也是一次就成功, 這不是幸運, 是之前的努力所結的美麗 果實。我做了相當多的驗證, 證明能正常載入 elf 檔案。 list 1 minicom result 1 Init complete! Hello World! 2 Init sd 3 Init sd ok 4 get cid ok 5 cid: 6 275048534430344720B0003FB4008CFB 7 oid: PH 8 pnm: SD04G 9 get csd ok 10 csd: 11 400E00325B5900001DE77F800A4000D5 12 sd_size: 7839744 13 dump sector 0: 47 total: 3900522, free: 1993300 48 from sd loaded ok 49 back list 2 是我第二次嘗試, 有什麼不同呢? 我加入了 bss, data section 的測試。確認 bss, data section 的值是正確的。也更進一步改善程式, 分段將 elf program body 載 入到正確的位置。所以很不幸的沒有一次就成功, 我花了一些時間完成她。 todo: 目前還無法處理有 2 個以上的 program body。 list 2. mincom result 1 1 Init complete! Hello World! 2 Init sd 3 Init sd ok 4 get cid ok 5 cid: 6 275048534430344720B0003FB4008CFB 7 oid: PH 8 pnm: SD04G 9 get csd ok 10 csd: 11 400E00325B5900001DE77F800A4000D5 12 sd_size: 7839744 47 total: 3900522, free: 1993230 48 elf_pheader.p_filesz: 640 49 read_times: 03 50 r_len: 256 51 elf_pheader.p_vaddr + read_len * i: 536936448 52 r_len: 256 53 elf_pheader.p_vaddr + read_len * i: 536936704 54 r_len: 128 55 elf_pheader.p_vaddr + read_len * i: 536936960 56 from sd loaded ok 57 12345678 58 76 59 98 60 CD 61 AB 62 back to loader myur_168M.c 已經不再需要初始化 usart2, 因為 loader 已經做過了, 直接呼叫 usart output function 即可。 myur_168M.c L101 - 106 是 bss section 測試, 應該是 0, 但我印出 76, 98, CD, AB, 這是因為我改了 bss init 的值, 我用了 0xabcd9876 來初始化 bss, 確認這部份是 對的之後就改回 0 了。 myur_168M.c 1 #include "stm32f4xx_usart.h" 2 #include "stm32f4xx_rcc.h" 3 #include "stm32f4xx_gpio.h" 4 5 6 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data) 7 { 8 /* Check the parameters */ 9 assert_param(IS_USART_ALL_PERIPH(USARTx)); 10 assert_param(IS_USART_DATA(Data)); 11 12 /* Transmit Data */ 13 USARTx->DR = (Data & (uint16_t)0x01FF); 14 } 15 16 17 void ur_puts(USART_TypeDef* USARTx, volatile char *s) 18 { 19 while(*s) 20 { 21 // wait until data register is empty 22 while( !(USARTx->SR & 0x00000040) ); 23 USART_SendData(USARTx, *s); 24 *s++; 25 } 26 } 27 28 void init_bss() 29 { 30 extern unsigned long _bss; 31 extern unsigned long _ebss; 32 unsigned long *bss_dest; 33 34 for (bss_dest = &_bss; bss_dest < &_ebss;) 35 { 36 //*bss_dest++ = 0xabcd9876; 37 *bss_dest++ = 0x0; 38 } 39 } 40 41 char* s32_itoa(uint32_t n, char* str, int radix) 42 { 43 char digit[]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 44 char* p=str; 45 char* head=str; 46 uint8_t count=0; 47 //int radix = 10; 48 49 // if(!p || radix < 2 || radix > 36) 50 // return p; 51 if (n==0) 52 { 53 *p++='0'; 54 *p++ = '0'; 55 *p=0; 56 return str; 57 } 58 if (radix == 10 && n < 0) 59 { 60 *p++='-'; 61 n= -n; 62 } 63 64 while(n) 65 { 66 ++count; 67 *p++=digit[n%radix]; 68 //s32_put_char(*(p-1), (u8*)(0xb8000+80*2)); 69 n/=radix; 70 } 71 if (count == 1) 72 *p++ = '0'; 73 74 *p=0; 75 #if 1 76 for (--p; head < p ; ++head, --p) 77 { 78 char temp=*head; 79 if (*(p-1) != '-') 80 { 81 *head=*p; 82 *p=temp; 83 } 84 } 85 #endif 86 return str; 87 } 88 89 u8 buf[4]; 90 int x=0x12345678; 91 int mymain(void) 92 { 93 init_bss(); 94 char str[]= "from sd loaded ok\r\n"; 95 ur_puts(USART2, str); 96 97 char fmt_str[20]; 98 s32_itoa(x, fmt_str, 16); 99 ur_puts(USART2, fmt_str); 100 ur_puts(USART2, "\r\n"); 101 for (int i=0 ; i < 4; ++i) 102 { 103 s32_itoa(buf[i], fmt_str, 16); 104 ur_puts(USART2, fmt_str); 105 ur_puts(USART2, "\r\n"); 106 } 107 108 return 2; 109 } 有了這功能後, 就不需要寫入 stm32f4discovery 內建 flash, 可以像樹莓派一樣, 將程 式寫在 sd card 上執行, 不過沒有樹莓派從開機到載入 sd card 執行檔的黑箱, 這個過 程我已經很清楚明瞭。自己完成這些東西, 爽度暴增。 source code: https://github.com/descent/stm32f4_prog/tree/master/load_from_sd ( https://goo.gl/Ox2ora ) git commit: dc5daa7151badde8c76dc2af9ae5aa9ed1bb5cff // 本文使用 Blog2BBS 自動將Blog文章轉成縮址的BBS純文字 http://goo.gl/TZ4E17 // blog 原文 http://descent-incoming.blogspot.tw/2016/11/for-stm32f4discovery-183-sd-card-elf.html shorten: https://goo.gl/8yUB6i -- 紙上得來終覺淺,絕知此事要躬行。 --



※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 101.9.39.184
※ 文章網址: https://webptt.com/m.aspx?n=bbs/C_and_CPP/M.1480739354.A.678.html
1F:推 wtchen: 好強,只能推了! 12/03 18:03
2F:推 wnmin: 強 可是要怎麽玩才會把flash寫爆啊 12/03 21:23
3F:推 james732: 推 12/03 23:12







like.gif 您可能會有興趣的文章
icon.png[問題/行為] 貓晚上進房間會不會有憋尿問題
icon.pngRe: [閒聊] 選了錯誤的女孩成為魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一張
icon.png[心得] EMS高領長版毛衣.墨小樓MC1002
icon.png[分享] 丹龍隔熱紙GE55+33+22
icon.png[問題] 清洗洗衣機
icon.png[尋物] 窗台下的空間
icon.png[閒聊] 双極の女神1 木魔爵
icon.png[售車] 新竹 1997 march 1297cc 白色 四門
icon.png[討論] 能從照片感受到攝影者心情嗎
icon.png[狂賀] 賀賀賀賀 賀!島村卯月!總選舉NO.1
icon.png[難過] 羨慕白皮膚的女生
icon.png閱讀文章
icon.png[黑特]
icon.png[問題] SBK S1安裝於安全帽位置
icon.png[分享] 舊woo100絕版開箱!!
icon.pngRe: [無言] 關於小包衛生紙
icon.png[開箱] E5-2683V3 RX480Strix 快睿C1 簡單測試
icon.png[心得] 蒼の海賊龍 地獄 執行者16PT
icon.png[售車] 1999年Virage iO 1.8EXi
icon.png[心得] 挑戰33 LV10 獅子座pt solo
icon.png[閒聊] 手把手教你不被桶之新手主購教學
icon.png[分享] Civic Type R 量產版官方照無預警流出
icon.png[售車] Golf 4 2.0 銀色 自排
icon.png[出售] Graco提籃汽座(有底座)2000元誠可議
icon.png[問題] 請問補牙材質掉了還能再補嗎?(台中半年內
icon.png[問題] 44th 單曲 生寫竟然都給重複的啊啊!
icon.png[心得] 華南紅卡/icash 核卡
icon.png[問題] 拔牙矯正這樣正常嗎
icon.png[贈送] 老莫高業 初業 102年版
icon.png[情報] 三大行動支付 本季掀戰火
icon.png[寶寶] 博客來Amos水蠟筆5/1特價五折
icon.pngRe: [心得] 新鮮人一些面試分享
icon.png[心得] 蒼の海賊龍 地獄 麒麟25PT
icon.pngRe: [閒聊] (君の名は。雷慎入) 君名二創漫畫翻譯
icon.pngRe: [閒聊] OGN中場影片:失蹤人口局 (英文字幕)
icon.png[問題] 台灣大哥大4G訊號差
icon.png[出售] [全國]全新千尋侘草LED燈, 水草

請輸入看板名稱,例如:Soft_Job站內搜尋

TOP