|
○ はじめに
前回 は、タイマ/カウンタ1 の高速 PWM 機能を使ってサーボモータを動かしてみました。PWM 機能は、あるピンに対して自動的に PWM 信号を出力してくれるため非常に便利です。ATmega8535 の場合、タイマ/カウンタ0〜2 全ての PWM 機能を使うと OC0/OC1A/OC1B/OC2 ピンの最大 4 本の PWM 出力を生成できます。
今回は、タイマ/カウンタ0 の比較一致割り込みを使って自力で PWM 出力を作成し、サーボモータを動かしてみたいと思います。自力で PWM 出力を作成すると、4 本といわず好きな本数の PWM 出力を生成することも可能になります(入出力ポート数や、実際には実行速度等の制限を受けると思いますが…)。
回路図は 前回 と同じなので、いきなりサンプルプログラムと説明にいきたいと思います。
○ サンプルプログラムと説明
タイマ/カウンタ0の比較一致割り込みを使って自力でサーボモータを動かすサンプルプログラムを 10pwm2.tar.gz からダウンロードできます。はじめに のページから、全てのサンプルプログラムを 固めた tar ファイルをダウンロードすることもできます。
中には
- Makefile
- util.h
- util.c
- timer0.h
- timer0.c
- main.c
が格納されています。util.h/util.c については LECTURE 4 A/D 変換でボリューム(可変抵抗)値を読む の内容と同じ、timer0.h/timer0.c については LECTURE 7 タイマ/カウンタ0を使う の内容と同じで Makefile もほとんど変わらないので、main.c について以下に載せます。なお、以下に掲載しているソースコードには修正 BSD ライセンス条項を含めていませんが、ソースコード配布は修正 BSD ライセンスに従います(はじめに のページの免責事項を参照してください)。
これまでにヒューズビットの設定を行ったことが一度も無い場合は、まず最初に make fuse を実行してください。fuse ターゲットについては、LECTURE 2 LED を点滅させる のサンプルプログラムの項を参照してください。
make 実行後、プログラムをアップロードする場合は make uisp を実行してください。
サーボモータが約 1 秒毎に反時計回り60°→中心→時計回り60°…の順に回転角を変えます。前回 と動作内容が同じなので、動画は載せません。
- main.c
1 #include <avr/interrupt.h> 2 #include <avr/io.h> 3 #include <avr/sleep.h> 4 5 #include "timer0.h" 6 #include "util.h" 7 8 9 #define OCR0_AS_TOP 99 10 11 static void timer0_comp_interrupt(void); 12 13 14 int 15 main(void) 16 { 17 DDRD |= _BV(DDD5); /* PD5 ピンを出力に設定 */ 18 pullup(); 19 20 timer0_comp_enable(); 21 timer0_comp_set_interrupt(timer0_comp_interrupt); 22 timer0_set_wave_mode(WGM0_CTC); 23 timer0_set_count(0); 24 timer0_set_ocr0(OCR0_AS_TOP); 25 26 sei(); 27 28 /* (8MHz/8)/100 = 10KHz */ 29 timer0_start(TIMER0_CK_8); 30 31 while (1){ 32 sleep_mode(); 33 } 34 35 return 0; /* 警告を避けるため */ 36 } 37 38 39 static void 40 timer0_comp_interrupt(void) 41 { 42 static int duty[3][2] = { 43 {21, 179}, 44 {15, 185}, 45 { 9, 191} 46 }; 47 48 static int dir = 0; /* 方向 */ 49 static int state = 0; /* H/L */ 50 static int count = 0; 51 static int pulse_count = 0; 52 53 /* 1秒毎に方向を変える */ 54 if (++ count == 10000) { 55 dir = (dir + 1) % 3; 56 state = count = pulse_count = 0; 57 PORTD |= _BV(PORTD5); 58 return; 59 } 60 61 /* PWM 制御 */ 62 if (++ pulse_count == duty[dir][state]) { 63 state = (state + 1) % 2; 64 pulse_count = 0; 65 PORTD ^= _BV(PORTD5); 66 } 67 } 68 69 /* End of main.c */
- 14 〜 36 行目 (main())
- pullup() 前に PORTD5 のみ出力方向に設定
- pullup() (pullup.h、PORTD5 以外の全入力ポートの内部プルアップを有効化)
- timer0_comp_enable() (timer0.h、比較一致割り込み有効化)
- timer0_comp_set_interrupt() (timer0.h、比較一致割り込み時に実行するユーザ関数の登録)
- timer0_set_wave_mode() (timer0.h、波形生成種別を設定)
- timer0_set_count() (timer0.h、TCNT0 の初期値を設定)
- timer0_set_ocr0() (timer0.h、OCR0 の値を設定)
を行った後、sei() (avr/interrupt.h) を実行してシステムの割り込みを有効化しています。
この初期化フェーズで、timer0_set_wave_mode(WGM0_CTC) によって波形生成種別を "比較一致タイマ/カウンタクリア(CTC)"に設定しています。これにより TCNT0 と OCR0 が一致した時点で TCNT0 をクリアし、0 からカウントを再開する動作を選択しています。
その後に timer0_start() を実行してタイマ/カウンタ0 の動作を開始します。 timer0_start() に TIMER0_CK_8 を指定しており、かつ OCR0 の値によりカウンタ最大値は 99 となるため、比較一致割り込みの周期は約 10KHz ((8MHz/8)/100)となります。
以後 sleep_mode() にてアイドル動作へ移行し、LED の点滅処理は比較一致割り込みハンドラに任せます。
- 39 〜 67 行目 (timer0_comp_interrupt())
比較一致割り込み時に実行されるユーザ関数です。
42 〜 45 行目にて、自力で PWM 制御する際に PD5 ピンの H/L を維持する割り込み回数を定義しています。割り込み周期が 10KHz なので、割り込み 1 回が 0.1ms となります。そのため
static int duty[3][2] = { {21, 179}, /* H:2.1ms、L:17.9ms 反時計回りに 60°*/ {15, 185}, /* H:1.5ms、L:18.5ms 中心位置 */ { 9, 191} /* H:0.9ms、L:19.1ms 時計回りに 60°*/ };
となります。
53 〜 59 行目にて、static 変数である count で割り込み回数を数え、10000 回に一度 (0.1ms x 10000 = 1s)サーボモータの回転角度を変えるべく、static 変数である dir の値を変更します。PWM 制御を行う際に、H/L を維持する割り込み回数の組み合わせを duty[dir] として参照するため、1s 毎に H/L を維持する割り込み回数を変えることになります。
61 〜 66 行目で実際にピンの出力を変えることで PWM 制御を行います。 62 行目にて、まず static 変数である pulse の値が H/L を維持すべき割り込み回数 に到達したかどうかを確認しています。 H/L を維持すべき割り込み回数は duty[dir][state] として参照します。 state = 0 の場合は H、state = 1 の場合は L を維持します。
維持すべき割り込み回数に到達した場合は、63 〜 65 行目にて以下を行います。
- 次に維持すべき状態を参照する様に state を更新
- カウントしなおすために pulse をクリア
- PD5 ピンの出力を反転
これにより、意図した時間幅のパルスを生成することが出来ます。
以上で解説は終わりです。サーボモータを動かすことに慣れてきた所で、今度はボリュームを回してサーボモータを回転させてみようと思います。