顯示具有 before_os_cm3 標籤的文章。 顯示所有文章
顯示具有 before_os_cm3 標籤的文章。 顯示所有文章

2022年12月2日 星期五

作業系統之前的程式 for stm32f4discovery (19) - 遲來的 linker script

雄辯是銀, 沉默是金
這個系列的最後一篇是 2016, 過去 6 年了, 很可惜我還是沒寫出一個 os for stm32f4discovery, 有些忘記 linker script 的用法了, 用這篇文章來介紹 linker script, 當然, 只是入門等級的介紹。這個應該要早點介紹的, 不過怎麼開發 stm32f4 比較要緊, linker script 就這麼被耽擱了。

找資料期間發現一篇寫的很不錯的文章「linker script 簡單教學」, 真的很高興不用總是讀那些簡體中文文章, 不要誤會, 簡體中文文章本身沒問題, 但我總是不習慣中國人的用語, 難得可以看到本土的技術文章。

linker script 最主要是要讓執行檔長成可以執行的樣子, 這是什麼意思呢? 就是字面上的意思。每個平台都不同, 例如 cortex-m 前 4 個 byte 是 stack address, 所以就必須想辦法讓執行檔的前 4 byte 指向設定好的 stack 區域, 那你說不是應該要指定 sp 暫存器來設定嗎? 一般來說是這樣, 不過 cortex-m 不需要使用組合語言就可以設定 sp 暫存器。可以參考: 作業系統之前的程式 for stm32f4 - discovery (1) - 1 加到 10 , asm version

因為這是 bare-metal 程式, 所以才會有這樣的需求, 如果是 linux/windows 執行檔, 就不需要這麼嚴格讓執行檔長成某個樣子。

當然, 我自己是寫不出來這個 linker script, 這是從某本書上抄來的, 一般開發商付的 sdk 也會有一個 linker script。

最容易讓我困擾的是 VMA/LMA, 老是記不住誰是誰? 可以使用以下指令觀察 VMA/LMA arm-none-eabi-objdump -h mygpio_led.elf, 這個指令很有幫助, 讓我可以區分 VMA/LMA, 使用 readelf 我不知道怎麼讀出這個資訊。

mygpio_led.c
 1 #define USE_STDPERIPH_DRIVER
 2 #include "stm32.h"
 3 
 4 int val=0x123456ab;
 5 
 6 void Delay(uint32_t delay )
 7 {
 8   int d1=5;
 9   while(d1) delay--;
10 }
11 
12 
13 int main(void)
14 {
15   Delay(0x3FFFFF);
16 }

list 1. stm32.ld
 1 /*
 2 MEMORY
 3 {
 4   FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K
 5   SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
 6 }
 7 */
 8 
 9 /* Specify the memory areas */
10 /* form: STM32F4-Discovery_FW_V1.1.0/Project/Peripheral_Examples/IO_Toggle/TrueSTUDIO/IO_Toggle/stm32_flash.ld */
11 MEMORY
12 {
13   FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 1024K
14   SRAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 192K
15   MEMORY_B1 (rx)  : ORIGIN = 0x60000000, LENGTH = 0K
16 }
17 
18 
19 SECTIONS
20 {
21   .text :
22   {
23     KEEP(*(.isr_vector .isr_vector.*))
24     *(.text .text.*)
25     *(.rodata .rodata*)
26     _etext = .;
27   } > FLASH
28   .data : AT (_etext)
29   {
30     _data = .;
31     *(.data .data.*)
32     _edata = .;
33   } > SRAM
34   .bss(NOLOAD) :
35   {
36     _bss = .;
37     *(.bss .bss.*)
38     *(COMMON)
39     . = ALIGN(4);
40     _ebss = .;
41   } > SRAM
42   .stackarea(NOLOAD) :
43   {
44     . = ALIGN(8);
45     *(.stackarea .stackares.* .stackares)
46     . = ALIGN(8);
47   } > SRAM
48 
49   . = ALIGN(4);
50   _end = .;
51 }

list 2. arm-none-eabi-objdump -h mygpio_led.elf
 1 
 2 mygpio_led.elf:     file format elf32-littlearm
 3 
 4 Sections:
 5 Idx Name          Size      VMA       LMA       File off  Algn
 6   0 .text         00000138  08000000  08000000  00010000  2**2
 7                   CONTENTS, ALLOC, LOAD, READONLY, CODE
 8   1 .data         00000004  20000000  08000138  00020000  2**2
 9                   CONTENTS, ALLOC, LOAD, DATA
10   2 .bss          00000000  20000004  0800013c  00020004  2**0
11                   ALLOC
12   3 .stackarea    00000104  20000004  0800013c  00020004  2**2
13                   ALLOC
14   4 .debug_info   000001d8  00000000  00000000  00020004  2**0
15                   CONTENTS, READONLY, DEBUGGING
16   5 .debug_abbrev 00000137  00000000  00000000  000201dc  2**0
17                   CONTENTS, READONLY, DEBUGGING
18   6 .debug_aranges 00000020  00000000  00000000  00020313  2**0
19                   CONTENTS, READONLY, DEBUGGING
20   7 .debug_line   0000008b  00000000  00000000  00020333  2**0
21                   CONTENTS, READONLY, DEBUGGING
22   8 .debug_str    00000156  00000000  00000000  000203be  2**0
23                   CONTENTS, READONLY, DEBUGGING
24   9 .comment      00000031  00000000  00000000  00020514  2**0
25                   CONTENTS, READONLY
26  10 .ARM.attributes 00000033  00000000  00000000  00020545  2**0
27                   CONTENTS, READONLY
28  11 .debug_frame  000000f8  00000000  00000000  00020578  2**2
29                   CONTENTS, READONLY, DEBUGGING


觀察 list 2. 就可以發現 .data section VMA 是 20000000, LMA 是 08000138。mygpio_led.c L4 變數 val 就是位於這裡, 那應該會有個疑問, 當我 printf &val 時, 位址是 20000000 還是 08000138?

list 5. L28 拿掉 AT (_etext), 再觀察 list 6. L8 就可以發現 .data section VMA, LMA 都是 20000000。

list 5. stm32.ld
 1 /*
 2 MEMORY
 3 {
 4   FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K
 5   SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
 6 }
 7 */
 8 
 9 /* Specify the memory areas */
10 /* form: STM32F4-Discovery_FW_V1.1.0/Project/Peripheral_Examples/IO_Toggle/TrueSTUDIO/IO_Toggle/stm32_flash.ld */
11 MEMORY
12 {
13   FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 1024K
14   SRAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 192K
15   MEMORY_B1 (rx)  : ORIGIN = 0x60000000, LENGTH = 0K
16 }
17 
18 
19 SECTIONS
20 {
21   .text :
22   {
23     KEEP(*(.isr_vector .isr_vector.*))
24     *(.text .text.*)
25     *(.rodata .rodata*)
26     _etext = .;
27   } > FLASH
28   .data : 
29   {
30     _data = .;
31     *(.data .data.*)
32     _edata = .;
33   } > SRAM
34   .bss(NOLOAD) :
35   {
36     _bss = .;
37     *(.bss .bss.*)
38     *(COMMON)
39     . = ALIGN(4);
40     _ebss = .;
41   } > SRAM
42   .stackarea(NOLOAD) :
43   {
44     . = ALIGN(8);
45     *(.stackarea .stackares.* .stackares)
46     . = ALIGN(8);
47   } > SRAM
48 
49   . = ALIGN(4);
50   _end = .;
51 }

list 6. arm-none-eabi-objdump -h mygpio_led.elf
 1 
 2 mygpio_led.elf:     file format elf32-littlearm
 3 
 4 Sections:
 5 Idx Name          Size      VMA       LMA       File off  Algn
 6   0 .text         00000138  08000000  08000000  00010000  2**2
 7                   CONTENTS, ALLOC, LOAD, READONLY, CODE
 8   1 .data         00000004  20000000  20000000  00020000  2**2
 9                   CONTENTS, ALLOC, LOAD, DATA
10   2 .bss          00000000  20000004  20000004  00020004  2**0
11                   ALLOC
12   3 .stackarea    00000104  20000004  20000004  00020004  2**2
13                   ALLOC
14   4 .debug_info   000001d8  00000000  00000000  00020004  2**0
15                   CONTENTS, READONLY, DEBUGGING
16   5 .debug_abbrev 00000137  00000000  00000000  000201dc  2**0
17                   CONTENTS, READONLY, DEBUGGING
18   6 .debug_aranges 00000020  00000000  00000000  00020313  2**0
19                   CONTENTS, READONLY, DEBUGGING
20   7 .debug_line   0000008b  00000000  00000000  00020333  2**0
21                   CONTENTS, READONLY, DEBUGGING
22   8 .debug_str    00000156  00000000  00000000  000203be  2**0
23                   CONTENTS, READONLY, DEBUGGING
24   9 .comment      00000031  00000000  00000000  00020514  2**0
25                   CONTENTS, READONLY
26  10 .ARM.attributes 00000033  00000000  00000000  00020545  2**0
27                   CONTENTS, READONLY
28  11 .debug_frame  000000f8  00000000  00000000  00020578  2**2
29                   CONTENTS, READONLY, DEBUGGING


