ウィンチV2

最初に四つのステッピングモーターで作ったのですが、扱いの面倒さ、トルクのなさから反省して、シンプルにひとつのモーターでカメラを昇降することにしました。そもそもカメラを水平なテーブルに乗せることを諦め、金属板に取り付けて昇降することにしました。

次のようなイメージ

状況把握のためにエンコーダー付きDCモーターを使います。たいていギアで減速しており、20キロ、30キロのトルクは普通です。エンコーダーはモーターの回転数を測れますが、ギアで減速した軸の回転はギア比で計算しないといけないので注意してください。良い点もあってギアのトルクで、電源が落ちてもかなりの重量を支えられます。

ハードウェアのレイアウトは次のようにした。リレー使ってるのでモーター側に改めてスイッチはいらないです。

ケースに取り付けたらモーターの軸とArduinoのUSBケーブルだけ出せばいいです。

回転数をエンコーダーで検知できるので、回転を記録して最小、最大を管理します。
Arduinoのコードは次のとおり

// センサー付きDCモーターをシリアルコマンドでコントロールするプログラム
// リールをつけているので回転数は有限とする(MAXROTAION)
// 04/2025 Umineco Company (C) All Rights reserved.
// #define DEBUG
// モーター仕様
// 赤 - モーター + -
// 黒 - エンコーダー - Arduinoのマイナス
// 黄色 ^ C1 11カウントで一周 2ピン
// 緑 - C2 3ピン
// 青 エンコーダー + Arduinoの5V 
// 白 - モーター - +

// エンコーダー出力ピンを定義
#define ENCA 2  // ピン2にA相を接続
#define ENCB 3  // ピン3にB相を接続
#define RELYA 4  // ピン4リレーA
#define RELYB 5  // ピン5リレーB

// コマンドリスト
#define STOP 0
#define START 1
#define REWIND 2
#define RESET 3
const String KEYS[] =  {"stop", "start", "rewind", "reset"};
#define UNKNOWN 9

// Motor JGB37-520
#define PPR 11 // 1回転のカウント
#define RR 169 // Reduction Ratio 減速比
// Reelの最大値
#define MAXROTATION 50

unsigned int nowmode = 0; // コマンド保持
// arduinoのlong intの範囲は-2,147,483,648 2,147,483,648まで
// モーター tickの最大値 11*169*200 = 371800
volatile long ticks = 0; // 回転位置カウンタ
volatile int rotation = 0; // リール回転数

// tickごとの割込み処理:回転を検知しカウントする。
// モーターのセッティングで+-は決まるので注意
void encoderInterrupt() {
  int bState = digitalRead(ENCB);
  
  if (bState == HIGH) {
    ticks--; // 時計回り
  } else {
    ticks++; // 反時計回り
  }
}

// 回転の妥当性チェック
void checkrotation(){
  if (nowmode == STOP) return;

  rotation = ticks / (PPR * RR); // 最初に回転数を保持

  if ( rotation > MAXROTATION){
    // stop process.
    digitalWrite(RELYA, LOW);
    digitalWrite(RELYB, LOW);
    ticks = ticks - (PPR * RR); // 一回転リワインド 止まったままになってしまう
    rotation--;
    nowmode = STOP;
    Serial.println("Auto Stop(Max)");
  }

  if (ticks < 0){
    digitalWrite(RELYA, LOW);
    digitalWrite(RELYB, LOW);
    nowmode = STOP;
    ticks = 0;
    rotation = 0;
    Serial.println("Auto Stop(0)");
  }
}

// コマンド処理
void docommand(int command){
  switch(command){
    case 0: // stop
      digitalWrite(RELYA, LOW);
      digitalWrite(RELYB, LOW);
      break;
    
    case 1: // start
      digitalWrite(RELYA, LOW);
      digitalWrite(RELYB, HIGH);
      break;

    case 2:  // rewind
      digitalWrite(RELYA, HIGH);
      digitalWrite(RELYB, LOW);
      break;

    case 3: // reset
      digitalWrite(RELYA, LOW);
      digitalWrite(RELYB, LOW);
      ticks = 0;
      rotation = 0;
      break;
  }
}

