ワークショップ・ねぶりん

開発したものとか。

まずは試してみましょう

ソフトウェアの準備ハードウェアの準備が終わったら、まずは試してみましょう。但し、何も知らない状態から始める事を想定しているので、とても説明が長いです。その結果、大長編となりました。どうか頑張って、最後までお付き合い下さい。

まずIDEを起動して、以下の様にメニューから新規プロジェクトを作成を選択します。

f:id:neb4nebrin:20181116195245p:plain
新規プロジェクト作成:メニュー位置

すると、以下の様な新規プロジェクト作成ダイアログが表示されます。

f:id:neb4nebrin:20181116195414p:plain
新規プロジェクト作成:初期状態

左のペインから、Installedの配下にあるAssemblerを選択します。

f:id:neb4nebrin:20181116195537p:plain
新規プロジェクト作成:Assembler選択

下のペインのNameにプロジェクト名(とソリューション名)として、BlinkAsmと入力します。Solution name(ソリューション名)は、Nameを入力した名前と同じ名前が、自動的に設定されますので、入力は不要です。

f:id:neb4nebrin:20181116195712p:plain
新規プロジェクト作成:プロジェクト名指定

同じペインのLocationを確認します。作成したソリューションは、このLocationフォルダー配下に作成されます。デフォルトで問題はありませんが、今後ソリューションが増えてくると、乱雑になって管理も大変になるので、分かり易いサブフォルダー等を指定しておくのも良いでしょう。以下は、デフォルトから一連の本記事用のフォルダー配下に変更した所です。

f:id:neb4nebrin:20181116195837p:plain
新規プロジェクト作成:ソリューション格納先指定

これでOKボタンを押します。バイス選択ダイアログが表示されます。

f:id:neb4nebrin:20181116200007p:plain
バイス選択ダイアログ:初期状態

上のペインのDevice Familyプルダウンメニューから、ATmegaを選択します。

f:id:neb4nebrin:20181116200143p:plain
バイス選択ダイアログ:ATmega選択

下のペインの一覧をスクロールして、ATmega328Pを選択します。ATmega328とか、ATmega328PBという似た様な名前もありますが、間違えないで下さい。

f:id:neb4nebrin:20181116200254p:plain
バイス選択ダイアログ:ATmega328P選択

これでOKボタンを押します。ソリューションとプロジェクトが作成されます。遅いPCをお使いの場合は、少し時間がかかるかもしれません。作成が終わると、IDEが以下の様になります。各ウィンドウのレイアウトや大きさは、各人によって異なります(自由に移動したりリサイズ出来ます)ので、多少違っていても気にしないで下さい。

f:id:neb4nebrin:20181116200403p:plain
プロジェクト:初期状態

コードを書き始める前に、先に環境を設定しておきます。以下の様にメニューを選択して、プロジェクトのプロパティを開きます。

f:id:neb4nebrin:20181116200522p:plain
プロジェクトのプロパティ:メニュー位置

プロパティが表示されました。

f:id:neb4nebrin:20181116200625p:plain
プロジェクトのプロパティ:初期状態

プロパティウィンドウの左のペインから、Toolを選択します。

f:id:neb4nebrin:20181116200743p:plain
プロジェクトのプロパティ:Tool

プロパティウィンドウの右のペインのSelected debugger/programmerで、Simulatorを選択します。MCUにいきなり書き込む前に、まずはSimulatorでテストする為です。

f:id:neb4nebrin:20181116200852p:plain
プロジェクトのプロパティ:Simulator指定

プロパティの変更を保存して、プロパティウィンドウを閉じます。

f:id:neb4nebrin:20181116200935p:plain
プロパティ保存:メニュー位置

f:id:neb4nebrin:20181116201017p:plain
プロパティタブを閉じる:ボタン位置

コードエディターウィンドウに戻ります。このままの状態でアセンブルして実行しても、動作はしますが、実行内容はR16レジスタをインクリメントするだけの無限ループです。初めての××系では、最初は大抵Hello World!Lチカが主流みたいなので、どちらにしようか悩んだのですが、ここはLチカで行きたいと思います。

Lチカも、Arduinoだとdelay()が使えて、n秒間隔の点滅などは簡単に記述出来るのですが、アセンブリ言語だとそのようなライブラリは無いので、自分で1から記述する事になります。加えて割り込みやタイマーも、まだ一連の本記事では学習していないので、原始的で力技ですが待機する命令サイクル数(ステップ数とかクロック数とか言う人も居ます)を自分で計算してコードを記述する羽目になります。まぁ習っていないのだから仕方ありません、我慢です。

結果、出来上がったコードは次の様になります。これをコードエディターで入力します。; で始まる行はコメント行ですので、入力しなくても構いませんが、後で見返した時に困るので、ちゃんと書く事をお勧めします。

;
; BlinkAsm.asm
;
; Created: 2018/11/06 18:04:49
; Author : neb4nebrin
;

