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/cn.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灯, 水草

请输入看板名称,例如:Tech_Job站内搜寻

TOP