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

開発したものとか。

C++を使うという事は?

あれこれと考慮した結果、C++を使います。理由はC言語アセンブリ言語に関しては、既に多数の解説があるからです。

しかし悲しい事に、組み込み開発でC++を利用しようとすると、猛烈に怒り出す方々が一定数います。これも良く考えると、そもそも本記事が業務用途(費用対効果を考えてメモリ容量の削減等の制約があります)を考慮する必要はないですし、ホビー用途でそんな事を言われる筋合いもありません。

AVRは小規模なMCUである事に加え、ハードもソフトも必要ならば全て自前で揃える事が出来るので、学ぶには最適な環境だと思っています。なので本記事では、C++を使うメリットと、何故C++を使うと怒られるのかを学ぼうと思います。

そうと決まったので、Atmel Studio 7.0に含まれるC++の機能を調査した所、大まかに以下の様な結果になりました。

  • GCC 5.4.0

    • C++ 14まで対応(実験的実装であるC++ 17を除く)
    • libstdc++がない

      注:GCCの最新(開発中を除く)は、現時点(2020/03/07)で9.2.0であり、これはC++ 17まで対応(実験的実装であるC++ 20を除く)

  • avr-libc 2.0.0

    • C++用の機能対応が不十分
    • 全くメンテナンスしてないと思われる機能が存在
    • ソースavr-libc-2.0.0.tar.bz2にパッチを適用しようとすると、エラーが出る箇所がある

プログラミング言語は常に最新の仕様を使いましょう、がモットーの私としては、このままでは問題点だらけです。

  • C++ 17を使いたい
  • C++の本当に使いたい機能(type_traits等)の殆どはlibstdc++の上に成り立っている
  • avr-libcを用いずに、共通ランタイム(crtxxxx.o)等を記述する自信も時間もない

これらを解決する為に、GCC 9.2.0とそのlibstdc++を含む最新ツールチェーンを自分で作成して、このツールチェーンをAtmel Studio 7.0に読み込ませる事で対応しました。

作成プラットフォームはmsys2で行い、ここで以下のツール・ライブラリ等を作成しました。

  • winpthreads-1.0mingw-w64-v7.0.0に含まれています)
  • binutils-2.33.1
  • gcc-9.2.0AVRlibstdc++対応の改造バージョン)
  • avr-libc-2.1.02.0.0ソースへのパッチ適用を諦め、最新subversionリポジトリから取得したソースに対するlibstdc++対応の大改造バージョン)
  • gdb-8.3.1Atmel Studio 7.0で使うだけなら不要)
  • avrdude-6.3Atmel Studio 7.0で使うだけなら不要)
  • avrtestwinavrに含まれています。Atmel Studio 7.0で使うだけなら不要)

記事のコンセプトを練るのに約1年、加えてここまでの環境を構築するのに半年以上を費やす事となり、進捗が大幅に遅れました。加えて、今後も更に問題が発覚した場合、その都度ツールチェーンを再構築する必要があります。2020/03/07現在、作成したlibstdc++の全ての機能が全てのAVRで動作するかは、確認していません(というか出来ません)。また AVRで利用出来ないlibstdc++の機能をどうするか? については、libstdc++のソースを出来るだけ改変しない様に、外部で対応する事にしました。具体例として、以下の様に対応しています。

  • avr-libc
    • include/unistd.h
      • read関数(externとして宣言されているが定義されていない)

        静的インライン関数として定義し、errnoENOTSUPを設定して、-1を返す様に改造

この結果、iostreamの機能はありますが、利用しても動作しません。他には、所謂インクルードガードを追加したり、extern "C"を追加したり等です。

補足:上記ツールチェーンの作成手順を公開しても良いのですが、何も読まないで「あれが動かない!直せ!」と怒り出す方々が一定数いて、この対応に苦慮する事が容易に想像出来るので、現時点では公開する予定はありません。とりあえず、まずは読み物だけと思って頂ければ幸いです

