サーボモータを動かしてみます

〔マイコンのトップに戻る〕


ここでは産業機械等で利用されているサーボモータではなく、ホビー用途のサーボモータを回してみます、
ホビー用途のサーボモータと言えばラジコンを始め、最近では2足歩行ロボットの関節用に利用されていますね。
実はぁ、知り合いからRCサーボモータを貰ったのです、なら、動かしてみるかぁってんでぇ、記事にしました。

ArduinoはServoライブラリが有るのでこれを利用したら簡単に動作させられます、
ArduinoでのServoライブラリ使用方法は建築発明工作ゼミ2008さんのこちらHPを参照下さい。
この頁ではPIC(12F1822)を使ってサーボモータを動かしてみます。

サーボモータを調べてみると、同じ物が秋月電子のこちら(S03N 2BBM/フタバ)で販売されていました。
上の物は販売ページがなくなっているので、こちら(S03N 2BBM/JR)を載せて置きます。 *3)

接続線は3端子有り、GND(黒線)、5V(赤線)、信号線(白線)のフタバタイプ仕様です、
他にJRタイプ仕様が有り、GND(茶線)、5V(赤線)、信号線(オレンジ線)で信号の配列は同じです。
又、制御方法も同じで、フタバタイプはコネクタの逆挿し防止が有る様です。 *3)

サーボモータによっては360°回転する物も有るようですが、このサーボも含み通常は180°までです。
360°等の連続回転をさせるにはステッピングモータを利用するのではないかと思います。
尚、ステッピングモータを動かす記事はこちらを見て下さい。

サーボモータの制御方法について

動かすにはPWM信号をサーボモータの信号線に出力すれば良いだけです。

制御方法
左図の様に1周期約20ms(50Hz)のパルス信号を出力します。
パルスのONさせる幅を可変させることによりモータ回転軸の角度が決まります。

今回のこのサーボモータでぇだいたいぃ、幅がぁ、
1520usで90°中央(ニュ−トラル)位置です。
630usで中央から時計回り方向に更に90°回転し、
2350usで中央から反時計回り方向に-90°回転します。
よって制御する幅は630us〜2350usにしましたが、各サーボモータにより調整しましょう。

  回転図
           0°位置                                    90°位置                                 180°位置

1周期20msを作るには50HzのPWM信号を作成すれば良いので、PICのCCP1機能(PWM)を利用使用と思い 周期の設定値を計算したら50Hzを作るにはPICのメインクロックを1MHz以下にしないとだめです、
え、ぇぇ〜、他の機器を制御したりするのを考えるとぉ、なんかぁ、いやですねぇ。
だから、16ビットタイマーのTIMER1を使い割り込みで処理する方法を選びました。

又、例えば現在 90°中央位置に回転軸が有るとして、630usを出力したとします。
するとサーボモータは時計回り方向に 90°回転しますが、 回転が終わる前にPWM信号が途絶えると 90°は回転せずに途中で停止してしまいます。
PWM信号が出ていない状態はモータはフリー状態です、力が加わるとモータ軸位置はずれます、
PWM信号が出ているとサーボモータは軸位置がずれても元の位置に戻ろうとします、 モータ軸を手で回転させてみて下さい元の位置に動こうとします。(あまり力は入れないでね)

《 配線図 》

配線図  左図サーボモータではリード線が赤・黒・オレンジ(JRタイプ)ですが、
 今回使用の物は赤・黒・白(フタバタイプ)です。
 制御線の色が異なっています。
 尚、サーボモータの制御線は、PICのRA2(5番ピン)デジタル出力
 につながっています。

 何時もこのHPで利用しているトリマータイプの可変抵抗でなく、
 つまみ回し易いので小型のボリュームタイプを使用しています。
 手持ちで500KΩを使いましたが抵抗値は適当で良いかと思ふ。
 (出来るならアナログ入力で0-1023まで入力出来る物が良さげ)
 尚、ボリュームの中央端子はPICのAN3(3番ピン)アナログ入力
 につながっています。
 このボリュームは左回しで0方向、右回しで1023方向の値が
 得られます。

 サーボモータを複数個繋いだ場合にどうなるか気にかかる所ですが
 1個しかないのでそのうち手に入れたらやって見ようと思うが.........