; ArduinoのBlinkスケッチと同じ様に配線しようと考えると、
; Arduino Uno Rev.3でのLED_BUILTINは、digital 13pinとなる。
; これをATmega328P-PUのピン配置で読み替えると、PB5となる。
; しかし、データシートによるとPB5(SCK/PCINT5)と書かれており、
; このピンは、ISP用に使われている事が判る。
; よって本プログラムでは、PB5からPD5に出力先を変更する。
; よってPortDを初期化しなくてはならないので、PD5を出力に設定する。
    sbi     ddrd,pd5

; メインループ処理解説:
; PD5をオン->1秒遅延->PD5をオフ->1秒遅延->最初に戻る
MainLoop:
    sbi     portd,pd5
    rcall   DelayLoop
    cbi     portd,pd5
    rcall   DelayLoop
    rjmp    MainLoop

; サブルーチン解説の前に:
;
; 時間指定の遅延の考え方:
; 16MHzで動作するATmega328P-PUは、1秒間に16,000,000サイクルの命令を実行できる。
; つまり1秒待機するという事は、16,000,000サイクル分の命令を無駄に実行させれば良い。
; 実際に16,000,000きっちり無駄にするには、まずループプログラムを書いた後に、
; その総処理サイクル数を求めて、ループ回数を調整する必要がある。
; なおこの考え方は、水晶が必ず正確に16MHzを発振する事が前提の考え方なので、
; 個体差や温度等による影響で、実際にはかなり誤差があると思われる。
; よって、多少サイクル回数が増減しても、問題はないと思われる。

; 実行する回数が指定されたループの考え方:
; 汎用レジスタは8bitの大きさなので、[0 - 255]までの最大256回しか実行出来ない。
; しかし、ループを入れ子とすると、最大256回 * 256回 = 65536回実行出来る。
; もしこれでも足りないのであれば、さらに桁を足していく事を繰り返す。



; 遅延ループ処理解説:
; まず基本として、各命令のサイクル数を調べる。
; ldi/nop/decは1サイクル命令。
; brneは2サイクル命令
; retは4サイクル命令。
;
; InnerLoop3ラベルから、brne InnerLoop3迄の命令サイクルは、計4サイクル。
; その外側のInnerLoop2ループは、InnerLoop3の分を除くと、計4サイクル。
; さらにその外側のInnerLoop1ループは、同様に計4サイクル。
; 残るサブルーチンの最初と最後は、計5サイクル。
;
; 内部ループ3は、r22 * r21 * r20回実行される。
; 内部ループ2は、r21 * r20回実行される。
; 内部ループ1は、r20回実行される。
; よって内部ループの総サイクル数は、以下の計算式で求められる。
; 内部ループの総サイクル数 =
;   (r22 * r21 * r20 * 4) + (r21 * r20 * 4) + (r20 * 4)
;
; これより総サイクル数は、以下の通りとなる。
; 総サイクル数 = 
;   5 + (r22 * r21 * r20 * 4) + (r21 * r20 * 4) + (r20 * 4)
;
; このコードに設定した値で計算すると、
; 総サイクル数 =
;   5 + (249 * 250 * 64 * 4) + (250 * 64 * 4) + (64 * 4) = 16,000,261
;
; もしお使いのクロック周波数が異なるのであれば、これらの考え方で初期値を再定義する事。
DelayLoop:
    ldi     r20,64
InnerLoop1:
    ldi     r21,250
InnerLoop2:
    ldi     r22,249
InnerLoop3:
    nop
    dec     r22
    brne    InnerLoop3
    dec     r21
    brne    InnerLoop2
    dec     r20
    brne    InnerLoop1
    ret

入力が終わったら、コードをセーブしてから、アセンブルします。IDEでは、アセンブルだのコンパイルだの区別せずに、単にビルドすると言います。

f:id:neb4nebrin:20181116201256p:plain
コードを保存:メニュー位置

f:id:neb4nebrin:20181116201341p:plain
ビルド:メニュー位置

Build Solutionを選択するとビルドが開始され、結果がOutputウィンドウに表示されていきます。正常に終了すると、次の様に表示されます。

f:id:neb4nebrin:20181116201511p:plain
Outputウィンドウ:正常終了

このウィンドウを見つけられない場合は、以下の様にOutputメニューを選択して下さい。

f:id:neb4nebrin:20181116201613p:plain
Outputウィンドウ:メニュー位置

OutputウィンドウにBuild succeeded以外の出力が表示されたなら、環境もしくは入力したソースコードに問題があります。

もし環境に問題があるならば、前の手順に戻って、プロジェクトのプロパティを再確認します。もしソースコードに問題があるならば、以下の様にError Listウィンドウに問題点が表示されます。

f:id:neb4nebrin:20181116201733p:plain
Error Listウィンドウ:初期状態

このウィンドウを見つけられない場合は、以下の様にError Listメニューを選択して下さい。

f:id:neb4nebrin:20181116201855p:plain
Error Listウィンドウ:メニュー位置

IDEならではの機能ですが、このError Listウィンドウの行を選択して、ダブルクリックすると、直ちにコードエディターを表示して、問題のある行に移動してくれます。これで直ぐに修正作業に移る事が出来ます。