作成したツールチェーンは、どこか1つのフォルダーに纏めてインストールしておき、このフォルダーをAtmel Studio 7.0に追加します。まずToolsメニューからOptions...を選択し、Optionsダイアログを表示します。左のペインからToolchainを選択し、右のToolchains:Atmel AVR 8-bit (CPP language)に設定します。下のFlavours:Nativeしかありませんが、Add Flavourボタンを押して、表示されるダイアログに項目を設定してAddボタンを押せば完了です。以下の例では、Package NameG++9.2.0を、Package Base PathC:\AvrToolchain\binを設定しています。

f:id:neb4nebrin:20200308173752p:plain
新しいツールチェーンを追加

これで、準備が整いました。テストしてみましょう。

新しいプロジェクトを作成します。GCC C++ Executable Projectを選択して、適当な場所に名前はHelloWorldとして作成しました。デバイスATmega328Pを使いますが、今回はSimulatorを使います。

作成したら、まず最初にプロジェクトのプロパティを設定します。ProjectメニューからHelloWorld Properties...を選択し、プロパティを表示します。左のペインからAdvancedを選択し、右のToolchain Flavour:NativeからG++9.2.0に変更します。

f:id:neb4nebrin:20200308173944p:plain
ツールチェーンの変更

同様に左のペインからToolを選択し、Selected debugger/programmerSimulatorに設定します。

f:id:neb4nebrin:20200308174156p:plain
ツールの設定

最後も同様に左のペインからToolchainを選択します。上のConfiguration:All Configurationsに変更した後、下のAVR/GNU C++ Compiler配下のMiscellaneousを選択し、Other flags:-std=gnu++17と入力します。

f:id:neb4nebrin:20200308174308p:plain
ツールチェーンの設定

これでプロパティの設定は終わりです。

次にmain.cppを、以下の様にします。

#include <avr/io.h>
#include <string>

int main(void)
{
    std::string letters{ "Hello World!" };
    for (auto letter : letters)
    {
        GPIOR0 = letter;
    }
    return 0;
}

libstdc++をリンクする為に、敢えてstd::stringを利用している所がポイントで、これをビルドします。

ビルドが完了したら、まず最初に生成されたマップファイルを調べます。上から順に内容を確認していくと、以下の様に出力されています。

Archive member included to satisfy reference by file (symbol)

C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5\libstdc++.a(del_op.o)
                              main.o (operator delete(void*))
C:/AvrToolchain/lib/gcc/avr/9.2.0/avr5\libgcc.a(_exit.o)
                              C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5/crtatmega328p.o (exit)
C:/AvrToolchain/lib/gcc/avr/9.2.0/avr5\libgcc.a(_copy_data.o)
                              main.o (__do_copy_data)
C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5\libc.a(malloc.o)
                              C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5\libstdc++.a(del_op.o) (free)
C:/AvrToolchain/lib/gcc/avr/9.2.0/avr5\libgcc.a(_clear_bss.o)
                              C:/AvrToolchain/lib/gcc/avr/9.2.0/../../../../avr/lib/avr5\libc.a(malloc.o) (__do_clear_bss)

ATmega328Pavr5に属するので、正しい各種ランタイムが選択されている事が判ります。

次は実行してみます。GPIOR0 = letter;の行にブレークポイントを設定し、GPIOR0の値の変化を確認した所、正しく文字列Hello World!の先頭'H'から末尾'!'std::stringではC文字列の終端文字'\0'を利用しません)まで順に出力されている事が判ります。

どうやら、各種ツールチェーンの作成結果は良さそうに見えますので、次回からはこの環境を用いて、色々と試してみたいと思います。

2020/03/08


目次はこちら

GNU Guix System 日本語インストール方法

gccの最新版やクロスコンパイラ等をコンパイルする環境として、GNU謹製のディストロであるGuix Systemをインストールしてみました。ただ、そのまま.isoからインストールすると、日本語に設定したつもりでも日本語が化けてしまいます。そこで、マニュアルの別の場所に記載されている様に、コンソールから

$ guix install font-adobe-source-han-sans

と入力して、後から追加インストールを行っても良いのですが、これだとシステム全体にインストールが行われる訳ではないので、例えばログインマネージャーの表示は化けたままですし、ほかのユーザーにスイッチした場合も、上記コマンドを再度入力する必要があります。

これを回避するには、システムのインストール時に必要なパッケージを予め追加しておくのが、一番簡単です。

