2014年11月7日 星期五

作業系統之前的程式 for stm32f4discovery (10) - 印出浮點數

漢兒學得胡兒語, 爭上牆頭罵漢人

終於來到 10 了, 這是目前最長的系列, 真是長壽。浮點數原來是這麼麻煩的東西, 這篇並不是要介紹浮點數的格式、原理, 而是怎麼把浮點數印出來。

printf("%f") 就搞定了, 你這麼說: 不過標題是作業系統之前的程式, 所以我們得自己提供這個功能, 當然不用把 printf 做出來, 先把 float 轉成 C 字串就可以了。

先來個提外話:

我在 p103 模擬器上測試程式碼時, 使用不同版本的 toolchain, 得到了以下的錯誤訊息, p103 的範例有很多是我不能掌握的程式碼, 單純是為了方便而使用 p103 模擬器。

/tmp/ccRko2hC.s: Assembler messages:
/tmp/ccRko2hC.s:813: Error: registers may not be the same -- `strexb r3,r2,[r3]'
/tmp/ccRko2hC.s:862: Error: registers may not be the same -- `strexh r3,r2,[r3]'
make: *** [main.elf] Error 1

若你遇到這問題, 請參考 Fix “registers may not be the same” ARM GCC error

core_cm3.diff
 1 diff --git a/libraries/CMSIS/CM3/CoreSupport/core_cm3.c b/libraries/CMSIS/CM3/CoreSupport/core_cm3.c
 2 index 56fddc5..0e8c3c4 100644
 3 --- a/libraries/CMSIS/CM3/CoreSupport/core_cm3.c
 4 +++ b/libraries/CMSIS/CM3/CoreSupport/core_cm3.c
 5 @@ -733,7 +733,7 @@ uint32_t __STREXB(uint8_t value, uint8_t *addr)
 6  {
 7     uint32_t result=0;
 8    
 9 -   __ASM volatile ("strexb %0, %2, [%1]" : "=r" (result) : "r" (addr), "r" (value) );
10 +   __ASM volatile ("strexb %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );
11     return(result);
12  }
13  
14 @@ -750,7 +750,7 @@ uint32_t __STREXH(uint16_t value, uint16_t *addr)
15  {
16     uint32_t result=0;
17    
18 -   __ASM volatile ("strexh %0, %2, [%1]" : "=r" (result) : "r" (addr), "r" (value) );
19 +   __ASM volatile ("strexh %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );
20     return(result);
21  }
22  

大概是這樣修正, 我不知道為什麼用了不同的 toolchain 會這樣, 至於 & 是表示被修飾的操作符只能作為輸出。

回到浮點數。

ftoa.cpp 從 How to implement “ char * ftoa(float num) ” without sprintf() library function in C, C++ and JAVA? 抄來的, 我做了一些修改, 避免使用 math.h 的東西, 拿掉 log10 和 pow 這兩個 function, 作業系統之前的程式都要自己來, 儘量不要用現成的 library; 作業系統之下的版本當然就直接 call log10 和 pow。這是面試題目, 多了解不會吃虧的。

float.cpp
217 static inline int mypow(int x, int y)
218 {
219   int r=1;
220   for (int i=0 ; i < y ; ++i)
221     r*=x;
222   return r;
223 }
224 
225 const int PRECISION = 6;
226 
227 static inline u8* float_to_str(float num)
228 {
229    int whole_part = num;
230    int digit = 0, reminder =0;
231    //int log_value = log10(num), 
232    int index;
233    float wt = 0.0;
234 
235    // String containg result
236    static u8 str[30];
237 
238    //Initilise stirng to zero
239    //memset(str, 0 ,20);
240    
241    //u8 whole_str[20] ;
242    s32_itoa(whole_part, str, 10);
243 #if 0
244    //Extract the whole part from float num
245    for(int  i = 1 ; i < log_value + 2 ; i++)
246    {
247        wt  =  mypow(10,i);
248        reminder = whole_part  %  wt;
249        digit = (reminder - digit) / (wt/10);
250 
251        //Store digit in string
252        str[index--] = digit + 48;              // ASCII value of digit  = digit + 48
253        if (index == -1)
254           break;    
255    }
256 #endif
257     //index = log_value + 1;
258     index = s_strlen((const char*)str);
259     str[index] = '.';
260 
261    float fraction_part  = num - whole_part;
262    float tmp1 = fraction_part,  tmp =0;
263 
264 #if 0
265    cout << "fraction_part: " << fraction_part << endl;
266    cout << "num: " << num << endl;
267    cout << "whole_part: " << whole_part << endl;
268 #endif
269    //Extract the fraction part from  num
270    for( int i= 1; i < PRECISION; i++)
271    {
272       wt =10; 
273       tmp  = tmp1 * wt;
274       digit = tmp;
275       //cout << "digit: " << digit << endl;
276       //cout << "tmp: " << tmp << endl;
277       //cout << "tmp1: " << tmp1 << endl;
278 
279       //Store digit in string
280       str[++index] = digit +48;           // ASCII value of digit  = digit + 48
281       tmp1 = tmp - digit;
282       if (tmp1 == 0.0)
283         break;
284    }    
285    str[index+1] = 0;
286 
287    return str;
288 }
289 
290 
291 #endif

這個演算法的重點有兩個:
  1. c 語言型別轉換知識 (這你不可能想得到, 只有靠閱讀 c 語言資料才會知道)
  2. 演算法 (你可以想出來怎麼做)
c 怎麼把浮點數轉成整數?
How do I round numbers?
C's floating to integer conversion truncates (discards) the fractional part
這是知識。

怎麼把數字 123 轉成 c 字串的 123, 這是演算法, 缺一不可。程式員真辛苦, 除了讀書還要思考, 可不是嘴炮般的說些自己想像的東西。

int i = 1.35f; 浮點數 1.35 會轉成整數 1 存給 i, 再用 1.35 - 1 得到 0.35, 有了 1 和 0.35, 想辦法把他們變成 char 存到 c 字串就搞定。

請參考 float.cpp L242 把整數 1 轉成字串, L207 把 0.35 轉成字元 3, 5, 最後在他們中間加上 "." 就搞定了。

int main(void)
{
  init_rs232();
  USART_Cmd(USART2, ENABLE);
  
  float f=1.5;
  myprint_float(f);
  myprint("\r\nabc");
  while(1);
}

還沒完, 怎麼 compile 浮點運算的程式碼可是個問題, 目標放在使用軟體的浮點運算函式庫 - libgcc, 所以要加上 -mfloat-abi=soft, link 時需要加上 -lgcc (arm-none-eabi-gcc -print-libgcc-file-name 可以顯示 libgcc 的路徑), 才剛說了大話馬上被打臉, 我還是用上了 gcc 裡頭的浮點運算函式庫。

我不是想偷懶, 只是神功還沒練成, 秘笈在這:



k_stdio.o: In function `float_to_str':
/home/descent/git/stm32_p103_demos/demos/uart_echo/k_string.h:229: undefined reference to `__aeabi_f2iz'
/home/descent/git/stm32_p103_demos/demos/uart_echo/k_string.h:261: undefined reference to `__aeabi_i2f'
/home/descent/git/stm32_p103_demos/demos/uart_echo/k_string.h:261: undefined reference to `__aeabi_fsub'
/home/descent/git/stm32_p103_demos/demos/uart_echo/k_string.h:273: undefined reference to `__aeabi_fmul'
/home/descent/git/stm32_p103_demos/demos/uart_echo/k_string.h:274: undefined reference to `__aeabi_f2iz'
/home/descent/git/stm32_p103_demos/demos/uart_echo/k_string.h:281: undefined reference to `__aeabi_i2f'
/home/descent/git/stm32_p103_demos/demos/uart_echo/k_string.h:281: undefined reference to `__aeabi_fsub'
/home/descent/git/stm32_p103_demos/demos/uart_echo/k_string.h:282: undefined reference to `__aeabi_fcmpeq'

如果你遇上這些連結錯誤的話, 可能是忘了加上 -lgcc, 很奇怪, 有的 toolchain 不需要明確指出 -lgcc, 有的卻需要, libgcc 明明是 gcc 內建的, 怎麼會需要特別指定呢? 奇怪。這是我在 p103 模擬器上的測試程式碼, p103 的範例有很多是我不能掌握的 code, 是我純粹為了方便測試為主。你可能發現前面講過了, 因為是從這裡複製貼上修改到前面的。

模擬器執行結果
 1 descent@debianlinux:uart_echo$ /media/2/qemu_stm32/arm-softmmu/qemu-system-arm -M stm32-p103 -kernel mymain.bin  -nographic
 2 STM32_UART: UART1 clock is set to 0 Hz.
 3 STM32_UART: UART1 BRR set to 0.
 4 STM32_UART: UART1 Baud is set to 0 bits per sec.
 5 STM32_UART: UART2 clock is set to 0 Hz.
 6 STM32_UART: UART2 BRR set to 0.
 7 STM32_UART: UART2 Baud is set to 0 bits per sec.
 8 STM32_UART: UART3 clock is set to 0 Hz.
 9 STM32_UART: UART3 BRR set to 0.
10 STM32_UART: UART3 Baud is set to 0 bits per sec.
11 STM32_UART: UART4 clock is set to 0 Hz.
12 STM32_UART: UART4 BRR set to 0.
13 STM32_UART: UART4 Baud is set to 0 bits per sec.
14 STM32_UART: UART5 clock is set to 0 Hz.
15 STM32_UART: UART5 BRR set to 0.
16 STM32_UART: UART5 Baud is set to 0 bits per sec.
17 STM32_UART: UART5 clock is set to 0 Hz.
18 STM32_UART: UART5 BRR set to 0.
19 STM32_UART: UART5 Baud is set to 0 bits per sec.
20 STM32_UART: UART4 clock is set to 0 Hz.
21 STM32_UART: UART4 BRR set to 0.
22 STM32_UART: UART4 Baud is set to 0 bits per sec.
23 STM32_UART: UART3 clock is set to 0 Hz.
24 STM32_UART: UART3 BRR set to 0.
25 STM32_UART: UART3 Baud is set to 0 bits per sec.
26 STM32_UART: UART2 clock is set to 0 Hz.
27 STM32_UART: UART2 BRR set to 0.
28 STM32_UART: UART2 Baud is set to 0 bits per sec.
29 STM32_UART: UART1 clock is set to 0 Hz.
30 STM32_UART: UART1 BRR set to 0.
31 STM32_UART: UART1 Baud is set to 0 bits per sec.
32 LED Off
33 CLKTREE: HSI Output Change (SrcClk:None InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
34 CLKTREE: HSI/2 Output Change (SrcClk:HSI InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
35 CLKTREE: SYSCLK Output Change (SrcClk:HSI InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
36 CLKTREE: HCLK Output Change (SrcClk:SYSCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
37 STM32_RCC: Cortex SYSTICK frequency set to 8000000 Hz (scale set to 125).
38 STM32_RCC: Cortex SYSTICK ext ref frequency set to 1000000 Hz (scale set to 1000).
39 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
40 CLKTREE: PCLK2 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
41 CLKTREE: HSE Output Change (SrcClk:None InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
42 CLKTREE: HSE/2 Output Change (SrcClk:HSE InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
43 CLKTREE: PLLXTPRE Output Change (SrcClk:HSE InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
44 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
45 CLKTREE: PLLCLK Output Change (SrcClk:PLLXTPRE InFreq:8000000 OutFreq:72000000 Mul:9 Div:1 Enabled:1)
46 CLKTREE: SYSCLK Output Change (SrcClk:PLLCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
47 CLKTREE: HCLK Output Change (SrcClk:SYSCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
48 STM32_RCC: Cortex SYSTICK frequency set to 72000000 Hz (scale set to 13).
49 STM32_RCC: Cortex SYSTICK ext ref frequency set to 9000000 Hz (scale set to 111).
50 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:72000000 OutFreq:36000000 Mul:1 Div:2 Enabled:1)
51 CLKTREE: PCLK2 Output Change (SrcClk:HCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
52 CLKTREE: GPIOA Output Change (SrcClk:PCLK2 InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
53 CLKTREE: AFIO Output Change (SrcClk:PCLK2 InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
54 CLKTREE: UART2 Output Change (SrcClk:PCLK1 InFreq:36000000 OutFreq:36000000 Mul:1 Div:1 Enabled:1)
55 STM32_UART: UART2 clock is set to 36000000 Hz.
56 STM32_UART: UART2 BRR set to 0.
57 STM32_UART: UART2 Baud is set to 0 bits per sec.
58 STM32_UART: UART2 clock is set to 36000000 Hz.
59 STM32_UART: UART2 BRR set to 3750.
60 STM32_UART: UART2 Baud is set to 9600 bits per sec.
61 1.5
62 abc

正確印出 float 1.5, 別看我寫的很輕鬆, 期間可是克服不少問題才把這 1.5 辛苦秀出來。

我當然不會滿足模擬器上的測試, 以下是 stm32discovery 完整範例:
https://github.com/descent/stm32f4_prog/tree/master/float

我有 gcc 4.7, 4.8, 4.9 三個版本的 toolchain, 測試結果有所不同, stm32discovery 的版本我在 gcc 4.91 測試成功, 其他的都不正常。

debian 提供的 arm-none toolchain 有支援 mulit-lib, 什麼是 multi-lib, 很難解釋, 直接看:

debian64:~# dpkg -L libnewlib-arm-none-eabi|grep libm
/usr/lib/arm-none-eabi/newlib/libm.a
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/libm.a
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/fpu/libm.a
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/softfp/libm.a
/usr/lib/arm-none-eabi/newlib/fpu/libm.a
/usr/lib/arm-none-eabi/newlib/thumb/libm.a
/usr/lib/arm-none-eabi/newlib/thumb/armv7-ar/fpu/vfpv3-d16/be/libm.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/libm.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/fpu/libm.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/softfp/libm.a
/usr/lib/arm-none-eabi/newlib/armv6-m/libm.a
/usr/lib/arm-none-eabi/newlib/armv7-m/libm.a

debian64:~# dpkg -L libnewlib-arm-none-eabi|grep libc
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/fpu/libc.a
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/fpu/libc_nano.a
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/softfp/libc.a
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/softfp/libc_nano.a
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/libc.a
/usr/lib/arm-none-eabi/newlib/armv7-ar/thumb/libc_nano.a
/usr/lib/arm-none-eabi/newlib/fpu/libc.a
/usr/lib/arm-none-eabi/newlib/fpu/libc_nano.a
/usr/lib/arm-none-eabi/newlib/libc.a
/usr/lib/arm-none-eabi/newlib/thumb/armv7-ar/fpu/vfpv3-d16/be/libc.a
/usr/lib/arm-none-eabi/newlib/thumb/armv7-ar/fpu/vfpv3-d16/be/libc_nano.a
/usr/lib/arm-none-eabi/newlib/thumb/libc.a
/usr/lib/arm-none-eabi/newlib/thumb/libc_nano.a
/usr/lib/arm-none-eabi/newlib/libc_nano.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/fpu/libc.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/fpu/libc_nano.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/softfp/libc.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/softfp/libc_nano.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/libc.a
/usr/lib/arm-none-eabi/newlib/armv7e-m/libc_nano.a
/usr/lib/arm-none-eabi/newlib/armv6-m/libc.a
/usr/lib/arm-none-eabi/newlib/armv6-m/libc_nano.a
/usr/lib/arm-none-eabi/newlib/armv7-m/libc.a
/usr/lib/arm-none-eabi/newlib/armv7-m/libc_nano.a

同一套 toolchain 提供了不同版本的 libm.a, 這就是 multi-lib, 那要怎麼知道要 link 那一個, 施主, 程式你寫的, 自己要知道阿!

list 1.
1 descent@debian64:compiler$ arm-none-eabi-gcc -print-multi-lib
2  .;
3 thumb;@mthumb
4 fpu;@mfloat-abi=hard
5 armv6-m;@mthumb@march=armv6s-m
6 armv7-m;@mthumb@march=armv7-m
7 armv7e-m;@mthumb@march=armv7e-m
8 armv7-ar/thumb;@mthumb@march=armv7
9 armv7e-m/softfp;@mthumb@march=armv7e-m@mfloat-abi=softfp@mfpu=fpv4-sp-d16
10 armv7e-m/fpu;@mthumb@march=armv7e-m@mfloat-abi=hard@mfpu=fpv4-sp-d16
11 armv7-ar/thumb/softfp;@mthumb@march=armv7@mfloat-abi=softfp@mfpu=vfpv3-d16
12 armv7-ar/thumb/fpu;@mthumb@march=armv7@mfloat-abi=hard@mfpu=vfpv3-d16
13 thumb/armv7-ar/fpu/vfpv3-d16/be;@mthumb@march=armv7@mfloat-abi=hard@mfpu=vfpv3-d16@mbig-endian

-print-multi-lib 這個參數可以顯示出什麼參數會去用那個路徑的 libm。

descent@debian64:$ arm-none-eabi-gcc -print-file-name=libm.a
/usr/lib/gcc/arm-none-eabi/4.9.3/../../../arm-none-eabi/lib/libm.a

descent@debian64:$ arm-none-eabi-gcc -march=armv6s-m -mthumb -print-file-name=libm.a
/usr/lib/gcc/arm-none-eabi/4.9.3/../../../arm-none-eabi/lib/armv6-m/libm.a

list 1. L5 的 @mthumb@march=armv6s-m, 就是指 -mthumb -march=armv6s-m, 把 @ 換成 - 就可以了。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。