f:id:neb4nebrin:20181116201957p:plain
Error Listウィンドウ:ダブルクリック

ビルドが正常に終了するまで、注意深く修正作業を行ってください。上の画面キャプチャーで、コードエディターに色がついているのが判ると思います。コメント行は緑、MCUへの命令は青、命令に対するオペランドやラベル等は黒です。青にならなければならないのに、黒で表示されている等、基本的なミスは色でも判ります。IDEならではの便利な機能を利用して、少しでも楽に開発を進めましょう。

ビルドが正常終了したら、次はSimulatorを用いて動作テストをします。以下の様にStart Debugging and Breakメニューを選択します。

f:id:neb4nebrin:20181116202120p:plain
デバッグ開始:メニュー位置

すると、以下の様にデバッグ用ウィンドウレイアウトに自動的に切り替わり、コードの先頭位置で実行が中断した状態となります。

f:id:neb4nebrin:20181116202227p:plain
デバッグ開始:初期状態

コードのデバッグを開始する前に、デバッグに用いるウィンドウを用意します。全てのウィンドウの位置や大きさは、各人の環境で異なると思いますが、IDEでは各ウィンドウをそれぞれ好きな位置に配置したり、ドッキングしたりする事が出来ますので、もし見当たらない場合は、よく探してみて下さい。探しても見つけられない方の為に、今回用いるウィンドウの開き方も載せます。

まず必要となるのは、MCUの状態を表すProcessor Statusウィンドウです。ここにはプログラムカウンターや各種汎用レジスターが表示される他、Simulatorの動作クロックを設定したり、自分の知りたい範囲のコードを実行した結果のサイクル数が表示されていたりします。

f:id:neb4nebrin:20181116202343p:plain
Processor Statusウィンドウ:初期状態

このウィンドウを見つけられない場合は、以下の様にProcessor Statusメニューを選択して下さい。

f:id:neb4nebrin:20181116202449p:plain
Processor Statusウィンドウ:メニュー位置

今回のソースコードは、16MHzで動作する事を前提としていますので、Simulatorのクロック周波数を変更します。Frequencyの値(1.000MHzとなっている箇所)をクリックすると、以下の様に修正可能になります。

f:id:neb4nebrin:20181116202557p:plain
Processor Statusウィンドウ:クロック周波数変更1

これを16.000MHzに修正してEnterキーを押すと、以下の様に赤字で表示されて確定されます。

f:id:neb4nebrin:20181116202705p:plain
Processor Statusウィンドウ:クロック周波数変更2

次に必要となるのは、I/Oの状態を表すI/Oウィンドウです。プロジェクトを作成した時にATmega328Pを選択しているので、最初からATmega328Pが使えるI/Oだけが表示されています。

f:id:neb4nebrin:20181116202812p:plain
I/Oウィンドウ:初期状態

このウィンドウを見つけられない場合は、以下の様にI/Oメニューを選択して下さい。

f:id:neb4nebrin:20181116202920p:plain
I/Oウィンドウ:メニュー位置

今回は、Port D5番ピンを用いますので、I/O Port (PORTD) を選択します。

f:id:neb4nebrin:20181116203018p:plain
I/Oウィンドウ:PortD選択

すると、I/Oウィンドウの下ペインにPort Dの詳細が表示されます。データシートにも記載されていますが、リセット直後のPort Dの状態は、全て値(Valueの欄です)が0x00となっています。つまり全て入力ピン(内蔵プルアップ抵抗無し)として設定されています。PINDDDRDPORTDの詳細は、後の章で学ぶので省略しますが、ざっくり説明(本当はもう少し面倒くさい)すると、PINDが入力ピンの扱いで、DDRDが入出力方向指定レジスタとなり、PORTDが出力ピンの扱いとなります。Arduinoでもそうした様に、Portの設定はコード中に記述します。

以上が、今回用いるデバッグ用のウィンドウとなります。

次にBreakpointを学習しましょう。今回作成したDelayLoopサブルーチンは、1秒待機するループです。後述するステップ実行を1秒に相当するサイクル数分回すのは、手が疲れるだけでは済まないでしょう。加えてSimulatorを使っているので、どんなに速いPC上で動かしたとしても、実機と同じように16,000,000サイクルを1秒でシミュレートする事は不可能です。そういった訳で、1回のデバッグである程度の量(ブロックとかでも良い)のコードが動いている事が確認出来たら、一度デバッグ実行を終了し、次回のデバッグ実行の時には、次に中断させて状態を確認したいコードの部分をマークしておき、既に確認済み部分は普通に実行させて早送りするのです。この止めたい位置の事をBreakpointと呼びます。

Breakpointの作り方は簡単です。止めたい命令のある行をクリックして選択した後、右クリックメニューからInsert Breakpointを選択します。

f:id:neb4nebrin:20181116203141p:plain
Breakpoint:メニュー位置

設定されたBreakpointは、以下の様に、行番号の前に塗りつぶした赤丸として表示されていて、行が赤で強調表示されています。