注意

現在のGNU Guix Systemは、UbuntuFedora等の比較的使いやすいディストロではなく、Linuxに慣れた人向け(というよりも開発者向け)です。GUIで設定出来る項目は非常に少なく、システムのメンテナンスには、CUIのコマンドを駆使する必要があります。よって、本記事ではSlackware等で環境を構築出来る方が対象となります。パーティションの設定方法等は、他の環境と同じ考え方なので、全て割愛させて頂きます。また、バージョンが更新されている場合がありますので、そこは適時読み替えて下さい。

作業した環境

Guix System のインストール

Graphical install using a terminal based interfaceを選択して、以下の画面が表示されるまでインストーラを進めます。

f:id:neb4nebrin:20190802181859p:plain
インストール準備完了画面

ここでCtrl + Alt + F3を押下し、別のターミナルを呼び出します。表示されている通りEnterを押し、キーマップを設定(ここでは日本語106キーボード)します。

f:id:neb4nebrin:20190802182314p:plain
別ターミナルを開く

エディターを起動して、/mnt/etc/config.scmを編集します。ここではviを使っていますが、マニュアルによるとGNU nanoがお勧めらしいです。

追加したいパッケージは、私の場合は以下の通りです。基本的にマニュアルに注意点が記載されていたパッケージと、システム管理で頻繁に使うコマンドとなります。必要に応じて差し替えて下さい。

  • 日本語フォント
    • font-adobe-source-han-sans
    • font-wqy-zenhei
  • xset
  • xlsfonts
  • fontconfig
  • gcc-toolchain
  • file
  • vim

このファイル(config.scm)に、以下の様な記述があります。

(packages
    (append
        (list (specification->package "nss-certs"))
        %base-packages))

この部分を以下の様に書き換えて、保存してエディターを終了します。

f:id:neb4nebrin:20190802182417p:plain
構成情報編集

Ctrl + Alt + F1を押下し、元のインストーラの画面に戻ります。表示されている構成情報は、編集結果を反映していませんが、OKを押してこのまま続けます。

インストーラが正常に終了し、再起動を求められますが、実機と違ってブートメディアが自動でイジェクトされないので、何らかの方法(例:再起動ではなく、一度電源OFFする)で除去します。その後、再起動(もしくは電源ON)をした直後の、Guix Systemのログインマネージャーの画面を以下に示します。

f:id:neb4nebrin:20190802182514p:plain
GNOMEログインマネージャー

ちゃんと日本語が表示されています。このままログインしてみます。

f:id:neb4nebrin:20190802182553p:plain
ログイン

大丈夫そうです。

f:id:neb4nebrin:20190802182634p:plain
GNOMEデスクトップ

GNOMEデスクトップも日本語で表示されていますので、これで完了です。

上手くいかない場合は、config.scmの修正ミスが考えられます。この場合、大抵インストーラの画面にメッセージが表示されるので分かり易いと思います。が、現バージョンだとメッセージが表示された後に、上手くインストールプロセスの復帰が出来ないので、毎回仮想マシンをリセットする羽目になります。加えて、毎回config.scmを手作業で書き換える必要があります。config.scmを、仮想マシンではなくホストOS側で管理出来ると、少しは楽になるかもしれません。

インストール後のセキュリティアップデートや、日本語の入力システムの構築については、また別の話となりますので、ここでは割愛します。


以上

まずは試してみましょう

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

まず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


目次はこちら

ハードウェアの準備

Atmel Studio 7.0で行うAVR開発の一連の記事では、MCUとしてATmega328P-PU(PDIP-28)を用います。理由は単純で、Arduino Uno等で広く使われているので、MCUの機能を理解する事が、他のAVRよりも早いと思われるからです。加えて価格も入手性も全く問題が無く、地方の方でも通販で簡単に入手する事が出来ます。価格が安いという事は、例え実験で破壊したとしても、あまりダメージを受けない、という事です。学習に失敗は付き物で失敗はする物、という気持ちで気楽に扱いたいと思います。

