有没有办法阻止伺服器“抖动”?


20

很简单,我根据从其他地方读取的一些数据来控制伺服器(9g Micro Servos)。一切正常,除了伺服器会不断“抖动”。也就是说,它们以非常细微的运动向后振动(间歇运动为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)。如果我编写的函数使得“或”的值小于DEG,那么如果我一次只更换一个伺服器该怎么办?所以有一个大..

这仅仅是伺服器的特性(也许便宜)吗?还是有解决方法?


包含粘贴链接会简单得多。这是完整的代码:http : //pastie.org/8191459

我将两个伺服器与一个激光指示器连接在一起,以实现两个自由度(X,Y)。根据多个按钮的状态,有一些选项可以通过各种方式控制伺服器。第一个是“运动”,其中有两个光敏电阻,根据曝光量,它们会影响伺服机构的位置。我尚未实现通过Xbox控制器控制伺服器的代码。第三种选择是随机运动。

在此处输入图片说明


4
您的伺服控制器显然有一些不稳定或噪音。但是,除了未记录的行“ positionServo();”外,您进入了很多与伺服控制器无关的细节,我们只能猜测是细节被掩埋了。微型控制器中的伺服控制器是否关闭?对外关闭?模拟还是数字?如果是数字的,则以什么分辨率进行测量?显示整个系统的图。
Olin Lathrop

您要给伺服器施加多少负载?
克里斯·拉普兰特

4
@OlinLathrop-(S)他使用的是标准的无线电遥控模型伺服器,该伺服器将整个伺服回路烘焙到设备中。sherrellbc-“伺服”是一个非常非常笼统的术语。不幸的是,RC模型组件制造商为生产的设备选择了最少描述性的术语。由于我们在这里处理大多数不同类型的伺服器和伺服系统,因此最好指定您的“伺服”是无线电控制的模型伺服器。
康纳·沃尔夫,

1
您的系统对我们来说太复杂了,无法为您进行故障排除。对其进行简化,然后看问题是否依然存在。当您拥有一个可以重现问题的最小系统,但仍然无法自行解决问题时,寻求帮助就变得很合适。
Phil Frost

12
设计激光定向系统的一般注意事项:将反射镜放在伺服系统上,然后将另一个定向。这样,您不必将一个伺服器安装在另一个伺服器上,也不必将激光安装在伺服器上,然后就可以将它们全部牢固地固定下来。
pjc50

Answers:


27

在Arduino上使用Servo库时,常见的伺服器嗡嗡声是中断驱动的伺服例程实际上并不能提供非常稳定的输出脉冲。由于AVR在Arduino运行时中会使用中断来服务millis()时钟和其他事物,因此Servo库中的抖动约为几微秒,这会导致伺服器发生很多移动。

解决方法是编写自己的脉冲。像这样:

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中获得更好的分辨率。

伺服器中引起嗡嗡声的另一个原因是带有廉价传感器的廉价伺服器,其中传感器有噪声,或者当传感器实际无法编码脉冲请求的确切位置时。伺服器将看到“移至位置1822”并尝试执行此操作,但最终显示为传感器读数1823。然后,伺服器将显示“向后移一点”,并最终显示为传感器读数1821。重复!解决此问题的方法是使用高质量的伺服器。理想情况下,根本不是业余伺服器,而是带有光学或磁性绝对编码器的真实伺服器。

最后,如果伺服器没有足够的功率,或者您尝试从Arduino的5V导轨驱动其功率,则如上所述,这将在伺服器中产生电压骤降引起的嗡嗡声。您可能可以使用大型电解电容器来固定它(无论如何,这对于常规滤波而言是个好主意),但是您更可能希望确保伺服电源实际上可以在伺服电压下提供几安培的电流。


1
R / C伺服控制信号为PWM。脉冲宽度标称值为1-2毫秒,脉冲重复间隔为20到50毫秒。我希望脉冲宽度的变化超过10微秒会导致伺服器抖动。如果脉冲宽度稳定,则PRI中的抖动通常不会成为问题。(我的简易555控制器以相同的量来改变脉冲宽度和PRI:伺服器不在乎。)
John R. Strohm