f:id:neb4nebrin:20181116203253p:plain
Breakpoint:設定済み

Breakpointは、とても頻繁に設定・解除を行うので、毎回メニューから操作するのも面倒です。そこで、簡易手段を使います。コードエディター上で、背景色が異なる行番号の前の部分をクリックするだけで、設定・解除がトグルされます。

f:id:neb4nebrin:20181116203352p:plain
Breakpoint:簡易手段

上記赤丸部分をクリックすると、

f:id:neb4nebrin:20181116203453p:plain
Breakpoint:クリックした所

Breakpointが簡易設定されました。解除も同じ方法で、赤丸をクリックします。これでBreakpointを使って、実行を中断させる方法を学びました。次に移る前に、今設定した全てのBreakpointを解除して下さい。

さて、いよいよデバッグを開始します。今、プログラムは先頭の位置で中断されています。この位置は、コードエディターでBreakpointを簡易設定したのと同じ位置に、黄色の矢印として表示されていて、行が黄色で強調表示されています。

f:id:neb4nebrin:20181116203606p:plain
デバッグ開始:停止位置

この強調表示された行が、次に実行される命令となります。つまり、まだ何も実行されていません。そしてこの、中断されている状態と、実行(デバッグ)を終了している状態を勘違いしないで下さい。実行を終了している状態では、一部のウィンドウが表示出来ません(自動的に閉じてしまいます)し、コードエディター上で黄色の矢印と行の強調表示もされません。

またデバッグ中に、コードエディターでコードを修正しても、今のデバッグ状態を継続したまま新しいコードを入力して試す事も出来ません。修正する時は、一度デバッグを終了します。コードを修正後にビルドし直して、再びデバッグする必要があります。

では、最初の命令を実行してみましょう。ステップ実行には幾つか種類があり、それぞれ次の様な特徴があります。

  • Step Into

    次に実行する命令がサブルーチンコール(C/C++の場合は、関数やメソッドのコールも含む)の場合は、そのサブルーチンの先頭を次の中断位置とする。サブルーチンコールではない場合、次の中断位置は、大抵次の行の命令となる。

  • Step Over

    次に実行する命令がサブルーチンコールの場合は、次の中断位置は、そのサブルーチンから戻ってきた次の命令(大抵今の中断位置の次の行にある)とする。サブルーチンコールではない場合は、Step Intoと同じ。

  • Step Out

    現在サブルーチン内部を実行中の場合、次の中断位置は、そのサブルーチンから戻った(要は呼び出し元の)次の命令とする。もしメインルーチンでこれを実行した場合、どこかにBreakpointがなければ中断する事はない。

まずは基本のStep Intoです。最初の命令sbi ddrd,pd5は、サブルーチンコールではないので、この状態でStep Intoすると、次の中断位置はラベルMainLoopの次の行(ラベルは命令ではありません)のsbi portd,pd5となりそうです。やってみましょう。それぞれのステップ実行は、Debugメニューの中にあります。

f:id:neb4nebrin:20181116203720p:plain
StepInto:メニュー位置

Breakpointと同様に、毎回メニューから操作をするのは面倒です。ここはショートカットキーやツールバーの出番です。ショートカットキーは、表示されたメニューに書いてあるので省略します。Debugツールバーは、次の様に表示されています。

f:id:neb4nebrin:20181116203835p:plain
Debugツールバー

このツールバーが見つからない人は、ツールバー領域で右クリックメニューから、Debugを選んでください。

f:id:neb4nebrin:20181116203931p:plain
Debugツールバー:メニュー位置

ツールバーの各アイコンの上にマウスカーソルを移動すると、それぞれ意味が表示されますので、各アイコンの説明は割愛します。以下は、Step Intoを実行した結果です。

f:id:neb4nebrin:20181116204027p:plain
デバッグ開始:Step Into結果その1

予想した通りの結果となりました。同様に今度はStep Overすると、次の中断位置はrcall DelayLoopとなりそうです。やってみましょう。

f:id:neb4nebrin:20181116204148p:plain
デバッグ開始:Step Over結果その1

予想した通りの結果となりました。次の命令はrcall DelayLoopなので、サブルーチンコールとなります。ここでStep Intoすると、次の中断位置はラベルDelayLoopの次の行のldi r20,64となりそうです。やってみましょう。

f:id:neb4nebrin:20181116204253p:plain
デバッグ開始:Step Into結果その2

予想した通りの結果となりました。この先は、1秒を待機するループ処理です。折角サブルーチン内部に入ったのだから、Step Outを試してみたい所ですが、実際に自分で試した所、うんざりする程長時間待たされる結果となってしまいました。なので、Step Out機能は封印して、先に習ったBreakpointを使います。更についでに、このループの処理時間を計算させてみましょう。

まずProcessor Statusウィンドウで、Stop Watchの行の上で、右クリックメニューを表示し、Reset Stop Watchを選択します。

f:id:neb4nebrin:20181116204411p:plain
デバッグ開始:実行時間初期化メニュー位置