しかし、Arduinoでもそうでしたが、実験でFlash ROMEEPROMを頻繁に書き換えると、書き換え上限(ATmega328Pでは、データシートによるとFlashが1万回、EEPROMが10万回)に達してしまった後の動作保証はありません。そこで、通常は出来る限りIDESimulatorを用います。実際にハードウェアを使わないといけない状況になった場合(例:シリアル通信を行う)に初めて、物理的なハードウェアを利用します。従って、最初の章の方ではSimulatorだけで事足りる事が多く、後半に進むに従ってSimulatorと用意したハードウェアの両方を使う事になります。そういった理由で、最初からハードウェア全てを、無理に準備する必要はありません。

  1. MCUボード

    MCUボードを用意する手段はいくつかあります。

    • 既存のMCUボード(orキット)を利用する

      ATmega328P-PUを用いたMCUボード(キット)は、探すと結構見つかります。ISPICSPとも言う。以下ISPで統一)の端子が付いていれば良いです。沢山あるので一例(私は使った事がありませんが)を挙げると、以下で買えると思います。

      秋月電子通商 -

      ATmega168/328マイコンボードキット

    • Arduino(or互換機)を流用する

      ATmega328P-PUが搭載されている入手性の良いArduinoは、Arduino Uno Rev.3と、その互換機となります。Arduino Uno Rev.3にはISP端子があるので問題ありません。互換機では、ISP端子はあったりなかったりします。これらは例を挙げるまでもなく、普通に通販で買えると思います。

      Caution: この選択肢を選んだ場合、Arduinoが用いるブートローダーを破壊してしまう事になります。よって元に戻す(Arduinoに)つもりがあるけれど、自分で戻す方法が判らないのであれば、この選択肢は諦めた方が良いと思います。

      Note: Arduino Uno Rev.3には、PCとのUSB-シリアル通信用にATmega16u2-MUが搭載されていて、こちらにもISP端子があります。間違えない様、注意が必要です。

    • 自作する

      これが一番融通が利く上に、自身の経験値も上がる、良い組み合わせだと思います。私は、自作を選びました。但し、保護回路等が全く追加されていないので、ちょっとした不注意で簡単に壊れてしまいます。が、壊れたら直せば(交換等)良いのです。

      下の写真が、ユニバーサル基板に自作した実験用MCUボードです。5V/16MHzで動作します。実験ボードなので、クロック回路・リセット回路(Arduinoとして使う場合の書き込み用)・電源On表示用LED回路・パスコンがあるだけで、後は端子と配線だけです。

    f:id:neb4nebrin:20181103170646p:plain
    自作MCUボード

    Arduino Uno Rev.3と並べると、これぐらいの大きさです。

    f:id:neb4nebrin:20181103170920p:plain
    Unoとの比較

    Note: 今になって考えると、水晶振動子をピンソケットに刺す形にすれば良かったと後悔しています。

  2. プログラマー

    MCUに対して、作成したプログラムやデータ等を書き込む装置です。Atmel純正のAVR ISP mkIIは、秋葉や通販で気軽に買えるとは言えません。Atmel純正のAtmel-ICEを利用する場合、32bitのAVRを扱うならともかく、ATmega328P-PUを扱うには高価で機能がオーバースペックです。従って、何とか他のプログラマーを探す必要があります。これは、あまり選択肢がありません。

    • AVR ISP mkII互換機を用いる

      IDEとは、最も親和性が高い組み合わせだと思います。Atmel純正品は入手性に難がありますが、互換機は探せばいくつか見つかります。が、中国製が多く、マニュアルが読めない等の問題もあります。私は使った事がありませんが、以下で買えるみたいです。

      千石電商 -

      Seeed Studio Atmel AVRISP2.0 STK500 USB ISPプログラマー

    • IDEと協調動作出来るプログラマーを用いる

      Atmelが発売したスターターキットに、STK500(廃番)がありました。このSTK500と同様の書き込み手順を用いるプログラマーは、IDEと協調動作が出来ます。このタイプは、あまり多くはありませんが、探すと見つかります。私もこのタイプを使っています。以下で買えます。

      スイッチサイエンス -

      Pololu USB AVRプログラマ v2.1

      Pololu USB AVRプログラマ v2 (廃番ですが、本記事で使っています)

      下の写真が、Pololu USB AVR Programmer v2です。意外と小さくて、びっくりしました。ISP用の端子に加えて、USB-シリアル変換用の端子も付いています。PololuのHPはこちら

      f:id:neb4nebrin:20181103171229p:plain
      Pololu USB AVR Programmer v2

  3. USB-シリアル変換アダプター

    以後、シリアル変換アダプターと記述します。Arduinoデバッグするのに、シリアル通信モニタを利用した方は多いと思います。IDEを用いた開発も同様で、MCUISPだけだと、デバッグするのが困難な場合が殆どかと思います。なので、なるべく早い段階でMCUUSART(シリアル通信)機能を学習し、デバッグモニタとして使える様にしたいと思います。これは、その時から利用します。Arduinoへの書き込み用を持っている方は、それをそのまま利用して下さい。それ以外用でも、USARTをカスタマイズすれば使えると思います。Arduino用は、探せばいくつも見つかりますが、動作電圧だけは注意して下さい。使用するMCUボードの電圧と合わせます。私が使っているタイプは、以下で買えます。

    スイッチサイエンス -

    FTDI USBシリアル変換アダプター(5V/3.3V切り替え機能付き)

    下の写真が、私の使っているアダプターです。3.3V/5Vの両対応なので、省電力運用するArduinoを扱うのにもピッタリです。

    f:id:neb4nebrin:20181103171413p:plain
    シリアル変換アダプター