void setup() {
  pinMode(ENCA, INPUT);
  pinMode(ENCB, INPUT);
  pinMode(RELYA, OUTPUT);
  pinMode(RELYB, OUTPUT);

  Serial.begin(9600);
  
  attachInterrupt(digitalPinToInterrupt(ENCA), encoderInterrupt, RISING);
  
  nowmode = STOP;
}

void loop() {

  if (Serial.available() > 0){
    unsigned int current = UNKNOWN;

    // .availableのみでは1000msec待つ。
    String input = Serial.readStringUntil(0x0a);
    input.trim();
    // デコード
    for (unsigned int i=0; i < sizeof(KEYS)-1;i++){
      if ( KEYS[i] == input ){
        current = i; 
        break;
      }
    }
    // 新しいモードの処理
    if (nowmode != current) {
      if (current != UNKNOWN){
        Serial.print("command: ");
        Serial.println(input);
        nowmode = current;
        docommand(nowmode);
      }

      #ifdef DEBUG
        char buf[40]; // 文字列の長さに注意
        sprintf(buf, "Motor Status: %ld (%d)", ticks, rotation);
        Serial.println(buf);
      #endif


    }

  } // end of Serial command process

  // check loop.
  checkrotation();

}

Arduinoのシリアルライブラリーのreadstringだけだと文字列読み取って1000ミリ待つことになっているのでレスポンスが良くありません。readstringuntilを使い、文字列の終わりに必ずLF(¥n)をつけます。そうするとlfを発見すると即座にプログラムに制御が渡るのでいいレスポンスとなります。

Raspberry PI上のPythonプログラムは次のとおり。
FC側からPWMでSTOP, START, REWINDを送る。

# Needed to start 'systemctl start pigpiod' 
import serial
import pigpio 
from time import sleep 
# pigpio callback function 
# 1. io pin 
# 2. raise or fall 
# 3. micro sec / reset 72 min. 

# Constant 
SENSE_PIN = 12 // connect FlightController AUX PWM Pin
P_MIN = 1000 
P_TRIM = 1500 
P_MAX = 2000 
MAX_Text = "rewind" 
TRIM_Text = "start" 
MIN_Text = "stop"
SERIAL_DEV = "/dev/ttyUSB0"

# Global 
riseTick = 0 
status = "" 
pwmWidth = 0
previous = ""

def gpiocallback(gpio, level, tick): 
    global riseTick 
    global status
    global pwmWidth 
    
    range = 50 
    if level == 1: 
        riseTick = tick 
    else: 
        pwmWidth = tick - riseTick
        
        if pwmWidth > 0: 
            if pwmWidth > (P_MIN -range) and pwmWidth < (P_MIN+range): 
                status = MIN_Text 
            elif pwmWidth > (P_TRIM -range) and pwmWidth < (P_TRIM+range):
            	status = TRIM_Text 
            elif pwmWidth > (P_MAX -range) and pwmWidth < (P_MAX + range): 
                status = MAX_Text 
            else: status = "" 

# GPIO setup             
pi = pigpio.pi()  
pi.set_mode(SENSE_PIN, pigpio.INPUT) 
pi.callback(SENSE_PIN, pigpio.EITHER_EDGE, gpiocallback) 

#serial communication setup
ser = serial.Serial(SERIAL_DEV, '9600',timeout=0.3)
 
while True:
    if status != "":
        if previous == status:
            pass # do nothing
        else:
            previous = status
            print("Status Change:" + status)
            encodedText = status.encode('utf-8')
            ser.write(encodedText)
    
    sleep(1)        

ソフトウェアでPwmを検知している例は少ないと思います。ラジコン信号は3通りしかないのでかなり雑でも区別できればいいと割り切ってます。

タイトルとURLをコピーしました