f:id:neb4nebrin:20181116204452p:plain
デバッグ開始:実行時間初期化

Stop Watchが初期化出来たので、次にサブルーチンからの戻り先にBreakpointを設定します。

f:id:neb4nebrin:20181116204602p:plain
デバッグ開始:Breakpoint設定

今度はステップ実行ではなく、Continue(続けて実行)をします。もちろんメニューからではなく、ツールバーの同じアイコンを押しても問題ありません。

f:id:neb4nebrin:20181116204714p:plain
デバッグ開始:Continue

この操作は少し待たされると思います。が、Step Outよりもずっと早く終わります。

f:id:neb4nebrin:20181116204825p:plain
デバッグ開始:Breakpointによる中断

中断したら、Processor StatusウィンドウのStop Watchの値を確認します。

f:id:neb4nebrin:20181116204938p:plain
デバッグ開始:実行時間確認

999,012.25us(マイクロをuと表記)となっています。1秒=1,000,000マイクロ秒ですので、約1秒経過している事が判ります。よって、処理の妥当性が証明されました。

次の命令はcbi portd,pd5となっていますが、これもStep Intoします。

f:id:neb4nebrin:20181116205054p:plain
デバッグ開始:Step Into結果その3

次の命令がrcall DelayLoopなので、ここでStep Overすると、次の中断位置はrjmp MainLoopとなりそうです。やってみましょう。

f:id:neb4nebrin:20181116205208p:plain
デバッグ開始:Step Over結果その2

これも少し待たされますが、ちゃんと次の行の命令で中断しています。rjmpはジャンプ命令なので、次の実行はMainLoopの先頭に戻る事になります。ここまでに大きな動作確認が出来ましたが、まだ確認出来ていないのは、Port Dの動作です。

ここで、Breakpointをさらに追加します。今中断している状態でrjmp MainLoopにBreakpointを設定します。すると、この様になる筈です。

f:id:neb4nebrin:20181116205325p:plain
デバッグ開始:Breakpoint追加

現在のPort Dの状態を、I/Oウィンドウで確認しましょう。

f:id:neb4nebrin:20181116205431p:plain
デバッグ開始:Port D状態その1

Port D5番ピン(つまりNamePORTDとなっているValue(値)を見て、最下位bitを0bit目とした時の5bit目)が0となっているので、Arduinoで書く所のLOWとなっています。ここでContinueすると、MainLoopの先頭に戻り、sbi portd,pd5を実行し、最終的に次のBreakpointであるcbi portd,pd5で中断すると予想されます。sbi命令は、指定されたI/Oの指定されたビットをセット(Set BIt)する命令です。つまり、ここでPort D5番ピン1にします。これはArduinoで書く所のHIGHです。やってみましょう。

f:id:neb4nebrin:20181116205601p:plain
デバッグ開始:Port D状態確認その1

中断したので、I/Oウィンドウを確認します。

f:id:neb4nebrin:20181116205716p:plain
デバッグ開始:Port D状態その2

Port D5番ピン1になりました。このままContinueすると、cbi portd,pd5が実行されて、最終的に次のBreakpointであるrjmp MainLoopで中断すると予想されます。cbi命令は、指定されたI/Oの指定されたビットをクリアー(Clear BIt)する命令です。つまり、ここでPort D5番ピン0にします。これはArduinoで書く所のLOWです。やってみましょう。

f:id:neb4nebrin:20181116205839p:plain
デバッグ開始:Port D状態確認その2

中断したので、I/Oウィンドウを確認します。

f:id:neb4nebrin:20181116205946p:plain
デバッグ開始:Port D状態その3

Port D5番ピン0になりました。これでMainLoopのコメントに書いた仕様(PD5をオン->1秒遅延->PD5をオフ->1秒遅延->最初に戻る)が、全て正しく動作する事が判りましたので、Simulatorでのテストを終了しましょう。

デバッグを終了するには、Stop Debuggingを選択します。もちろんメニューからではなく、ツールバーの同じアイコンを押しても問題ありません。

f:id:neb4nebrin:20181116210105p:plain
デバッグ終了:メニュー位置

もし、ここまでの何処かで想定した動作とならなかった場合は、書いたコードに問題があります。大抵の場合、ミスタイプが原因です。例えばレジスタの名前を間違っている(例:r16->r17)・似た様な命令(例:brne->brie)に打ち間違えて意図した処理になっていない等です。こうなった場合は、直ちにデバッグを終了し、ソースコードを確認・修正した後、ビルドしなおしてから再デバッグを行います。そして全てが正しく動作するまでこの手順を繰り返します。

と、この様な感じで動作テストは進みます。アセンブリ言語の場合、それぞれの命令が実行する結果は、とても単純です。ですから、ある程度の命令を纏めて1つの機能ブロックと考えて、それぞれの実行結果を確認していくと良いと思います。そういった理由で、ソースコードには、機能ブロック単位でコメントを記述しています。

