2016年8月31日

[c/c++]私のC言語 (9) - ローカル変数を宣言する位置

C99からだと思うが、変数の宣言位置がどこでもよくなった。
ローカル変数の話なのだが、あれ、用語としては宣言と定義のどっちだ?

・・・K&Rでは「変数の宣言」になっているから、宣言としておこう。
と書いたものの、externの説明では「外部変数の定義」と書いているな。
付録の「宣言」のところには「メモリを確保する宣言は定義と呼ばれる」と書かれている。
つまり、"宣言"の特殊形が"定義"ということか。

もう1冊持っていた「C言語 重要用語解説」では、以下の"宣言"を"定義"と呼ぶそうだ。

  • オブジェクトに対しては、そのオブジェクトの記憶域を確保する宣言
  • 関数に対しては、関数本体を含む宣言
  • 列挙定数またはtypedef名に対しては、その識別子の宣言

んで、その下に「"int i;"のようなオブジェクトの定義は一般的には宣言と呼ぶ傾向にある」と記載してあった。
なんだよ、傾向って。。。

確かに私の中でも、static変数は「定義」って感じがするけど、ローカル変数は「宣言」で済ませたい気持ちがある。
スタックポインタをずらすだけだからだろうか?

というわけで、ここでは「宣言」にしておくので、各自の用語定義で読み直してほしい。


宣言位置はC++がそうだったからあまり気にしていなかったのだが、ANSI-Cなどはブロックの先頭で行う言語仕様だったからそうしていた。
今は「C99で書いてます」と言っておけば、使う時になって宣言すればよいので、気持ちとしては楽だ。

私は、ローカル変数の宣言時に強制的に初期化するのが嫌でねぇ。。。
後で書き換えられない可能性があるならよいけど、書き換えられるのがわかっているなら省きたい。
それだったら、使用する直前に宣言して、同時に代入してしまった方が無難だ。

ただ、変数の戻り値ret、みたいな変数はあちこちで使うので、これはブロックの先頭に書くね。

 

switch文の中でしか使わない変数の場合は、ちょっと揺れる。
ブロックを作るか、switch文の外に宣言しておくか。。。
caseの中にブロックを書くと、インデントをどうするか悩むのだ。
私は、caseをインデントしない派なので、こんな感じになってしまう。

