サーボの「揺れ」を止める方法はありますか?


20

非常に簡単に言えば、私は他の場所から読み込まれたデータに基づいてサーボ(9gマイクロサーボ)を制御しています。サーボが常に「揺れる」ことを除いて、すべてが正常に機能します。つまり、非常に微妙な動きで振動します(1/2-> 1cm程度の断続的な動き)。

私は次のようなことをしてソフトウェアでこの問題を修正しようとしました:

  do{
    delay(DTIME);
    positionServo();
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("X position: ");
    lcd.print(xRead);
    lcd.setCursor(0,1);
    lcd.print("Y position: ");
    lcd.print(yRead);
  }while( readChange() ); //while there has been change

do-whileが必要な場合は、マッピングされたサーボ値を格納する変数を初期化します(arduinoサーボライブラリを使用)。

readChange()関数は次のように定義されます:

int readChange(){
  int x_Temp, y_Temp;

  x_Temp = map(analogRead(x_axisReadPin), 0, 1023, 0, 179);
  y_Temp = map(analogRead(y_axisReadPin), 0, 1023, 0, 179);

  if( abs(x_Temp - xRead) < DEG && abs(y_Temp - yRead) < DEG ) return 0; // no change 
  else return 1; //change
}

xReadは初期化された値です(最初のマップされたサーボ出力)。

しかし、これは本当に良いアプローチではありません。両方の値がDEGの係数(約10度、または私の場合は約0.28V)だけ変化していないことが必要です。ORがDEG未満になるように関数を記述した場合、一度に1つのサーボのみを変更した場合はどうなりますか?デリマがあります..

これは単にサーボの特性ですか(おそらく安価なものですか?)、または回避策はありますか?


ペーストのリンクを含める方がはるかに簡単です。完全なコードは次のとおりです。http//pastie.org/8191459

2つの自由度(X、Y)を可能にするために、2つのサーボとレーザーポインターを一緒に取り付けました。いくつかのボタンの状態に基づいて、さまざまな方法でサーボを制御するオプションがあります。1つ目は「モーション」で、露光量に基づいてサーボの位置に影響を与える2つのフォトレジスターがあります。Xboxコントローラーでサーボを制御するコードをまだ実装していません。3番目のオプションは、ランダム化された動きです。

ここに画像の説明を入力してください


4
サーボコントローラーに、少し不安定またはノイズがあるようです。ただし、ドキュメント化されていない行「positionServo();」以外は、サーボコントローラーとは何の関係もないように思われるものの多くの詳細に進みます。マイクロでサーボコントローラーが閉じていますか?外部閉鎖ですか?アナログかデジタルか?デジタルの場合、どの解像度で測定されていますか?システム全体の図を表示します。
オリンラスロップ

どのくらいの負荷をサーボにかけていますか?
クリスラプランテ

4
@OlinLathrop-(S)彼は標準の無線制御モデルのサーボを使用しており、サーボループ全体がデバイスに焼き付けられています。sherrellbc-「サーボ」は非常に一般的な用語です。残念ながら、RCモデルのコンポーネントメーカーは、製造するデバイスについて最も記述的でない用語を選択しました。ここではほとんどの種類のサーボとサーボシステムを扱っているため、「サーボ」が無線制御モデルのサーボであることを指定することはおそらく良い考えです。
コナーウルフ

1
システムが複雑すぎるため、トラブルシューティングを行うことができません。単純化して、まだ問題があるかどうかを確認してください。問題を再現する最小限のシステムがあり、それでも自分で解決できない場合、助けを求めることが適切になります。
フィルフロスト

12
レーザー誘導システムを設計する際の一般的な注意事項:サーボにミラーを置き、一方をもう一方に向けます。そうすれば、1つのサーボを他のサーボにマウントしたり、レーザーをサーボにマウントしたりする必要がなく、それらをすべてしっかりと固定できます。
pjc50

回答:


27

ArduinoでServoライブラリを使用する場合、サーボバズの一般的な原因は、割り込み駆動のサーボルーチンが実際には非常に安定した出力パルスを与えないことです。AVRは、Arduinoランタイムのmillis()クロックおよびその他の処理のために割り込みを受け取るため、サーボライブラリのジッターは数マイクロ秒のオーダーであり、これはサーボの多くの動きに変換されます。

