2014/11/29

[keil]32KBの壁はどこにある?

「keilのフリー版は32KB制限がありますよね」という記事を書いていたのだが、読んでくださった方々から情報をいただいた。
ありがたやありがたや。
私があんまり社交的じゃないためか、このブログはマイナーなんですよ。
ときどき、閉鎖して本業に集中しようかとも思うけど、見てくださる方がいるのであれば、もうちょっと続けようかという感じでやっとります。


さて、本題。

hiro99ma blog: [ble]BLEモジュールを使った場合のがんばりどころは?

コメントをいただいた中で、誰もが「32KBを超えても大丈夫だった」ということだった(2人しかコメントがないが、これを「誰もが」と書くことで、すごく多くの人がコメントをくれたようなイメージを与えるテクニックだ)。

では、試してみなくてはならぬ。

まず、32KB、がどこを指しているのか。
ARM KEIL組み込み開発システム 統合開発環境 <MDK-ARM 正規代理店>|横河ディジタルコンピュータ株式会社
こちらの表を見ると、デバッガ、シミュレータ、そしてコンパイラが32KBとのこと。
しかし、コンパイルのどこら辺が32KBかは、見てもわからない。

セカンド・オピニオン (412) MCUで遊ぼう Part2 (3) | マイナビニュース
これを読むと、コード/データが32KBとある。
プログラムは基本的にROMとRAMの2つに分かれるが、ROMもデータとプログラムに分かれる。
RAMも、初期値があるものと、ないものに分かれる。
初期値があるものは、その初期値自体はROMに置かれることになる。じゃないと、初期値を保持しておくところがないからね。
C言語では、初期値を持つ変数というのは、staticなものしかない。
そのstaticな変数の中でも、初期値を代入しているものは「初期値あり」だが、代入していないものは「ゼロ」になる。
だから、staticな変数であっても、初期値の有無でROMのサイズが変わってくる。

なんでこんなことをごにゃごにゃ書いたかというと、32KBのROMをどうやったら作り出せるか、ということを考えていたからだ。
プログラムを書いて32KB作るのは、けっこう難しい。
できれば、var[32768]みたいな感じで、楽に32KBを作りたい。
でも、staticとかvolatileとかつけても、単に宣言しただけだとRAM領域の話になるので、コードサイズにならない。
初期値がないから、ゼロになるだけだ。
私が見たことのあるコンパイラだと、main()が呼ばれる前にmemset()とか、あるいはアセンブラなどでゼロ埋めするものがほとんどだったが、オプションによってゼロ埋めする処理すら省略するものもあった。

我々が求めているのは、ゼロ以外の初期値を持つ変数だ。
そうなると、初期値の部分がROMに配置されるので、今回のコードサイズ制限を調べるのに役立つ。

#include <nrf51.h>
#include <stdint.h>
#define FILL16 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
#define FILL256 FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16
#define FILL1K FILL256,FILL256,FILL256,FILL256
static const uint8_t mem[256 * 1024 * 1024] = {
    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,
    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K
};
int main(void)
{
    uint8_t jan = mem[256];
    __wfe();
    return 0;
}

16byteのFILL16を、16個並べたFILL256を、4つ配置。これでFILL1K。
とりあえずエラーを見てみたいので、これを16個並べたら、16KBだからよさそうだけど、エラーになった。

Build target 'Target 1'
compiling main_32k.c...
main_32k.c(15): warning:  #177-D: variable "jan" was declared but never referenced
        uint8_t jan = mem[256];
main_32k.c: 1 warning, 0 errors
linking...
.\Objects\test.axf: error: L6047U: The size of this image (268436284 bytes) exceeds the maximum allowed for this version of the linker
Finished: 0 information, 0 warning, 0 error and 1 fatal error messages.
".\Objects\test.axf" - 1 Error(s), 1 Warning(s).
Target not created.

でたー。
これが、maximumなエラーメッセージらしい。
少し満足。

まあ、256 * 1024 * 1024だと、どの環境でもエラーになりそうよねぇ、ということで、32KB制限近くでやってみた。

#include <nrf51.h>
#include <stdint.h>
#define FILL16 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
#define FILL256 FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16
#define FILL1K FILL256,FILL256,FILL256,FILL256
static const uint8_t mem[31 * 1024] = {
    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,
    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K
};
int main(void)
{
    uint8_t jan = mem[256];
    __wfe();
    return 0;
}

これのビルド結果は、こう。

Rebuild target 'Target 1'
compiling main_32k.c...
main_32k.c(15): warning:  #177-D: variable "jan" was declared but never referenced
        uint8_t jan = mem[256];