最後にソースコードの各機能を説明すると、ソースコードの先頭部分(1行しかありませんが)は初期化コードで、Arduinoでいう所のsetup()に該当します。MainLoopから始まりrjmpで終わる部分はメインルーチンで、Arduinoでいう所のloop()に該当します。DelayLoopから始まりretで終わる部分は、Arduinoでいう所のdelay()関数に相当するユーザー定義のサブルーチンとなります。また、今回はDelayLoopの詳細動作についての動作テストを割愛しましたが、もし興味がありましたら、汎用レジスタ(r20, r21, r22)に対する初期値(64, 250, 249)を、デバッグ用に(1, 1, 1)と書き換えてテストすると良いと思います。ループ処理の動作が確認出来たら、元の初期値に戻せば良いのです。

さて、次はMCUボードを用いて動作させてみましょう。MCUボードがまだ用意出来ていない方は、記事を最後の方までずっと進めて、MCU上のメモリサイズの確認に進んで下さい。

まず最初にソースコード中のBreakpointを、全て解除します。

次に、実行する環境をSimulatorからプログラマー(に接続してあるMCUボード)に変更します。この記事の最初でやった通りにして、もう一度プロジェクトのプロパティを開きます。そして、ToolSelected debugger/programmerを、用意したプログラマー(この場合はSTK500)に変更します。InterfaceISPに設定します。

f:id:neb4nebrin:20181116210316p:plain
プログラミング:環境設定

変更を終えたら、プロジェクトのプロパティ保存してから閉じます。

もしMCUボードを自作して、新品で1度も使用していないMCUを取り付けた場合、MCUが内蔵オシレータを使って動作する設定となっているので、これを外部の水晶振動子を用いる様に、Fuseビットを書き換える必要があります。この作業手順は、新品のMCUを使う最初の1回だけ必要です。製品のMCUボードを利用する方は、大抵設定済みMCUが付属していると思います。まずは、お手持ちのマニュアル等を参考にして下さい。設定済みのMCUボードを利用する方は、ブレッドボードにLEDと抵抗を接続する手順に進んで下さい。未設定のMCUボードを利用する場合は、この手順を参考にして、ご自身で設定して下さい。

まず最初に知らなくてはいけない事は、データシートによると、出荷時のMCUはデフォルトクロックソースとして内蔵オシレータ(8MHz)が有効で、CKDIV8というFuseがプログラムされている、と記述されています。この結果、出荷時のMCU1MHzで動作します。つまり、水晶振動子や外部オシレータをMCUに接続しただけでは、そのクロック周波数では動作しないのです。以下に、新品のATmega328P-PUFuseビットとLockビットを、ICライターで読み出した結果を示します。

f:id:neb4nebrin:20181116210457p:plain
新品のATmega328P-PUのFuseビットとLockビット

Fuseビットは、1の場合が未プログラム状態で、0の場合がプログラム状態となります。画像内で、例えばCKDIV8=0と書いてあるのは、チェックマークが入っていたらプログラム状態で、それは0である事を意味します。つまりチェックマークが無いなら、1を表します。

読み方を理解したら、データシートに戻ります。CKSEL3からCKSEL0までを、CKSEL3が最上位ビット(MSB)となる様に並べると、4bitの値が出来ます。これをCKSEL[3:0]の様に記述します。上の読み取り結果のCKSEL[3:0]は2進数で0b0010と表現出来るので、これをデータシートの13.2 Clock Sourcesを参照すると、Calibrated Internal RC Oscillatorが選択されている事が判ります。また、CKDIV8がプログラムされているので、クロックソースを8分周する事も判ります。13.6 Calibrated Internal RC Oscillatorを調べると、確かに結果として大体1MHzとして動く事が判ります。加えてSUT[1:0]0b10となっているので、Power ConditionsSlowly rising powerで、Start-Up Time from Power-down and Power-Save6CKで、Additional Delay from Reset14CK + 65msに設定されている事も判ります。これらの値は、電源投入直後等(回路が不安定です)から、内蔵オシレータ起動をどれだけ待つか?を表しています。

次に自分のMCUボードです。XTAL1ピンとXTAL2ピンに16MHz水晶振動子22pFのセラミックコンデンサーからなる発振回路を接続しています。データシートの13.1 Clock Systems and Their Distributionを参照すると、XTAL1XTAL2ピンに接続したCrystal Oscillatorというパターンになります。このパターンと13.2 Clock Source13.3 Low Power Crystal Oscillator13.4 Full Swing Crystal Oscillatorを参照した結果、MCUボードをLow Power用に設計していないので、Full Swing Crystal Oscillatorが該当する事になります。つまり、13.4 Full Swing Crystal Oscillatorを参照して値を決める事になります。まずCKSEL[3:1](最下位ビット(LSB)の値が1となっている事に注意)の値は0b011となります。次に残っているCKSEL0SUT[1:0]の値も決めます。このMCUボードは特殊用途ではないので、MCUの起動タイミングをシビアにする必要もありません。つまり、基本的に出荷時動作(汎用設定)と同じ様な動作で問題はありません。よって内蔵オシレータの設定を流用して、Oscillator Source/Power ConditionsCrystal Oscillator, slowly rising powerで、Start-Up Time from Power-down and Power-save16K CKで、Additional Delay from Reset14CK + 65msである行を参照すると、CKSEL01で、SUT[1:0]0b11と記述されています。また16MHzでそのまま動作させるので、このクロックを分周する必要はありません。これらの結果から、最終的に設定する値は、CKSEL[3:0]0b0111で、SUT[1:0]0b11で、CKDIV81となります。