ハードウェアが用意出来たら、次はIDE側でもハードウェアの準備をします。お使いのプログラマーに記載されている作業手順に従って、進めて下さい。

以下は、IDE側の準備を完了した後のAvailable Toolsウィンドウです。私の環境では、STK500(COM4) Program onlyと書かれている箇所が、プログラマーとなります。Simulatorは、最初から表示されています。

f:id:neb4nebrin:20181103171602p:plain
プログラマーの準備

このウィンドウが見当たらない場合は、以下の画像の様に、Available Atmel Toolsメニューを選択すると表示されます。

f:id:neb4nebrin:20181103171716p:plain
Available Toolsメニュー位置

シリアル変換アダプターは、IDE側では特に何も準備する必要はありません。

これでIDEとハードウェアの両方の準備が終わりましたので、双方を接続して簡単な接続テストをしてみましょう。

下は、私が自作したMCUボードをPololu USB AVR Programmer v2と接続し、スイッチサイエンス製シリアル変換アダプターから給電した写真です。当たり前ですが、プログラマーとシリアル変換アダプターは、PCとも接続しています。

f:id:neb4nebrin:20181103171842p:plain
MCUボードの接続

実はこのプログラマー、付属の設定ユーティリティーを使って、ターゲット(MCU)ボードへの電源供給を行う事が出来ます。が、自作したMCUボード側で、ISP端子・シリアル端子もしくはそれ以外のどの端子から給電するか?という選択機能がないので、自分でどこから給電するのか決めなくてはなりません。結論として、シリアル変換アダプターを接続しっぱなし(USARTを使わなくても)にして、常にここから給電する事としました。

ハードウェアを全て接続し、通電を確認したら、IDEAvailable Toolsウィンドウから、プログラマーを右クリックして表示されるメニューから、Device Programmingを選択します。

f:id:neb4nebrin:20181103172125p:plain
Device Programmingメニュー位置

すると、以下の様なDevice Programmingダイアログが表示されます。

f:id:neb4nebrin:20181103172248p:plain
Device Programmingダイアログ:初期状態

このダイアログのToolを、お使いのプログラマーに設定します。私の環境では、STK500(COM4) となります。DeviceATmega328Pで、InterfaceISPを指定します。

f:id:neb4nebrin:20181103172358p:plain
Device Programmingダイアログ:ツール指定

Applyボタンを押すと、プログラマー経由でMCUの情報を読み出し、ダイアログが以下の様になります。

f:id:neb4nebrin:20181103172616p:plain
Device Programmingダイアログ:Interface Settings

左のペインの選択を変える事で、このMCUの各種状態を知る事が出来ます。

f:id:neb4nebrin:20181103172742p:plain
Device Programmingダイアログ:Device Information

MCUを消去したり、FlashEEPROMに対するHexファイルの読み書きをしたり、