val 變數位址是 0x20000000 還是 0x08000138 有什麼差異呢? 這大概要有碰過 embedded system 的開發人員才有體會, 0x08000138 是 flash 的位址, 讀的時候沒問題, 可以 int c = val, 但是寫的話 val = 6, 是無法作到的 (ddr 位址範圍才可以這麼做), flash 有一套寫的指令, 需要一點複雜的操作才可以寫入, 但對 c 程式員來說, val=6 就要完成這個操作, c 程式員不應該還要去煩心這是 flash 還是 ddr 位址。

所以當作 val = 6 時, 是使用 0x20000000 位址, 也就是 6 會寫到 ddr 0x20000000 的位址上。那 0x08000138 是幹麻用的? 如果你 printf("val: %#x\n", val), 應該會預期要得到 0x123456ab, 但開機時, ddr 應該是隨機值, 有可能會是 0x123456ab 嗎? 所以 c runtime 有一段 code 把 0x123456ab 複製到 0x20000000, 那麼要從哪裡找到 0x123456ab, 答對了, 就是從 0x08000138 把 0x123456ab 複製到 0x20000000 (stm32.h L20, 21)。放在 data section 的變數都要這樣處理, 這就是 c runtime 的祕密, 每個平台的 c runtime 要做的事情都不同, 像是 rpi2 就不用這麼做, 這是 cortex M 獨有的。

這引發另外一個問題, 要怎麼確定把這個執行檔燒錄到 flash 時, 0x08000138 正好是 0x123456ab? list 7 0x138 剛好是 123456ab, 這個就是 val, 在寫入 mygpio_led.bin 到 flash 位址 0x08000000, 就變成 0x08000138, 這就是整個被 linker script 安排好的魔法, 除了靠 linker script, 也要靠 c runtime 的 code。