switch (calc_val) {
case 1:
    {
        int a = calc_val * rand();
        ...
    }
    break;

別によいのだけど、せっかくcaseをインデントさせなくて浅めにしたいのに、ブロックで深くなるともったいない、とも思ってしまうのだ。

じゃあ外に書けばいいやん、となるけど、その変数ってここでしか使わないのに・・・と、こっちはこっちで気持ちが微妙になる。


forのループ変数をその場で定義するのは、よくやる。

for (int loop = 0; loop < 10; loop++) {
   ...
}

ただ、途中でbreakしたかどうかを知りたいときは、抜けた後で変数の数を見たいから、外で宣言することになる。

それだったら、最初から外で宣言すればいいやん、と思われそうだけど、うーん、あまりそれは好きじゃないのだよなぁ。
理由が説明できないのだが、forを終わったらスタックポインタが戻るから、とかか?
でもレジスタに割り当てられることもしばしばだから。。。

うん、さっきのswitchと同じで、単に好みの問題だな。

2016年8月30日

[c/c++]私のC言語 (8) - メモリの確保

小さいシステムで、メモリがぎりぎりになるようなときの事前計算が非常に苦手です。
ほどほどに余裕があればよいのですが、足りない=マイコンの変更=コストアップ!なので、ぎりぎりになりそうなくらいであれば、その前から大きめのRAMを持つマイコンにしてもらうようにしています。

だって!
後から変更がきかないのですもの!!

その時は、試作する段階があり、その段階でサイズがあまり空いていなかったのでこのRAMサイズでは厳しいよなー、というのがわかり、大きめにしてもらいました。

しかし、その段階では通常アプリの仕様もわかっていないことが多いので、「どのくらい?」と聞かれても困ります。
それに、作っている途中で要求が変わってきたり、回避できない現象が出たりするので、「間に合うかも」のサイズで実装を開始するのはよろしくないと思います。

部品代が上がると製品コストに直結するので、嫌がる人も多いのですが、じゃあそのために(ハード以外で)費やされたコストをどこで回収するのか?ということになります。

もし、最終的に足りないならば、結局ハードを変更することになるので、最初からハードを変更していた方が議論するコストなどを考えずに済みます。
ハードを変えずにRAMの消費を減らすとなると、組み込み屋さんのソースコードはそこまで無駄食いをするようなコードを書かないので、元々余力(=減らす力)はあまりなさそうに思います。

どうせこのあと、不具合だとか仕様変更とかでやることが増えるんでしょう?と思うと、あんまりピッチリしたコードを書くよりも、遊びを持たせて余裕がある作りにしておいた方が安全じゃないか、と思いつつあります。

いや、昔は、そんな余裕のあることを言っていられないほどメモリが少なくてね。。。


ということで、だいたいこういうことにしています。

  • ハードがまだ確定していない場合は、めいっぱいメモリが取れる(マイコンを使ってもらえる)ようにがんばる
  • ハードが確定していて、メモリが少ないなら、がんばってメモリを使わないようにする
    • 機能削減も視野に入れよう

うん、がんばるところが違いますな。
そして、後者の方が大変なのは、だいたいわかります。。。
でも、前者ができない場合もあるでしょう。

そんなこんなありますが、このレベルでは圧縮が効かないので、何とかするしかありません。
つらいですね。。。

[git]一時的なリリースは一時的なブランチへ

新しい酒は新しい革袋で、みたいな感じだが、全然違う話だ。

新しい酒は新しい革袋に盛れ - 故事ことわざ辞典

 

gitで、こんな運用をしていた。
通常は開発ブランチで、リリースするときだけリリースブランチにマージしている。
一人でやっているので、そこまできっちりしてなくてもよいのだが、いざというときのためにね。

image

なので、リリース直後は、だいたい開発ブランチとリリースブランチが同じになっている。
違うのは、リリース時にはリリース用のバージョンを振り直すくらいか。

 

今回、いろいろ試したいということで、少し違ったものをちょこちょこ作っていって、このパターンを残して後はいいや、ということになった。
ちなみに、開発ブランチのものも「開発版」ということでリリースすることがあり、この×がついているものもリリースしてpushされている(revertなどせず、実際は残っている)。

image

じゃあ、ということで古いやつをマージしてリリースした。
そこまではよかった。

これまでのマージ直後は、開発ブランチもリリースブランチも、gitのグラフで見ると同じポイントを指していたのだが、これが分かれてしまった。
今思えば、×がついたものをrevertしてからリリースブランチとマージすればよかったように思うが、pushしてしまったので後の祭りだ。
開発ブランチに移動して、リリースブランチの方をマージしたのだが、コンフリクトして修正するため、やはりブランチが分かれてしまう。。

 

別に開発ブランチを捨てて新しく作ってもよいのだけど、今からでもうまくやる方法はあるのだろうか?
反省としては、一時的に作ると予めわかっているのなら、一時的なブランチを作った方がよいだろう、ということだ。

今回は、またいつか会える日も来るさ、とクールに別れようと思う。

[c/c++]私のC言語 (7) - 構造体の初期化

だんだん「私の」がどうでもよくなってきた。。。
さっきやっていて、気付いた話だ。

構造体のメンバを初期化していた。
全部のメンバを初期化したいわけではなかったので、ちまちま代入させていたのだが、初期化子としてメンバ名を指定するタイプもあったことを思い出した。
「指定初期化子」と呼ぶらしい。
GCC拡張っぽい書き方だが、C99で採用されたようだ。

 

そっちで書いた方がサイズが小さくなるかと思ったが、結果的には増えていた。
なんでじゃ?と思い、代入していた頃に戻して、構造体の初期化で「={0}」を追加すると同じサイズになった。
なるほどねぇ。

今回試したのは、メンバが4つくらいしかないため、ちまちま代入させた方がサイズが小さくなるけれども、0を代入するような初期化であれば「={0}」なり指定初期化子なりを使った方がサイズが小さくなるかもしれない。

昔いくつか見たときには、「={0}」ってmemset()に変換されていたけど、今はどうなんだろうね?
標準ライブラリを組み込まないときに「memsetがない」って怒られたときは、訳がわからなかったぞ。。。

2016年8月27日

[nfc]iPhoneに載るのか?

前に、NFC Forumの記事を読んで、iPhoneにFeliCaってのは考えづらいよなぁ、という感想で終わらせた。
hiro99ma blog: [nfc]NFC Forumジャパンミーティングの記事がよくわからん

 

が、最近、こういう記事がちらほら見られた。

iPhoneがおサイフケータイ(FeliCa) に対応か。Apple Payが間もなく日本上陸 - Engadget Japanese

ほう。
もし載るのだったら、ジャパンミーティングの報告よりもずいぶん前から話が進んでいたのだろう。

 

ただまあ、こういうのは、蓋を開けるまでわからないからねぇ。。。
とりあえず私は懐疑派で、もし出てきたら「やっぱり勘が外れたのかぁ」と思うことにしよう。

RC-S390もそうだし、wenaもそうなのだけど、iOSがけっこう優先されているな、と思ったのだ。
優先されているというか、AndroidでもできそうなのにiOSしか対応していないな、と。
まあ、関係あるのか知らんけどね。


もし載せるのだったら、今と同じ、オンボードでチップを載せることになるのかな。
モバイルFeliCaチップと、あとはメモリとアンテナか。

カード型は読み取られるだけだから電源はいらないけど、モバイル型はチップを動かすので電源がいる。
詳しくないけど、R/W機能がないなら、出力はそこまでがんばらなくても良さそうだから、今のままでよいのかな?
そして、チャージをアプリから行うしくみがいるので、そういうSDKも販売していたと思う。
ハードの改造もいるし、アプリの開発もいるのだ。

 

新しく電子マネーを携帯端末で使えるようにしようとしたら、そういう端末側だけじゃなくて、店舗で読み取ってもらうR/W作ったり配布したり、サーバだとかセキュリティとか、たぶんうじゃうじゃやることがあるのだろう。
そういうのは大変なので、あまりやっているところがないのだと思う。

そこら辺を、もし海外の業者がやるんだったらどうなるのか、という一例として見るのが面白いのかもしれんです。

2016年8月26日

[c/c++]私のC言語 (6) - 整形

C言語は、書き方が自由だ。
スペースだったりタブだったり、改行を入れたり入れなかったり。
インデントだって、けっこう人それぞれだ。

それはそれでよいと思うのだが、自分のスタイルじゃないソースコードを読むのは、けっこう苦痛である。
そういうとき、整形ツールで整形させるとよいだろう。

私は、これを使っている。
http://astyle.sourceforge.net/

 

たとえば、括弧の位置については、こういうオプションがある。
http://astyle.sourceforge.net/astyle.html#_Bracket_Style_Options
私はk&rかなぁ、と思ったが、stroustrupとの違いは何だろう?
namespaceやclassはk&rでは対象外と言うことかな。

1行でも括弧を付けてほしいので、これをつける。
http://astyle.sourceforge.net/astyle.html#_add-brackets

スペースかハードタブかは、お好みだな。
私の最近の流行りは、スペースだ。
http://astyle.sourceforge.net/astyle.html#_Tab_Options

2016年8月25日

[c/c++]私のC言語 (5) - FFF

私は、だいたい実装するとユニットテストをしている。

実機ではなかなか通しにくいルートだけど、通ったときの動作は見ておきたい、という場合によいし、コードを書き換えた後でテストを通し、前との違いを比較する場合にも良い。

テストを作るのは面倒で時間はかかるのだが、自分の書いたものが期待通りかどうかを知るためにもやっておきたいものだ。

gccはカバレッジを取るためのビルドができるので、通ったことがないかどうかを目視しやすい。
一度通ったルートはカウントアップされるだけなので、このときにここを通った、というのには向かないのだが、それを知りたければ1つだけテストを書いて実行すれば良いだけだ。


意図通りのルートを通したいとなると、標準関数やドライバも意図通りの値を返してほしい。
が、それは難しいので、スタブやらモックやらを作る。
私は、それをFFFというフレームワークを使ってやっている。

meekrosoft/fff: A testing micro framework for creating function test doubles

検索して探すときは「fff fake」などで出てくるだろう。
使っている人の例もあるので、そっちの方がわかりやすいかも。

 


まず、githubからcloneしよう。
今の最新は3cd33edだ。
私の環境は、cygwinである。

こんなソースを書いたとしよう。
ソースファイルべた書きで済まぬ。。。

my_open(), my_close(), my_access()の3つがある。
my_open()とmy_close()UARTのオープン/クローズのイメージ。
my_access()は、コマンドを送信するとレスポンスが返ってくるデバイスがつながっていて、1回のコマンド送信に対してレスポンスを2回に分けてチェックしながら受信する、という想定だ。
ファイルディスクリプタは、static変数sfdで保持しておく。


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

static int sfd = -1;

int my_open(const char *pTty)
{
    if (sfd != -1) {
        return -100;
    }

    int fd = open(pTty, O_RDWR);
    if (fd == -1) {
        return -1;
    }
    sfd = fd;
    return sfd;
}

void my_close(void)
{
    close(sfd);
    sfd = -1;
}

int my_access(void)
{
    const uint8_t WRT[] = { 0x00, 0x01, 0x02, 0x03, 0x04 };

    uint8_t buf[20];
    ssize_t sz;
   
    sz = write(sfd, WRT, sizeof(WRT));
    if (sz != sizeof(WRT)) {
        return -2;
    }

    sz = read(sfd, buf, 5);
    if ((sz != 5) || (buf[4] != 0xff)) {
        return -3;
    }
    sz = read(sfd, &buf[5], 3);
    if ((sz != 3) || (buf[7] != 0x35)) {
        return -4;
    }

    return 0;
}


まず、my_open()のテストをするとしよう。

最初にsfdが-1以外かどうかをチェックして、オープン済みなら終わらせる。
次にopen()を呼んで、戻り値が-1だったら、エラーとして終わる。
それ以外なら、sfdに保存して終わる。

というわけで、ここでやりたいのは、

  • sfdを-1以外にして、-100が返ってくるのを確認する
  • sfdを-1にして、open()が-1を返すようにし、-1が返ってくるのを確認する
  • sfdを-1にして、open()が-1以外を返すようにし、その値が返ってくるのを確認する

という3つだ。
これを、FFFを使ったテストコードにすると、こうなる。

TEST_F(test, open_1)
{
    sfd = 3;

    int fd = my_open("/dev/ttyS1");

    ASSERT_EQ(-100, fd);
}

TEST_F(test, open_2)
{
    sfd = -1;

    open_fake.return_val = -1;

    int fd = my_open("/dev/ttyS1");

    ASSERT_EQ(-1, fd);
}

TEST_F(test, open_3)
{
    sfd = -1;

    open_fake.return_val = 5;

    int fd = my_open("/dev/ttyS1");

    ASSERT_EQ(5, fd);
}

長くなるので省略しているが、これよりも前の部分がある。
実はそこで、テスト対象のソースファイルをincludeしている。
だから、直接sfdが扱えるのだ。
なんとなく邪道じゃろう?
でも、楽なのだよ。

open_fake.return_val、というのは、関数をモック化する手続きをしておくと、<関数名>_fakeという構造体ができるようになっていて、そのreturn_valという変数だ。
これに値を入れておくと、その値が戻り値になってくれる。

ASSERT_EQ()は、FFFではなくGoogle Testが提供するものだ。
FFFはGoogle Testと併用しやすくなっている。


では、my_access()みたいにread()を何度も呼び出すタイプはどうするか?
いくつかやりかたはあるのだが、戻り値も変える、引数で返す値も返るとなると、自分でモックを定義した方が簡単だと思っている。

たとえば、戻り値が0になるテストだと、こういう感じだ。

TEST_F(test, access_1)
{
    sfd = 5;

    class dummy {
    public:
        static ssize_t read(int fd, void *p, size_t sz) {
            static int count = 0;
            int ret = -1;
            uint8_t* buf = (uint8_t *)p;
            switch (count) {
            case 0:
                ret = 5;
                buf[4] = 0xff;
                break;
            case 1:
                ret = 3;
                buf[2] = 0x35;
                break;
            default:
                assert(0);
            }
            count++;
            return ret;
        }
    };

    write_fake.return_val = 5;
    read_fake.custom_fake = dummy::read;

    int ret = my_access();

    ASSERT_EQ(0, ret);
}

read_fake.custom_fakeはデフォルトがNULLで、その場合はreturn_valの値を返す(配列で順番に値を返してもらうようにもできる)。
設定すると、その関数が呼ばれる。

今回は、dummyクラスのread()を呼ぶようにしている。
普通の関数にしても良いのだが、テストごとに定義していると名前の重複が発生して面倒になりそうだから、こういう形にしている。


今回作ったものは、ここに置いた。
https://github.com/hirokuma/fff_examples

作ったのは、func., test_func.cppと、Makefileをいじっただけだ。
それ以外のものは、FFFからコピーしただけである。

慣れるまでは使いづらいかもしれないが、いくつか作って自分の作り方を蓄積させておくと、だんだん楽になっていくかもしれない。

[nrf52]大きいNDEFデータにすると不安定だ

nRF52832は、Type2 Tagとして動作することができる。

Type2 Tagとは、MIFARE Ultralightみたいなやつだ。
以下、ULと略す。
ちなみに、Ultralight Cというのもあって、そっちはセキュリティ機能があるらしい。
NXPに申請しないと資料をもらえないようなので、詳しくは知らない。

 

サンプルを動かして、AndroidアプリのNXP TagInfoで読み取った方はわかるだろうが、普通のタグに比べると読み取りに時間がかなりかかる。
これはタグのエミュレーションに時間がかかっているわけではなく、データが多いためだ。

たとえば、よく見かけるNTAG203では48ブロック分しかないのだが、nRF52832のnRF5 SDK v11.0.0では256ブロック分あるのだ。
1ブロック4バイトなので、1KB。

このサイズは、タグとしてはなかなかない。
MIFARE Classicは、1Kとか4Kとかが普通だったけど、Type2 Tagなんかは小さいものが多いのだ。

まあ、通常は使うブロックだけ読めばよいので、使い勝手に違いはないだろう。


せっかく大きいデータが使えるので、大きいデータを流してみよう。
大きいデータと言えば、やはり画像だろう。
PNGデータを作って、TNF=0x02、MediaTypeがimage/pngのNDEFデータを作ってみた。

が・・・Androidで読ませても、なんか毎回データが違う。

不安定なのか?と思い、Windows10 + PaSoRiで読込ませた。
幸いWindows10はNDEFを解釈してくれるので、画像ファイルとして保存してくれた。

が・・・。
こちらも、毎回結果が違う。

image

原因がどこら辺にあるのかはわからないけど、大きいデータは使わない方がよいということはわかった。

2016年8月24日

[nrf52]Android 7じゃないとNFCペアリングはうまくいかないの?

Nordicブログには珍しく、新しいAndroidのことが記事になっていた。
うちのNexus5は対象から外れたらしいのだが、7用のドライバだけでも提供してくれたら自分でビルドして遊んでみるのだけどなぁ。

nRF52832 and Android Nougat – simple and secure touch-to-pair.

英語なので読み飛ばそうと思ったのだが、気になる一文を見つけた。

support for Bluetooth pairing with NFC using the out-of-band (OOB) method is included.

んん??
もうちょっと読んでみた。

In fact, we have had examples to do NFC pairing using the OOB method in our nRF5 SDK for quite a while and it worked with Android Nougat “out-of-the-box”.

「out of the box」は、Raspberry Piのところでも出てきたが、すぐに使える、という意味だ。
つまり、なんもせんでもAndroid 7だったらSDKに入っているNFCペアリングのサンプルが使えるよ、という意味だろう。

なんだよ、動かないってわかっていたのかよ!!

確かに、私の調べでも、Androidがタイミングを変えてくれない限りはどうしようもない、というところだったのだけど。。。
どこかで、Nexus5は対応しているような記述があったような気がしたのだが、今探しても見つからない。

うぅぅぅ。。。

 

とにかく、今までのでは動かない、というのは間違っていなかったので、それでよしとしよう。

[mbed]mbed OSはRTOSなのか?

しばらく前に、mbed OS 5がリリースされた。
今はもう5.1だが、メジャーバージョンアップだ。。。というか、OSの2と3を足して5らしい。

Introducing mbed OS 5 | mbed

 

FreeRTOSを調べていたので、そういえばmbed OSはRTOSなのだっけ?と気になった。
まあ、組み込みのOSでRTOSじゃなかったら、使うことは無さそうな気がするので、たぶんそうだろう。

リアルタイムオペレーティングシステム - Wikipedia
ここに列挙されているだけでもたくさんあるな。
私が使ったことがあるのは、わずかだ。

 

mbed OSはどうなのか調べようと思ったが、mbed blogの一番最初に「RTOS」と書いてある。
だから、RTOSなのだろう。


mbed OS | mbed

こちらを見ると、CMSIS-RTOS RTXをベースにしている、と書かれている。
元はCで書かれているので、C++でラッパされているのだろうか?
そうらしい。

試しに、LED点滅サンプルをインポートした。

image

「mbed-os」のようだ。
URLはgithubだったけど、移動したのかしら?

githubのReleasesは、こう。

image

コンパイラのリビジョンは、こう。

image

なので、今の私はmbed OS 5.1.2を使っているのだろう。

 

書いてあるソースは、こんだけ。

image

ビルドすると、こんなもの。

image

 

KeilとSTM32CubeF4を使って、RTXを使ったLED点滅サンプルを作ってみた。

Program Size: Code=7624 RO-data=552 RW-data=80 ZI-data=3344 

うーん、mbed OSは周辺機器制御の部分がリンク時でもそぎ落とせないのかな?
Keilで試したいところだが、このサイズだと無料版ではリンクできないのだ。

ARM-GCC版をexportさせようとしたけど、どうにもうまくいかない。
Code Sourcery版もダメだ。LPCXpressoはグレーアウトされてるし。


あー、今はmbed CLIというのがオフラインコンパイル手段としてあるのか。
でもなんか、説明が長いぞ。。。

また今度だな。


mbed OS 5.1風のThreadにすると、こういう書き方になりそうだ。

void task_led()
{
    while (true) {
        led1 = !led1;
        Thread::wait(500);
    }
}

int main() {
    Thread th;
   
    th.start(task_led);
    while (true) {}
}

Threadでwarningが出ていたから、気になったのだよ。
Thread.hはこうで「MBED_DEPRECATED_SINCE()」の定義を使うとwarningが出ていたようだ。
マクロでこういうことできるんだ。。。

コンストラクタで指定するのではなく、start()で指定してほしいらしい。
型のmbed::Callback<void()>がよくわからんのだが、引数無し、戻り値voidというシグネイチャという意味なのだろうか。

 

値を渡したい場合は、これでよいのかな。

void task_led(int *wt)
{
    int waittime = *wt;
    while (true) {
        led1 = !led1;
        Thread::wait(waittime);
    }
}

int main() {
    Thread th;

    int wt = 1000;
    th.start(&wt, task_led);
    while (true) {}
}