main_32k.c: 1 warning, 0 errors
assembling arm_startup_nrf51.s...
compiling system_nrf51.c...
linking...
Program Size: Code=598 RO-data=31970 RW-data=4 ZI-data=4196  
FromELF: creating hex file...
".\Objects\test.axf" - 0 Error(s), 1 Warning(s).

この、memの31を32にすると、リンクエラーになった。

Rebuild target 'Target 1'
compiling main_32k.c...
main_32k.c(15): warning:  #177-D: variable "jan" was declared but never referenced
        uint8_t jan = mem[256];
main_32k.c: 1 warning, 0 errors
assembling arm_startup_nrf51.s...
compiling system_nrf51.c...
linking...
.\Objects\test.axf: error: L6047U: The size of this image (33596 bytes) exceeds the maximum allowed for this version of the linker
Finished: 0 information, 0 warning, 0 error and 1 fatal error messages.
".\Objects\test.axf" - 1 Error(s), 1 Warning(s).
Target not created.

こっちが、warningだけだったコード。

#include <nrf51.h>
#include <stdint.h>
#define FILL16 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
#define FILL256 FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16
#define FILL1K FILL256,FILL256,FILL256,FILL256
static const uint8_t mem[31 * 1024] = {
    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,
    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K
};
int main(void)
{
    uint8_t jan = mem[256];
    __wfe();
    return 0;
}

こっちが、エラーになったコード。

#include <nrf51.h>
#include <stdint.h>
#define FILL16 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
#define FILL256 FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16,FILL16
#define FILL1K FILL256,FILL256,FILL256,FILL256
static const uint8_t mem[32 * 1024] = {
    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,
    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K,    FILL1K
};
int main(void)
{
    uint8_t jan = mem[256];
    __wfe();
    return 0;
}

違いは、memの添字になっている値が、31か32か、だ。

こうやって見る分には、ちゃんと32KBで制限がかかっていると感じます。


翌日。
飲みながら調べると、なんかいろいろ馬鹿なことやってるなー、と反省(したふり)。

リンクができる境界を調べて、以下のソースになった。
配列の202を203にすると、リンクエラーになる。

static const unsigned char mem[31 * 1024 + 202] = {0};
int main(void)
{
    return mem[0];
}

image

mapファイルのお尻だけ拾うと、こんな感じ。

      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   
       594         88      32170          4       4196       7352   Grand Totals
       594         88      32170          4       4196       7352   ELF Image Totals
       594         88      32170          4          0          0   ROM Totals
==============================================================================
    Total RO  Size (Code + RO Data)                32764 (  32.00kB)
    Total RW  Size (RW Data + ZI Data)              4200 (   4.10kB)
    Total ROM Size (Code + RO Data + RW Data)      32768 (  32.00kB)

配列の要素数は、1024 * 31 + 202だから、31946。
mapでは32170になってるから、あと224バイトはコード以外のROMデータがあるらしい。

      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Object Name
        84         36        192          0       4096        648   arm_startup_nrf51.o
        12          6      31946          0          0       2055   main_32k.o
       232         30          0          4          0       4241   system_nrf51.o
    ----------------------------------------------------------------------
       328         72      32170          4       4096       6944   Object Totals
         0          0         32          0          0          0   (incl. Generated)
         0          0          0          0          0          0   (incl. Padding)

incl. Generated、というのが32バイトあるらしい。
ARMのヘルプを見ると、これはリンカが生成したオブジェクトらしい。

ともかく、うちで試した限りでは32KB制限というのは発生していることが確認できた。

 

{0}なのに初期値ありとみなされるんだ、というのが多少ショックだった。
前見たコンパイラは、{n}のような感じでnに数値を入れるとmemset(0)で勝手に0初期化してたのだ。
組込用のコンパイラだったから、それを正とみなしてたらいかん、という教訓を得た。

4 件のコメント:

  1. 境界の実験ありがとうございます。とても勉強になりました。

    やっぱりMAPファイルのTotal ROM Sizeが32KBを越えなければ大丈夫ということですね。
    サンプルプログラムをちょこちょこ改造して試しているところですが、20KB位が最大でしたのでそうそう32KB超えることはなさそうです。

    返信削除
    返信
    1. 20KBくらいなら、まだ余裕がありますね。
      BLEのスタックが別になっているので、広々しててありがたいです。
      (CC2540とかは一緒にリンクするので制限有りコンパイラでは使えないという記述を見たことがあったので)

      削除
  2. 詳しく調査していただき、公開していただいてどうもありがとうございました。
    日本語の情報が少ない中、とても貴重な Blog だと思います。
    愛読していますので、今後も頑張ってください。

    返信削除
    返信
    1. ありがとうございます。
      好き勝手に調べているだけのブログではありますが、いつか世に羽ばたく日が来る(かも)、という野望を胸に抱きつつ続けていきます。

      削除

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。