list 7. hexdump -C mygpio_led.bin
 1 00000000  08 01 00 20 41 00 00 08  f5 00 00 08 f5 00 00 08  |... A...........|
 2 00000010  f5 00 00 08 f5 00 00 08  f5 00 00 08 f5 00 00 08  |................|
 3 00000020  f5 00 00 08 f5 00 00 08  f5 00 00 08 c5 00 00 08  |................|
 4 00000030  f5 00 00 08 f5 00 00 08  a1 00 00 08 e9 00 00 08  |................|
 5 00000040  80 b5 82 b0 00 af 11 4b  7b 60 11 4b 3b 60 07 e0  |.......K{`.K;`..|
 6 00000050  3b 68 1a 1d 3a 60 7a 68  11 1d 79 60 12 68 1a 60  |;h..:`zh..y`.h.`|
 7 00000060  3b 68 0c 4a 93 42 f3 d3  0b 4b 3b 60 04 e0 3b 68  |;h.J.B...K;`..;h|
 8 00000070  1a 1d 3a 60 00 22 1a 60  3b 68 08 4a 93 42 f6 d3  |..:`.".`;h.J.B..|
 9 00000080  00 f0 50 f8 00 bf 08 37  bd 46 80 bd 38 01 00 08  |..P....7.F..8...|
10 00000090  00 00 00 20 04 00 00 20  04 00 00 20 04 00 00 20  |... ... ... ... |
11 000000a0  80 b5 82 b0 00 af 05 23  7b 60 7b 68 01 33 7b 60  |.......#{`{h.3{`|
12 000000b0  03 48 00 f0 25 f8 00 bf  08 37 bd 46 80 bd 00 bf  |.H..%....7.F....|
13 000000c0  ff ff 3f 00 80 b5 00 af  05 4a 05 4b 1b 68 43 f0  |..?......J.K.hC.|
14 000000d0  80 53 13 60 03 48 00 f0  13 f8 00 bf 80 bd 00 bf  |.S.`.H..........|
15 000000e0  04 ed 00 e0 ff ff 3f 00  80 b4 00 af 00 bf bd 46  |......?........F|
16 000000f0  80 bc 70 47 80 b4 00 af  00 bf bd 46 80 bc 70 47  |..pG.......F..pG|
17 00000100  80 b4 85 b0 00 af 78 60  05 23 fb 60 02 e0 7b 68  |......x`.#.`..{h|
18 00000110  01 3b 7b 60 fb 68 00 2b  f9 d1 00 bf 14 37 bd 46  |.;{`.h.+.....7.F|
19 00000120  80 bc 70 47 80 b5 00 af  02 48 ff f7 e9 ff 00 23  |..pG.....H.....#|
20 00000130  18 46 80 bd ff ff 3f 00  ab 56 34 12              |.F....?..V4.|
21 0000013c


另外 bss section 的值都要是 0, 也是用類似的作法完成的, 取得 bss 開始位址和結束位址 (list 1 L34 ~ L41, stm32.h L22 ~ L23), 把整個 bss 設定為 0 (stm32.h L22, 23)。cortex M 只要做這樣, 就完成 c runtime, 之後就可以用 c 語言了。當然, 如果寫程式都只用組合語言, 就不必做這些事情。

stm32.h
  6 #define STACK_SIZE 64
  7 extern unsigned long _etext;
  8 extern unsigned long _data;
  9 extern unsigned long _edata;
 10 extern unsigned long _bss;
 11 extern unsigned long _ebss;
 12 
 13 int main(void);
 14 
 15 void ResetISR(void)
 16 {
 17   unsigned long *pulSrc, *pulDest;
 18 
 19   pulSrc = &_etext;
 20   for (pulDest = &_data; pulDest < &_edata;)
 21     *pulDest++ = *pulSrc++;
 22   for (pulDest = &_bss; pulDest < &_ebss;)
 23     *pulDest++ = 0;
 24 
 25   main();
 26 }


linker script 當然不是只有這麼簡單, 這只是很基本的介紹。

ref:
GNU LD 手冊略讀 (2): Chapter 3.6 SETCIONS (這是一位朋友寫的, 前陣子得知其往生的消息, 令人感傷, 感謝他的文章)

2021年3月27日 星期六

作業系統之前的 scheme

the 1st edition: 20141127 (4)
the 2nd edition: 20210327 (6)
會選擇 c++ 來開發 scheme, 最主要是因為 c++ 的標準程式庫有很多好用的 class: string, vector, map, list ... 不用為了這些基本的東西傷腦筋; 但這次要開發的是 - 在作業系統之前的 scheme (開發平台是 stm32f4discovery), 這些好東西通通派不上用場, 沒有 malloc/new, 又怎麼能用這些東西呢! 甚至連 global object 都不能用, 所以幾乎和使用 c 一樣, 不過還是能用上點 c++ 的特性。

原本程式中用到 list, vector, map 的部份, 通通要改, 全部改用 array, 不夠彈性, 但容易實作, 至於 input/output function, 需改用 uart 這部份的程式碼, 所以直接把之前搞定的 uart 程式碼複製一份過來修改。

getline 我得換成 getchar uart 的版本, cout 也要換成 myprint uart 的版本, 這之前都做過了, 把以前的努力都集合起來就可以, scanner 的作法則改為一次讀一個 byte 來轉成 token, s-express 不複雜, 但我覺得還是不容易, 沒想到花點時間竟然搞定, 感謝两周自制脚本语言的 scanner 這節提供的知識。

再來是 malloc Cell* 的部份, 由於在重寫 struct Cell 時, 我就考慮到在 stm32f4discovery 開發板上執行, 也就是沒有 os 的環境, 所以才使用了 array pool 來得到一個 Cell 的記憶體。雖然沒有彈性, 但能簡化問題, 再搭配一些人工手法, 可以減低 pool 可能會不夠用的問題。

我先在 qemu stm32 p103 emulator 上測試, 等成功了再上到 stm32f4discovery, 減少 flash 讀寫次數。也的確有必要, 光在模擬器上就測試了無數次, 減少了多次的 flash 讀寫。

在製作 p103 emulator 的版本階段, 出了一個 link 問題:

git@github.com:descent/stm32_p103_demos.git stm32_p103_demos/demos/uart_echo git commit: 6b2df2c6e62f3c48a6c6dc20c76367124c1ca447

bss_to_big
1 /home/descent/arm-cs-tools/lib/gcc/arm-none-eabi/4.7.3/../../../../arm-none-eabi/bin/ld: mymain.elf section `.bss' will not fit in region `RAM'
2 /home/descent/arm-cs-tools/lib/gcc/arm-none-eabi/4.7.3/../../../../arm-none-eabi/bin/ld: region `RAM' overflowed by 34085848 bytes
3 collect2: error: ld returned 1 exit status


我以為我已經很了解 link 的問題了, 這應該難不倒我, 就是哪個 section 太大, 整個記憶體空間不足嘛! 嚇不了我的。但隨著時間的過去, 我還是無法處理這問題, 亂 google 找問題, 我 - 開始緊張了, 原來我還有沒搞懂的東西。

當然最後還是搞定了, 原因是: bss 竟然需要 34M 的記憶體空間, 嚇壞我了, p103 只有 20K ram, 把 main.ld 的 RAM 從 20K 改成 48M 就可以編過, 但想也知道, 放入 flash 執行, 一定掛掉。

main.ld  MEMORY {   FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K   RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K -> 48M } ... ...


我當然是不會用這麼蠢的改法。程式中用到相當多的 global variable (global variable 不一定佔據 bss, 得找對才行), 來把他們減少, shorten_bss L32 的效果最好,

/bin/ld: region `RAM' overflowed by 877288 bytes collect2: error: ld returned 1 exit status

從 34M 降到 877K, 效果不錯, 可是還不夠 ...

shorten_bss
 1 diff --git a/cell.h b/cell.h
 2 index d7238ea..eb36b5c 100644
 3 --- a/cell.h
 4 +++ b/cell.h
 5 @@ -11,7 +11,7 @@
 6  
 7  using namespace std;
 8  
 9 -const int MAX_POOL = 1000;
10 +const int MAX_POOL = 70;
11  
12  enum PairAttr {HEAD, FIRST, SECOND};
13  enum CellType {STRING, SYMBOL, NUMBER, PAIR, PRIMITIVE_PROC, NULL_CELL, INVALID};
14 @@ -21,7 +21,7 @@ struct Environment;
15  struct Cell;
16  typedef Cell *(*ProcType)(Cell *);
17  
18 -const int MAX_SIZE = 256;
19 +const int MAX_SIZE = 20;
20  // a variant that can hold any kind of lisp value
21  struct Cell 
22  {
23 diff --git a/s_eval.h b/s_eval.h
24 index 37048ac..2da9ae5 100644
25 --- a/s_eval.h
26 +++ b/s_eval.h
27 @@ -4,14 +4,14 @@
28  #include "cell.h"
29  #include "token_container.h"
30  
31 -const int MAX_ENVIRONMENT_POOL = 1000;
32 +const int MAX_ENVIRONMENT_POOL = 10;
33  
34  #ifdef USE_CPP_MAP
35  typedef std::map<std::string, Cell*> Frame;
36  #else
37 -const int FRAME_LEN = 128;
38 +const int FRAME_LEN = 56;
39  
40 -const int LINE_SIZE = 256;
41 +const int LINE_SIZE = 128;
42  
43  struct EnvElement
44  {
45 @@ -29,7 +29,7 @@ struct Environment
46  
47      Frame frame_;
48  
49 -    char name_[255]; // for debug
50 +    char name_[12]; // for debug
51      int free_frame_index_;
52    private:
53  };


胡亂修改後, 總算 link 成功。

機器上的記憶體有 20K, 程式本身有 19K, 載入到記憶體後, 可以正常執行的嗎? 不一定, bss 區域不會佔據程式本身, 但是會佔用載入時的記憶體位址, 所以還得加上 bss 佔用的記憶體, 若 bss 佔據了 2K, 19k+2k = 21k, 記憶體就不夠用了。

這便是 link 不過的原因。

還有問題二:

高興的將程式載入模擬器後, 準備欣賞自己的傑作, 發現:

repl 的第一個參數 prompt, 不知道為什麼會被 TokenContainer tc 所影響 (傳進來的參入位址會變成 0), 我把 TokenContainer tc 改成 global variable 才正常 (原本是 auto variable)。

我猜測是 stack 被覆蓋了, 不過沒有認真追, 也有可能是其他問題。

再來還有一些後續的小問題, 克服他們後, 就是下面影片的成果。



應該離成功不遠了 ...

真實機器篇 - stm32f4discovery



事情果然沒那麼順利, 在我將模擬器的程式移植到 stm32f4discovery 後, 沒什麼問題, 提示符號出來了, 但是打下 (+ 1 2) 之後, 程式沒有印出預期的 3, 而是停止不動。猜測是 stack 問題 (又是 stack), 將 stack 改大一點就正常執行了。在 x86 記憶體很大, 不太容易遇到 stack 太小的問題, 但在 stm32f4discovery, 記憶體不大, 只有 192k, 考驗我的程式能力。

下面影片是在 minicom 的執行結果:



後來知道了 ccm 這東西, 把 stack 移到 ccm, 成功擠出更多記憶體。

支援 backspace



這個常見的的功能還真沒那麼簡單, call library 果然比較舒服, 思考良久之後, 決定使用 deque 來完成這個功能, 我參考了 c++ 的 std::deque, 實作了一個 MyDeque, 雖然我用的是 c++, 不過作業系統之前沒有 std::deque 可以用。

MyDeque 提供以下 member function:
push_front() 給 ungetch() 用
pop_front() 在 getchar() return 時傳最前頭的 char
push_back() 把輸入的 char 存到這個 deque
pop_back() 提供了 backspace 的功能

因為這個 mydeque 需要成為一個 global variable, 所以沒有提供 ctor, 避免 compile 不過的問題 (你很疑惑為什麼吧?), 不過這不是什麼難題, 我使用了 init() 來變通, 得自己呼叫 mydeque.init(); 來作初使化的動作, 也如你預期的, 我老是忘記 call .init(), 每每在驚呼「為什麼?」之後才想到自己的疏忽。

使用了一大堆 global variable, 完全不是 c++ 哲學, 就跟你說了, 這是一個長的很像 c 的 c++ 程式。

為了支援 backspace 沒想到要多做一個 class, 這是經過深思熟慮後的想法, 也才決定用 deque (念做 deck), 這還真是個神奇的資料結構。

下面影片顯示支援 backspace 的操作:



不過這個實作還有點問題, 超過 MyDeque 的大小, 就會有問題, 所以我把 MyDeque 設到 128 個 int 大小, 暫時躲避這問題。

line edit history



20141115 我加上了 line edit history 的功能, 按下 up/down 就會把之前打過的指令取出來, 很基本的功能吧! 但要實作這個功能可花了我好大的力氣。

我寫了一個 deque 的 template 的版本, 一個 CString class 用來支援這樣的功能, 所以前面那個 MyDeque 被移除了。我第一次覺得 c++ template 這麼好用, 感動到痛哭流涕。雖然 c++ 現在複雜的不像話, 不過程式員只要選擇自己要用的部份就好了, 重點是把程式寫出來, 而不是用上什麼新特性。這個程式沒有 malloc/free 還不是照樣可以完成。

偵測 up/down key 是個小問題, 答案分別是: up: 27 '[' 'A' down: 27 '[' 'B' right: 27 '[' 'C' left: 27 '[' 'D'

要抓出 3 bytes 才能判斷是哪一個方向鍵。

而 scanner 程式整個重新改寫。辛苦完成的 MyDeque 派不上用場了, 以下影片示範這個功能:



當做出 history 功能時, 自己都覺得很酷, 這個功能真是無敵好用。

這個程式集 os 觀念和 scheme interpreter 之大成。是繼 simple os 之後的心血, 所有的東西都自己打造, 很有成就感。

這裡有篇履歷的文章: How the HR department and a programmer reads your resume? 我結合 os, interpreter, 可以加 20 分嗎?



疑, 這次怎麼沒有提供 source code, 因為這次的 souce code 分佈在 3 個地方, 太麻煩就不列了。有興趣的朋友一定會自己發問的。

寫下這篇文章很久之後, 我終於整合好所有程式碼,

source code:
https://github.com/descent/simple_scheme

可以使用 p103 這個平台編譯, 這是跑在 qemu 模擬器上。

好久沒做烘焙產品了, 該來做一個蛋糕慶祝一下, 犒賞自己。

2016年11月25日 星期五

作業系統之前的程式 for stm32f4discovery (18.3) - 載入並執行 sd card 上的 elf

十年磨一劍, 霜刃未曾試
你可能有興趣的文章:
這個就是目前開發板很常見的 sd card booting, 以前的開發板很少有這樣的功能。

從開發 stm32f407 的程式以來, 每一個小程式都是為了打造 os 而設計的, 終於來到這裡了, 有了sd card, 就可以有大容量的檔案系統。不過 os 還是沒打造出來, 得要加把勁才行。

想了好久的點子能把它實現真是一件美好的事情。現在很多開發板都提供了從 sd 開機的功能, 例如樹莓派, 比起直接操作 flash 簡單很多, 但要自己實作這功能, 就沒那麼容易, 從 fig 0 的金字塔圖就可以看出需要很多知識, 這是集合了之前努力所開的果實, 現在讓我們把之前的努力集合起來。

我在很久之前就有這想法了, 但直到在《20150117 成功大學 UniDEMO 期末聯合 DEMO 計畫》看到 mp3 player 那組的作品後, 我才對於 sd card 的讀取有點想法, 這是我最難克服的部份, 沒想到成大的學生在專題就把 sd card 搞定了, 真是厲害。直到 20161119 我才完成, 從想法到實現, 應該超過兩年。

為什麼有這樣的想法, 是因為 flash 壽命有限, 如果因為 flash 寫爛了, 這塊開發板就不能用了豈不可惜, 透過 sd card boot 就可以避免這樣的問題, 我再也不用常常改寫 flash 的 boot code 了, 讓開發板生命可以持續更久。

《前一篇 - 在 sd card 上使用 fat 檔案系統》在 linux 下的測試只能把 elf 有關程式碼的部份印出, 而不能真的載入, 這是因為需要載入到 0x20010000, 而這個位址不見得在 os 環境下可以正確要到, 我曾試過 mmap, 不見得可以得到所要的位址。

讀取 elf program header 的程式碼不算太難, 但得先對 elf 有略懂的理解才行, 我不會說明這部份, 因為《一步步写嵌入式操作系统:ARM编程的方法与实践》chapter 8 裡頭有介紹, 我不可能寫得比他還好了。這本書我已經提過很多次了, 如果你沒有, 應該想辦法搞到它。

elf program header 裡頭的資訊就是在描述這個 elf 的機械碼在 elf 檔案的什麼地方, 是很重要的資訊, 這就是 cpu 可以執行的部份。

fig 0 金字塔學習門檻

延續《前一篇 - 在 sd card 上使用 fat 檔案系統》的努力, 我「只要」把 elf 的內容複製到某個位址就可以了, spi_sdcard.c L970 - L1013 就是在做這樣的努力。

elf_pheader.p_vaddr 就是這個某個位址, 把 elf 內容複製 elf_pheader.p_vaddr 這個位址, 然後再透過 elf header 找出 elf entry point 就可以執行這個 elf 了。

那這個程式困難在哪裡? 一樣是在微小的記憶體, 這個 myur_168M.elf 有 68848, 128k + 64k ccm 總共只有 192k 的記憶體, 要怎麼載入這個 elf 執行檔而還要執行它呢?

我原本的想法是把 elf 載入到 ccm, 然後再從 ccm 位址 parse 這個 elf 並載入到 0x20010000 的位址, 但不管我怎麼縮, 這個 elf 就是無法在 64k 之內。這時候只好靠進階程式技術了, 以分段讀取的方式, 把所需要的 elf 部份載入到記憶體。這可不是容易的程式技巧, 我曾經栽在其下, 在好久好久之前, 我得完成一個用分段讀取的方式, 解開 firmware 檔案並且更新它, 而因為當時程度太差, 這個分段讀取功能花了好大的除錯時間才搞定。可恥阿!

一步步写嵌入式操作系统:ARM编程的方法与实践》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 程式呢? 有幾個方法:
  • function call
  • goto
  • 弄成一個 process
柿子挑軟的吃, 我沒那麼大的野心, 弄成一個 process 太複雜了, 就用 function call 來搞定就好。最簡單的當然是 goto, 直接跳去那裡執行就好了, 也不用返回 loader 了。

被載入的 elf stack 要設計自己的 stack 嗎? 這太麻煩, 就先用 loader 的吧, 反正是以 function call 的方式執行, 並不會破壞原有的 stack 內容。

程式員如果有操作程式碼的能力, 這是一個強力的技術, 什麼是操作程式碼的能力? 就是我要讓程式怎麼跑, 程式就怎麼跑。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 倍以上 (我絕對沒有誇張)。不相信可以看看《
c++ exception handling (1) - 原理篇》, 絕對沒唬爛。

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];         // test bss section
 90 int x=0x12345678;  // test data section
 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
git commit: dc5daa7151badde8c76dc2af9ae5aa9ed1bb5cff

2016年11月19日 星期六

作業系統之前的程式 for stm32f4discovery (18) - 在 sd card 上使用 fat 檔案系統

問渠那得清如許? 為有源頭活水來
裙子好像太短了, 離題了。這次要站在巨人的肩膀上, 使用的是這個 FatFs - Generic FAT File System Module 讀寫 fat 的 library, 這個 library 支援 fat12, fat16, fat32, 還有長檔名以及中文檔名, 也會從硬碟分割區找到第一個 fat 檔案系統, 真的好用, 站在巨人的肩膀真好。

一樣是參考 STM32 不完原手冊的範例:
相關函式庫的用法:
FatFs - Generic FAT File System Module
當然, 在可以使用這些函式之前得先把要移植的部份搞定才行。

該怎麼開始驗證呢? 先從能正常讀取 fat 的檔案開始, 依照慣例, 得想個好方法來測試, 我決定在 pc 上測試這個 library, 一開始就在 stm32f4discovery 開發板寫程式, 不好除錯, 也很容易失敗。

先建立 fat 檔案系統的 image file, 再用它來測試 fat, 慣用 linux 的人對這一定不陌生。

以下分別建立軟碟以及硬碟影像檔, 建立硬碟影像檔是為了測試能不能從硬碟分割表找到 fat 檔案系統。

建立軟碟影像檔 create floppy images
sudo mkfs.msdos -C imagefile.img 40960 # 40M, fat 12
sudo mount imagefile.img /media/1/

# format to fat32
How to format a usb drive with FAT32 file system on Linux

mkdosfs -F 32 -I /dev/sdc1

硬碟影像檔比較複雜一點, 用 boch bximage 建立硬碟影像檔。

用 boch bximage 建立硬碟影像檔 Loop-mounting partitions from a disk image
bximage # 有選單可以選, 我建立了 flat 80MB 的 c.img

再用 fdisk c.img 切出 2 個 fat 分割區
root@debian64:load_from_sd# fdisk c.img

以 sector 為單位, 列出分割區
root@debian64:load_from_sd# fdisk -lu c.img
Disk c.img: 79.8 MiB, 83607552 bytes, 163296 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xb976a32b

Device     Boot Start    End Sectors  Size Id Type
c.img1           2048  83967   81920   40M  c W95 FAT32 (LBA)
c.img2          83968 163295   79328 38.8M  c W95 FAT32 (LBA)

mount 這個 c.img, mount 硬碟影像檔一樣比磁片複雜些, 需要上述紅色部份的資訊 (2048, 83968), 這個單位需要是 secotr, 所以才使用 -u 參數, 而且不是直接 mount c.img, 需要透過 /dev/loop device files。

把硬碟分割表的磁區 offset 傳給 losetup
  
losetup  /dev/loop0 fat_disk -o $((62*512))
root@debian64:load_from_sd# mkfs.vfat /dev/loop0 
mkfs.fat 3.0.28 (2015-05-16)
Loop device does not match a floppy size, using default hd params

root@debian64:load_from_sd# file -s /dev/loop0 
/dev/loop0: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, root entries 512, Media descriptor 0xf8, sectors/FAT 80, sectors/track 32, heads 64, sectors 79328 (volumes > 32 MB) , serial number 0xc8373271, unlabeled, FAT (16 bit)

建立 fat32 檔案系統
root@debian64:load_from_sd# mkdosfs -F 32 -I /dev/loop0 
mkfs.fat 3.0.28 (2015-05-16)
Loop device does not match a floppy size, using default hd params
root@debian64:load_from_sd# file -s /dev/loop0 
/dev/loop0: DOS/MBR boot sector, code offset 0x58+2, OEM-ID "mkfs.fat", Media descriptor 0xf8, sectors/track 32, heads 64, sectors 79328 (volumes > 32 MB) , FAT (32 bit), sectors/FAT 610, serial number 0xcd3c9115, unlabeled

file -s /dev/loop0 
mount /dev/loop0  /media/2/
umount /media/2
losetup  -d /dev/loop0

fig 2 是不完原手冊講解這部份的內容, 建議仔細閱讀這章節。

fig 2 STM32 不完原手冊相關章節


在 diskio.c 補上讀取 fat image 的相關程式:

FILE_IMAGE_01, FILE_IMAGE_02 就是我額外補上的, 所以

f_mount(fs[2],"2:",1))

2, 2: 就是 FILE_IMAGE_01
3, 3: 就是 FILE_IMAGE_02
0, 0: 就是執行 spi sd card 相關的程式碼

diskio.c
 34 #define SD_CARD  0  //SD卡,卷標為0
 35 #define EX_FLASH 1 //外部flash,卷標為1
 36 #define FILE_IMAGE_01 2
 37 #define FILE_IMAGE_02 3

 50 //初始化磁碟
 51 DSTATUS disk_initialize (
 52  BYTE pdrv    /* Physical drive nmuber (0..) */
 53 )
 77                 case FILE_IMAGE_01:
 79                 {
 80 #ifndef STM32F407
 81   printf("FILE_IMAGE_01\n");
 82   //const char *fn = "fat32.img";
 83   const char *fn = "c.img";
 84 
 85   if (fs_01 == 0 )
 86   {
 87     fs_01 = fopen(fn, "r");
 88     if (fs_01 == NULL)
 89     {
 90       perror("open imagefile.img error\n");
 91       exit(1);
 92     }
 93     printf("the 1st open %s ok\n", fn);
 94   }
 95   else
 96   {
 97     printf("already open %s ok\n", fn);
 98   }
 99 
100   return 0;
101 #endif
102                   break;
103                 }
104                 case FILE_IMAGE_02:
105                 {
106 #ifndef STM32F407
107   printf("FILE_IMAGE_02\n");
108   const char *fn = "fat12.img";
109 
110   if (fs2 == 0 )
111   {
112   fs2 = fopen(fn, "r");
113   if (fs2 == NULL)
114   {
115     perror("open fat12.img error\n");
116     exit(1);
117   }
118   printf("open %s ok\n", fn);
119   }
120   else
121   {
122     printf("%s already open\n", fn);
123   }


148 DRESULT disk_read (
149  BYTE pdrv,  /* Physical drive nmuber (0..) */
150  BYTE *buff,  /* Data buffer to store read data */
151  DWORD sector, /* Sector address (LBA) */
152  UINT count  /* Number of sectors to read (1..128) */
153 )
154 {
155   u8 res=0; 
156   if (!count)return RES_PARERR;//count不能等於0,否則返回參數錯誤     
157   switch(pdrv)
158   {
159  case SD_CARD://SD卡
161    res=SD_ReadDisk(buff,sector,count);  
162    if(res)//STM32 SPI的bug,在sd卡操作失敗的時候如果不執行下面的語句,可能導致SPI讀寫異常
163    {
164      SD_SPI_SpeedLow();
165      SD_SPI_ReadWriteByte(0xff);//提供額外的8個時鐘
166      SD_SPI_SpeedHigh();
167    }
172    break;
173    #if 0
174   case EX_FLASH://外部flash
175  for(;count>0;count--)
176  {
177    SPI_Flash_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
178    sector++;
179    buff+=FLASH_SECTOR_SIZE;
180  }
181    res=0;
182    break;
183                 #endif
184                 case FILE_IMAGE_01:
185                 {
186 #ifndef STM32F407
187                         printf("call disk_image_read()\n");
188                         disk_image_read(buff, sector, count, fs_01);
189                         printf("call disk_image_read() ok\n");
190 #endif
191                         return RES_OK;  
192                   break;
193                 }
194                 case FILE_IMAGE_02:
195                 {
196 #ifndef STM32F407
197                         printf("call disk_image_read()\n");
198                         disk_image_read(buff, sector, count, fs2);
199                         printf("call disk_image_read() ok\n");
200 #endif
201                         return RES_OK;  
202                   break;
203                 }
204    default:
205      res=1; 
206  }

diskio.c L188 disk_image_read() 就是很單純的呼叫 fread 而已。

大概描述一下 fatfs 運作方式:

f_mount() call find_volume()

一開始會從 sector 0 開始讀起, 看看是不是 fat, 如果不是, 就當作 partition 來看待, 找出第一個 partition (L2248), 在 L2303 會確認出是那一個 fat 類型, fat12, fat16 或是 fat32。

ff.c
2244  /* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */
2245  bsect = 0;
2246  fmt = check_fs(fs, bsect);  /* Load sector 0 and check if it is an FAT boot sector as SFD */
2247         printf("fmt: %d\n", fmt);
2248  if (fmt == 1 || (!fmt && (LD2PT(vol)))) { /* Not an FAT boot sector or forced partition number */
2249   UINT i;
2250   DWORD br[4];
2251                 printf("xxx\n");
2252 
2253   for (i = 0; i < 4; i++) { /* Get partition offset */
2254    BYTE *pt = fs->win+MBR_Table + i * SZ_PTE;
2255    br[i] = pt[4] ? LD_DWORD(&pt[8]) : 0;
2256   }
2257   i = LD2PT(vol);  /* Partition number: 0:auto, 1-4:forced */
2258   if (i) i--;
2259   do {  /* Find an FAT volume */
2260    bsect = br[i];
2261                         printf("bsect: %d\n", bsect);
2262    fmt = bsect ? check_fs(fs, bsect) : 2; /* Check the partition */
2263   } while (!LD2PT(vol) && fmt && ++i < 4);
2264  }
...
2300  if (!nclst) return FR_NO_FILESYSTEM; /* (Invalid volume size) */
2301  fmt = FS_FAT12;
2302  if (nclst >= MIN_FAT16) fmt = FS_FAT16;
2303  if (nclst >= MIN_FAT32) fmt = FS_FAT32;
2304 
2305         static char *fat_str[] = {"", "fat12", "fat16", "fat32"};
2306 #ifndef STM32F407
2307         printf("fat type: %s\n", fat_str[fmt]);
2308 #endif

這就是其支援硬碟分割表的程式碼。

再來我把 malloc/free 的函式改成使用 array pool 去要記憶體, 雖然我已經實作了 c++ 標準程式庫 (有 new/delete), 但連我自己也不敢用讓這個實驗簡單點是我的原則, 也讓程式很容易就可以移植到任何系統上。不過 bss 又暴了, 我得縮小這些 array pool。

ff.c 就是用這樣的手法改動 malloc/free。ff.c L538 的 512 太大了, bss 暴了, 改成 8 就可以了, 這是在 128k ram 上寫程式的挑戰, 我曾經認為 128k 的記憶體實在太小, 不過我現在很享受這樣的挑戰。

所需要的改動不只這樣, 只要 malloc/free 的部份, 全都被我改成類似的作法。

ff.c
 538 // #define BUF_POOL_SIZE 512
 539 #define BUF_POOL_SIZE 8
 540 u32 buf_pool_index = 0;
 541 #define INIT_BUF(dobj) {  \
 542                          static u8 buf_pool[(_MAX_LFN + 1) * 2][BUF_POOL_SIZE]; \
 543                                                                                 \
 544                          if (buf_pool_index >= BUF_POOL_SIZE)                \
 545                                   {  \
 546                                     printf("cannot alloc memory\n");  \
 547         LEAVE_FF((dobj).fs, FR_NOT_ENOUGH_CORE);  \
 548                                   } \
 549                                     \
 550                                   lfn = &buf_pool[buf_pool_index]; \
 551                                   ++buf_pool_index; \
 552                                   /* printf("alloc buf_pool_index: %d\n", buf_pool_index); */ \
 553                                   (dobj).lfn = lfn; \
 554                                   (dobj).fn = sfn;  \
 555                                 }
 556 #define FREE_BUF()         { \
 557                                   if (buf_pool_index > 0 ) \
 558                                     --buf_pool_index; \
 559                                     /* printf("free buf_pool_index: %d\n", buf_pool_index); */ \

最後測試了 f_open, f_opendir 這些函式, load_elf.c L178 FILINFO f_info 一定要宣告成 static, 要不然會有 segment fault 的錯誤, 我搞不懂為什麼會這樣? 文件範例也是宣告成 static。

load_elf.c
 94   exfuns_init(); // 配置 fatfs 相關變數所使用的記憶體
 95   if (FR_INVALID_DRIVE == f_mount(fs[2],"2:",1)) 
 96   {
 97     printf("f_mount fail\n");
 98     return -1;
 99   }
100 
101   while(exf_getfree("2:",&total,&free))    //得到SD卡的總容量和剩餘容量
102   {
103   }
104 
105   printf("total: %d, free: %d\n", total, free);
106 #if 1
107 
108 
109   FIL fil;       /* File object */
110   char buf[BUF_SIZE]; /* Line buffer */
111   FRESULT fr;    /* FatFs return code */
112   //fr = f_open(&fil, "1.txt", FA_READ);
113   //const char *fn = "2:/MYUR_1~1.ELF";
114   const char *fn = "2:/myur_168M.elf";
115   fr = f_open(&fil, fn, FA_READ);
116   if (fr) 
117   {
118     printf("open %s fail\n", fn);
119     return (int)fr;
120   }
121 
122   TCHAR* pos=0;
123   DWORD fsize = f_size (&fil);
124   printf("fsize: %d\n", fsize);
125   int r_len = 0;
126   while (1)
127   {
128     f_read(&fil, buf, BUF_SIZE, &r_len);
129     printf("r_len: %d\n", r_len);
130     fsize -= BUF_SIZE;
131     if (fsize < BUF_SIZE)
132       print_packet(buf, fsize);
133     else
134       print_packet(buf, BUF_SIZE);
135 
136     Elf32Ehdr elf_header = *((Elf32Ehdr*)buf);
137     printf("sizeof Elf32Ehdr: %d\n", sizeof(Elf32Ehdr));
138     printf("sizeof Elf32Phdr: %d\n", sizeof(Elf32Phdr));
139     printf("elf_header.e_phoff: %d\n",  elf_header.e_phoff);
140     if (elf_header.e_phoff > BUF_SIZE)
141     {
142       printf("error elf_header.e_phoff > BUF_SIZE!!\n");
143       break;
144     }
145     Elf32Phdr elf_pheader = *((Elf32Phdr*)((u8 *)buf + elf_header.e_phoff)); // program header
146     printf("elf_header.e_phnum: %d\n", elf_header.e_phnum);
147     for (int i=0 ; i < elf_header.e_phnum; ++i)
148     {
149       int ret;
150       printf("p_vaddr: %#x offset: %#x size: %d\n", elf_pheader.p_vaddr, elf_pheader.p_offset, elf_pheader.p_filesz);
151       ret = f_lseek(&fil, elf_pheader.p_offset);
152       if (elf_pheader.p_filesz > BUF_SIZE)
153       {
154         printf("can not read: elf_pheader.p_filesz > BUF_SIZE\n");
155       }
156       else
157       {
158         f_read(&fil, buf, elf_pheader.p_filesz, &r_len);
159         printf("yy r_len: %d\n", r_len);
160         print_packet(buf, r_len);
161         #if 0
162         s32_memcpy(elf_pheader.p_vaddr, buf, r_len);
163         (*(void(*)())elf_code)();
164         #endif
165       }
166     }
167     break;
168 
169     // printf(line);
170   }
171 
172   /* Close the file */
173   f_close(&fil);
174 #endif
175 
176 #ifdef TEST_DIR
177   DIR dir;
178   static FILINFO f_info; // why need static, if no static, f_readdir will get segment fault
179   FRESULT ret;
180   TCHAR long_name_pool[_MAX_LFN * 2 + 1];
181 
182   f_info.lfsize = _MAX_LFN * 2 + 1;
183   f_info.lfname = long_name_pool;
184 
185   char path[255] = "2:\\";
186   ret = f_opendir(&dir, path);
187   if (ret == FR_OK)
188   {
189     #if 1
190     while(1)
191     {
192       ret = f_readdir (&dir, &f_info);
193       if (ret != FR_OK || f_info.fname[0] == 0) 
194         break;  /* Break on error or end of dir */
195       if (f_info.fattrib & AM_DIR)                     /* It is a directory */
196       {
197         //int i = strlen(path);
198         //sprintf(&path[i], "/%s", f_info.fname);
199         #if 0
200         ret = scan_files(path);                    /* Enter the directory */
201         if (res != FR_OK) break;
202                 path[i] = 0;
203         #endif
204       } 
205       else 
206       {                                       /* It is a file. */
207         printf("%s/%s\n", path, f_info.fname);
208         printf("%s/%s\n", path, f_info.lfname);
209       }
210     }
211     #endif
212     f_closedir(&dir);
213   }
214 #endif

FATFS文件系统的中文长文件名配置的几个注意事项

《STM32 不完原手冊範例》配置的設定就可以支援中文以及長檔名, 繁體中文要用 cp950, 雖然是 unicode, 不過讀出來的卻是 big5 編碼, 真是奇怪, 我得把終端機設定為 big5 編碼, 才能正確讀到中文檔名。

ffconf.h diff
+++ b/load_from_sd/fatfs/src/ffconf.h
@@ -57,7 +57,7 @@
-#define _CODE_PAGE     936             //採用中文 GBK 編碼
+#define _CODE_PAGE     950             //採用中文 big5 編碼

我的重點並不是把 fat 檔案列出來而已, 我要讀取 elf, 並載入其 program body, 所以我加入了這些測試。

在 elf 的測試方面, 已經可以正確讀到 elf program header 以及其中的 machine code, 再來就只剩下載入以及執行它就好了, 這部份就不太好在 linux 環境中測試了。

這個 library 似乎只會用 512 byte 讀取一個 sector, 算是記憶體用量很小的程式, 這樣的程式可不好寫, 蠻厲害的。

這篇夠折磨人了, 到這裡就好了, 下一篇再來談談怎麼載入這個 elf 和執行它。

ref:
stm32 fat 檔案系統文件

2016年11月5日 星期六

作業系統之前的程式 for stm32f4discovery (17) - sd card 初探 by spi

實踐出真知

為什麼我之前特別專注在 spi 上呢? 不是 i2c, 也不是 i2s, 就是因為我要使用 spi sd card, 這才是我真正的目的。sd card 的界面有 sdio 或是 spi, 我選擇了比較簡單的 spi。

spi 已經可以通訊了, 再來要把 spi 和 sd card 插槽連接起來, 這真的難倒我。

spi 的 4 個 pin 我已經了解, 沒有問題, ground 也沒問題, 問題是 3.3v, 5v 那個才是要接的 pin, 又要接在開發板的哪裡呢?

詢問過有經驗的朋友後, 決定把 sd 板的 3.3v 接在 stm32f4 開發板的 vdd 上, 也用了三用電錶確定 vdd 輸出的是 3V 電壓, 我一個軟體人員自然不會這些, 麻煩別人幫我完成這些事情。

5v 據說也可以, sd card slot 有降壓 ic 會把 5v 降到 3.3v。


SD(HC)-memory card and MMC interface conditioning chapter 2 提到 2.7 ~ 3.6 V sd card 的工作電壓

Today many appliances use 2.7 V to 3.6 V operating mode. It enables the use of a fixed
voltage interface and power supply to reduce cost and complexity of the control circuitry.
All further descriptions are related to this high-voltage range: 2.7 V to 3.6 V supply voltage
operated interfaces.

使用《作業系統之前的程式 for stm32f4discovery (16) - spi》提過的 spi1 來連接 sd card, 在該篇文章中, 我已經打通了 spi1 的通訊, 把它接過來這邊就可以了。

和《作業系統之前的程式 for stm32f4discovery (16) - spi》不一樣的是 cs (NSS) 需要接起來, 接到 sd card 的 SDCS, 但是設定 spi 時, 還是設定 soft nss。

stm32f4discovery 開發板 (msater) sd card (slave)
NSS: PA4 SDCS
SCK: PA5 SCK
MISO: PA6 MISO
MOSI: PA7 MOSI
ground ground
vdd3.3v



stm32f4discovery 線路圖可以參考 stm32f4discovery 線路圖, 我看了 vdd, 5v, 3.3v 的這些部份, 當然還是看不懂, 有個朋友為我說明其中概念, 這是認識 ee 人的好處, 要不然會看得很辛苦。

硬體接線搞定了, 再來剩下軟體的問題, spi 沒問題, sd card 的初始化還真的很複雜, 最後找了 STM32 不完原手冊的範例:

4)正点原子miniSTM32F103rct开发板资料:
基础资料下载总连接:http://pan.baidu.com/s/1qXYP1b2
手把手教你学STM32》参考视频(下载战舰B,C,D盘即可):http://pan.baidu.com/s/1eSAwPrW
B盘独立压缩包: http://pan.baidu.com/s/1i5GwEqT 视频盘,《手把手教你学STM32-M3》视频
C盘独立压缩包:http://pan.baidu.com/s/1miPJYeW 视频盘,《手把手教你学STM32-M3》视频
D盘独立压缩包:http://pan.baidu.com/s/1i4UZ4Lf 视频盘,《手把手教你学STM32-M3》视频  

UCOS/EMWIN/LWIP视频下载链接:

MiniSTM32 V2.0开发板资料(适合3.0之前所有版本):http://pan.baidu.com/s/1o7Hs8OU
<<原子教你玩STM32>>系列视频教程(30集):http://pan.baidu.com/s/1dD3Uvg1
STM32学习短视频(STM32解读/工具使用/开发板/模块测试):http://pan.baidu.com/s/1kT1GpRT
STM32学习从0开始(13讲): http://pan.baidu.com/s/1nvETqQ1

這個網站的 stm 相關開發板有很豐富的資源, 還有教學影片, 他們為了賣開發板做了這麼多的努力, 真的不簡單。這次的 SD card 程式碼參考了 STM32 不完原手冊的範例, 我修改為符合 stm32f4discovery 開發板後在 stm32f4discovery 執行。這是最麻煩的部份, 目前似乎沒有書籍專為 stm32f4discovery  來撰寫, 書籍大都需要搭配某塊開發板, 而我使用的開發板和這些書籍寫的都不同, 把範例改到可以正確執行還蠻麻煩的。


電子書可以下載, 想知道 sd card 初始化流程可以看看, 我只想套用這段程式碼, 站在巨人的肩膀真的輕鬆不少。由於我打通了 stm32f4discovery 的 spi1, 只要單純把 spi init 的程式碼換成我那段成功的部份就可以了, 當然, 還有 spi1 讀寫資料的部份也要一併置換。

這次能完成這個實驗要感謝很多人, 特別是 telegram 群組的 Yen-Chin Lee, 他給我很多意見以及相關資訊。

接上電源後, 開始跑程式了, 我很有信心, 一開始的 SD_Initialize() 應該會過才是, 但 ... 沒有, 我懷疑是硬體沒接好, 轉頭一看, 果然 3.3 v 的線沒插好, 再試一次, 還是不行, 再轉頭一看, 下方的 spi1 線鬆開了, 再來一次, 果然順利把 SD_Initialize() 執行完畢, 我就說是硬體沒接好吧! 這是難得的幸運, 一次就好, 之前的努力沒有白費, 真是讓人開心。



不過再來該做什麼呢? 把 CID, CSD 讀出來確認 sd card 真的有認到, 說不定 SD_Initialize() 只是運氣好過了, 而無法正確讀到 sd card 資訊。

在和 linux 抓到的 cid, csd 對照後, 除了最後的 CRC 不同外, 其他都一樣, 爽阿!

linux 下的 cid, csd 資訊
root@ds:/sys/block/mmcblk0# cat /sys/block/mmcblk0/device/cid
275048534430344720b0003fb4008c01
root@ds:/sys/block/mmcblk0# cat /sys/block/mmcblk0/device/csd
400e00325b5900001de77f800a400001

從 stm32f4discovery 開發板讀到, usart2 印出的結果
cid:
275048534430344720B0003FB4008CFB
csd:
400E00325B5900001DE77F800A4000D5

確定了, cid, csd 都是對的, 不過要把 spi 降速會比較穩,spi 設定在高速時不太穩, 有時候會卡在 spi read function。

Internal SD Card Information
NameFieldLinux attribute*Description
Manufacturer IDMIDmanfidAssigned by SD-3C, LLC.
OEM/Application IDOIDoemidIdentifies the card OEM and/or the card contents. Assigned by SD-3C, LLC.
Product NamePNMname5 characters long (ASCII)
Product RevisionPRVhwrev, fwrevTwo binary coded decimal (BCD) digits. Each is four bits. The PRV is in the form x.y. The PRV can also be found by using the hwrev and fwrev, where x=hwrev and y=fwrev
Serial NumberPSNserialThis 32 bit field is intended to be read as an unsigned integer
Manufacture Date CodeMDTdateManufacture date is stored in the form yym (offset from 2000)
CRC7 checksumCRC7 bit code used for checking errors in the card register

再來 dump sd card 的第 0 個 sector 內容, 確定可以正確讀到 sector 0 的資料。

list 1 . dump section 0 data
 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: 
14 
15 FA 31 C0 8E D8 8E D0 BC 00 7C 89 E6 06 57 8E C0 
16 FB FC BF 00 06 B9 00 01 F3 A5 EA 1F 06 00 00 52 
17 52 B4 41 BB AA 55 31 C9 30 F6 F9 CD 13 72 13 81 
18 FB 55 AA 75 0D D1 E9 73 09 66 C7 06 8D 06 B4 42 
19 EB 15 5A B4 08 CD 13 83 E1 3F 51 0F B6 C6 40 F7 
20 E1 52 50 66 31 C0 66 99 E8 66 00 E8 21 01 4D 69 
21 73 73 69 6E 67 20 6F 70 65 72 61 74 69 6E 67 20 
22 73 79 73 74 65 6D 2E 0D 0A 66 60 66 31 D2 BB 00 
23 7C 66 52 66 50 06 53 6A 01 6A 10 89 E6 66 F7 36 
24 F4 7B C0 E4 06 88 E1 88 C5 92 F6 36 F8 7B 88 C6 
25 08 E1 41 B8 01 02 8A 16 FA 7B CD 13 8D 64 10 66 
26 61 C3 E8 C4 FF BE BE 7D BF BE 07 B9 20 00 F3 A5 
27 C3 66 60 89 E5 BB BE 07 B9 04 00 31 C0 53 51 F6 
28 07 80 74 03 40 89 DE 83 C3 10 E2 F3 48 74 5B 79 
29 39 59 5B 8A 47 04 3C 0F 74 06 24 7F 3C 05 75 22 
30 66 8B 47 08 66 8B 56 14 66 01 D0 66 21 D2 75 03 
31 66 89 C2 E8 AC FF 72 03 E8 B6 FF 66 8B 46 1C E8 
32 A0 FF 83 C3 10 E2 CC 66 61 C3 E8 62 00 4D 75 6C 
33 74 69 70 6C 65 20 61 63 74 69 76 65 20 70 61 72 
34 74 69 74 69 6F 6E 73 2E 0D 0A 66 8B 44 08 66 03 
35 46 1C 66 89 44 08 E8 30 FF 72 13 81 3E FE 7D 55 
36 AA 0F 85 06 FF BC FA 7B 5A 5F 07 FA FF E4 E8 1E 
37 00 4F 70 65 72 61 74 69 6E 67 20 73 79 73 74 65 
38 6D 20 6C 6F 61 64 20 65 72 72 6F 72 2E 0D 0A 5E 
39 AC B4 0E 8A 3E 62 04 B3 07 CD 10 3C 0A 75 F1 CD 
40 18 F4 EB FD 00 00 00 00 00 00 00 00 00 00 00 00 
41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
42 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 02 
43 03 01 0C 18 D8 CC 00 20 00 00 00 80 77 00 00 00 
44 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
45 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
46 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA 

和用 hexdump 做比較, 當然是一樣的。

hexdump -Cv -n 512 /dev/sdb
 1 00000000  fa 31 c0 8e d8 8e d0 bc  00 7c 89 e6 06 57 8e c0  
 2 00000010  fb fc bf 00 06 b9 00 01  f3 a5 ea 1f 06 00 00 52 
 3 00000020  52 b4 41 bb aa 55 31 c9  30 f6 f9 cd 13 72 13 81 
 4 00000030  fb 55 aa 75 0d d1 e9 73  09 66 c7 06 8d 06 b4 42 
 5 00000040  eb 15 5a b4 08 cd 13 83  e1 3f 51 0f b6 c6 40 f7 
 6 00000050  e1 52 50 66 31 c0 66 99  e8 66 00 e8 21 01 4d 69 
 7 00000060  73 73 69 6e 67 20 6f 70  65 72 61 74 69 6e 67 20 
 8 00000070  73 79 73 74 65 6d 2e 0d  0a 66 60 66 31 d2 bb 00 
 9 00000080  7c 66 52 66 50 06 53 6a  01 6a 10 89 e6 66 f7 36 
10 00000090  f4 7b c0 e4 06 88 e1 88  c5 92 f6 36 f8 7b 88 c6 
11 000000a0  08 e1 41 b8 01 02 8a 16  fa 7b cd 13 8d 64 10 66 
12 000000b0  61 c3 e8 c4 ff be be 7d  bf be 07 b9 20 00 f3 a5 
13 000000c0  c3 66 60 89 e5 bb be 07  b9 04 00 31 c0 53 51 f6 
14 000000d0  07 80 74 03 40 89 de 83  c3 10 e2 f3 48 74 5b 79 
15 000000e0  39 59 5b 8a 47 04 3c 0f  74 06 24 7f 3c 05 75 22
16 000000f0  66 8b 47 08 66 8b 56 14  66 01 d0 66 21 d2 75 03 
17 00000100  66 89 c2 e8 ac ff 72 03  e8 b6 ff 66 8b 46 1c e8 
18 00000110  a0 ff 83 c3 10 e2 cc 66  61 c3 e8 62 00 4d 75 6c 
19 00000120  74 69 70 6c 65 20 61 63  74 69 76 65 20 70 61 72 
20 00000130  74 69 74 69 6f 6e 73 2e  0d 0a 66 8b 44 08 66 03 
21 00000140  46 1c 66 89 44 08 e8 30  ff 72 13 81 3e fe 7d 55
22 00000150  aa 0f 85 06 ff bc fa 7b  5a 5f 07 fa ff e4 e8 1e 
23 00000160  00 4f 70 65 72 61 74 69  6e 67 20 73 79 73 74 65 
24 00000170  6d 20 6c 6f 61 64 20 65  72 72 6f 72 2e 0d 0a 5e 
25 00000180  ac b4 0e 8a 3e 62 04 b3  07 cd 10 3c 0a 75 f1 cd
26 00000190  18 f4 eb fd 00 00 00 00  00 00 00 00 00 00 00 00 
27 000001a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
28 000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 80 02 
29 000001c0  03 01 0c 18 d8 cc 00 20  00 00 00 80 77 00 00 00 
30 000001d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
31 000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00 
32 000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa 
33 00000200

linux 抓到的 blocks 是 7839744 也和 list 1. L12 計算的是一樣的值。這個值是說這張 sd card 有幾個 sector, 而每一個 sector 是 512 byte。

linux sd card info
[10848.308565] sd 1:0:0:0: Attached scsi generic sg1 type 0
[10849.270876] sd 1:0:0:0: [sdb] 7839744 512-byte logical blocks: (4.01 GB/3.74 GiB)

source code:
https://github.com/descent/stm32f4_prog/tree/master/spi_sdcard

突破這關後, 再來的 fat 檔案系統是純軟體, 對我來說就沒有那麼難了, 我煩惱的是要自己寫, 還是用現成的 library? 該讓我自己站在巨人的肩膀了, 造了那麼多輪子也是會累的。

me:
spi cpol, cpha 在 sd card 上一定要設成某個值嗎?
還是都可以:
ex:
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;

Yen-Chin Lee:
SPI mode 0  或是 mode 3 都可以, mode 0 比較合適

不過暫時不急著搞 fat, 要先來玩玩我好久好久之前就想做的事情
fat 我搞定了, 而且也完成我想做的事情了。

ref:

About the module - pairs in 8x2 pin header are connected, which leaves 8 pins. And of those 2 are gnd and 2 are vcc (3,3 and 5 V). The schematic on the module suggests SPI pins correspond to SD interface as follows:
miso->dat0
gnd->gnd
sck->clk
3.3V->vcc
gnd->cmd
mosi->dat3
cs->dat2


2016年10月31日 星期一

作業系統之前的程式 for stm32f4discovery (16.3) - spi1 與 spi2 對接

蝸牛角上爭何事,石火光中寄此身。
左邊 DD 的服飾是不是太華麗了, 衣服很好看, 但不好穿上, 搞得我滿頭大汗才順利裝扮完畢, 尤其是那大腿襪, 真的有夠難穿, 總是卡在腳踝的部份, 大腿襪 size 一定有問題。疑 ... 這篇好像不是談這個 ... 趕緊進入正題。

在完成《作業系統之前的程式 for stm32f4discovery (16) - spi》之後, 我打算把 stm32f407 discovery board 的 spi1 <-> spi2 互接, 並測試能否從 spi1 送資料, 從 spi2 收到這筆資料。

這麼有創意的點子當然不是我想到的,《STM32自学笔记》裡頭有個 spi 實驗就是這麼做的。

spi 接腳
使用 soft nss
spi1 : pa5, pa6, pa7 master
spi2: pb13, pb14, pb15 slave
pa5 <-> pb13 sck
pa6 <- pb14 miso
pa7 <-> pb15 mosi

spi1 設定為 master。
spi2 設定為 slave。

spi1 接在 ABP2 再呼叫 SystemInit() 之後被設定為 84MHz, spi2 接在 APB1, 被設定為 42MHz。

對接之後, spi2 是吃 spi1 sck 的時脈, 所以 spi2 的 clock 設定並不是依照暫存器的值來反應 clock。

由於我已經知道怎麼設定 spi, 此役我信心滿滿, 應該沒問題吧, 不過沒有搞懂 spi 是在 master 送出資料時, clock 才會啟動, 而這個時候不只是把 master 資料送出, 如果 slave 要接收資料時, 也要在 master 送出資料的程式碼這邊, 接在下方補上收 slave 資料的程式碼, 這樣 slave 才能在這時間點收到資料, 用講的很亂, 請直接參考 my_spi.c spi1_spi2_send_recv() 就知道我在說什麼。

fig 1 fig 2

當把所有錯誤排除之後, 終於可以脫離 LA 了, 最終我可以從 usart2 來印出從 spi1/spi2 互相傳輸的資料, fig2, fig2 圖示這次的線路接法, 看來有點複雜, 其實並沒有那麼可怕, 由於我把 pa2, pa3 拿去用在 spi1, 所以原本的 usart2 pa2, pa3 就不能用了, 我修改了原本的 usart2 程式碼, 讓它可以接 pd5, pd6, 所以後面的那組接線是 usart2, 前面的上方則是用 LA 接出來看波形的, 下方才是 spi1 與 spi2 對接。

source code:
https://github.com/descent/stm32f4_prog/blob/master/spi/my_spi.c

my_spi.c
 837 void spi1_spi2_send_recv(u8 spi1_data, u8 spi2_data, u8 *r_spi1_data, u8 *r_spi2_data)
 838 {
 839   //SPI1->DR = 0xfa; // write data to be transmitted to the SPI data register
 840   //SPI2->DR = 0xfe; // write data to be transmitted to the SPI data register
 841   SPI1->DR = spi1_data; // write data to be transmitted to the SPI data register
 842   SPI2->DR = spi2_data; // write data to be transmitted to the SPI data register
 843   while( !(SPI1->SR & SPI_I2S_FLAG_TXE) ); // wait until transmit complete
 844   while( !(SPI1->SR & SPI_I2S_FLAG_RXNE) ); // wait until receive complete
 845   while( SPI1->SR & SPI_I2S_FLAG_BSY ); // wait until SPI is not busy anymore
 846   *r_spi1_data = SPI1->DR; // return received data from SPI data register
 847 #if 1
 848   while( !(SPI2->SR & SPI_I2S_FLAG_TXE) ); // wait until transmit complete
 849   while( !(SPI2->SR & SPI_I2S_FLAG_RXNE) ); // wait until receive complete
 850   while( SPI2->SR & SPI_I2S_FLAG_BSY ); // wait until SPI is not busy anymore
 851   *r_spi2_data = SPI2->DR; // return received data from SPI data register
 852 #endif
 853 }

感謝 juluos 陳兄的意見, 讓我得以完成這個實驗。

usart 和 spi 都好了, 再來就可以搞更有趣的事情了。

2016年10月23日 星期日

作業系統之前的程式 for stm32f4discovery (16) - spi

少年辛苦終身事,莫向光陰惰寸功

相關文章:
fig 0 金字塔門檻

fig 0 的金字塔圖表現出所需要的知識, 得從最下層一一突破, 才能完成最上層的目標。

spi 的用法真的難倒我, 從《20150117 成功大學 UniDEMO 期末聯合 DEMO 計畫》開始我就沒成功過, 我真的很懷疑書上/網路上的範例程式是真的可以運作嗎? 20161013 我再次挑戰, 這次我補充了需要的知識以及武器, 可以來對付 spi 了。

fig 1 spi1 可以使用的 pin

一樣要有以下的知識以及武器:
  • 邏輯分析儀
  • 搞懂 stm32f4 clock source tree
  • 那些 pin 是可以拿來當 spi 的那 4 個 pin
fig 2
還是參考《精通STM32F4(库函数版)》SPI 的實驗, 但我稍微簡化它, 因為 stm32f4discovery 並沒有接上的 SPI flash, 我無法照原樣套用。書上的範例程式我搞不定, 最後找到另外的版本。

我打算用 SPI1 送出資料, 所以得先找那個 pin 可以用來當作 SPI1, 參考 fig 1 的 datasheet, 我本來找了:

NSS: PA15
SCK: PB3
MISO: PB4
MOSI: PB4

來當 spi1 使用, 不過失敗了。最後找到可運作的程式碼是使用

NSS: PA4
SCK: PA5
MISO: PA6
MOSI: PA7

來當 spi1。

由於用到了 gpio A, 所以還要開啟 gpio A 相關的時脈, GPIO 接在 AHB1, 這是為什麼要呼叫 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE)。

