LECTURE 10 サーボモータを動かす(比較一致割り込み使用)

○ はじめに

前回 は、タイマ/カウンタ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 ピンの出力を反転

      これにより、意図した時間幅のパルスを生成することが出来ます。

以上で解説は終わりです。サーボモータを動かすことに慣れてきた所で、今度はボリュームを回してサーボモータを回転させてみようと思います。