これに対する修正は、独自のパルスを書くことです。このようなもの:

cli();
long start = micros();
digitalWrite(PIN, HIGH);
while (micros() - start < duration)
  ;
digitalWrite(PIN, LOW);
sei();

これにより、他の割り込みがオフになり、よりクリーンなPWMパルスが生成されます。ただし、「millis()タイマーがいくつかのクロックティックをミスします。(「micros()」関数は他の何かと呼ばれる可能性があります。正確には何を忘れます。)

一般に、タイミングが重要なコードの場合、Arduinoランタイムを完全に取り除き、Arduino環境を強化するavr-gccコンパイラーとavr-libcライブラリーを使用して独自のランタイムを作成します。その後、タイマーを設定して、マイクロ秒ごとに4回、またはマイクロ秒ごとに16回のティックを設定し、PWMの分解能を大幅に向上させることができます。

サーボのバズのもう1つの原因は、センサーがノイズの多い安価なセンサーを備えた安価なサーボ、またはパルスで要求された正確な位置を実際にセンサーでエンコードできない場合です。サーボは「1822の位置に移動」し、それを試みますが、センサーの読み取り値1823で終わります。その後、サーボは「少し戻って」と言い、センサーの読み取り値1821で終わります。繰り返します!これに対する修正は、高品質のサーボを使用することです。理想的には、趣味用のサーボではなく、光学式または磁気式のアブソリュートエンコーダを備えた実際のサーボです。

最後に、サーボに十分な電力が供給されない場合、またはArduinoの5Vレールから電力を駆動しようとすると、上記で提案したように、サーボに電圧低下に起因するバズが発生します。大きな電解コンデンサで固定することもできます(いずれにしても一般的なフィルタリングに適しています)が、実際には、サーボ電源が実際にサーボ電圧で数アンペアの電流を供給できるようにする必要があります。


1
R / Cサーボ制御信号はPWMです。パルス幅は通常1〜2ミリ秒、パルス繰り返し間隔は20〜50ミリ秒です。パルス幅の変動が約10マイクロ秒を超えると、サーボが不安定になります。パルス幅が安定していれば、一般にPRIのジッタは問題になりません。(私の汚れの簡単な555コントローラーは、パルス幅とPRIを同じ量変化させました。サーボは気にしませんでした。)
ジョンR.ストローム

ジッタを除いて、あなたの言うことはすべて真実です。パルス幅が10マイクロ秒「オフ」になる前にサーボがジッタします。そして、(ライブラリを追加する前の)プレーンArduinoの割り込みジッターは10 usにも達することがあります!貼り付けたコードは、Arduino環境でロック安定パルスを生成することを目的としています。これは、専用の555回路ほどロック安定サーボパルスが得意ではありません。
ジョンワット

4
上記のコードと同様に、Arduinoで正確なパルス生成する方法を示す記事を書きましたが、タイマーハードウェアを使用し、割り込みをオフにしてArduinoランタイムを台無しにする必要はありません。
bigjosh

Arduinoはいくつかのピン(PWMピン)でのみタイマー出力をサポートし、この方法にTimer0ピンを使用できないことに注意してください。したがって、通常のArduino UNOでは実際に機能するピンは4つしかありません。4個以下のサーボを駆動する必要があり、他の何かのためにタイマーを必要としない場合、それは良いオプションです。
ジョンワット

21

これは「バズ」と呼ばれます。

それを引き起こすものがいくつかあります。サーボへの電力の不安定さが一般的な原因です。R / Cサーボは、モーターを最初に動かしたときにBIGスパイクを発生させることがあります。

何年も前、私はTower Hobbies Royal Titan Standardサーボを使って、555と1トランジスタインバーターで制御していました。シンプルな制御回路。連続運動中に、サーボモーターが5V電源から250 mAを消費することを知りました。ざわめき、それは簡単にハーフアンプのスパイクを描きました。(たぶん、私はベンチ電源の電流計を監視しているだけで、電流検出シャントをスコーピングしていませんでした。)

それを飼いならすために、サーボ全体で直接220 uFかかりました。