《サンプルプログラム》

@上記配線図画面の様に配線しましょう。

AMPLAB X(v2.15)を起動させます。 *2)

B下記がPIC12F1822でのサンプルのプログラムソースです、
  MPLAB(R) XC8 C Compiler Version 1.32コンパイラを使用しています。
  プロジェクトを作成して新規ファイルにコピーペーストして貼り付けて下さい。 *2)
---------------------------------------------------------------------
#include <xc.h>
#include <stdlib.h>

#define _XTAL_FREQ 8000000    // delay用に必要(クロック8MHzを指定)
#define SERVO_PIN  RA2        // サーボモータに接続しているピン番号

int TMR1ON_Flag ;             // タイマーの割り込み待ちフラグ

// コンフィギュレーション1の設定
#pragma config FOSC     = INTOSC   // 内部クロック使用する(INTOSC)
#pragma config WDTE     = OFF      // ウオッチドッグタイマー無し(OFF)
#pragma config PWRTE    = ON       // 電源ONから64ms後にプログラムを開始する(ON)
#pragma config MCLRE    = OFF      // 外部リセット信号は使用せずにデジタル入力(RA3)ピンとする(OFF)
#pragma config CP       = OFF      // プログラムメモリーを保護しない(OFF)
#pragma config CPD      = OFF      // データメモリーを保護しない(OFF)
#pragma config BOREN    = ON       // 電源電圧降下常時監視機能ON(ON)
#pragma config CLKOUTEN = OFF      // CLKOUTピンをRA4ピンで使用する(OFF)
#pragma config IESO     = OFF      // 外部・内部クロックの切替えでの起動はなし(OFF)
#pragma config FCMEN    = OFF      // 外部クロック監視しない(OFF)

// コンフィギュレーション2の設定
#pragma config WRT    = OFF        // Flashメモリーを保護しない(OFF)
#pragma config PLLEN  = OFF        // 動作クロックを32MHzでは動作させない(OFF)
#pragma config STVREN = ON         // スタックがオーバフローやアンダーフローしたらリセットをする(ON)
#pragma config BORV   = HI         // 電源電圧降下常時監視電圧(2.5V)設定(HI)
#pragma config LVP    = OFF        // 低電圧プログラミング機能使用しない(OFF)

