JavaScriptでわかる!組込みプログラミングの神髄

第1回JavaScriptと組込みプログラミングは似ている!

最近は、組込みシステムという言葉もずいぶん一般的になりました。みなさんの中にも、すでに組込みシステムのプログラミングに挑戦したり、あるいはこれから挑戦してみたいと思っている方がたくさんいらっしゃると思います。

お手軽な学習キットや入門書もたくさん出ており、敷居はとても低くなりました。その一方で、組込みプログラミングに不安を持っていたり、あるいはすでに挑戦してみたけれどよくわからなかった、という人もいるかも知れません。

そこで本稿では、パソコン上で誰でも簡単に実行できるJavaScriptを使用して、組込みプログラミングの基礎について説明してみたいと思います。

古典的なプログラミングと、なにが違うのか

筆者は以前、Wikipediaに書き込みを行っているIPアドレスがいくつくらいあるのか知りたくなりました。これには更新ログをダウンロードして、IPアドレスを抽出し、種類を数えればOKです。次のようなコマンドラインになりました(実際は、100GB以上ある更新ログを置く場所がなかったので、アーカイバからパイプで入力しました⁠⁠。調べたところ、日本語版Wikipediaの場合で、たしかIPアドレス総数が約100万、IPグループ総数が約10万でした。

awk '/*IPアドレスのみを抽出*/' < 更新ログ ¦ sort -u ¦ wc

筆者が子供のころ、プログラミングの入門書には「1から10までの整数の総和を求めよ」みたいな例題がたくさん出ていましたが、これも本質は同じです。つまり、入力データがあり、それをまとめて処理する、というプログラムです。

加速度センサーから入力されたデータを積分して、衝撃の強さを測定するプログラムはどうでしょうか。これもやはり考え方は同じです。データを入力して、処理して、結果を返します。

ではこのプログラムを、自動車のエアバッグ制御に使うことは可能でしょうか。

車が衝突したあとで、衝撃の強さを計算することはできます。その強さを見て、⁠この衝突ではエアバッグを動作させるべきです」と表示することもできます。しかしこれでは意味がありませんね。衝突が進行している途中で、エアバッグを起動するかどうかを判断する必要があります。このためにはまったく異なったプログラミングテクニックが必要になります。

さきほどのWikipediaのプログラムで考えてみましょう。追記される更新ログを追いかけて、ユニークIP数がちょうど100万になったときに警報を出すわけです。この場合も、まったく違ったプログラミングが必要になりますね。

「まとめて処理」だったのを「ちょっとずつ処理」にするのは大変なのですが、組込みプログラミングにはこの視点が絶対に必要です。逆にいえば、これが組込みプログラミングの難しいところでもあります。では、どうやればうまくできるのか、そのテクニックを見ていくことにしましょう。

10秒待とう

最初は簡単な例題です。スイッチを押して10秒たったらメッセージを表示する、というプログラムを考えてみましょう。

リスト1 wait10s.html
<HTMLM><HEAD><TITLE>wait10s</TITLE>
<SCRIPT type="text/javascript"><!--

function        press_button()
{
        var     start = new Date();

        for (;;) {
                var     now = new Date();

                if (now.getTime() > start.getTime() + 10000)
                        break;
        }
        document.getElementById("message").innerHTML = "ok!";
}

// --></SCRIPT>
</HEAD><BODY>
<H1>wait10s</H1>

<P><BIG><BIG>
<SPAN onmousedown="press_button()">[CLICK ME]</SPAN>
<SPAN id="message"></SPAN>
</BIG></BIG></P>

</BODY></HTML>

JavaScriptのプログラムをはじめて見る方には、少しわかりにくいかも知れません。このHTMLは以下の部分に分かれています。

  • </HEAD> より前の部分
  • <BODY> から後の部分

HEADの部分は、定義です。TITLE以外は画面に表示されません。基本的に、JavaScriptのプログラムはここに置きます。この例題では、press_button関数を定義しています。

BODYの部分は、表示です。画面に表示するものはここに書きます。

BODYの中に「onmousedown="press_button()"」という部分があります。これで、クリックされたときに関数を呼び出しています。

press_button関数の方では、new Date()を使って現在時刻を取得しています。現在時刻が10000ミリ秒(=10秒)だけ進んだら、ループを抜けて、メッセージを表示しています。オブジェクト指向プログラミングになじみのない方のためにC言語の範囲で説明しますと、new Date()という構文は、メモリを確保して構造体として初期化し、そのポインタを返します。now = new Date();としておき、now.getTime()とすると、構造体の中のgetTime()関数が呼びだされ、現在時刻が得られることになります。

本題ではありませんが、document.getElementById("message")というの部分は、<SPAN id="message">というタグに対応しています。innerHTMLは、このタグの中身と連動していますので、innerHTMLを更新すると、表示に反映されることになります。これはJavaScriptのDOMという機能です。

実行してみると

説明が長くなりました。では、実際にこのプログラムを実行してみましょう。⁠CLICK ME」のところをクリックすると、たしかに10秒後にメッセージが表示されました。しかし、10秒たつのをループで待っているため、CPU使用率が100%になってしまい、他の作業が進まなくなってしまいます。このようなやり方を、⁠ビジーループ」「ポーリング」と呼びます。

「クリックしたときに関数が呼ばれる」というのがピンとこない方は、次のようなプログラムを想像してください。これも同じ問題点を持っています。ちなみにこれはArduinoのプログラミングスタイルですが、関数からリターンしないと他の関数が呼びだされないのは、こういう構造になっているためです。

 void main()
 {
   for (;;) {
     if (クリック)
       press_button();
     if (キー入力)
       キー処理関数();
     if (データ受信)
       通信処理();
   }
 }

また、今回はボタンが1つですから問題ありませんが、もしボタンが2つ以上あったら、このプログラムでは並列的に処理することができません。キッチンタイマーを2つ用意しても、片方を使用中にもう片方が利用できないのでは、2つ用意した意味がありませんね。

JavaScriptではonmousedownの部分を「イベントハンドラ」と呼んでいます。組込みシステムでは「割り込み」と呼んでいます。例外的なケースを除いて、組込みプログラミングでも割り込み処理でビジーループを使ってはいけません。

タイマー割り込みを使用する

では、10秒待つというプログラムはどうやったら作れるのでしょう。これには、組込みシステムでは「タイマー割り込み」というものを使用します。タイマー割り込みは、一定時間ごとに関数が呼びだされるというものです。

説明の前に、改良したプログラムを見てみましょう。

リスト2 wait10s2.html
<HTMLM><HEAD><TITLE>wait10s2</TITLE>
<SCRIPT type="text/javascript"><!--

var     start = null;

function        int_t0()
{
        document.getElementById("message").innerHTML += ".";
        if (start == null)
                return;
        var     now = new Date();
        if (now.getTime() <= start.getTime() + 10000)
                return;
        start = null;
        document.getElementById("message").innerHTML = "ok!";
}

function        press_button()
{
        start = new Date();
}

function        init()
{
        setInterval(int_t0, 1000);
}

// --></SCRIPT>
</HEAD><BODY onload="init()">
<H1>wait10s2</H1>

<P><BIG><BIG>
<SPAN onmousedown="press_button()">[CLICK ME]</SPAN>
<SPAN id="message"></SPAN>
</BIG></BIG></P>

</BODY></HTML>

一気に複雑になったように見えますが、そんなに大したことはやっていません。このプログラムがどう実行されるのか、順番に見ていきましょう。

まず、HTMLの読み込みが完了すると「onload="init()"」の部分が実行され、init関数が呼びだされます。この中に「setInterval(int_t0, 1000)」とあります。これにより、int_t0関数が1000ミリ秒ごとに呼びだされるように設定されます。

int_t0関数が1秒ごとに呼びだされるのを確認するため、ここでメッセージに「.」を表示してみました。このHTMLを開くと1秒に1つずつ「.」が表示されるのが確認できます。

ボタンをクリックすると、press_button関数が呼びだされます。前回のプログラムではここで10秒待っていましたが、今回のプログラムではstart変数をセットするだけで、すぐにリターンします。

int_t0関数は、start変数がセットされていると、時刻をチェックします。そして、10秒経過していたら、メッセージを表示して、start変数をリセットします。10秒経過していなかったら、すぐにリターンします。

JavaScriptの場合、int_t0()が呼び出されているあいだは、press_button()は呼び出されません。また、press_button()が呼びだされているあいだはint_t0()は呼びだされません。ですから、どちらかを実行しているときに、他の割り込みが勝手に呼びだされて変数の値が変わってしまうということを心配する必要はありません。

実際にやってみると、10秒待っているあいだもCPU負荷が高くならないことが確認できます。

処理を小分けにするテクニック

上記の例は、単に時間の経過を待つだけでした。このように、条件を待つだけであれば、それをタイマーハンドラに書いておけば、ビジーループを避けることができます。

たとえば、5秒後、10秒後、15秒後にそれぞれメッセージを表示したい場合、普通のプログラムなら以下のようになります。

 function 実行()
 {
   sleep(5);  /* 5秒待ち */
   printf("表示a");
   sleep(5);
   printf("表示b");
   sleep(5);
   printf("表示c");
 }

これを、タイマー割り込みを使うようにすると、以下のようになります。

 var nexttime = 0;
 var done = 0;
 
 function 実行()
 {
   nexttime = (new Date()).getTime() + 5000;  /* 5秒後 */
   done = 0;
 }
 
 function タイマーハンドラ()
 {
   if (nexttime 

関数からいったんリターンしないといけないので、nexttimeやdoneというグローバル変数を使って、どこまで進んだかを管理することになります。

時間を待つ処理以外でも、基本的な考え方は同じです。ただ、このままでは一続きの処理があちこちに分散して、プログラムが読みにくくなってしまいますから、言語の機能をうまく利用するとよいでしょう。

次回は、この辺のテクニックについて説明していきます。

おすすめ記事

記事・ニュース一覧