ArduinoのanalogWriteでサーボ制御
こんにちは、くろべこです。
ここにきて初めてのサーボモーター記事です。この記事を見ている方々はライブラリを使ってのサーボ制御はすでにやっている方がほとんどだと思うので、今回はArduinoのanalogWriteでサーボモーターを制御していきたいと思いますよ。
そこで障害となってくるのはPWMを発生するときに必要なanalogWriteの周期なんですね。詳しいことは後述しますが、analogWriteの周期は490Hz(980Hz)に なります。これではサーボモーターの制御には不都合があり、動かすことができません。
という事で、今回はちょちょっとArduinoのレジスタを操作することもしていきたいと思います。
そして、最終的にはこの記事で使用する格安サーボモーターの定格パルス範囲を調べていきたいと思います!
では、どうぞ!
サーボモーターとは
サーボモーター(Servo Motor)のサーボってどのような意味かご存じですか?
語源はラテン語のServus(英:Slave)からきており、奴隷のように主人の指令で忠実に動くモーターからサーボモーターというんですね。このサーボモーターはDCモーターやステッピングモーターとは異なり自分の位置を記憶することが出来ます。
こんな従順なサーボモーターには何種類かありますが、ここでは【DCサーボ】と【ACサーボ】に大分したいと思います。現在FA分野で多く使われているのはACサーボですが、今から使うものは【DCサーボ】です。
DCサーボは磁力の向きを変えるために回転する整流子に対してブラシと呼ばれる接点を接触させる必要があります。なので、FA機器などの産業用ではメンテナンス性が悪かったり、ブラシの摩耗があるためにあまり普及していません。(以下はミニ四駆などに使われるDCモーターの模式図です)
そして、位置を記憶する検出器には大きく分けて【インクリメント型】と【アブソリュート型】があります。
- インクリメント型:軸の回転変位量によりパルスを出力し、パルスを内部のカウンタにより読み取り事で位置を特定する。基準となる位置は持っておらず、原点でカウンタをリセット。
- アブソリュート型:基準のゼロ点を持っています。基準からの絶対的な位置を出力することが可能です。電源遮断時でも位置の記憶が可能なものもあります。
本記事で取り上げるサーボモーターは電子工作やラジコンなどに用いられるマイクロサーボ(RCサーボ)になります。原理としては内部にDCモーターが入っており、ギヤにより減速し駆動軸を動かします。現在位置の情報は軸と連動するポテーションメーターによりフィードバックされます。
回転角度はメーカーにはよりますが180度前後で、定格の回転角度以上に指令を与えてしまうとメカ的に回転角度を制限されてしまうので制御指令の計算には注意が必要です。
サーボモーター(QUIMAT/QKY66)
今回使用するのはQUIMAT社のQKY66というSG-90( Tower Pro)の格安パチモン品です。
【仕様】
回転トルク:1.5kg/cm
動作電圧:4.2-6V
動作速度:0.3秒/60度
制御仕様についてQKY66データシートを探したのですが見つからなかったのでSG-90の物を参考にします。(ちなみにケーブル色と割り当てている機能はSG-90と同じでした)
制御は周波数固定のPWM制御で行います。
- PWM仕様周期:20ms(50Hz)
- Duty cycle(0.5ms):-90°
- Duty cycle(1.45ms):0°
- Duty cycle(2.4ms):90°
そして、冒頭でも書きましたがArudinoのanalogWriteで制御を行う上で初期状態では周期がその障害となります。
Arduino(Uno, nano)ではマイクロチップ社のATmega328Pを使用しています。
このチップでは3つのタイマーを持っていて、何気なく使用しているPWMピンに割り当てられています。具体的には以下の通りです。
- D5, D6:Timer0(980Hz)
- D9,D10:Timer1(490Hz)
- D3,D11:Timer2(490Hz)
Timer0はdelay関数などに割り当てられているのでレジスタはいじりたくないので、今回はTimer1をいじっていきます。
Timer1をいじる
Timer1の初期設定の確認
まずはTimer1が初期でどのような設定になっているか制御レジスタTCCR1Aを見てましょう。
- TCCR1A
COM1A, COM1Bは比較出力OC1AとOC1Bの挙動を制御します。WGM1はTimer1の動作を定義します。詳しくは巻末にATmega328(Arduino-nanoチップ)のデータシートのリンクを添付したので参照をお願いします。
【TCCR1A確認コード】
1 2 3 4 5 6 7 |
void setup() { Serial.begin(9600); Serial.print("TCCR1A:");Serial.println(TCCR1A,BIN); } void loop() { } |
結果
“1”という事は位相基準のPWM動作になり、両傾斜(三角波)を基準波とした8bit分解能のPWM動作になります。
次に前置分周期の初期設定を見てみましょう。いわゆるプリスケーラ―というメインクロックを整数分の1することでTimer周波数を下げることのできるものです。これはTCCR1Bレジスタで定義されます。
- TCCR1B
分周期の設定は0bit~2bit目のCS部分になります。
結果
“11”ですね。これはメインクロック/64を意味します。今回使用しているArduino-nanoのメインクロックは16MHzです。
という事で、上記の結果からPWM周波数を求めると
Timer1のPWM周波数を50Hzにする
ピッタリ20msの周期にPWMを設定するには少し工夫する必要があります。
初期状態では8ビット分解能の両傾斜カウンタによりPWMを作っていました。その他にも9ビット、10ビットの分解能でPWMを作ることが可能ですが、これだと微妙な周期でのPWM波形を作ることが出来ません。
そこで、今回はICR1(Input Capture Register 1)を使用して任意の分解能の両傾斜カウンタを作成し、20ms周期のPWMをつくりたいとおもいますよ。
まずICR1とはTimer1のカウンタ(TCNT1)の値をキャッチすることでTOP値を定義することが出来ます。例えば、8ビット分解能では255がTOP値ですが、ICR1をTOP値と定義することで16ビット内で任意にTOP値を定義することが出来る優れものです。
上記の事を基にTimer1レジスタを設定します。
【TCCR1A】
WGM1 1bit,0bit(TOP値をICR1とする位相基準PWM):10
【TCC1B】
WGM1 1bit,0bit(TOP値をICR1とする位相基準PWM):10
CS 2~0(プリスケーラ― clk/256):100
【ICR1】
エクセルでテーブルを作り算出しました。計算式は前項の周波数を計算した式で255(9bit)の部分をxとしました。
このテーブルよりICR1は625という事が分かります。
analogWriteでサーボモーターを動かす
デバック1
では、早速コードを以下に示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void setup() { pinMode(9, OUTPUT); Serial.begin(9600); Serial.println("**************初期**************"); Serial.print("TCCR1A:");Serial.println(TCCR1A,BIN); Serial.print("TCCR1B:");Serial.println(TCCR1B,BIN); TCCR1A = 0b10;//位相基準PWM(TOP) TCCR1B &= ~0b111;//プリスケーラ―クリア TCCR1B |= 0b100;//256プリスケーラ― TCCR1B |= 0b00010000;//TOP値ICR1 ICR1 =625;//20ms周期 Serial.println("**************修正**************"); Serial.print("TCCR1A:");Serial.println(TCCR1A,BIN); Serial.print("TCCR1B:");Serial.println(TCCR1B,BIN); Serial.print("ICR1:");Serial.println(ICR1,BIN); } //SG-90のパルス範囲0.5ms ~ 2.5ms void loop() { analogWrite(9, 19);//だいたい1.5ms delay(500); } |
レジスタの設定については前項で説明した通りです。
analogWriteはご存じの通り8bitの分解能を持っています。今回は20ms周期という事でサンプルプログラムのでは1.5msパルスを作成しています。
PWM = 1.5ms / (20ms / 255)
さて、まずは信号がちゃんと20ms周期で1.5msパルスが出ているかロジックアナライザで見てみましょう。(1000円で買える格安ロジックアナライザの使い方の解説記事は以下に載せてありますのでどうぞ。)
20msの周期でPWMが出ていますがっ、、、、
1.5msのパルスは出ていない気がします。ちょっと拡大してみてみると
やはり出ていません。うーーん、なんでだろう。
仕方ないので力技で1.5msのパルスが出るPWM値を探してみます。
結果はは47でした!なぜかはわかりません!わかる方がいたら原因を教えてほしいです。
とりあえずサーボに電源を供給してみたら動きましたねー
せっかくなので、0.5ms~2.5msのPWM範囲を調べてみますか(力技で!!)
結果は15(0.5ms) ~ 78(2.5ms)でした。
でも、2.5msではなんか160度以上回ってしまってうなりを上げていたので、現物合わせで調べてみると21(0.5ms) ~ 65(2.05ms)が定格の使用範囲かなと検証できました。
デバック2
最後に前項までの検証を基に0度から160度まで10度間隔ずつ動かすArduinoコードと動画を載せて今回は失礼します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void setup() { pinMode(9, OUTPUT); //PWMレジスタ設定 TCCR1A = 0b10;//位相基準PWM(TOP) TCCR1B &= ~0b111;//プリスケーラ―クリア TCCR1B |= 0b100;//256プリスケーラ― TCCR1B |= 0b00010000;//TOP値ICR1 ICR1 =625;//20ms周期 Serial.println("**************PWM設定確認**************"); Serial.print("TCCR1A:");Serial.println(TCCR1A,BIN); Serial.print("TCCR1B:");Serial.println(TCCR1B,BIN); Serial.print("ICR1:");Serial.println(ICR1,BIN); } //QUIMATの実験パルス範囲0.65ms~2.05ms void loop() { //10°ずつ駆動 for(int i=21;i<66;i+=3){ analogWrite(9, i);//0.65ms delay(500); } //-10°ずつ駆動 for(int i=65;i>22;i-=3){ analogWrite(9, i);//0.65ms delay(500); } } |
コメント