// タイマー1割込みの処理
void interrupt InterTimer( void )
{
     if (TMR1IF == 1) {           // タイマー1の割込み発生か?
          TMR1ON_Flag = 0 ;
          TMR1IF = 0 ;            // タイマー1割込フラグをリセット
     }
}
// アナログ値を読み込む処理
unsigned int adconv()
{
     unsigned int temp;

     GO_nDONE = 1 ;         // PICにアナログ値読取り開始を指示
     while(GO_nDONE) ;      // PICが読取り完了するまで待つ
     temp = ADRESH ;        // PICは読取った値をADRESHとADRESLのレジスターにセットする
     temp = ( temp << 8 ) | ADRESL ;  // 10ビットの分解能力です

     return temp ;
}
// マップ関数(ある数値から他の数値へレベル変換を行う)
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min ;
}
// サーボモータに出力を行う処理(1サイクルのみ出力)
// value : 回転角度に応じたパルスの幅を指定する(単位はus)
void ServoOut(int value)
{
     unsigned int c ;

     c = 65536 - value ;
     TMR1H = (c >> 8) ;       // パルス幅(ON)の時間(us)を設定
     TMR1L = (c & 0x00ff) ;   // 1カウントは1usです
     TMR1ON_Flag = 1 ;
     SERVO_PIN = 1 ;          // パルスON
     TMR1ON = 1 ;             // タイマー1カウント開始
     while(TMR1ON_Flag == 1) ;// 設定した時間で割り込みが発生するまで待つ
     TMR1ON = 0 ;             // タイマー1カウント停止
     SERVO_PIN = 0 ;          // パルスOFF
     __delay_us(17000) ;      // 通常は 20ms - ON時間 だけですが 17msに固定
}
// メインの処理
void main()
{
     unsigned int num , x , i ;

     OSCCON = 0b01110010 ;    // 内部クロックは8MHzとする
     ANSELA = 0b00010000 ;    // アナログはAN3を使用し、残りをすべてデジタルI/Oに割当
     TRISA  = 0b00011000 ;    // AN3(RA4)/RA0だけ入力その他のピンは出力に割当てる(RA3は入力専用)
     PORTA  = 0b00000000 ;    // 出力ピンの初期化(全てLOWにする)
     // A/Dの設定
     ADCON1 = 0b10010000 ;    // 読取値は右寄せ、A/D変換クロックはFOSC/8、VDDをリファレンスに
     ADCON0 = 0b00001101 ;    // アナログ変換情報設定(AN3から読込む)
     __delay_us(5) ;          // アナログ変換情報が設定されるまでとりあえず待つ
     // タイマー1の設定(1カウントは1usで設定)
     T1CON = 0b01110000 ;     // 内部クロック(8MHz)でTIMER1をカウントする、プリスケーラカウント値 1:8
     TMR1IF = 0 ;             // タイマー1割込フラグを0にする
     TMR1IE = 1 ;             // タイマー1割り込みを許可する
     PEIE   = 1 ;             // 周辺装置割り込みを許可する
     GIE    = 1 ;             // 全割り込み処理を許可する 

     __delay_ms(3000) ;       // 3秒後に実行する

     while(1) {
          // 3番ピン(AN3)から可変抵抗の値を読み込む
          num = adconv() ;
          // 読んだ値(0-1023)を出力するパルスのON幅値(2350-630)に変換する
          x = map(num,0,1023,2350,630) ;
          // サーボモータに出力する
          ServoOut(x) ;
     }
}
---------------------------------------------------------------------
CコンパイルPIC書き込みを実行して下さい。

DPICをブレッドボードに取付けてボリュームを回せばその方向にサーボモータが動くと思います。
 ボリュームの回す方向と逆に動く場合は?
 ボリュームの配線、5V と GND を入れ替えて下さい、
 或は、x = map(num,0,1023,2350,630) を x = map(num,0,1023,630,2350) と書換えて下さい。

A/Dの設定について

アナログI/O
 アナログピンの設定は下記のレジスターにて設定します。
 ANSELA = 0b00010000 ; アナログ入力を行うピンの指定をします
               赤数字右からAN0(7ピン),AN1(6ピン),AN2(5ピン),AN3(3ピン)の順
               1でアナログ、0でデジタル、この設定例はAN3をアナログで使用する。

 指定したAN3のアナログピンを入力に指定するには下記のレジスターにて設定します。
 TRISA = 0b00010000 ; この設定はAN3のみ入力に設定するです。
               1で入力、0で出力、右からAN0,AN1,AN2,x,AN3

アナログ変換情報設定
 ADCON1 = 0b10010000 ; 緑数字部分でA/D変換時のリファレンス電圧をどうするかの設定です。
               00:VDDのPIC電圧を使用する。
               10:6番ピン接続の外部VREFを使用する。
               11:PIC内蔵の固定電圧をしようする。
               赤数字部分でA/D変換を行う速度のクロックを設定します。
               000:FOSC/2 001:FOSC/8  010:FOSC/32
               100:FOSC/4 101:FOSC/16 110:FOSC/64
               又、それ以外の数字部分の設定は今回このまま使用して下さい。

 ADCON0 = 0b00001101 ; この設定でAN3から読込めと指示しています
               他のAN3以外から読込みたい場合は、赤数字の部分を変更します。
               AN0=00000,AN1=00001,AN2=00010,AN3=00011と変更して下さい
               赤数字の部分以外の設定は今回このまま使用して下さい。
               又、ADCON0の設定には5us程かかります、delayを入れて置きましょう

