PHPプログラムで学ぶアセンブラのしくみ

第3回アセンブラをハードウェアに載せて動かしてみよう

これまで、アセンブラそのものを扱ってきました。ただ、いくらバイナリが出力できても、実際にマイコンに書き込むには開発環境が必要です。どうにか、サーバ上のバイナリをマイコンに書き込む方法はないものでしょうか。

今回は、マイコンにLANモジュールをつなぎ、マイコンがサーバからバイナリをダウンロードしてきて自分自身に書き込み、実行するようにしてみました。この方法を解説します。

PICがバイナリをダウンロードする

今回使用するマイコン基板は、以前ライントレーサで使った基板です。28ピンPICに、LEDとモータと反射センサーとLANモジュールがつながっています。

PIC16F88やPIC16F88xシリーズには、プログラムが格納されているメモリを、プログラム自身から書き換えるという機能があります。28ピンタイプですと、PIC16F883やPIC16F886になります。ライントレーサでは、LANモジュールからHTTPリクエストを出し、描画パターンを取得しました。ですので、LANモジュールを使ってバイナリのダウンロードも可能です。必要なものはあらかたそろっていることになります。

というわけで、さっそくやってみました。まず、前回まで紹介したアセンブラでバイナリファイルを作ります。これを、PICからダウンロードします。

元のアセンブラファイルは以下に置きました。

解凍して現れるdownload.asmが、ネットワークからのダウンロードを司る、PIC側のプログラムです。motor.asmのほうは、実際にデモを行うためのモータ回転のプログラムです。

なお、メモリ(EEPROM)には書き換え回数に限界があります。LANがつながっていないと応答をずっと待ってしまうことも考えられます。ですから、ダウンロードするかどうかをユーザが簡単に選べるといいですね。そこで、LANモジュールからの受信の信号にスイッチを入れます。通信が行われていないときは、この信号はHです。スイッチを押すと、これがLになります。電源が入ったときにこの信号をチェックして、Lだったらスイッチが押されていると判断し、ダウンロードを行うようにしました。あと、ダウンローダそのものを上書きしないように、書き込むアドレスに制限を設けました。

ダウンローダプログラムが動作しているところ。右ウィンドウがダウンロードするバイナリ、左にダウンロード時のHTTPリクエストが表示される
ダウンローダプログラムが動作しているところ。右ウィンドウがダウンロードするバイナリ、左にダウンロード時のHTTPリクエストが表示される

では、実際にやってみましょう。

※ 言い間違えが多々あり申し訳ありませんm(. .)m。

動画では、まずダウンローダだけが書き込まれたPICに、モータ制御のプログラムをダウンロードしています。PIC基板のスイッチを押したまま電源を入れると、しばらくしてLinuxマシンにアクセスが行われ、アクセスログが表示されます。モータ制御のプログラムがダウンロードされ、モータが回転します。

次に、ブラウザ上でプログラムを修正し、タイマーの値を大きくします。ブラウザの操作もアクセスログに表示されます。このあと、PIC基板のスイッチを押したまま電源を入れると、変更後のプログラムがダウンロードされ、先ほどよりもモータがゆっくり回転します。

プログラムはPIC内に書き込まれていますので、LANモジュールを外しても、最後にダウンロードしたプログラムが動作します。

PICの開発環境が対応していないarmのLinuxマシンで、ちゃんとプログラムが変更できていますね。

ダウンローダの使い方

このダウンローダは、0x200以降にダウンロードしたプログラムを格納します。実行開始も0x200です。筆者はPICでは割り込みを使わないのですが、本来の割り込みベクタにはgoto 0x204などとしてありますので、0x4のかわりに0x204を割り込みベクタとして使えるはずです。

電源が入ると、LANモジュールからの受信を確認します。これがHなら、スイッチが押されていないということです。ダウンロードは行わず、0x200にジャンプします。一定時間、受信がLであれば、ダウンロードモードに入ります。LANモジュールのリセットと、DHCP時のアドレス取得には時間がかかります。そこで、10秒ほど待ってからHTTPリクエストを発行します。これで、バイナリをダウンロードして、メモリへの書き込みが行われます。サイズが大きい場合は、何回かリクエストが発行されます。全部のダウンロードが終わると、ダウンロードしたプログラムが実行されます。

使うときは、以下の点に注意してください。

  • プログラムは0x200以降に配置する(org 0x200)
  • アドレスが変わるので、PCLを直接書き換える場合は、PCLATHの設定に注意する
  • コンフィギュレーションは固定となる(必要ならば、クロックモードの切り替えは可能)
  • ダウンロードしたプログラムが実行される前に、LANモジュールのポートの入出力が設定される
  • ダウンロード中は、LANモジュールのポート以外は、ポート設定はすべて入力となる(リセット時のままとなる)
  • あらかじめ、LANモジュールは、シリアルから1文字でも受信したら、サーバに接続にいくモードに設定しておく