少なくとも100 uFの電解コンデンサをサーボの電源に直接、できるだけ電気的に近い位置に配置してみて、それが役立つかどうかを確認してください。

これらの実験に基づいて、コンデンサを追加せずに何かにR / Cサーボを使用することは考えません。これには、無線制御モデルが含まれます。

これは、サーボ内部のサーボポットの汚れによっても発生します。最初にコンデンサを試してください。


6

サーボの限界(0度または180度)で、またはそれに近いときにのみ、ブザー/シェーキングが発生しますか?もしそうなら、あなたのための簡単な修正があるかもしれません。安価なサーボは、その動きの限界にうまくとどまる方法をよく知らないことがわかりました。ただし、範囲を10〜170度に制限すると、問題は修正されます。

それで十分でない場合は、他の回答で言及されている、より優れたパワー、より良いサーボセンサーなど、より複雑な修正に従うことができます。


はい、SG90の場合、これらの値は18〜162です。実際には32度に到達できませんでした。
マキシムカチュロフスキー

5

移動した後、「サーボをオフにする」ことで問題を修正しました。例:

pinMode(PIN, OUTPUT);
myservo.write(degree);
//give servo time to move
delay(5000);
pinMode(PIN, INPUT);

PINサーボに接続されているPWMピンです。入力モードに切り替えることで、振動を止めることができました。これは最適なソリューションではありません。他のソリューションを最初に試すことをお勧めします。


他の解決策を試しましたが、これが唯一の解決策でした、+ 1。他のすべてが失敗したときの素晴らしいアイデア!
スナッパワパ

3

MG90Sサーボ(ジッタリング)でも同じ問題があり、信号線は比較的長く(60〜70cm)、信号線と接地線に103(10nF)コンデンサを配置すると問題が解決しました(コンデンサをどこかに配置しました)真ん中、元のサーボケーブルが私の内部ケーブルに接続するポイント)。

さらに、Arduino Megaで取得する最初のタイマーはTimer-5であり、周波数測定に必要なため、標準のServoライブラリを使用できませんでした。10個のサーボのみを使用しているため、Servoライブラリからキーコードを抽出し、Timer-1を使用するように変更しました(各タイマーはMegaで最大12個のサーボをサポートします)。

スタンドアロンコードは参照用です。独自のプロジェクトに含める場合は、上部のみを使用できます。下部は上部をテストします(シリアルポートでリッスンし、sXを指定できます) vXコマンドは、sXがサーボを選択し、s0が最初のサーボを選択し、vXがサーボ位置を設定するため、v1500は、s0コマンドを最初に与えたと仮定して、servo0を中間位置に設定します。

//----------------------------------------------------------------
// This is the actual servo code extracted from the servo library
//----------------------------------------------------------------

#include <avr/pgmspace.h>

//----converts microseconds to tick (assumes prescale of 8)
#define usToTicks(_us)    (( clockCyclesPerMicrosecond()* _us) / 8)

#define MIN_PULSE_WIDTH     544     // the shortest pulse sent to a servo  
#define MAX_PULSE_WIDTH     2400    // the longest pulse sent to a servo 
#define DEFAULT_PULSE_WIDTH 1500    // default pulse width when servo is attached
#define REFRESH_INTERVAL    20000   // minumim time to refresh servos in microseconds

#define TRIM_DURATION       2       // compensation ticks to trim adjust for digitalWrite delays // 12 August 2009

struct s_servar {
    //----counter for the servo being pulsed for each timer (or -1 if refresh interval)
    int8_t  channel;
};
static volatile struct s_servar gl_vars;

//----maximum number of servos controlled by one timer 
#define SERVOS_PER_TIMER    12
//----this can not be higher than SERVOS_PER_TIMER
#define SERVO_AMOUNT        6

struct s_servo {
    volatile unsigned int   ticks;
    unsigned char           pin;
};
struct s_servo  gl_servos[SERVO_AMOUNT] = {
    { usToTicks(DEFAULT_PULSE_WIDTH), 22 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 23 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 24 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 25 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 26 },
    { usToTicks(DEFAULT_PULSE_WIDTH), 27 },
};