adconv()
 アナログの値を読み取ります、値は0〜1023(0V〜5V)の範囲です。
 可変抵抗器によっては0-1023ではなく例えば60-530の範囲だったりしたりします、
 (この様な場合サーボモータが0°〜180°まで動作しません)
 その場合は、x = map(num,0,1023,2350,630) を x = map(num,60,530,2350,630) に
 変更しましょう、0°〜180°まで動作する様になります。

タイマー1の設定について

T1CONの設定
 T1CON = 0b01110000 ;
 青数字部分でTimer1のクロックソースを選択します。
  00 = Fosc/4  01 = Fosc  10 = 外部入力  11 = 内蔵の容量検知オシレータ (CAPOSC)
 緑数字部分でプリスケーラの設定を行います。
  00 = 1:1  01 = 1:2  10 = 1:4  11 = 1:8
 赤数字部分 1=TIMER1を動作させる 0=TIMER1を動作させない

ワンカウントの時間計算
  (1/システムクロック周波数) x プリスケーラ設定値
 システムクロックが8MHzでワンカウント1usにしたい場合は
 クロックソースが 01 = Fosc でプリスケーラの設定が 11 = 1:8 なら
 (1/8MHz)x 8 = 1us
 サーボモータへの出力パルスON幅を 1520us 出したい場合は
     c = 65536 - 1520 ;       // c = 64016
     TMR1H = (c >> 8) ;       // パルス幅(ON)の時間(us)を設定
     TMR1L = (c & 0x00ff) ;   // 1カウントは1usです
 64016 から 65536 までカウントアップしていき 65536 で割り込みが発生します。

__delay_us(17000)
 1サイクル(20ms/50Hz)でのパルスOFF時間は、20ms-パルスON時間なので
 例えば、1520ms だけONを出力したら、20000-1520=18480ms OFFすれば良いが、
 __delay_us()は変数が使えない、なので__delay_us(17000)で固定します。

関数について

ans = map(long x, long in_min, long in_max, long out_min, long out_max) *1)
  指定した数値( x )を、他の数値範囲(out_min〜out_max)へレベル変換を行う処理
  x         : 変換したい数値を指定します
  in_min   : 変換する数値の最小値
  in_max  : 変換する数値の最大値
  out_min : 変換後数値の最小値
  out_max: 変換後数値の最大値
  ans      : 変換した結果の数値を返します
  例)
  long x ;
  x = map(512,0,1023,2350,630) ;  // xは1476となる
  x = map(1100,0,1023,2350,630) ; // xは630となる

ServoOut(int value)
  サーボモータにPWM信号を1サイクル(1周期)出力します。
  value: ONするパルスの幅を指定します(630us-2350us)
       630us-2350usの範囲以外の数値も指定可能です、サーボモータによって調整しましょう。
       範囲以外の数値をサーボモータに与えた場合は壊れる可能性が有ります。

  サーボモータを配線する端子を変えたい場合は
  #define SERVO_PIN RA2  // サーボモータに接続しているピン番号
  の行 RA2(デジタル出力) を変更しましょう。

《 その他 》

実験風景  実験風景の写真です。
 電池は新品を!
 最初は古い電池で動かなく悩んだぞぉぉ

 サーボモータを増やす場合はその個数だけ
 ServoOut()関数を増やせばOKと思われますが
 1個しかないのでにゃんともです。
 また、サーボモータが動作時の電圧を測ったら
 約150mv程電圧降下が有ったので其れなりに
 別電源等の考慮が必要かも。

次回以降はステッピングモータでも動かしてみようかと考えています。

尚、
Arduinoを使ってステッピングモータを動かす記事はこちらを見て下さい。
PICを使ってステッピングモータを動かす記事はこちらを見て下さい。
I2C接続16チャンネル12ビットPWMサーボシールドを使ってサーボモータを動かす記事はこちらを見て下さい。 *3)



記事一部変更(*3) 2018/02/13
MPLAB X用に記事変更(*2) 2015/10/27
map関数をint→longに変更(*1) 2014/01/15


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2018 Shigehiro Kimura All Rights Reserved.