f:id:neb4nebrin:20181103172905p:plain
Device Programmingダイアログ:Memories

Fuseビットを読み書きしたり、

f:id:neb4nebrin:20181103173014p:plain
Device Programmingダイアログ:Fuses

Lockビットを読み書きしたり、

f:id:neb4nebrin:20181103173120p:plain
Device Programmingダイアログ:Lock Bits

ELFファイルの読み書きも出来ます。

f:id:neb4nebrin:20181103173218p:plain
Device Programmingダイアログ:Production File

もし上手く動かない場合は、プログラマーMCUボードに問題があると思います。まずは、プログラマーを接続したCOMポート番号(COMx)がWindowsで認識出来ているか?認識したCOMポート番号とIDEに設定したプログラマーのCOMポート番号が合っているか?を確認して下さい。Windowsで認識出来ていないならば、プログラマーを再インストールして下さい。COMポート番号が合っていないならば、IDEAvailable Toolsウィンドウ上でプログラマーを右クリックし、表示されたメニューからRemove(削除)して、再設定して下さい。それ以外ならば、それぞれの製品のFAQ等を読んで対処して下さい。

ここまで終われば、ソフトウェアとハードウェアの両方の基本的な準備は完了です。

2018/11/03


目次はこちら

ソフトウェアの準備

Atmel Studio 7.0で行うAVR開発の一連の記事では、IDEとしてAtmel Studio 7.0(以後IDEと表記)を使用します。Microchip(旧Atmel)のHPから無償でダウンロード出来ます。PICの開発環境とは違い、有償版はないので、いつでも全ての機能を利用する事が出来ます。MicrochipのHPはこちら

HPが更新されてしまい、記事が無用とならない様に、具体的なDLリンクへのページ遷移は省略しますが、HP上の検索ボックスで "Atmel Studio 7" を検索すると見つけられると思います。

Note: MCU(参照:ハードウェアの準備:別途記述)としてATmega328P-PUを用いるので、HP上の検索ボックスでATmega328Pの製品ページを検索し、Documentの様なリンクを辿り、ATmega328Pのデータシートもついでに入手しておきます。データシートには、Summaryと呼ばれる簡易版がありますが、こちらではなく完全版を入手します。

Atmel Studio 7.0インストーラ(Webインストーラでもオフラインインストーラのどちらでも構いません)をDLし終わったら、早速インストールします。

Note: Atmel Studio 7.0は、Microsoft Corp.Visual Studio IDEをベースに開発されているので、WindowsVisual Studioを使ったことがある方は、一目で大まかな機能が利用出来ると思います。しかしVisual Studioを一度も使ったことが無い方は、何度もMicrosoft Corp.のHPで、Visual Studioの使い方を学ぶ必要があるかもしれません。Visual StudioのHPはこちら

インストールが終わったら、早速起動します。そしてソフトウェアのアップデートを行います。アップデートは、以下の画像の様に、Extensions and Updatesメニューを選択します。

f:id:neb4nebrin:20181025165556p:plain
アップデートメニュー位置

すると、以下の様なアップデートダイアログが表示されます。

f:id:neb4nebrin:20181025165910p:plain
アップデートダイアログ初期表示

左のペインからUpdatesを選択します。

f:id:neb4nebrin:20181025170129p:plain
アップデート可能な機能拡張一覧

ここに表示されている機能拡張は全て更新し、IDE常に最新の状態にして使用して下さい。更新は、IDEを終了しないと始まらないものがあるので、画面に表示されている指示に従って下さい。注意: 上の画像で表示されているMarkdown Mode機能拡張は、標準ではインストールされていないので、表示されません。ご自身のダイアログに表示されている機能拡張で読み替えて下さい。

