0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Servo ライブラリ

Arduino の Servo ライブラリ を用いると、設定する角度を指示し、サーボ制御用の PWM 信号を出力することができる。
このライブラリを使用する際、制御を行う Servo オブジェクトのインスタンスで attach 関数を呼び出すことにより、信号を出力するピンを指定する。
このとき、角度「0度」(min) および「180度」(max) に対応するパルス幅を設定することもできる。

#include <Servo.h>

Servo s1, s2;

void setup() {
  s1.attach(5); // デフォルトのパルス幅の範囲を用いる
  s2.attach(6, 600, 2000); // 「0度」を 600 us、「180度」を 2000 us にする
}

void loop() {
}

いいこと思いついた?

attach 関数で指定できる「0度」および「180度」の角度の範囲について、ドキュメントには特に記載が見当たらない。
そのため、一見「常識的」な範囲で任意の角度を設定できそうに見える。

そこで、有効なパルス幅の範囲のうち一部を指定し、その範囲内を高い解像度で動いてもらおうとした。

うまく動かないコード
#include <Servo.h>

Servo servo;

void setup() {
  servo.attach(5, 600, 800); // 600 us ~ 800 us の範囲で動いてもらいたい
  servo.write(90); // 真ん中の 700 us を出力したい
}

void loop() {
}

これを実行すると、仕様通り約 20 ms 間隔でパルスが出力された。

Servo ライブラリにより出力されたパルス

しかし、出力されたパルスの幅は 1725 μs となっており、期待した 700 μs からは約 2.5 倍という非常に大幅な誤差が生じてしまった。

Servo ライブラリにより出力されたパルスの幅

どうしてこうなってしまうのだろうか?

ライブラリの実装を見てみる

ドキュメント の「Go to repository」や「Go to repository」から、ライブラリのソースコードが格納されたリポジトリを開くことができる。

このライブラリのライセンスは LGPL である。
見すぎには注意しよう。

attach 関数で minmax を指定すると、それぞれ MIN_PULSE_WIDTH および MAX_PULSE_WIDTH との差が記録される。

MIN_PULSE_WIDTH の値は 544MAX_PULSE_WIDTH の値は 2400 である。

そして、記録先はなんと int8_t 型の変数なのである。

int8_t は8ビット符号付き整数であり、-128 ~ 127 の値しか格納できない。

逆算すると、min は 33 ~ 1059、max は 1889 ~ 2915 であれば記録する値がこの範囲に収まるが、minmax がこの範囲の外だと記録する値がこの範囲に収まらず、意図しない値が記録されてしまうことがわかる。

実行結果の考察

先ほどの例

servo.attach(5, 600, 800); // 600 us ~ 800 us の範囲で動いてもらいたい

では、min の値 600 は問題ないが、max の値 800 は正しく記録できる範囲から外れてしまっている。

このとき、400 (0x190) を記録しようとし、単純にはみ出した上位ビットを捨てると -112 (0x90) が記録される。
これは、max として 2848 を指定したとき記録される値である。

min に設定した値 600 と、max に設定したとみなされる値 2848 の中間は 1724 であり、観測されたパルスの幅である 1725 μs に近い値となる。

どうすればよかったのか

map() 関数を用いることで、ある範囲の値を別の範囲に変換できる。
この関数を用いて 0 ~ 180 の数値をパルス幅に変換し、writeMicroseconds() 関数で設定することで、attach における minmax の情報の保存先の制約を回避し、有効な範囲の一部を高い解像度で指定できる。

#include <Servo.h>

Servo servo;

void setup() {
  servo.attach(5); // パルス幅の範囲はデフォルトを用いる
  // 0 ~ 180 の範囲で表す位置 90 を、600 us ~ 800 us のパルス幅に変換して指定する
  servo.writeMicroseconds(map(90, 0, 180, 600, 800));
}

void loop() {
}

map() 関数は内部では long (32 bit) で計算を行うので、int (16 bit) の範囲の値同士であれば問題なく変換できるだろう。

さらに、この方法なら入力の範囲も自由に設定できる。
すなわち、0 ~ 180 での指定にとらわれず、0 ~ 127 などのアプリケーションで都合の良い指定方法を使うことができる。

writeMicroseconds() は、指定するパルス幅を attach() の実行時に設定した範囲内に収める補正を行う。
デフォルトの範囲 (544 ~ 2400) を超えるパルス幅を用いる場合、用いるパルス幅が全て範囲内に収まるよう、attach()minmax を設定しておかなければならない。
当然、この際に指定する値は前述の制約を受ける。

おまけ

GitHub の Issues を見ていると、この罠の被害にあったと思われる投稿があった。

Servo write() vs writeMicroseconds() · Issue #73 · arduino-libraries/Servo

この投稿では、min1400max1600 に設定しようとして、それぞれ 3752620 付近に設定されてしまったと主張している。

14001059 超であり、16001889 未満なので、いずれの値も正常に記録できる範囲の外である。

min に関しては、-214 (0xff2a) を記録しようとして 42 (0x2a) を記録し、これは 376 を指定したとき記録される値である。
max に関しては、200 (0xc8) を記録しようとして -56 (0xc8) を記録し、これは 2624 を指定したとき記録される値である。
これらの値は、観測されたと主張している値とだいたい一致している。

まとめ

Arduino の Servo ライブラリの attach() 関数で minmax を指定する場合、min の値は 33 ~ 1059 の範囲、max の値は 1889 ~ 2915 の範囲でないと、内部でオーバーフローを起こし、予期せぬ結果を生む。

この範囲を超える下限や上限を扱いたい場合、map() などを用いてアプリケーション側でパルス幅を求め、writeMicroseconds() で直接指定することで実現できることがある。

ただし、writeMicroseconds() で設定できるパルス幅は、attach() の実行時に設定した範囲内のみに限られる。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?