ISR(TIMER1_COMPA_vect) {
    unsigned char       servooff;
    if(gl_vars.channel < 0 ) {
        //----channel set to -1 indicated that refresh interval completed so reset the timer
        TCNT1 = 0;
    }
    else{
        servooff = gl_vars.channel;
        if(servooff < SERVO_AMOUNT) {
            //----end the pulse
            digitalWrite(gl_servos[servooff].pin, LOW);
        }
    }
    //----increment to the next channel
    gl_vars.channel++;
    servooff = gl_vars.channel;
    if(servooff < SERVO_AMOUNT) {
        //----set timer interrupt for pulse length
        OCR1A = TCNT1 + gl_servos[servooff].ticks;
        //----start the pulse
        digitalWrite(gl_servos[servooff].pin, HIGH);
    }
    else {
        // finished all channels so wait for the refresh period to expire before starting over
        //----allow a few ticks to ensure the next OCR1A not missed
        if(((unsigned)TCNT1) + 4 < usToTicks(REFRESH_INTERVAL)) {
            OCR1A = (unsigned int)usToTicks(REFRESH_INTERVAL);
        }
        else {
            //----at least REFRESH_INTERVAL has elapsed
            OCR1A = TCNT1 + 4; 
        }
        //----this will get incremented at the end of the refresh period to start again at the first channel
        gl_vars.channel = -1;
    }
}

void InitServoISR() {
    unsigned char   ct;
    gl_vars.channel = -1;
    //----init timer 1
    TCCR1A = 0;             // normal counting mode
    TCCR1B = _BV(CS11);     // set prescaler of 8
    TCNT1 = 0;              // clear the timer count
    TIFR1 |= _BV(OCF1A);    // clear any pending interrupts;
    TIMSK1 |= _BV(OCIE1A);  // enable the output compare interrupt
    //----set all servo pins to output
    for(ct = 0; ct < SERVO_AMOUNT; ct++) {
        pinMode(gl_servos[ct].pin, OUTPUT); 
    }
}

void SetServoMicroSecs(unsigned char servooff, unsigned short value) {
    uint8_t oldSREG;
    if(servooff < SERVO_AMOUNT) {
        //----ensure pulse width is in range
        if(value < MIN_PULSE_WIDTH) { value = MIN_PULSE_WIDTH; }
        else {
            if(value > MAX_PULSE_WIDTH) { value = MAX_PULSE_WIDTH; }
        }
        value -= TRIM_DURATION;
        value = usToTicks(value);
        oldSREG = SREG;
        cli();
        gl_servos[servooff].ticks = value;
        SREG = oldSREG;
    }
}

//------------------------------------------------
// This is code to test the above servo functions
//------------------------------------------------

#define ERR_OK          0
#define ERR_UNKNOWN     1
#define ERR_OUTOFRANGE  2

#define SERDEBUG_CODE
#define MAX_SER_BUF     12

void setup() { 
    InitServoISR();

    #ifdef SERDEBUG_CODE
    Serial.begin(9600);
    Serial.println(F("Start"));
    #endif
}


void loop() {
    #ifdef SERDEBUG_CODE
    uint8_t         ct, chr;
    char            buf[MAX_SER_BUF];
    ct = 0;
    #endif   
    //----main while loop
    while(1) {
        #ifdef SERDEBUG_CODE
        //--------------------
        // Serial Port
        //--------------------
        while (Serial.available() > 0) {
            chr = Serial.read();
            if(chr == '\n') {
                ProcSerCmd(buf, ct);
                ct = 0;
            }
            else {
                //----if for some reason we exceed buffer size we wrap around
                if(ct >= MAX_SER_BUF) { ct = 0; } 
                buf[ct] = chr;
                ct++;
            }
        }
        #endif
    }
}

//------------------------------
// Serial Port Code
//------------------------------

#ifdef SERDEBUG_CODE
uint16_t RetrieveNumber(char *buf, uint8_t size) {
    //--------------------------------------------------------------
    // This function tries to convert a string into a 16 bit number
    // Mainly for test so no strict checking
    //--------------------------------------------------------------
    int8_t  ct;
    uint16_t    out, mult, chr;
    out = 0;
    mult = 1;
    for(ct = size - 1; ct >= 0; ct--) {
        chr = buf[ct];
        if(chr < '0' || chr > '9') { continue; }
        chr -= '0';
        chr *= mult;
        out += chr;
        mult *= 10;
    }
    return(out);
}