IDEや機能拡張の更新が終わったら、追加で必要と思われる機能拡張をインストールします。インストーラのバージョンにより、何が最初からインストールされているのか判らない為、私が現在インストールしている機能拡張リストを以下に示しますので、これを参考に必要であればインストールして下さい。インストールするにはIDEを再起動して、再びExtensions and Updatesメニューを選択します。

  • Annotated Assembly File Debugger
  • Atmel Kits
  • Atmel Software Framework
  • Atmel Start
  • AtmelToolchainProvider
  • Data Visualizer Extension
  • Doxygen Integrator
  • FreeRTOS Viewer
  • GdbConsole
  • Indent Guides
  • LiveWatch
  • LUFA Library
  • Markdown Mode
  • MemoryLogger
  • Microchip Gallery
  • Microsoft Advertising Framework (Windows開発用ですが、アンインストール出来ません。)
  • Microsoft Store Services Engagement Framework (Windows開発用ですが、アンインストール出来ません。)
  • Terminal for Atmel Studio
  • Visual Assist for Atmel Studio
  • Visual Studio Extensions for Windows Library for JavaScript (Windows開発用ですが、アンインストール出来ません。)

注意: これら機能拡張の一覧は、私の現時点の環境での一覧であり、私の必要に応じて追加・削除しています。今後の一連の記事で、必ず全て使用する訳ではありません。

機能拡張のインストールが終わったらIDEを再起動して、Device Pack Managerを起動して、各デバイスの情報(定義情報)を最新に更新します。これを行わないと、定義情報にバグがあった場合、修正されずにそのまま利用する事を意味します。以下の様に Device Pack Managerメニューを選択します。起動には、Windowsの管理者権限が必要です。

f:id:neb4nebrin:20181025170455p:plain
バイスパックマネージャーメニュー位置

起動したら、以下の様なウィンドウが表示されます。全画面で表示されますが、大きすぎるのでウィンドウを縮小しました。

f:id:neb4nebrin:20181025170713p:plain
バイスパックマネージャー初期状態

Check for Updatesボタンをクリックし、アップデータをチェックします。アップデータがある場合は、Installボタンをクリックして表示されるメニューから、Install all updatesを選択します。基本的に最新のデバイス定義を用いるので、全ての更新が終わったら、それぞれの古いバージョンはUninstallして構いません。全ての作業が終わったら、Device Pack Managerウィンドウを閉じて下さい。

f:id:neb4nebrin:20181025170834p:plain
バイスパックマネージャーアップデート

これで、基本的なソフトウェア環境が出来ました。今後IDEを起動する度に、更新のチェックが自動的に行われ、更新があればIDEウィンドウの右上に通知されます。

f:id:neb4nebrin:20181025171023p:plain
更新通知アイコン

この通知アイコンが強調表示されると利用可能な更新があるので、この通知アイコンをクリックして表示される通知ウィンドウ(以下の写真:通知なしですが)を読んで、指示に従って更新して下さい。

f:id:neb4nebrin:20181025184557p:plain
通知ウィンドウ

2018/10/25


目次はこちら

はじめに

Atmel Studio 7.0で行うAVR開発の一連の記事では、Atmel(現:Microchip)が提供している無償のIDE(Integrated Development Environment:統合開発環境)と、STK500プロトコルを備えたプログラマーMCUへの書き込み機)を用いて開発・デバッグ・プログラムを行います。avrdudeは使いませんので、予めご了承ください。今後、必要に応じて(例:32bit-AVRをJTAGデバッグしたい等)、Atmel-ICE等も視野に入れていきたいと思います。

私がこのカテゴリで記事を書こうと思ったのは、あまりにもAVRに関する日本語文献が足りなかったからです。Arduinoの入門書は数多くあれど、AVRの入門書は数少なく、また酷いのは10年以上前に刊行されて、それが今でも改版されずに出版されていることでした。

Atmel Studio 7.0で行うAVR開発の一連の記事は、要は簡単に言うと、私の学習ノートです。教科書や副読本を読んで、理解した事を纏め、後から見直せる様に残すものです。この先きっと理解が進むにつれて、古い記事を直す必要に迫られると思います。その時に新しい記事を書いてパッチを当てるのではなく、古い記事を更新する事を選びました。常に新しい記事だけ読めば良い訳ではない事をご理解下さい。

2018/04/11


目次はこちら

Atmel Studio 7.0で行うAVR開発

目次

  1. 初めに
  2. 準備
    1. ソフトウェアの準備
    2. ハードウェアの準備
    3. まずは試してみましょう
  3. C++での開発
    1. C++を使うという事は?

2020/03/08 更新