NT東京に出展したびっくりチキンのサーボーモーター制御のプログラム

こんにちは、id:takkumattsuです。

9/7(土)にあったNT東京に出展した『びっくりチキンをMIDIキーボードに繋いでみた』 のサーボモーター制御プログラムを公開しました!

github.com

構成

構成としては以下の感じ

ラズパイ上でMIDIの入力を受け付けてGPIOを介してサーボドライバに繋がったサーボーモーターを動かしています。

www.youtube.com

(動画は作成途中の映像なのでレスポンスがイマイチですが)

使ったライブラリ

コード解説

github.com

基本的にはmain.pyに全て書いてあるけど、今回重要になってくるのは対応するサーボのデューティー比と角度に対するパルス幅かと思います。

この辺の詳細な解説はボリュームを使ってサーボモータSG90を0~180°回す【テスター測定】 – ふじむぅスタジオさんが解説してくれているのでここをかなり参考にしました。

fujimoostudios.org

今回使ったサーボモーター TD-8125MGここの資料をみると

If the servo has less than 180 degrees range, the minimum (500us) and maximum pulse width (2500us) must be adjusted in the controller.

とあります。

サーボモーターはこのパルス幅の比で動かす角度を調整することができます。

コートだとこの辺で定義していますね

# 定数の定義
PWM_FREQUENCY = 50  # PWM信号の周波数 (Hz)
MIN_PULSE_WIDTH = 500  # 最小パルス幅 (µs)
MAX_PULSE_WIDTH = 2500  # 最大パルス幅 (µs)
ANGLE_RANGE = 180  # サーボモーターの角度範囲

get_duty_cycle(degree)get_pulse_width(degree) の関数解説

この2つの関数は、PWM(Pulse Width Modulation)を用いて角度に対応する信号を生成するためのものです

  1. get_pulse_width(degree):

    • 角度を指定して、その角度に対応するパルス幅(マイクロ秒)を計算します。
    • サーボモーターの場合、角度によって異なるパルス幅が必要で、この関数がそのパルス幅を決定します。
  2. get_duty_cycle(degree):

    • get_pulse_width(degree) で得られたパルス幅を使って、PWMのデューティサイクルを計算します。
    • デューティサイクルは、信号がオンの割合を示し、PWM信号の精度や強度を制御します。

get_pulse_width(degree) の詳細解説

def get_pulse_width(degree):
    """角度に対応するパルス幅を計算する関数"""
    pulse_width = MIN_PULSE_WIDTH + (MAX_PULSE_WIDTH - MIN_PULSE_WIDTH) * (degree / ANGLE_RANGE)
    return pulse_width
  1. 引数:

    • degree: 角度(度単位)。サーボモーターなどで目標とする位置の角度です。
  2. 処理内容:

    • MIN_PULSE_WIDTH: 最小のパルス幅(マイクロ秒)。モーターの最小角度に対応する信号幅。
    • MAX_PULSE_WIDTH: 最大のパルス幅(マイクロ秒)。モーターの最大角度に対応する信号幅。
    • ANGLE_RANGE: 操作できる角度の範囲(例えば、0度から180度など)。
    • (MAX_PULSE_WIDTH - MIN_PULSE_WIDTH) * (degree / ANGLE_RANGE):
      • 全角度範囲に対して、現在の角度がどれだけの割合かを計算し、その割合に応じたパルス幅を算出します。
    • MIN_PULSE_WIDTH + ...:
      • 上で計算した割合のパルス幅に、最小パルス幅を加算することで、現在の角度に対応するパルス幅を得ます。
  3. 返り値:

    • 指定された角度に対応するパルス幅を返します。このパルス幅は後でデューティサイクル計算に使われます。

get_duty_cycle(degree) の詳細解説

def get_duty_cycle(degree):
    """角度に対応するデューティサイクルを計算する関数"""
    pulse_width = get_pulse_width(degree)
    duty_cycle = int((pulse_width / 1000000) * PWM_FREQUENCY * 65535)
    return duty_cycle
  1. 引数:

    • degree: 角度(度単位)。サーボモーターなどの制御に使用する角度です。
  2. 処理内容:

    • get_pulse_width(degree):
      • 指定された角度に対応するパルス幅を計算します。
    • pulse_width / 1000000:
      • パルス幅をマイクロ秒から秒に変換します。
    • (pulse_width / 1000000) * PWM_FREQUENCY * 65535:
      • 秒に変換したパルス幅を基に、PWMの周波数(PWM_FREQUENCY)と16ビットの最大値 65535 を掛けてデューティサイクルを算出します。
    • int(...):
      • 結果を整数に変換します。デューティサイクルは整数で表現されることが多いためです。
  3. 返り値:

    • 計算されたデューティサイクルの整数値を返します。これにより、PWM信号のオンの時間の割合が決定されます。

その他

あとはMIDIの音階に合わせて対応するサーボモーターを動かしているという感じですね

def move_servo(channel, target_angle):
    """サーボモーターを指定の角度に動かす関数"""
    duty_cycle = get_duty_cycle(target_angle)
    timestamped_print(f"Moving to {target_angle:>3}° on channel {channel} (Duty Cycle: {duty_cycle}, Pulse Width: {get_pulse_width(target_angle)})")
    if should_send_signal:
        servo_channels[channel].duty_cycle = duty_cycle

def perform_servo_movement(channel):
    """サーボモーターを一連の動作に従って動かす関数"""
    try:
        move_servo(channel, ORIGIN_ANGLE)
        time.sleep(SLEEP_TIME_MS / 1000)
        if use_target_angle:
            move_servo(channel, TARGET_ANGLE)
            time.sleep(SLEEP_TIME_MS / 1000)
        move_servo(channel, START_ANGLE)
    except Exception as e:
        timestamped_print(f"An error occurred: {str(e)}", error=True)
    finally:
        timestamped_print("End.")

今回はチキンを押すために45度位置をスタートとして0度(チキンを押す)、そして45度の位置に戻るようにプログラムしています。

終わりに

今回はサーボモーターを動かしているpythonのコードについて書いてみました。
実際にチキンの音を変える方法とかはまた記事書くかも?

もしチキンを鳴らすときの参考になりましたら幸いです。