まずは試してみましょう
ソフトウェアの準備とハードウェアの準備が終わったら、まずは試してみましょう。但し、何も知らない状態から始める事を想定しているので、とても説明が長いです。その結果、大長編となりました。どうか頑張って、最後までお付き合い下さい。
まずIDE
を起動して、以下の様にメニューから新規プロジェクトを作成を選択します。
すると、以下の様な新規プロジェクト作成ダイアログが表示されます。
左のペインから、Installedの配下にあるAssemblerを選択します。
下のペインのNameにプロジェクト名(とソリューション名)として、BlinkAsmと入力します。Solution name(ソリューション名)は、Nameを入力した名前と同じ名前が、自動的に設定されますので、入力は不要です。
同じペインのLocationを確認します。作成したソリューションは、このLocationフォルダー配下に作成されます。デフォルトで問題はありませんが、今後ソリューションが増えてくると、乱雑になって管理も大変になるので、分かり易いサブフォルダー等を指定しておくのも良いでしょう。以下は、デフォルトから一連の本記事用のフォルダー配下に変更した所です。
これでOKボタンを押します。デバイス選択ダイアログが表示されます。
上のペインのDevice Familyプルダウンメニューから、ATmegaを選択します。
下のペインの一覧をスクロールして、ATmega328Pを選択します。ATmega328とか、ATmega328PBという似た様な名前もありますが、間違えないで下さい。
これでOKボタンを押します。ソリューションとプロジェクトが作成されます。遅いPCをお使いの場合は、少し時間がかかるかもしれません。作成が終わると、IDE
が以下の様になります。各ウィンドウのレイアウトや大きさは、各人によって異なります(自由に移動したりリサイズ出来ます)ので、多少違っていても気にしないで下さい。
コードを書き始める前に、先に環境を設定しておきます。以下の様にメニューを選択して、プロジェクトのプロパティを開きます。
プロパティが表示されました。
プロパティウィンドウの左のペインから、Toolを選択します。
プロパティウィンドウの右のペインのSelected debugger/programmerで、Simulatorを選択します。MCU
にいきなり書き込む前に、まずはSimulator
でテストする為です。
プロパティの変更を保存して、プロパティウィンドウを閉じます。
コードエディターウィンドウに戻ります。このままの状態でアセンブルして実行しても、動作はしますが、実行内容は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
では、アセンブルだのコンパイルだの区別せずに、単にビルドすると言います。
Build Solutionを選択するとビルドが開始され、結果がOutputウィンドウに表示されていきます。正常に終了すると、次の様に表示されます。
このウィンドウを見つけられない場合は、以下の様にOutputメニューを選択して下さい。
OutputウィンドウにBuild succeeded以外の出力が表示されたなら、環境もしくは入力したソースコードに問題があります。
もし環境に問題があるならば、前の手順に戻って、プロジェクトのプロパティを再確認します。もしソースコードに問題があるならば、以下の様にError Listウィンドウに問題点が表示されます。
このウィンドウを見つけられない場合は、以下の様にError Listメニューを選択して下さい。
IDE
ならではの機能ですが、このError Listウィンドウの行を選択して、ダブルクリックすると、直ちにコードエディターを表示して、問題のある行に移動してくれます。これで直ぐに修正作業に移る事が出来ます。
ビルドが正常に終了するまで、注意深く修正作業を行ってください。上の画面キャプチャーで、コードエディターに色がついているのが判ると思います。コメント行は緑、MCU
への命令は青、命令に対するオペランドやラベル等は黒です。青にならなければならないのに、黒で表示されている等、基本的なミスは色でも判ります。IDE
ならではの便利な機能を利用して、少しでも楽に開発を進めましょう。
ビルドが正常終了したら、次はSimulator
を用いて動作テストをします。以下の様にStart Debugging and Breakメニューを選択します。
すると、以下の様にデバッグ用ウィンドウレイアウトに自動的に切り替わり、コードの先頭位置で実行が中断した状態となります。
コードのデバッグを開始する前に、デバッグに用いるウィンドウを用意します。全てのウィンドウの位置や大きさは、各人の環境で異なると思いますが、IDE
では各ウィンドウをそれぞれ好きな位置に配置したり、ドッキングしたりする事が出来ますので、もし見当たらない場合は、よく探してみて下さい。探しても見つけられない方の為に、今回用いるウィンドウの開き方も載せます。
まず必要となるのは、MCU
の状態を表すProcessor Statusウィンドウです。ここにはプログラムカウンターや各種汎用レジスターが表示される他、Simulator
の動作クロックを設定したり、自分の知りたい範囲のコードを実行した結果のサイクル数が表示されていたりします。
このウィンドウを見つけられない場合は、以下の様にProcessor Statusメニューを選択して下さい。
今回のソースコードは、16MHzで動作する事を前提としていますので、Simulator
のクロック周波数を変更します。Frequencyの値(1.000MHzとなっている箇所)をクリックすると、以下の様に修正可能になります。
これを16.000MHzに修正してEnterキーを押すと、以下の様に赤字で表示されて確定されます。
次に必要となるのは、I/O
の状態を表すI/Oウィンドウです。プロジェクトを作成した時にATmega328P
を選択しているので、最初からATmega328P
が使えるI/O
だけが表示されています。
このウィンドウを見つけられない場合は、以下の様にI/Oメニューを選択して下さい。
今回は、Port Dの5番ピンを用いますので、I/O Port (PORTD) を選択します。
すると、I/Oウィンドウの下ペインにPort Dの詳細が表示されます。データシートにも記載されていますが、リセット直後のPort Dの状態は、全て値(Valueの欄です)が0x00となっています。つまり全て入力ピン(内蔵プルアップ抵抗無し)として設定されています。PIND
とDDRD
とPORTD
の詳細は、後の章で学ぶので省略しますが、ざっくり説明(本当はもう少し面倒くさい)すると、PIND
が入力ピンの扱いで、DDRD
が入出力方向指定レジスタとなり、PORTD
が出力ピンの扱いとなります。Arduino
でもそうした様に、Portの設定はコード中に記述します。
以上が、今回用いるデバッグ用のウィンドウとなります。
次にBreakpointを学習しましょう。今回作成したDelayLoopサブルーチンは、1秒待機するループです。後述するステップ実行を1秒に相当するサイクル数分回すのは、手が疲れるだけでは済まないでしょう。加えてSimulator
を使っているので、どんなに速いPC上で動かしたとしても、実機と同じように16,000,000サイクルを1秒でシミュレートする事は不可能です。そういった訳で、1回のデバッグである程度の量(ブロックとかでも良い)のコードが動いている事が確認出来たら、一度デバッグ実行を終了し、次回のデバッグ実行の時には、次に中断させて状態を確認したいコードの部分をマークしておき、既に確認済み部分は普通に実行させて早送りするのです。この止めたい位置の事をBreakpointと呼びます。
Breakpointの作り方は簡単です。止めたい命令のある行をクリックして選択した後、右クリックメニューからInsert Breakpointを選択します。
設定されたBreakpointは、以下の様に、行番号の前に塗りつぶした赤丸として表示されていて、行が赤で強調表示されています。
Breakpointは、とても頻繁に設定・解除を行うので、毎回メニューから操作するのも面倒です。そこで、簡易手段を使います。コードエディター上で、背景色が異なる行番号の前の部分をクリックするだけで、設定・解除がトグルされます。
上記赤丸部分をクリックすると、
Breakpointが簡易設定されました。解除も同じ方法で、赤丸をクリックします。これでBreakpointを使って、実行を中断させる方法を学びました。次に移る前に、今設定した全てのBreakpointを解除して下さい。
さて、いよいよデバッグを開始します。今、プログラムは先頭の位置で中断されています。この位置は、コードエディターでBreakpointを簡易設定したのと同じ位置に、黄色の矢印として表示されていて、行が黄色で強調表示されています。
この強調表示された行が、次に実行される命令となります。つまり、まだ何も実行されていません。そしてこの、中断されている状態と、実行(デバッグ)を終了している状態を勘違いしないで下さい。実行を終了している状態では、一部のウィンドウが表示出来ません(自動的に閉じてしまいます)し、コードエディター上で黄色の矢印と行の強調表示もされません。
またデバッグ中に、コードエディターでコードを修正しても、今のデバッグ状態を継続したまま新しいコードを入力して試す事も出来ません。修正する時は、一度デバッグを終了します。コードを修正後にビルドし直して、再びデバッグする必要があります。
では、最初の命令を実行してみましょう。ステップ実行には幾つか種類があり、それぞれ次の様な特徴があります。
Step Into
次に実行する命令がサブルーチンコール(C/C++の場合は、関数やメソッドのコールも含む)の場合は、そのサブルーチンの先頭を次の中断位置とする。サブルーチンコールではない場合、次の中断位置は、大抵次の行の命令となる。
Step Over
次に実行する命令がサブルーチンコールの場合は、次の中断位置は、そのサブルーチンから戻ってきた次の命令(大抵今の中断位置の次の行にある)とする。サブルーチンコールではない場合は、Step Intoと同じ。
Step Out
現在サブルーチン内部を実行中の場合、次の中断位置は、そのサブルーチンから戻った(要は呼び出し元の)次の命令とする。もしメインルーチンでこれを実行した場合、どこかにBreakpointがなければ中断する事はない。
まずは基本のStep Intoです。最初の命令sbi ddrd,pd5
は、サブルーチンコールではないので、この状態でStep Intoすると、次の中断位置はラベルMainLoop
の次の行(ラベルは命令ではありません)のsbi portd,pd5
となりそうです。やってみましょう。それぞれのステップ実行は、Debugメニューの中にあります。
Breakpointと同様に、毎回メニューから操作をするのは面倒です。ここはショートカットキーやツールバーの出番です。ショートカットキーは、表示されたメニューに書いてあるので省略します。Debugツールバーは、次の様に表示されています。
このツールバーが見つからない人は、ツールバー領域で右クリックメニューから、Debugを選んでください。
ツールバーの各アイコンの上にマウスカーソルを移動すると、それぞれ意味が表示されますので、各アイコンの説明は割愛します。以下は、Step Intoを実行した結果です。
予想した通りの結果となりました。同様に今度はStep Overすると、次の中断位置はrcall DelayLoop
となりそうです。やってみましょう。
予想した通りの結果となりました。次の命令はrcall DelayLoop
なので、サブルーチンコールとなります。ここでStep Intoすると、次の中断位置はラベルDelayLoop
の次の行のldi r20,64
となりそうです。やってみましょう。
予想した通りの結果となりました。この先は、1秒を待機するループ処理です。折角サブルーチン内部に入ったのだから、Step Outを試してみたい所ですが、実際に自分で試した所、うんざりする程長時間待たされる結果となってしまいました。なので、Step Out機能は封印して、先に習ったBreakpointを使います。更についでに、このループの処理時間を計算させてみましょう。
まずProcessor Statusウィンドウで、Stop Watchの行の上で、右クリックメニューを表示し、Reset Stop Watchを選択します。
Stop Watchが初期化出来たので、次にサブルーチンからの戻り先にBreakpointを設定します。
今度はステップ実行ではなく、Continue(続けて実行)をします。もちろんメニューからではなく、ツールバーの同じアイコンを押しても問題ありません。
この操作は少し待たされると思います。が、Step Outよりもずっと早く終わります。
中断したら、Processor StatusウィンドウのStop Watchの値を確認します。
999,012.25us(マイクロをuと表記)となっています。1秒=1,000,000マイクロ秒ですので、約1秒経過している事が判ります。よって、処理の妥当性が証明されました。
次の命令はcbi portd,pd5
となっていますが、これもStep Intoします。
次の命令がrcall DelayLoop
なので、ここでStep Overすると、次の中断位置はrjmp MainLoop
となりそうです。やってみましょう。
これも少し待たされますが、ちゃんと次の行の命令で中断しています。rjmp
はジャンプ命令なので、次の実行はMainLoop
の先頭に戻る事になります。ここまでに大きな動作確認が出来ましたが、まだ確認出来ていないのは、Port Dの動作です。
ここで、Breakpointをさらに追加します。今中断している状態でrjmp MainLoop
にBreakpointを設定します。すると、この様になる筈です。
現在のPort Dの状態を、I/O
ウィンドウで確認しましょう。
Port Dの5番ピン(つまりNameがPORTDとなっているValue(値)を見て、最下位bitを0bit目とした時の5bit目)が0となっているので、Arduino
で書く所のLOW
となっています。ここでContinueすると、MainLoop
の先頭に戻り、sbi portd,pd5
を実行し、最終的に次のBreakpointであるcbi portd,pd5
で中断すると予想されます。sbi
命令は、指定されたI/O
の指定されたビットをセット(Set BIt)する命令です。つまり、ここでPort Dの5番ピンを1にします。これはArduino
で書く所のHIGH
です。やってみましょう。
中断したので、I/O
ウィンドウを確認します。
Port Dの5番ピンが1になりました。このままContinueすると、cbi portd,pd5
が実行されて、最終的に次のBreakpointであるrjmp MainLoop
で中断すると予想されます。cbi
命令は、指定されたI/O
の指定されたビットをクリアー(Clear BIt)する命令です。つまり、ここでPort Dの5番ピンを0にします。これはArduino
で書く所のLOW
です。やってみましょう。
中断したので、I/O
ウィンドウを確認します。
Port Dの5番ピンが0になりました。これでMainLoop
のコメントに書いた仕様(PD5をオン->1秒遅延->PD5をオフ->1秒遅延->最初に戻る)が、全て正しく動作する事が判りましたので、Simulator
でのテストを終了しましょう。
デバッグを終了するには、Stop Debuggingを選択します。もちろんメニューからではなく、ツールバーの同じアイコンを押しても問題ありません。
もし、ここまでの何処かで想定した動作とならなかった場合は、書いたコードに問題があります。大抵の場合、ミスタイプが原因です。例えばレジスタの名前を間違っている(例: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ボード)に変更します。この記事の最初でやった通りにして、もう一度プロジェクトのプロパティを開きます。そして、ToolのSelected debugger/programmerを、用意したプログラマー(この場合はSTK500)に変更します。InterfaceはISPに設定します。
変更を終えたら、プロジェクトのプロパティを保存してから閉じます。
もしMCUボードを自作して、新品で1度も使用していないMCU
を取り付けた場合、MCU
が内蔵オシレータを使って動作する設定となっているので、これを外部の水晶振動子を用いる様に、Fuse
ビットを書き換える必要があります。この作業手順は、新品のMCU
を使う最初の1回だけ必要です。製品のMCUボードを利用する方は、大抵設定済みMCU
が付属していると思います。まずは、お手持ちのマニュアル等を参考にして下さい。設定済みのMCUボードを利用する方は、ブレッドボードにLEDと抵抗を接続する手順に進んで下さい。未設定のMCUボードを利用する場合は、この手順を参考にして、ご自身で設定して下さい。
まず最初に知らなくてはいけない事は、データシートによると、出荷時のMCU
はデフォルトクロックソースとして内蔵オシレータ(8MHz)が有効で、CKDIV8
というFuse
がプログラムされている、と記述されています。この結果、出荷時のMCU
は1MHzで動作します。つまり、水晶振動子や外部オシレータをMCU
に接続しただけでは、そのクロック周波数では動作しないのです。以下に、新品のATmega328P-PU
のFuse
ビットとLock
ビットを、ICライターで読み出した結果を示します。
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 ConditionsがSlowly rising powerで、Start-Up Time from Power-down and Power-Saveが6CKで、Additional Delay from Resetが14CK + 65msに設定されている事も判ります。これらの値は、電源投入直後等(回路が不安定です)から、内蔵オシレータ起動をどれだけ待つか?を表しています。
次に自分のMCUボードです。XTAL1
ピンとXTAL2
ピンに16MHzの水晶振動子と22pFのセラミックコンデンサーからなる発振回路を接続しています。データシートの13.1 Clock Systems and Their Distribution
を参照すると、XTAL1
とXTAL2
ピンに接続したCrystal Oscillator
というパターンになります。このパターンと13.2 Clock Source
と13.3 Low Power Crystal Oscillator
と13.4 Full Swing Crystal Oscillator
を参照した結果、MCUボードをLow Power用に設計していないので、Full Swing Crystal Oscillator
が該当する事になります。つまり、13.4 Full Swing Crystal Oscillator
を参照して値を決める事になります。まずCKSEL[3:1]
(最下位ビット(LSB)の値が1となっている事に注意)の値は0b011となります。次に残っているCKSEL0
とSUT[1:0]
の値も決めます。このMCUボードは特殊用途ではないので、MCU
の起動タイミングをシビアにする必要もありません。つまり、基本的に出荷時動作(汎用設定)と同じ様な動作で問題はありません。よって内蔵オシレータの設定を流用して、Oscillator Source/Power ConditionsがCrystal Oscillator, slowly rising powerで、Start-Up Time from Power-down and Power-saveが16K CKで、Additional Delay from Resetが14CK + 65msである行を参照すると、CKSEL0
が1で、SUT[1:0]
が0b11と記述されています。また16MHzでそのまま動作させるので、このクロックを分周する必要はありません。これらの結果から、最終的に設定する値は、CKSEL[3:0]
が0b0111で、SUT[1:0]
が0b11で、CKDIV8
が1となります。
設定する値が決まったので、MCUボードとプログラマーとPCをそれぞれ接続し、通電します。IDE
に戻って、Available Toolsウィンドウからプログラマーを右クリックし、Device Programmingを選択します。
ハードウェアの準備で行った様にしてMCU
ボードと通信を行い、Fuse
ビットを読み出します。
LOW.CKDIV8のチェックを外します。そしてLOW.SUT_CKSELをExt. Full-swing Crystal; Start-up time PWRDWN/RESET: 16K CK/14 CK + 65 msに変更します。
変更した箇所のアイコンが、緑のチェックマークから、黄色の注意マークに変わります。この2か所以外に黄色マークがあった場合は、Readボタンを押して元の値に戻して下さい。LOWの値が0xF7となっているか確認したら、Programボタンを押して書き込みます。
Fuse
ビット変更に対するWarning(警告)ダイアログが表示されます。Continueボタンを押して継続します。以前に一度でもDon't show this warning againにチェックを入れていた場合は、このダイアログは表示されずに書き込みが行われますので、注意して下さい。
Fuse
ビットの書き込みが完了すると、黄色マークが緑マークになります。Closeボタンを押して、ダイアログを閉じます。もし書き込みに失敗した場合は、書き込む電圧が不安定である位しか思いつきません。自分の使っているプログラマーは、PC上で書き込んだ時のボードの電圧を確認する事が出来ます。以下は、今回Fuse
ビットを設定した時の、MCUボードの電圧です。
MCU
へのFuse
ビット設定が終わったら、MCUボードへの通電を止めます。
次は、ブレッドボード等を使用してLEDと抵抗を直列に接続し、それぞれの両端をMCUボードのPD5とGNDに接続します。この時LEDの極性に注意して下さい。もしLEDや抵抗をお持ちでない場合や、何を使えば良いのか判らない場合は、以下の説明を参考に購入 or 計算して下さい。
今回私が使用したLEDは、OptoSupply
というメーカー製のOSR5JA3Z74A
という3mmの赤色LEDです。OptoSupply
のLEDは、秋月電子通商で簡単に入手出来、データシートも公開(秋月電子通商の商品ページにリンクあり)されているので、良く判らないLEDを使うよりも使いやすいと思います。
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と抵抗をブレッドボードに配線しましょう。
電子工作のお約束には、赤は電源・黒はGNDというのがあり、これに従ってジャンプワイヤーを使っています。別に守らなくても電気的に問題はないのですが、パッと見て黒はGNDというのが理解出来ると、接続の際に極性を間違える事も少なくなると思います。それ以外の信号線については、赤・黒以外の自分の好きな色を使えば良いと思います。今回は、MCUボードのPD5とGNDに接続するので、上記の写真では、緑のジャンプワイヤーをPD5に、黒のジャンプワイヤーをGNDにそれぞれ接続します。MCUボードへの電力供給は、まだしないで下さい。
この様な感じで接続しました。写真ではプログラマーが既に通電されていますが、ここからMCUボードへの電力供給は行っていませんので、この時点ではMCU
は動作していません。
次にMCUボードへ電力供給を行います。私のMCUボードは、シリアル変換アダプターから電力供給を行うので、ここでシリアル変換アダプターをPCと接続します。プログラマーから電力供給したり、それ以外の外部電源を用いる方も、この時点から電力供給します。また、プログラマーもPCと接続して、ハードウェアが全て動作可能な状態として下さい。
ここからIDE
に戻ります。
これで漸く書き込める状態となりました。今までとは違って、今度はStart Without Debugging(デバッグ無しで実行)を選択します。今まで使ってきたDebugツールバーには、このアイコンはありませんので注意です。
このアイコンはStandardツールバーにあります。
多分最初から表示されていると思いますが、見つからない方は、ツールバー領域を右クリックしてStandardを選択して下さい。
では、書き込んで実行してみましょう。
動画では、大体1秒間隔でLEDが点滅している事が判ります。これでアセンブリ言語で記述する、ATmega328P-PU
のLチカプログラムが完成しました。
最後に、このプログラムが使ったMCU
上のメモリサイズを確認してみましょう。IDE
のSolution Explorerウィンドウで、Output Files配下のBlinkAsm.lssというファイルをダブルクリックして開きます。
このウィンドウが見つからない方は、以下の様にメニューを選択します。
開いたlssファイルの一番最後の行に移動します。
.csegの行はコードセグメント(要はFlash
です)に格納されるコードやデータを表しています。この場合、コードだけ34byte分使っています。.dsegの行はデータセグメント(要はSRAM
です)を表しています。今回はSRAM
にコードやデータ領域を作成していないので、コードもデータも0byteです。.esegの行はEEPROM
セグメントを表しています。これも同様に作成していないので、どちらも0byteです。結局、このプログラムはFlash
をたったの34byteしか使っていません。今回のコードと同様の処理をC/C++で記述すると、これよりもずっと大きなサイズを消費します。組み込み用に用いる小さなMCU
は、搭載しているメモリの量も小さい事が多く、C/C++で記述するとメモリに収まらない事も多いのです。試しにArduino
でBlinkスケッチを何も変更せずにコンパイルしてみましょう。
Flash
を928byte、SRAM
を9byte使っている事が判りました。アセンブリ言語で記述するメリットの一つに省メモリが挙げられるのは、この結果を見ても明らかです。勿論デメリットもありますから、結局はアセンブリ言語とC(出来たらC++も)の両方を使い分けられる様になる事が大事です。今後は、出来るだけアセンブリ言語とCの両方で学習したいと思っています。
補足ですが、今回作成したプロジェクトは、GitHubにアップロードしてあります。どうしても動作しない場合は、こちらを参考にして下さい。
2018/11/16
目次はこちら