您说的一切都是对的,除了抖动-伺服器会在脉冲宽度“关闭” 10 us之前抖动。普通Arduino的中断抖动(在添加库之前)可能高达10 us!我粘贴的代码旨在在Arduino环境中生成稳定的岩石脉冲,通常在稳定岩石的伺服脉冲方面不如专用555电路那么好。
乔恩·瓦特

4
我刚刚写了一篇文章,展示了如何像上面的代码一样在Arduino生成精确的脉冲,只是它使用了Timer硬件-无需关闭中断并弄乱Arduino运行时。
bigjosh

请注意,Arduino仅在几个引脚(PWM引脚)上支持计时器输出,并且您不能将Timer0引脚用于此方法。因此,只有4个引脚可以在常规Arduino UNO上真正使用。如果您需要驱动4个或更少的伺服器,并且不需要其他计时器,那是一个不错的选择。
乔恩·瓦特

21

这称为“嗡嗡声”。

有几件事会导致它。导致伺服电源不稳定的常见原因。R / C伺服器在首次使电动机运动时会产生一些大的峰值。

许多年前,我玩过Tower Hobbies Royal Titan Standard伺服器,由555和单晶体管逆变器控制它。简单的控制电路。我了解到,伺服电机在连续运动时从5V电源汲取了250 mA的电流。嗡嗡声很容易引起半安培峰值。(也许更多:我只是在监视台式电源上的电流表,而不是对电流检测分流器进行范围界定。)

直接在我的伺服器上花了220 uF来驯服它。

尝试将一个至少为100 uF的电解电容器直接跨接至伺服器的电源,并在电气上尽可能靠近伺服器,看看是否有帮助。

根据这些实验,我永远不会考虑在不增加电容器的情况下将R / C伺服器用于任何事情。这包括无线电控制模型。

这也可能是由于伺服器内部伺服罐中的脏物引起的。首先尝试电容器。


6

仅在达到或接近伺服器极限(0度或180度)时才发生嗡嗡声/抖动吗?如果是这样,可能有一个简单的解决方法。我发现廉价的伺服器不知道如何很好地保持其运动极限,这可能导致您提到的嗡嗡声/抖动。但是,如果仅将其范围限制为10〜170度,此问题将得到解决。

如果这还不足以解决您的问题,您可以按照其他答案中提到的更复杂的修复方法进行操作,例如更好的功率,更好的伺服传感器等。


是的,对于我的SG90,这些值是18到162。实际上并没有使32度无法到达,也许只有一半。
Maxim Kachurovskiy

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)的电容器,地线为我解决了这个问题(我将电容器放置在中间,在原始伺服电缆连接到我的内部电缆的位置)。

另外,我无法使用标准的Servo库,因为它在Arduino Mega上抓到的第一个定时器是Timer-5,我需要它来进行频率测量。由于我仅使用10个伺服器,因此我从伺服库中提取了关键代码,并将其更改为使用Timer-1(每个计时器在Mega上最多支持12个伺服器)。

下面是独立代码供参考,如果您想将其包含在自己的项目中,则只能使用顶部,下部是测试顶部(它侦听串行端口,可以给sX和vX命令,其中sX选择一个伺服,s0将选择第一个伺服,vX设置我们中的伺服位置,因此v1500会将伺服0设置为中间位置(假设您首先给出了s0命令)。

//----------------------------------------------------------------
// 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

在这种情况下,我最好的选择是在每个操作中附加和分离Servos。

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

PS。这根本不是质量,只是一种解决方法。


1

尽管其他人针对此伺服嗡嗡声问题提出了各种解决方案,但在该线程和其他Arduino论坛中,即:

  • 产生自己的脉冲
  • 单独提供5V电源
  • 避免将其推到极限(例如,使用10-170而不是0-180)
  • 跨接电容器
  • 移动后分离

就我而言,我发现在Arduino板上插入9V / 2A电源时,嗡嗡声停止了。但是,最简单的最终解决方案只是缓慢移动伺服器:

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,反电动势,电流)有所了解。有了这些信息,电机控制程序就可以预测伺服器响应来自控制器的给定输入(即电流/电压输入)将执行的操作,并在此基础上生成最佳输入以实现所需的输出。

可以想象,这有些复杂,但是在Internet上搜索伺服反馈将使您入门。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.