LECTURE 9 サーボモータを動かす(高速PWM機能使用)

○ はじめに

筆者がマイコンを使ってやりたかった事の一つに、サーボモータを動かしてみよう!というのがあります。別にマイコンでやらなくてもいいのですが、何となく…サーボモータは位置決めの精度が高く安定してトルクを出せるので、最近だと2足歩行ロボットの可動部によく使われていたりします。

サーボモータの制御には、回転角に応じた一定周期のパルス幅の信号を与える必要があります。回転角を変える場合はパルス幅を変える必要があります。これを PWM (Pulse Width Modulation) 制御と言います。ここでは、タイマ/カウンタ1 が備えている PWM 機能のうち高速 PWM 機能を使って、サーボモータを動かしてみます。

○ サーボモータについて

実験用に用意したのは、GRAND WING(台湾のメーカー) の S03T 2BB というサーボモータです。どこで購入したか忘れましたが、1600円くらいだった様な気がします。

サーボモータ

以下の様な仕様です。トルクは出るけれど速度が遅いのが特徴の様です。

  • 電源4.8V時 トルク:7.2kg-cm 速度:0.33sec/60°
  • 電源6.0V時 トルク:8.0kg-cm 速度:0.27sec/60°
  • 重さ 46g
  • 寸法 39.5×20.0×39.6mm

コネクタ部分は↓の様になっています。

コネクタ部分

GND 電源 制御信号
茶色 赤色 オレンジ色

※線の色はメーカーによって数種類存在する様です。

オレンジ色の制御信号線を OC1A ピンと接続します。メーカの仕様によると、1 周期を 20ms としてその間に発生させるパルス幅によって下表の様に回転角度が決まる様です。

パルス幅 回転角度
0.8〜0.9ms 時計回り(CW)時の安全領域
0.9ms 時計回り60°±10°
1.5ms 0°(中心位置)
2.1ms 反時計回り60°±10°
2.1〜2.2ms 反時計回り(CCW)時の安全領域

実験によると、約0.57ms 〜 1.5ms 〜 2.5ms 程度の範囲で 180°程の回転角度を確認できました。

回転方向と回転角

パルスの様子

○ タイマ/カウンタ1 の高速 PWM 機能について

タイマ/カウンタ1 の高速 PWM 機能では、TCNT1 レジスタと OCR1A/OCR1B レジスタとの比較一致および TCNT1 レジスタが 0x0000 に到達する際に OC1A/OC1B ピンの出力を反転することができます。カウンタの最大値と比較一致に用いる OCR1A/OCR1B の値を調節することで、OC1A/OC1B ピンに出力されるパルス幅を制御し、サーボモータを動かします。

高速 PWM 機能ではカウンタ最大値を 0xff (8ビット)/0x1ff (9ビット)/0x3ff (10ビット)/ICR1/OCR1A から選ぶことができます。本文書では ICR1 をカウンタ最大値として使用します。なおカウンタ最大値として ICR1 と OCR1A を選ぶ場合の違いは主に以下の通りです。

  • OCR1A への書き込みは TCNT1 が 0x0000 になるタイミングで反映されます。カウンタ最大値を変更する場合はタイミングが一定するためこちらが向いていますが、カウンタ制御以外に使用する比較一致レジスタが一つ減ることになります。

  • ICR1 への書き込みは即座に反映されます。カウンタ最大値を書き換えない場合にはこちらが使えます。

本文書では ICR1 = 19999 とし、タイマ/カウンタ1 のクロックは システムクロック/8 とします。これにより PWM 出力周波数は

f_pwm = f_system_clock / (8 x (1 + カウンタ最大値)) = 8MHz / (8 x 20000) = 50Hz

つまり 20ms となります。また OC1A ピンの出力モードを

比較一致時 TCNT1=0x0000
Low 出力 High 出力

に設定します。この場合の TCNT1 の変化と OC1A ピン出力の関係は下図の通りとなります。

高速 PWM 動作のタイミング

また、OCR1A を下表の値に変化させることで、OC1A ピンに接続したサーボモータを反時計回り60°→中心→時計回り60°と回転させます。本文書では、OCR1A の更新は TCNT1 が ICR1 に到達した際に発生する捕獲割り込み時に行います。

OCR1A パルス幅
2100 2.1ms
1500 1.5ms
900 0.9ms

○ 回路図

LECTURE 1 ATmega8535 および使用環境について に掲載してある全体回路図に対して、ATMega8535 の OC1A ピンおよびサーボモータと接続する信号線+コネクタ部分を赤枠で囲んだ図を こちら に置いておきます。

このコネクタとサーボモータのコネクタを、信号線が合う様に接続してください。

○ サンプルプログラムと説明

タイマ/カウンタ1の高速 PWM 機能を使ってサーボモータを動かすサンプルプログラムを 09pwm.tar.gz からダウンロードできます。はじめに のページから、全てのサンプルプログラムを 固めた tar ファイルをダウンロードすることもできます。

中には

  • Makefile
  • util.h
  • util.c
  • timer1.h
  • timer1.c
  • main.c

が格納されています。util.h/util.c については LECTURE 4 A/D 変換でボリューム(可変抵抗)値を読む の内容と同じ、timer1.h/timer1.c については LECTURE 8 タイマ/カウンタ1を使う の内容と同じで Makefile もほとんど変わらないので、main.c について以下に載せます。なお、以下に掲載しているソースコードには修正 BSD ライセンス条項を含めていませんが、ソースコード配布は修正 BSD ライセンスに従います(はじめに のページの免責事項を参照してください)。