設定する値が決まったので、MCUボードとプログラマーとPCをそれぞれ接続し、通電します。IDEに戻って、Available Toolsウィンドウからプログラマーを右クリックし、Device Programmingを選択します。

f:id:neb4nebrin:20181116210922p:plain
Fuseビット書き込み:メニュー位置

ハードウェアの準備で行った様にしてMCUボードと通信を行い、Fuseビットを読み出します。

f:id:neb4nebrin:20181116211026p:plain
Fuseビット書き込み:読み込み

LOW.CKDIV8のチェックを外します。そしてLOW.SUT_CKSELExt. Full-swing Crystal; Start-up time PWRDWN/RESET: 16K CK/14 CK + 65 msに変更します。

f:id:neb4nebrin:20181116211141p:plain
Fuseビット書き込み:変更

変更した箇所のアイコンが、緑のチェックマークから、黄色の注意マークに変わります。この2か所以外に黄色マークがあった場合は、Readボタンを押して元の値に戻して下さい。LOWの値が0xF7となっているか確認したら、Programボタンを押して書き込みます。

f:id:neb4nebrin:20181116211305p:plain
Fuseビット書き込み:Warning

Fuseビット変更に対するWarning(警告)ダイアログが表示されます。Continueボタンを押して継続します。以前に一度でもDon't show this warning againにチェックを入れていた場合は、このダイアログは表示されずに書き込みが行われますので、注意して下さい。

f:id:neb4nebrin:20181116211425p:plain
Fuseビット書き込み:完了

Fuseビットの書き込みが完了すると、黄色マークが緑マークになります。Closeボタンを押して、ダイアログを閉じます。もし書き込みに失敗した場合は、書き込む電圧が不安定である位しか思いつきません。自分の使っているプログラマーは、PC上で書き込んだ時のボードの電圧を確認する事が出来ます。以下は、今回Fuseビットを設定した時の、MCUボードの電圧です。

f:id:neb4nebrin:20181116211547p:plain
Fuseビット書き込み:MCUボードの電圧

MCUへのFuseビット設定が終わったら、MCUボードへの通電を止めます。

次は、ブレッドボード等を使用してLEDと抵抗を直列に接続し、それぞれの両端をMCUボードのPD5GNDに接続します。この時LEDの極性に注意して下さい。もしLEDや抵抗をお持ちでない場合や、何を使えば良いのか判らない場合は、以下の説明を参考に購入 or 計算して下さい。

今回私が使用したLEDは、OptoSupplyというメーカー製のOSR5JA3Z74Aという3mmの赤色LEDです。OptoSupplyのLEDは、秋月電子通商で簡単に入手出来、データシートも公開(秋月電子通商の商品ページにリンクあり)されているので、良く判らないLEDを使うよりも使いやすいと思います。

3mm赤色LED 70° OSR5JA3Z74A

LEDに流れる順方向電流を$I_{\rm F}$(A)、LEDの順方向電圧降下を$V_{\rm F}$(V)、電源電圧を$V_{\rm CC}$(V)と表記すると、LEDに接続する抵抗の値$R_{\rm LED}$($\Omega$)は、以下の式で表現出来ます。

$$ R_{\rm LED} = \frac{V_{\rm CC} - V_{\rm F}}{I_{\rm F}} $$

LEDのデータシートを確認すると、順方向電流$I_{\rm F}$が20mAの時、平均(Typ.)順方向電圧降下$V_{\rm F}$が2.1Vである事が判りました。しかし、実際にこれらの値を用いて試してみましたが、眩しすぎる位明るくなったので、$I_{\rm F}$を3mAとしました。これより求める抵抗の値$R_{\rm LED}$は、以下の通りとなります。

$$ R_{\rm LED} = \frac{5 - 2.1}{0.003} = 966.\dot{6} $$

実際に接続する抵抗値は、計算結果より大きくて一番近い値を用います。逆に計算結果より小さくて一番近い値を選択してしまうと、流れる電流が計算よりも多くなる事を意味するので、最悪の場合(データシートの$I_{\rm F}$の最大値を超えた場合)LEDが壊れます。大きくて一番近い値を選択すれば、計算よりは多少暗くなるかもしれませんが、壊れる事もありません。もしくは、複数の抵抗を直列と並列に組み合わせて、計算結果に等しい抵抗値を作り出しても良いのですが、あまり現実的ではありません。よって、今回は一番近い1k$\Omega$(カラーコード:茶黒赤金)を1つだけ用います。

LEDの抵抗値が求まったので、今度はこの抵抗の消費電力を計算します。LEDに接続する抵抗の消費電力$W_{\rm R}$(W)は、以下の式で表現出来ます。