次回は、このダウンローダの動作を解説する予定です。

リスト1 PHPのアセンブラプログラム。第1回のプログラムのバイナリを画面のほかtmp.HEXというファイルにも出力するように改修したバージョン
<?php

$labellist = array();
$codelist = array();
$datalist = array();

$codemap = array(
    "movwf" => 0x0080, 
    "clrf" => 0x0180, 
    "subwf" => 0x0200, 
    "decf" => 0x0300, 
    "iorwf" => 0x0400, 
    "andwf" => 0x0500, 
    "xorwf" => 0x0600, 
    "addwf" => 0x0700, 
    "movf" => 0x0800, 
    "comf" => 0x0900, 
    "incf" => 0x0a00, 
    "decfsz" => 0x0b00, 
    "rrf" => 0x0c00, 
    "rlf" => 0x0d00, 
    "swapf" => 0x0e00, 
    "incfsz" => 0x0f00, 
    "bcf" => 0x1000, 
    "bsf" => 0x1400, 
    "btfsc" => 0x1800, 
    "btfss" => 0x1c00, 
    "call" => 0x2000, 
    "goto" => 0x2800, 
    "movlw" => 0x3000, 
    "retlw" => 0x3400, 
    "iorlw" => 0x3800, 
    "andlw" => 0x3900, 
    "xorlw" => 0x3a00, 
    "sublw" => 0x3c00, 
    "addlw" => 0x3e00, 
    "nop" => 0x8000, 
    "return" => 0x8008, 
    "retfie" => 0x8009, 
    "sleep" => 0x8063, 
    "clrwdt" => 0x8064, 
    "clrw" => 0x8100, 
);

function    evaloprand($str)
{
    global    $labellist;
    
    $ret = 0;
    if (($str = trim(strtolower($str))) == "")    
        return null;
    if (substr($str, 0, 1) == '"')
        return $str;
    $remain = $str;
    while ($remain != "") {
        if (ereg('^([-+|&]?) *\((.*)$', $remain, $array)) {
            $sign = $array[1];
            $remain = $array[2];
            
            $level = 1;
            $pos = 0;
            while ($level > 0) {
                if ($pos >= strlen($remain))
                    die("unmatched bracket: ".htmlspecialchars($str)."\n");
                switch (substr($remain, $pos++, 1)) {
                    case    '(':
                        $level++;
                        break;
                    case    ')':
                        $level--;
                        break;
                }
            }
            $val = evaloprand(substr($remain, 0, $pos - 1));
            $remain = substr($remain, $pos);
        } else {
            ereg('^([-+|&]?) *([^-+|&() ]+) *([-+|&()].*)?$', $remain, $array);
            $sign = $array[1];
            $val = $array[2];
            $remain = $array[3];
            
            if (ereg('^[0-9]', $val)) {
                if (ereg('^[0-9]+$', $val))
                    $val += 0;
                else if (ereg('^0x[0-9a-f]+$', $val))
                    $val += 0;
                else if (ereg('^([0-9a-f]+)h$', $val, $array))
                    $val = ("0x".$array[1]) + 0;
                else
                    die("invalid number: ".htmlspecialchars($str)."\n");
            } else if (ereg("^h'([0-9a-f]+)'$", $val, $array))
                $val = ("0x".$array[1]) + 0;
            else if (($val = @$labellist[$val]) === null)
                die("undefined label: ".htmlspecialchars($str)."\n");
        }
        switch ($sign) {
            default:
            case    '+':
                $ret += $val;
                break;
            case    '-':
                $ret -= $val;
                break;
            case    '&':
                $ret &= $val;
                break;
            case    '|':
                $ret |= $val;
                break;
        }
    }
    return $ret;
}


$locallabel = "";
$org = 0;
if (@$argv[1] === "-")
    $remain = stream_get_contents(STDIN);
else  {
    if (($remain = @$_POST["src"]) === NULL) {
        print <<<EOO
<HTML><HEAD><TITLE>picasm.php</TITLE></HEAD><BODY>
<H1>picasm.php</H1>
<FORM method=POST>
<TEXTAREA cols=80 rows=30 name=src>

</TEXTAREA>
<BR>
<INPUT type=submit>
</FORM>
<HR>
</BODY></HTML>
EOO;
        die();
    }
    if (get_magic_quotes_gpc())
        $remain = stripslashes($remain);
}