PA4, 5, 6, 7 需要用 GPIO_PinAFConfig 來設定其對應的 spi1 功能。

spi 相關設定就不解釋, 《精通STM32F4(库函数版)》(可以從https://pan.baidu.com/s/1dEQxWDN 下載) 或是相關的書籍都會提到, 但都不是很詳細, 我看了幾本書中的介紹以及網路文章, 還是不太理解 spi, 但也算略懂。

而 spi1 接在 APB2, 所以需要呼叫 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE) 開啟 spi1 的時脈。

這就是為什麼要搞懂 stm32f407 clock soure tree, 除了系統時脈, 我們還得知道所要設定的週邊接在那個 bus 上。這些細節請參考《精通STM32F4(库函数版)》, 這本書讓我一舉突破 clock source tree 的關卡。

程式在初始化 spi1 之後, 很單純的從 spi MOSI 送出 0xaa, 0x12 而已, 怎麼驗證有正確送出呢? 用 LA, fig 2 中間上方便是接上 LA channel 0, 1, 2, 3, fig 3 則是結果。

一開始我以為設定好 spi1 enable 之後就可以在 LA 看到 SCK 的波形, 不過事與願違, spi1.c L736 SPI1->DR = data; 這之後, spi 才會開始運作, 也才會送出 spi clock。