$$ W_{\rm R} = I_{\rm F} \times I_{\rm F} \times R_{\rm LED} $$

つまり今回の消費電力は、以下の通りとなります。

$$ W_{\rm R} = 0.003 \times 0.003 \times 1000 = 0.009 $$

9mWという事は、1/6W($=166.\dot{6}\rm mW$)抵抗で充分という事になります。小さな組み込み機器を作成するなら、部品も小さい方が都合が良いので、1/6W抵抗を使いましょう。これも秋月電子通商で(100本単位ですが)買えます。

カーボン抵抗(炭素皮膜抵抗) 1/6W 1kΩ (100本入)

パーツの選定が終わったので、LEDと抵抗をブレッドボードに配線しましょう。

f:id:neb4nebrin:20181116211852p:plain
ブレッドボードに回路を作成

電子工作のお約束には、赤は電源・黒はGNDというのがあり、これに従ってジャンプワイヤーを使っています。別に守らなくても電気的に問題はないのですが、パッと見て黒はGNDというのが理解出来ると、接続の際に極性を間違える事も少なくなると思います。それ以外の信号線については、赤・黒以外の自分の好きな色を使えば良いと思います。今回は、MCUボードのPD5GNDに接続するので、上記の写真では、緑のジャンプワイヤーをPD5に、黒のジャンプワイヤーをGNDにそれぞれ接続します。MCUボードへの電力供給は、まだしないで下さい。

f:id:neb4nebrin:20181116212023p:plain
MCUボードとブレッドボードを接続

この様な感じで接続しました。写真ではプログラマーが既に通電されていますが、ここからMCUボードへの電力供給は行っていませんので、この時点ではMCUは動作していません。

次にMCUボードへ電力供給を行います。私のMCUボードは、シリアル変換アダプターから電力供給を行うので、ここでシリアル変換アダプターをPCと接続します。プログラマーから電力供給したり、それ以外の外部電源を用いる方も、この時点から電力供給します。また、プログラマーもPCと接続して、ハードウェアが全て動作可能な状態として下さい。

ここからIDEに戻ります。

これで漸く書き込める状態となりました。今までとは違って、今度はStart Without Debuggingデバッグ無しで実行)を選択します。今まで使ってきたDebugツールバーには、このアイコンはありませんので注意です。

f:id:neb4nebrin:20181116212204p:plain
プログラミング:メニュー位置

このアイコンはStandardツールバーにあります。

f:id:neb4nebrin:20181116212306p:plain
Standardツールバー

多分最初から表示されていると思いますが、見つからない方は、ツールバー領域を右クリックしてStandardを選択して下さい。

f:id:neb4nebrin:20181116212410p:plain
Standardツールバー:メニュー位置

では、書き込んで実行してみましょう。


1.3. まずは試してみましょう

動画では、大体1秒間隔でLEDが点滅している事が判ります。これでアセンブリ言語で記述する、ATmega328P-PULチカプログラムが完成しました。

最後に、このプログラムが使ったMCU上のメモリサイズを確認してみましょう。IDESolution Explorerウィンドウで、Output Files配下のBlinkAsm.lssというファイルをダブルクリックして開きます。

f:id:neb4nebrin:20181116212526p:plain
LSSファイルの場所

このウィンドウが見つからない方は、以下の様にメニューを選択します。

f:id:neb4nebrin:20181116212623p:plain
Solution Explorer:メニュー位置

開いたlssファイルの一番最後の行に移動します。

f:id:neb4nebrin:20181116212724p:plain
メモリ使用量

.csegの行はコードセグメント(要はFlashです)に格納されるコードやデータを表しています。この場合、コードだけ34byte分使っています。.dsegの行はデータセグメント(要はSRAMです)を表しています。今回はSRAMにコードやデータ領域を作成していないので、コードもデータも0byteです。.esegの行はEEPROMセグメントを表しています。これも同様に作成していないので、どちらも0byteです。結局、このプログラムはFlashたったの34byteしか使っていません。今回のコードと同様の処理をC/C++で記述すると、これよりもずっと大きなサイズを消費します。組み込み用に用いる小さなMCUは、搭載しているメモリの量も小さい事が多く、C/C++で記述するとメモリに収まらない事も多いのです。試しにArduinoBlinkスケッチを何も変更せずにコンパイルしてみましょう。

f:id:neb4nebrin:20181116212835p:plain
Arduinoコンパイル

Flash928byteSRAM9byte使っている事が判りました。アセンブリ言語で記述するメリットの一つに省メモリが挙げられるのは、この結果を見ても明らかです。勿論デメリットもありますから、結局はアセンブリ言語とC(出来たらC++も)の両方を使い分けられる様になる事が大事です。今後は、出来るだけアセンブリ言語とCの両方で学習したいと思っています。

補足ですが、今回作成したプロジェクトは、GitHubにアップロードしてあります。どうしても動作しない場合は、こちらを参考にして下さい。

2018/11/16


目次はこちら