while ($remain != "") {
    list($line, $remain) = split("\r|\n|\r\n", $remain, 2);
    if (($i = strpos($line, ";")) !== FALSE)
        $line = substr($line, 0, $i);
    list($label, $opcode, $oprand) = split("[ \t]+", $line, 3);
    $label = strtolower($label);
    $opcode = strtolower($opcode);
    $oprand = trim($oprand);
#    printf("label:%s\n  opcode:%s\n  oprand:%s\n", $label, $opcode, $oprand);
    
    if ($opcode == "org")
        $org = $nextorg = evaloprand($oprand);
    else {
        $nextorg = $org + 1;
    }
    if ($label != "") {
        if (ereg('^:', $label))
            $s = $locallabel.$label;
        else
            $s = $locallabel = $label;
        if ($opcode == "equ") {
            $labellist[$s] = evaloprand($oprand);
            continue;
        }
        $labellist[$s] = $org;
    }
    if (($l = @$codemap[$opcode]) !== null) {
        if (@$codelistl[$org] !== null)
            die("memory overwritten: ".htmlspecialchars($line)."\n");
        $codelist[$org] = array($l, $oprand);
        $org = $nextorg;
        continue;
    }
    switch ($opcode) {
        case    "#include":
            if (!ereg('^"([-_.0-9A-Za-z]+)"$', $oprand, $array))
                die("syntax error: ".htmlspecialchars($line)."\n");
            $remain = file_get_contents("include/".$array[1])."\n".$remain;
            continue 2;
        case    "__config":
            if (@$codemap[$addr = 0x2007] !== null)
                die("memory overwritten: ".htmlspecialchars($line)."\n");
            $codelist[$addr] = array(-1, $oprand);
            continue 2;
        case    "nolist":
        case    "ifdef":
        case    "ifndef":
        case    "endif":
        case    "messg":
        case    "":
        case    "end":
        case    "list":
        case    "__maxram":
        case    "__badram":
            continue 2;
        case    "org":
            continue 2;
        default:
            die("syntax error: ".htmlspecialchars($line)."\n");
    }
    
    
}

if (0) {
    foreach ($labellist as $key => $val)
        printf("%s\t%08x\n", $key, $val);
}

ksort($codelist);
foreach ($codelist as $key => $val) {
    list($l, $oprand) = $val;
    $array = explode(",", $oprand);
#    $oprand1 = evaloprand($oprand1);
#    $oprand2 = evaloprand($oprand2);
#printf("%08x: %04x %d, %d\n", $key, $l, $oprand1, $oprand2);
#continue;
    
    if ($l < 0)
        $l = evaloprand($oprand);
    else if (($l & 0x8000)) {
        if ($oprand != "")
            die("syntax error: ".htmlspecialchars($line)."\n");
        $l = $l & 0x3fff;
    } else if (($l & 0x3000) == 0x3000) {
        if (count($array) != 1)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($oprand);
        if (($oprand1 < 0)||($oprand1 > 0xff))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1;
    } else if (($l & 0x3000) == 0x2000) {
        if (count($array) != 1)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($oprand);
        if (($oprand1 < 0)||($oprand1 > 0x7ff))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1;
    } else if (($l & 0x3000) == 0x1000) {
        if (count($array) != 2)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($array[0]);
        if (($oprand1 < 0)||($oprand1 > 0x7f))
            die("out of range: ".htmlspecialchars($line)."\n");
        $oprand2 = evaloprand($array[1]);
        if (($oprand2 < 0)||($oprand2 > 7))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1 | ($oprand2 << 7);
    } else if (($l & 0x80)) {
        if (count($array) != 1)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($oprand);
        if (($oprand1 < 0)||($oprand1 > 0x7f))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1;
    } else {
        if (count($array) != 2)
            die("syntax error: ".htmlspecialchars($line)."\n");
        $oprand1 = evaloprand($array[0]);
        if (($oprand1 < 0)||($oprand1 > 0x7f))
            die("out of range: ".htmlspecialchars($line)."\n");
        $oprand2 = evaloprand($array[1]);
        if (($oprand2 < 0)||($oprand2 > 1))
            die("out of range: ".htmlspecialchars($line)."\n");
        $l = $l | $oprand1 | ($oprand2 << 7);
    }
#printf("%08x: %04x\n", $key, $l);
    $codelist[$key] = $l;
}

$hex = <<<EOO
:020000040000FA

EOO;

$s = "";
foreach ($codelist as $key => $val) {
    if ($s == "")
        $sum = 0;
    $h = ($val >> 8) & 0xff;
    $l = $val & 0xff;
    $s .= sprintf("%02X%02X", $l, $h);
    $sum += ($h + $l);
    if ((@$codelist[$key + 1] === null)||(($key & 7) == 7)) {
        $len = strlen($s) / 2;
        $addr = ($key - ($len / 2 - 1)) * 2;
        $sum += $len;
        $sum += ($addr >> 8) & 0xff;
        $sum += $addr & 0xff;
        $sum = (0x10000 - $sum) & 0xff;
        $hex .= sprintf(":%02X%04X00%s%02X\n", strlen($s) / 2, $addr, $s, $sum);
        $s = "";
    }
}

$hex .= <<<EOO
:00000001FF

EOO;

header('Content-Type: text/plain');
print $hex;

file_put_contents("tmp.HEX", $hex);

?>

おすすめ記事

記事・ニュース一覧