前回はモータードライバについて学びました。
今回はサーボの首振りを試してみましょう。
サーボモーターとは
サーボモーターは、DCモーター、トルクを生み出す減速ギア、センサー、そして制御回路基板で構成される小型のパッケージです。ほとんどのサーボモーターは、「ホーン」と呼ばれる突起部を介して180度の可動域を持ちます。(360度の可動域を持つサーボもあります。)サーボモータは単純なDCモーターよりも高いトルクを出力することができるため、ラジコンカー、ラジコン飛行機、ロボットなどの動作制御に広く使われています。
サーボモーターには通常3本のリード線があり、オスまたはメスの3ピンコネクタで接続します。2本の線は電源用で、プラス(2-VCC、赤い線)、マイナス(3-GND、茶色の線)となります。もう1本は信号線 (1-Signal、オレンジの線)で、キットに含まれているサーボモーターと同じ構成です。
サーボモーターを駆動するために、デューティサイクルが一定範囲内にある 50Hz の PWM 信号を使用します。PWM 単一サイクル中のハイレベルが 0.5ms から 2.5ms 持続する時間は、サーボモーターの角度 0 度から 180 度に線形に対応します。以下に対応値の一部を示します
と、テキストには書いてありますが、間違えている様に思います・・・
1.5msで90度、2msで135度が正しいかと
回路図
接続図
コード
/**********************************************************************
Filename : Servo Sweep
Description : Control the servo motor for sweeping
Auther : www.freenove.com
Modification: 2022/10/25
**********************************************************************/
#define SERVO_PIN 21 //define the pwm pin
#define SERVO_CHN 0 //define the pwm channel
#define SERVO_FRQ 50 //define the pwm frequency
#define SERVO_BIT 12 //define the pwm precision
void servo_set_pin(int pin);
void servo_set_angle(int angle);
void setup() {
servo_set_pin(SERVO_PIN);
}
void loop() {
for (int i = 0; i < 180; i++) { //make light fade in
servo_set_angle(i);
delay(10);
}
for (int i = 180; i > 0; i--) { //make light fade out
servo_set_angle(i);
delay(10);
}
}
void servo_set_pin(int pin) {
ledcSetup(SERVO_CHN, SERVO_FRQ, SERVO_BIT);
ledcAttachPin(pin, SERVO_CHN);
}
void servo_set_angle(int angle) {
if (angle > 180 || angle < 0)
return;
long pwm_value = map(angle, 0, 180, 103, 512);
ledcWrite(SERVO_CHN, pwm_value);
}
servo_set_pin()関数は複数のサーボを初期化する場合に、この様に関数化しておくと使い回しが聞いて良いですね。プログラムは同じコードを2度書かない、というのが大事です。ですので同じような処理をするなら関数化するなどして短縮するようにしましょう。そうすれば、のちのち修正が必要になったときも、修正する箇所が少なくてすみます。
servo_set_angle()関数がメインの処理ですね。38行目に着目してみましょう。角度の0~180を103~512にマップしています。ここがスッと理解できるでしょうか?(少なくとも私は計算しないと理解できません)
サーボの仕様として、50Hzで、0.5ms~2.5msの範囲で指定する、ということでした。50Hzの1周期は1/50秒ですので20msです。デューティー比にすると、 0.5ms/20ms=0.025 から 2.5ms/20ms=0.125 のデューティー比です。このデューティー比を12bitの0~4095にマップするので、0.025*4095=102.375 から 0.125*4095=511.875の範囲にマップするということになります。
こういう計算をするあたり、プログラムが数学に分類されるのも頷けますね。
void servo_set_angle(int angle) {
if (angle > 180 || angle < 0)
return;
long pwm_value = map(angle, 0, 180, 103, 512);
ledcWrite(SERVO_CHN, pwm_value);
}
可読性を上げるなら38行目を以下のように書き換えても良いです。
long pwm_value = map(angle, 0, 180, 0.5/20*4095, 2.5/20*4095);
ただし、処理するたびに割り算と掛け算を2回ずつ、計4回も計算しなければいけないとなると、負荷が高そうです。事前に数値が決まっているのであれば、事前に計算しておいたほうが処理が早いわけですね。このあたりはコンパイラの仕様にもよるので必ずしも計算コストが上がるわけではないかもしれませんが、プログラマとしてはエレガントに書きたいところです。
そういう意味ではsetup()関数の前に以下の様に書いておいても良いです。
#define MIN 103 // 0.5 / 20 * 4095 ≒ 103
#define MAX 512 // 2.5 / 20 * 4095 ≒ 512
そして38行目のところはこうですね。
long pwm_value = map(angle, 0, 180, MIN, MAX);
これだと計算コストは上がらずに、可読性も悪くないです。美しいですね。
次回はサーボをポテンションメータで動かしてみましょう。
コメント