AVR Libc Home Page | ![]() |
AVR Libc Development Pages | ||
Main Page | FAQ | Library Reference | Additional Documentation | Example Projects |
このプロジェクトはsimple projectの基本的考えを広げ、LEDをPWM出力でコントロールしました。しかし、LED輝度調整のためにいくつかの手段を追加しています。これはavrのたくさんの基本コンセプトを取り入れ、ゴールを達成するものです。
このプロジェクトを理解すれば、simple projetctも完全に理解できるでしょう。さらにAVRマイクロコンピュータの基本的なハードウェアについても理解を深められるでしょう。
このデモは、STK500に同梱されているATmega16を使って行われます。外部に必要なのはADCに接続する可変抵抗器だけです。これは10-pinリボンケーブルでportAコネクタと接続します。可変抵抗器の両端はpin9(GND)とpin10(Vcc)につなぎ、可動端子はpin1(ポートA0(PINA1))につなぎます。できればμF程度のバイパスキャパシタをpin1-pin9間に置く方がいいです。
Setup of the STK500
色が付いたパッチケーブルはいろんな内部接続に使います。STK500に付属のたこ足ケーブルは(足が)4本しかないので、このデモについてはあと2本余分に必要です。??2本目の黄緑の追加ケーブルがテーブルの合間に見えています。
代わりに、JTAG ICE kit用の"イカ足"ケーブルを使うこともできます。
The coloured patch cables are used to provide
various interconnections. As there are only
four of them in the STK500, there are two
options to connect them for this demo. The
second option for the yellow-green cable
is shown in parenthesis in the table. Alternatively,
the "squid" cable from the JTAG
ICE kit can be used if available.
※この項、訳に自信なし。
Port | Header | Color | Function | Connect to |
D0 | 1 | brown | RxD | RXD of the RS-232 header |
D1 | 2 | grey | TxD | TXD of the RS-232 header |
D2 | 3 | black | button "down" | SW0 (pin 1 switches header) |
D3 | 4 | red | button "up" | SW1 (pin 2 switches header) |
D4 | 5 | green | button "ADC" | SW2 (pin 3 switches header) |
D5 | 6 | blue | LED | LED0 (pin 1 LEDs header) |
D6 | 7 | (green) | clock out | LED1 (pin 2 LEDs header) |
D7 | 8 | white | 1-second flash | LED2 (pin 3 LEDs header) |
GND | 9 | unused | ||
VCC | 10 | unused |
Wiring of the STK500
下の写真は、もう1つの配線案です。LED1は接続されていますが、SW2は接続されていません。
Wiring option #2 of the STK500
このデモではATmega16の代わりにATmega8や、その後継であるATmega48/88/168を利用することもできます。これらのコントローラはポートAを持っていません。ADCはポートAではなくポートCに、OC1AはPD5ではなくPB1に置かれています。
このため、上記の結線図はPB1をLED0ピンに、PD6は未結線に変更されています。STK500を使う場合はジャンパケーブルの1本をこのために使います。その他のポートDピンはATmega16と同じ結線としてください。
STK500スターターキットを使わない場合は、(LEDのカソード側をLED接続ピンにつなげた後)、LEDの他端(アノード側)を抵抗を介してVccにつないでください。また、プッシュボタンの両端はそれぞれ入力ピンとGNDに接続してください。(プログラムにより)プッシュボタンのために内蔵プルアップが有効にされますので、外部にプルアップ抵抗を接続する必要はありません。
Makefile 内の MCU_TARGET
マクロを適切に設定してください。
このデモにおけるフラッシュROMとSRAMの消費はATmega48でも充分な程度少ないものです。それでもATmega16を使う最大の理由はJTAG経由でデバッグが可能になる点です。また、ATmega16はSTK500に付属しています。
これ以下の説明の中のポート・ピンなどの名称は、ATmega16に合わせて書かれています。
PD6 は内部クロック(をプリスケーラとカウンタで分周したクロック)で点滅されます(およそ10msec)。PD7は1秒に1回点滅します。
PD0 と PD1 はUART I/Oとして割り当てられており、デモキットとPC間の接続に使われています。通信条件は9600bps、8bit-data、Non-Parity、Stopbit=1です。デモアプリケーションはシリアルポートへの送信および受信を行います。
PD2 〜 PD4 は入力として設定され、シリアルポートからの命令がなければアプリケーションをコントロールします。PD2をGNDにショートするとPWM値(Duty比)を減少させ、PD3をショートすると増加させます。
PD4はGNDに落とされている間は、チャンネル0のAD変換が内蔵クロックによる周期に従って開始され、その結果をに従いPWM値(Duty比)も変化させます。これによりLEDの明るさはPC0アナログ入力に追随します。STK500上のVArefはVccと同じ値にしてください(※Vccに接続)
シリアルコントロールモードの間は、'r'が送られるとウォッチドッグ機能がデモされます。これはWatchdogクリアを行わない無限ループを回すもので、数秒後にはWatchdogがMCUをリセットします。この状況(Watchdogによりリセットされた)は、スタートアップにてMCUCSRレジスタを読み出してみることで確認できます。(※MCUCSRレジスタのWDRFビットがセットされている)
現在のPWM値は3秒間変更指令がなければEEPROMセルにバックアップされます。
再起動時、もしそのEEPROMセルが意味のある(消去後の初期値などではない)値を保持していれば、それをPWMの初期値に使います。
これは電源を落としても最後の設定値を保持していることになります。PWM値変更時すぐにEEPROMに書き込むのではなく、タイムアウト後のみ書き込むことで、EEPROMへの書き込み頻度を落としています。
この勝では個々のコードのパーツについて説明します。
source code を番号を振ったパートに分割し、続くサブセクションでそれらを説明します。
可読性や移植性を改善するため、いくつかのプリプロセッサマクロが定義されています。
最初のマクロは、LEDを接続するIOピンを定義しています。これはある種の「mini-HAL」(Hardware Abstraction Layer、ハードウェア抽象化層)を提供しています。これによりいくらか接続が変更されても、コード内部を変更する必要はなく、先頭部分をちょっと変更するだけで済みます。
PWM出力ピン一時対はハードウェアに依存することに注意してください。そのため(デバイス毎に決まってしまい)簡単には変更できません。新しい世代に属するATmega48/88/168コントローラーでは、レジスタ番号とビット名をここのマクロで変更し、ATmega8/16と互換な形式にして、移植性を高めてあります。
F_CPU
は、コントローラのCPUクロック周波数を表すマクロです。このデモプロジェクトでは、デフォルトの補正された内蔵1MHzRC発振器を使います。<util/delay.h>
で提供される関数を使うときに、F_CPUはこのヘッダファイルのインクルードより前に定義されなければなりません。
残りのマクロはソースコード内の自身のコメントを保持しています。マクロTMR1_SCALE
は、タイマ1のポストスケーラー設定値の計算のために、プリプロセッサの使い方とコンパイラの定数表現計算を示しています。??
計算式は少々複雑ですけど、マクロを使うことで、いちいち定数類を再計算をすることなしに自動的に新しいターゲットのソフトクロックやCPUシステムクロックに合わせることができます。
intflags
構造体はメモリ内にビット値を納めるデモとなっています。
各割り込みサービスルーチンはこの構造体内の1つのビットをセットします。アプリケーションのメインループ側はこれらのビットをモニタして、(割り込みがかかったことを知り)適切な処理をします。
割り込みルーチンとメインループとの間でやりとりする変数には全て volatile 属性をつけてください。
変数ee_pwm
は、古典的C言語的な変数とは異なります。これはlvalueでも表現式でもありません。??
代わりに、
is not a variable in the classical C sense that could be used as an lvalue or within an expression to obtain its value. Instead, the
__attribute__((section(".eeprom")))
↑は、これが EEPROM sectionに属していることを示します。このセクションはコンパイラが個々の変数をEEPROMに置くことを示すために使われます。コンパイラは最初の変数が割り当てられたことを追っかけて、通常、Makefileがこれらの初期値を(FLASHに納めるプログラムとは)分離された load file (この場合はlargedemo_eeprom.*
※1) に納めます。このファイルはEEPROMの初期値(※2)を決めるのに使われます。
※1 このloadfile名はmakefileの設定によって異なります。mfileが生成するMakefileの場合は、largedemo.eep
になると思われます。
※2 このloadfileが決める初期値は、AVRデバイスへの書き込み時に使われます。当然ですがリセット後の初期値とはなりません。
実際のEEPROM IOは手動で実行されなければなりません。??
※ライターで*.eepファイルを明示的に書き込まなければいけないという意味??
The actual EEPROM IO must be performed manually.
その他のセクション使用例として、変数 mcucrが
.noinit セクションに置かれています。これはアプリケーションスタートアップで
mcucrがクリアされるのを防ぐためにあります。※
mcucsr = MCUCSR;
が、スタートアップより前に実行される.init3セクションにあるため
タイマ1オーバーフロー割り込みのISR
マクロはソフトウェアクロックを生成します。タイマ1がPWMを走らせる間、PWM周期に従ってオーバーフロー割り込みハンドラも呼ばれます。そこでTMR1_SCALE
値を使って内部ソフトウェアクロックをさらに分周し(※TMR1_SCALE回に一度処理をさせる)ます。ソフトウェアクロックが立ち上がるとき、intflagsのtmr_int ビットフィールドがセットされ、残りの処理はメインループに任されます。
ADC割り込みISRマクロは、ADC変換結果値を取得してADC割り込み停止し、新しい値が得られたことを adc_int bitfieldを介してメインループに伝えます。ADC割り込みは必要ない間は禁止にされています。ADCはアイドルモードでのSLEEP命令(ADC目的以外のSLEEP含む)の実行でも起動されるからです。
(不要なADC起動を抑止する)もう一つの方法は、ADC機構を完全にオフにしてしまうことです。しかしこれはADCの起動時間を長くしてしまいます。このアプリケーションではたいした問題ではありませんが。
関数handle_mcucsr()
は、特別な目的のため2つの__attribute__
宣言を持っています。最初の指定は、コンパイラにこの関数のコードを.init3セクションに置くよう指示します。こうして、この関数はアプリケーション初期化シーケンスの一部となります。
これは(立ち上がり時に)リセットの原因が何であるか(パワーオンリセットかウォッチドッグリセットか)を、立ち上げ時できるだけ早期に知るために行われています。
今回のリセット要因を評価する前に既に次のリセットがかかってしまう可能性のある時間帯は短時間しかありません。これはさらにレジスタの値を複製する変数mcucsr
が .noinit セクションに置かれなければならない理由にもなります。そうしなければデフォルトの初期化ルーチン(.init3の後に実行される)がこの変数を初期化してしまうからです。
初期化コードはCALL/RETを使って呼び出されていません。他のプログラムと一つながりになっています。コンパイラは通常関数につけるプロローグ・エピローグを排除しています。これはnaked アトリビュートによってなされます。文法的にはhandle_mcucsr()
は関数ですが、コンパイラはスタックフレームを操作せず、RET命令も負荷しない形でコードを吐き出します。
※初期化のこの段階ではまだスタックポインタの設定がされていないのでスタックが使えないため
関数ioinit()
は全てのハードウェア初期設定を集めたものです。関数の最後の部分は、eeprom_read_word()
の引数に使えるEEPROMアドレスを保持する変数ee_pwm
の使い方のデモとなっています。
つつく関数は UARTの文字・文字列出力を担当します。UART入力はISR(割り込みハンドラマクロ)で扱われています。ここには2つの文字列出力関数があります。printstr()
とprintstr_p()
です。後者の関数はprogram memory内の文字列を得て送信します。
両関数とも 改行文字を復帰改行文字に変換します。これにより(改行をしたければ)ソースコード内では単純に\n
を送ればいいことになります。
関数set_pwm()
は新しいPWM値をPWM装置に伝える働きをします。
値が変更された時に、新しいPWMのDUTY比の%単位の値がシリアル結線により伝えられます。現在の値は利用できるPWM機構(OCR1Aレジスタ)にコピーされるので、その後PWM値は%値の計算に利用できます。※変数newを破壊してもいい
DUTY比の計算を簡単にして浮動小数点演算をなくすために、PWM値の最大値は1023ではなく1000に制限しています。これにより単純に10で割ることで%単位の値が出せます。人間の目の性質により、PWM値1000と1023の差異は見分けられません。
main()の先頭で、変数 mode が宣言され、これにより現在の動作モードが保持されます。ソースの可読性をあげるため 列挙型enumを利用しています。
デフォルトではコンパイラは列挙型定数の型を int 型にしますが、 packed 属性宣言子をつけることで可能な限り小さい整数型を割り当てるようにコンパイラに指令することができます。(ここでは8-bit整数を割り当てることになります)
いくつかの初期化動作の後に、アプリケーションのメインループが続きます。組み込みアプリケーションにおいてはこれは通常は無限ループを形成し、アプリケーションを「終了して」返る先は存在しません。
ループの先頭において、ウォッチドッグタイマがリセットされます。もしウォッチドッグタイマが2秒間リセットされなければ、ハードウェアリセットかがかります。この時間(2秒)以上かかる処理を設けないようにするか、もしくは、処理ルーチン自身の中で時々ウォッチドッグタイマリセットを書ける必要があります。この例では文字列入出力関数(シリアルポート)がその可能性を持ちます。極端に大きな文字列(9600baud時2000文字程度)を出力すると危険があります。あまり長い文字列については抑止した方がいいかもしれません。??
An example of such a code path would be the string IO functions: for an overly large string to print (about 2000 characters at 9600 Bd), they might block for too long.
ループそれ自身は、割り込み指示ビットフィールドに基づいて適切な処理を行い、最終的に電流節約のためCPUをスリープ状態にします。
最初の割り込みビットはおよそ100Hzの(ソフトウェア)タイマーです。CLOCKOUT
ピンはここでHi/Lowを繰り返しますので、オシロスコープがあればこのピンの出力を計測して正確なソフトウェアクロックを計測することができます。
次に、LED2のためのLEDフラッシャー(「俺は生きている」-LED)をドライ微志マス。これは50msec間LEDを点灯し、950msecの間消灯し、これを繰り返します。オペレーションモードに基づくいろいろな処理がこれに続きます。最後に、3秒バックアップタイマーが組み込まれており、これにより3秒PWM値に更新がなければPWM値をEEPROMに保存する動作を行います。
ADC割り込みルーチンはPWM値を調整する動作のみを行います。
UART受信割り込みルーチンは、UARTで受信された最後の文字を急いで取り出す処理を行います。
main()内で情報を通知するのに使われる全ての文字列定数は、program memory 内に置かれていますので、これらの文字列定数のためSRAMが消費されることはありません。これはPSTRマクロを用い、これをprintstr_p()関数に渡すことで実現されています。
ソースコードはこちら:: largedemo.c