void ProcSerCmd(char *buf, uint8_t size) {
    //-----------------------------------------------------------
    // supported test commands
    // sX   X = 0 to SERVO_AMOUNT       Sets the servo for test
    // vX   X = MIN to MAX PULSE WIDTH  Sets the test servo to value X
    //-----------------------------------------------------------
    static unsigned char    lgl_servooff = 0;
    uint8_t                 chr, errcode;
    uint16_t                value;
    errcode = 0;
    while(1) {
        chr = buf[0];
        //----test commands (used during development)
        if(chr == 's') {
            value = RetrieveNumber(buf + 1, size - 1);
            if(value < 0 || value >= SERVO_AMOUNT) { errcode = ERR_OUTOFRANGE; break; }
            lgl_servooff = (unsigned char)value;
            break;
        }
        if(chr == 'v') {
            value = RetrieveNumber(buf + 1, size - 1);
            if(value < MIN_PULSE_WIDTH || value > MAX_PULSE_WIDTH) { errcode = ERR_OUTOFRANGE; break; }
            SetServoMicroSecs(lgl_servooff, value);
            break;
        }
        errcode = ERR_UNKNOWN;
        break;
    }
    if(errcode == 0) {
        Serial.println(F("OK"));
    }
    else {
        Serial.write('E');    
        Serial.println(errcode);
    }
}
#endif

2

この場合の最良のオプションは、各操作でサーボを接続および切断することでした。

servo1.attach(pinServo1);
for (pos = 0; pos <= servoMax; pos += 1) {
    servo1.write(pos);
    delay(10);
}
servo1.detach(pinServo1);

PS。これはまったく品質ではなく、回避策です。


1

他の人は、このスレッドや他のArduinoフォーラムで、このサーボの話題の問題に対するさまざまな解決策を提案していますが、すなわち:

  • 独自のパルスを生成する
  • 5V電源を個別に供給する
  • 限界までプッシュしないでください(例:0-180ではなく10-170を使用)
  • コンデンサを接続する
  • 移動後に切り離す

私の場合、9V / 2Aの電源がArduinoボードに接続されると、ブザーが停止することがわかりました。しかし、最も簡単な究極の解決策は、単にサーボをゆっくり動かすことです。

for (pos = servo.read(); pos < 180; pos += 2) {
  servo.write(pos);
  delay(40);
}

YMMV。


1
#include <Servo.h>             //Servo library
Servo servo_test;        //initialize a servo object for the connected servo  

int angle = 0;
int sw1 = 7;   // pushbutton connected to digital pin 7
int val=0;

void setup()
{
   servo_test.attach(2);     // attach the signal pin of servo to pin2 of arduino
   pinMode(sw1, INPUT_PULLUP);
}

void loop()
{
    val = digitalRead(sw1);
    if (val == LOW)
    {  
        servo_test.attach(2);     // attach the signal pin of servo to pin2 of arduino
        for(angle = 0; angle < 90; angle += 1)   // command to move from 0 degrees to 90 degrees 
        {                                  
            servo_test.write(angle);                 //command to rotate the servo to the specified angle
            delay(5);                     
        } 
    }
    else
    {
        servo_test.detach();// After servo motor stops we need detach commmand to stop vibration
    }
}

0

私には、これはフィードバックループのエラーまたは調整ミスのように見えます。ハイエンドのサーボ制御システムは、モーターの特性(インダクタンス、トルク、ピーク電流、極数)、負荷(慣性モーメント)、および瞬時条件(位置、rpm、逆起電力、電流)についてある程度の知識を持っています。この情報を使用して、モーター制御プログラムは、コントローラーからの所定の入力(電流/電圧入力)に応じてサーボが何をするかを予測し、それに基づいて最適な出力を生成する最適な入力を生成します。

ご想像のとおり、これはやや複雑なものですが、サーボフィードバックに関するインターネット検索で開始できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.