これまでにヒューズビットの設定を行ったことが一度も無い場合は、まず最初に make fuse を実行してください。fuse ターゲットについては、LECTURE 2 LED を点滅させる のサンプルプログラムの項を参照してください。

make 実行後、プログラムをアップロードする場合は make uisp を実行してください。

サーボモータが約 1 秒毎に反時計回り60°→中心→時計回り60°…の順に回転角を変えます。

こちらはサーボモータ動作の動画です(10秒。YouTube に接続します)。

  • main.c
        1    #include <avr/interrupt.h>
        2    #include <avr/io.h>
        3    #include <avr/sleep.h>
        4    
        5    #include "timer1.h"
        6    #include "util.h"
        7    
        8    
        9    #define ICR1_AS_TOP     19999
       10    #define OCR1A_LIMIT_CCW 2100
       11    #define OCR1A_CENTER    1500
       12    #define OCR1A_LIMIT_CW  900
       13    
       14    static void timer1_ic_interrupt(void);
       15    
       16    
       17    int
       18    main(void)
       19    {
       20            DDRD |= _BV(DDD5);  /* PD5 ピンを出力に設定 */
       21            pullup();
       22    
       23            timer1_ic_enable();
       24            timer1_ic_set_interrupt(timer1_ic_interrupt);
       25            timer1_set_wave_mode(WGM1_FAST_PWM_ICR1);
       26            timer1_set_coma_mode(COM_A_HIGH_FIRST);
       27            timer1_set_count(0);
       28            timer1_set_icr1(ICR1_AS_TOP);
       29            timer1_set_ocr1a(OCR1A_LIMIT_CCW);
       30    
       31            sei();
       32    
       33            /* (8MHz/8)/20000 = 50Hz */
       34            timer1_start(TIMER1_CK_8);
       35        
       36            while (1){
       37                    sleep_mode();
       38            }
       39    
       40            return 0; /* 警告を避けるため */
       41    }
       42    
       43    
       44    static void 
       45    timer1_ic_interrupt(void)
       46    {
       47            static int ocra[3] = {
       48                    OCR1A_LIMIT_CCW,
       49                    OCR1A_CENTER,
       50                    OCR1A_LIMIT_CW,
       51            };
       52    
       53            static int dir   = 0;
       54            static int count = 0;
       55    
       56            /* 1秒毎に方向を変える */
       57            if (++ count == 50) {
       58                    dir = (dir + 1) % 3;
       59                    count = 0;
       60                    timer1_set_ocr1a(ocra[dir]);
       61            }
       62    }
       63    
       64    /* End of main.c */
    
    
    • 17 〜 41 行目 (main())

      • pullup() の前に PORTD5 のみ出力方向に設定
      • pullup() (pullup.h、PORTD5 以外の全入力ポートの内部プルアップを有効化)
      • timer1_ic_enable() (timer1.h、カウンタ最大値 ICR1 到達時の捕獲割り込み有効化)
      • timer1_ic_set_interrupt() (timer1.h、捕獲割り込み時に実行するユーザ関数の登録)
      • timer1_set_wave_mode() (timer1.h、波形生成種別を設定)
      • timer1_set_coma_mode() (timer1.h、比較A出力モードを設定)
      • timer1_set_count() (timer1.h、TCNT1 の初期値を設定)
      • timer1_set_icr1() (timer1.h、ICR1 の値を設定)
      • timer1_set_ocr1a() (timer1.h、OCR1A の値を設定)

      を行った後、sei() (avr/interrupt.h) を実行してシステムの割り込みを有効化しています。

      この初期化フェーズで timer1_set_wave_mode(WGM1_FAST_PWM_ICR1) によって波形生成種別を "高速 PWM 動作(カウンタ最大値は ICR1)"に設定しています。 また、timer1_set_coma_mode(COM_A_HIGH_FIRST) によって OC1A ピンの出力モードを

      比較一致時 TCNT1=0x0000
      Low 出力 High 出力

      に設定しています(タイマ/カウンタ1 の高速 PWM 機能について参照)。

      また、timer1_ic_enable()/timer1_ic_set_interrupt() を用いて捕獲割り込みを有効にしています。 高速 PWM 機能で ICR1 をカウンタ最大値にすると、オーバーフローと同時に捕獲割り込みが発生します。 ここではオーバーフロー割り込みの代わりにこちらを使うことにしました。

      以後 sleep_mode() にてアイドル動作に移行し、サーボモータの首振り制御は 捕獲割り込みハンドラに任せます。

    • 44 〜 62 行目 (timer1_ic_interrupt())

      捕獲割り込み時に実行されるユーザ関数です。何もしなければサーボモータは動きません。

      static 変数である count で割り込み回数を数え、50 回に一度 OCR1A の値を変えることでパルス幅を変えます。割り込み周期が 50Hz であるため、OCR1A の変更周期は 50/50 = 1 Hz = 1 秒となります。つまり 1 秒毎にサーボモータは向きを変えることになります。

以上で解説は終わりです。次回は、タイマ/カウンタ0 の比較一致割り込みを使って同じ様にサーボモータを動かしてみたいと思います。