fig 3 的 01 是 SCK, 也就是 clock, 326.5k, 而 spi1 是接在 APB2, 而 APB2 是 84 MHz, 而我用 SPI_BaudRatePrescaler_256 讓 spi1 除以 256, 84000000/256 = 328125, 好像很接近 325.5k, 我不確定是不是對的。03 是 spi 1 的 MOSI, 也就是 master output, 送出 170 (0xaa), 18 (0x12) 看來也是正確的。

fig 3

spi1.c 是將 spi1 設定為 master, soft nss, 也就是 cs 這個 pin 可以不用接, 用軟體的方式來實作。

spi1.c
  1 #include "stm32.h"
  2 #include "stm32f4xx_usart.h"
  3 #include "stm32f4xx_rcc.h"
  4 #include "stm32f4xx_spi.h"
  5 #include "stm32f4xx_gpio.h"
  6 
663 void init_SPI1(void)
664 {
665  
666  GPIO_InitTypeDef GPIO_InitStruct;
667  SPI_InitTypeDef SPI_InitStruct;
668  
669  // enable clock for used IO pins
670  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
671  
672  /* configure pins used by SPI1
673   * PA5 = SCK
674   * PA6 = MISO
675   * PA7 = MOSI
676   */
677  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_6 | GPIO_Pin_5;
678  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
679  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
680  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
681  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
682  GPIO_Init(GPIOA, &GPIO_InitStruct);
683  
684  // connect SPI1 pins to SPI alternate function
685  GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
686  GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
687  GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
688  
689  // enable clock for used IO pins
690  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
691  
692  /* Configure the chip select pin
693     in this case we will use PA4 */
694  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
695  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
696  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
697  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
698  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
699  GPIO_Init(GPIOA, &GPIO_InitStruct);
700  
701  GPIOA->BSRRL |= GPIO_Pin_4; // set PA4 high
702 #if 0
703  /* Configure the chip select pin
704     in this case we will use PE7 */
705  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
706  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
707  GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
708  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
709  GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
710  GPIO_Init(GPIOE, &GPIO_InitStruct);
711  
712  GPIOE->BSRRL |= GPIO_Pin_7; // set PE7 high
713 #endif 
714  // enable peripheral clock
715  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
716  
717  /* configure SPI1 in Mode 0 
718   * CPOL = 0 --> clock is low when idle
719   * CPHA = 0 --> data is sampled at the first edge
720   */
721  SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // set to full duplex mode, seperate MOSI and MISO lines
722  SPI_InitStruct.SPI_Mode = SPI_Mode_Master;     // transmit in master mode, NSS pin has to be always high
723  SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; // one packet of data is 8 bits wide
724  SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;        // clock is low when idle
725  SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;      // data sampled at first edge
726  SPI_InitStruct.SPI_NSS = SPI_NSS_Soft | SPI_NSSInternalSoft_Set; // set the NSS management to internal and pull internal NSS high
727  SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // SPI frequency is APB2 frequency / 4
728  SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;// data is transmitted MSB first
729  SPI_Init(SPI1, &SPI_InitStruct); 
730  
731  SPI_Cmd(SPI1, ENABLE); // enable SPI1
732 }
733 
734 uint8_t SPI1_send(uint8_t data){
735 
736  SPI1->DR = data; // write data to be transmitted to the SPI data register
737  while( !(SPI1->SR & SPI_I2S_FLAG_TXE) ); // wait until transmit complete
738  while( !(SPI1->SR & SPI_I2S_FLAG_RXNE) ); // wait until receive complete
739  while( SPI1->SR & SPI_I2S_FLAG_BSY ); // wait until SPI is not busy anymore
740  return SPI1->DR; // return received data from SPI data register
741 }
742 
743 /**
744  * @brief  Main program.
745  * @param  None
746  * @retval None
747  */
748 int main(void)
749 {
750 #ifdef SET_CPU_CLOCK
751   SystemInit();
752 #endif
753   init_SPI1();
754   uint8_t received_val = 0;
755 
756 #if 1
757   while(1)
758   {
759     //GPIOE->BSRRH |= GPIO_Pin_7; // set PE7 (CS) low
760     GPIOA->BSRRH |= GPIO_Pin_4; // set PE7 (CS) low
761     #if 1
762     SPI1_send(0xAA);  // transmit data
763     received_val = SPI1_send(0x12); // transmit dummy byte and receive data
764     #endif
765     //GPIOE->BSRRL |= GPIO_Pin_7; // set PE7 (CS) high
766     GPIOA->BSRRL |= GPIO_Pin_4; // set PE7 (CS) high
767   }
768 #endif
810 
811 }

ref: