Как определить минимальное время, за которое сервопривод достигнет пункта назначения?

Мы все знаем, что сервоприводы не двигаются мгновенно, поэтому принято добавлять задержку или цикл millis() всякий раз, когда вы вводите команду servo.write(), чтобы дать ей время достичь своей цели, прежде чем двигаться дальше. к следующему.

Что меня раздражает, так это то, что это может быть неэффективно в ситуациях, когда последняя позиция сервопривода может быть близка к своей цели - в таких случаях вам может понадобиться только очень короткая задержка, тогда как если он должен пройти полные 359 градусов, вам может понадобиться больше времени.

Я был разочарован тем, что servo.read() совершенно бесполезен для этой задачи (зачем вообще включать его в библиотеку?), как показано в следующем коде:

#include <Adafruit_TiCoServo.h>
Adafruit_TiCoServo servo;
const uint8_t PIN_SERVO = 9;

void setup() {
  servo.attach(PIN_SERVO);
  Serial.begin(9600);


  Serial.println("start");
  servo.write(180);
  delay(1000);

  int pos;
  while(pos != 0){
    pos = servo.read();
    Serial.println(pos); // потому что он возвращает только то, что было последней операцией write()
                         // был отправлен, он сразу же думает, что находится в позиции 0, и завершает работу.
    servo.write(0);
  }

  Serial.println("Done");
}

Так что это отстой. Кажется, мои варианты:

  1. просто используйте длительную задержку, соответствующую наихудшему сценарию. Я ненавижу это.
  2. добавьте концевой выключатель в конечное целевое положение и просто прочитайте состояние этого переключателя
  3. создайте какой-нибудь сложный энкодер, с которого вы сможете считывать абсолютный угол, или купите более дорогой сервопривод, который может сообщить вам эту информацию

Для справки, сервопривод, с которым я играю, — это сервопривод для хобби: MG90S

В частности, мой вопрос заключается в том, чтобы подтвердить, что для MG90S нет других вариантов, кроме тех, которые я перечислил выше.

, 👍0

Обсуждение

Чего именно вы пытаетесь достичь? Какое решение является лучшим, зависит от фактической цели вашего проекта. Если вы хотите только дождаться окончания движения сервопривода, прежде чем продолжить свой код, как насчет линейного масштабирования задержки с разницей углов? Фактор можно найти, проверив его самостоятельно. Также имейте в виду, что это также зависит от нагрузки на сервопривод., @chrisl

@jsotola, конечно, достаточно справедливо, но я оставлю вам возможность пропустить те части, которые вы считаете оскорбительными, и сосредоточиться на ответе. И вам другой комментарий, справедливое замечание - я попытался сделать более очевидным то, что я прошу. Вы знаете, как идут эти вопросы — вы начинаете их писать и в итоге пишете больше решений, чем ожидали. Но я все же хочу узнать от других, есть ли другие подходы., @Tom Auger

Я считаю servo.read() удобной функцией. Мне не нужно запоминать последнюю целевую позицию, я могу запросить объект через другие функции и методы. Я не знаю ни одного сервопривода для хобби, который возвращает фактическое положение, потому что они обычно используются в радиоуправляемых моделях, у которых нет пути обратной связи., @the busybee


2 ответа


Лучший ответ:

1

в этом случае в основном есть одно простое решение, а также есть несколько лучших.

проблема, с которой вы столкнулись при использовании метода: Прежде всего, функция read() действительно возвращает только то, что вы ей отправляете, в основном это полезно, если вы хотите знать, в какую позицию вы отправляете ее в какой-либо другой части кода (например, может использоваться для планирования того, каким образом ходить первым, если вам нужно пройти много точек, или если вы уже идете в нужную позицию). но так как радиоуправляемый сервопривод имеет только один сигнальный провод, и этот провод не отправляет никаких сигналов обратно, вы не знаете, в каком состоянии они находятся, радиоуправляемые сервоприводы в основном предназначены для простоты использования, а не для точности и скорости, если вам нужно, вы можете посмотреть в настоящий сервопривод (шаговый двигатель). так что да, эта функция не возвращает фактическую позицию, она возвращает желаемую позицию.

Решение 1: простой программный метод: для этого вы можете либо прочитать таблицу данных, чтобы прочитать скорость в продолжительности на градус, либо измерить ее, а затем использовать ее для расчета времени, которое потребуется . На самом деле здесь действительно используется функция Servo.read(), так что это прекрасный пример того, почему она вам иногда может понадобиться или понадобиться.

  1. сохранить продолжительность в миллисекундах на градус в переменной.
  2. следующее, непосредственно перед записью в сервопривод, используйте Servo.read(), чтобы получить текущую теоретическую позицию, и используйте ее в сочетании с позицией, в которую вы хотите перейти, чтобы вычислить разницу в держе (например, 100 и 40 приводят к разнице из 60), затем умножьте это на продолжительность каждого шага. и вы получите ответ о том, сколько времени займет изменение.

Пример 1 предупреждение, не проверено, может содержать орфографические ошибки и т. п.

 /* настроенная задержка Sweep
Этот пример кода находится в общественном достоянии.
Нет гарантии

изменено 27 марта 2023 г.
Даан В.Лун
*/

#include <Servo.h>
#define DELAYPERDEGREE 2 // это значение для сервопривода sg90 из таблицы данных (0,12 с (120 мс) на 60 градусов.
#define SAFETYMARGIN 110 // это добавление 10% дополнительного времени ожидания, чтобы убедиться, что оно завершится вовремя даже под нагрузкой.
//# define используется как переменная, которую не нужно менять, чисто в коде, а не в оперативной памяти
unsigned long waitDuration=0;//переменная, показывающая, как долго ждать.

Servo myservo; 

unsigned long CalcWait(Servo s,int desiredPosition)//метод для расчета и возврата длительности ожидания в мс 1000 мс это 1 с.
{
  int spos=s.read();
  if(spos>desiredPosition){return (((spos-desiredPosition)*DELAYPERDEGREE)*SAFETYMARGIN)/100;}
  else{ return(((desiredPosition-spos)*DELAYPERDEGREE)*SAFETYMARGIN)/100;}
}//конец метода

void ServoAndWait(int pos){
  //вставьте сюда код ограничения диапазона, если необходимо, предупреждая, что этот метод не проверяет, пытаетесь ли вы использовать числа больше или меньше, чем может обрабатывать ваш сервопривод или библиотека сервоприводов.
  waitDuration=CalcWait(myservo,pos);//метод вызова для обновления времени ожидания необходимо выполнить перед тем, как сообщить сервоприводу о движении.
  myservo.write(pos);//приказать сервоприводу вращаться
  delay(waitDuration);//блокировка ожидания завершения работы сервопривода
}
void setup() {
  myservo.attach(2); 
  ServoAndWait(180);//поворачиваем на 180 градусов и ждем, пока он не будет там+10% с запасом прочности 110
}

void loop() {
    ServoAndWait(0);
    ServoAndWait(180);
    // это должно привести к известному примеру развертки, однако затем на максимальной скорости вашего сервопривода, таким образом, вы можете протестировать его.
    // для оптимизации скорости используйте запас безопасности намного больше 100 (например, 200 или около того), затем уменьшите его, пока не увидите, что он просто останавливается или просто больше не останавливается на краях.
    // также обязательно сначала проверьте, может ли ваш сервопривод на самом деле обрабатывать 0 и 180 градусов, например, сначала проверьте, движется ли он, если вы скажете ему перейти от 10 до 0 и от 170 до 180, не все сервоприводы достигают полных 18 0 диапазон градусов.
    
  }

Пример 2: (без комментариев)

/* настроенная задержка Sweep
Этот пример кода находится в общественном достоянии.
Нет гарантии

изменено 27 марта 2023 г.
Даан В.Лун
*/

#include <Servo.h>
#define DELAYPERDEGREE 2
#define SAFETYMARGIN 110
unsigned long waitDuration = 0;
Servo myservo;

unsigned long CalcWait(Servo s, int desiredPosition)
{
  int spos = s.read();
  if (spos > desiredPosition) {
    return (((spos - desiredPosition) * DELAYPERDEGREE) * SAFETYMARGIN) / 100;
  }
  else {
    return (((desiredPosition - spos) * DELAYPERDEGREE) * SAFETYMARGIN) / 100;
  }
}

void ServoAndWait(int pos) {
  waitDuration = CalcWait(myservo, pos);
  myservo.write(pos);
  delay(waitDuration);
}
void setup() {
  myservo.attach(2);
  ServoAndWait(180);
}

void loop() {
  ServoAndWait(0);
  ServoAndWait(180);
}

Пример 3: Короче и больше переполнения стека на инопланетную тематику для людей, которые просто копируют и вставляют или считают это более понятным

/* настроенная задержка Sweep
Этот пример кода находится в общественном достоянии.
Нет гарантии

изменено 27 марта 2023 г.
Даан В.Лун
*/

#include <Servo.h>
#define DELAYPERDEGREE 2
#define SAFETYMARGIN 110

Servo myservo;

unsigned long CalcWait(Servo s, int desiredPosition)
{
  int spos = s.read();
  return (spos>desiredPosition?((spos - desiredPosition) * DELAYPERDEGREE * SAFETYMARGIN) / 100 : ((desiredPosition - spos) * DELAYPERDEGREE * SAFETYMARGIN) / 100);
}

unsigned long waitDuration = 0;
void ServoAndWait(int pos) {
  waitDuration = CalcWait(myservo, pos);
  myservo.write(pos);
  delay(waitDuration);
}

////---код, который вы увидите ниже, заголовки, функции и переменные выше---////
void setup() {
  myservo.attach(2);
}

void loop() {
  ServoAndWait(0);
  ServoAndWait(180);
}

Приведенные выше коды должны быть полностью функциональными, они должны работать на вас, все они работают одинаково. Однако я не проверял это с сервоприводом, но логика работала, когда я проверял это. также не забудьте изменить задержку на градус на то, что указано в вашей таблице данных, в этом случае в таблице данных указано 0,12 с / 60 градусов sg90 (очень распространенный сервопривод для Arduino), поэтому я превратил секунды в миллисекунды и разделил их на 60, поэтому 120/60 = 2, если у вас гораздо более быстрый сервопривод или вам нужны более точные результаты для разных сервоприводов, вы можете использовать запас прочности для настройки, или вы можете использовать микросекунды в расчетах или что-то в этом роде, или просто умножьте числа на 10, а затем в конце перед возвратом разделите его на 10.

А вот и более сложная аппаратная версия для этого вы можете либо попытаться использовать встроенное оборудование сервопривода и модифицировать сервопривод с помощью 4-го провода, либо добавить его к датчику вращения/потенциометру, либо добавить его к чипу, если у него есть место, где вы можете прочитать его напрямую, однако с этим вы сами по себе, так как это очень специфично для каждого сервопривода.

более простой способ сделать это — использовать уплотнитель (или что-то еще, что дает вам один цифровой выход (обычные сервоприводы с дистанционным управлением всегда будут держать двигатель включенным и просто очень быстро меняют направление, когда они находятся в положении, если это на самом деле останавливается, вы можете просто прочитать его как обычный цифровой сигнал (возможно, сделать что-то для защиты от скачков двигателя) и прочитать оба провода отдельно, а не сравнивать их друг с другом), чтобы сравнить напряжения на проводах с двигателем в сервопривод, так как, когда он движется, одна сторона имеет более высокое напряжение, чем другая сторона, когда он останавливается, они либо равны, либо быстро меняются, чтобы сохранить положение, поэтому вы просто проверяете, когда он изменяется, или если вы знаете направление, вы можете быть еще более точным и быстрым. это, конечно, требует, чтобы вы открыли сервопривод.

или вы действительно можете добавить что-то вроде поворотного энкодера, нет необходимости в сложном, так как вы можете просто считать полосы, и если вы вдруг больше не получаете изменений, сервопривод также останавливается, если вы что-то пропустили, или вы можете использовать что-то вроде небольшого микрофона или измерителя вибрации, так как сервопривод движется, однако это может стать неточным при более высокой нагрузке. просто постарайтесь не читать его с аналоговыми сигналами, так как то, что вы хотите использовать для аналоговых сигналов, может быть очень медленным.

также вы можете рассмотреть неблокирующие задержки, это не сделает сервопривод быстрее, но позволит вам делать другие вещи во время движения сервопривода, поэтому вам не придется ждать, пока он завершится.

,

Решение 1: я ожидаю, что скорость сервопривода будет зависеть от нагрузки, что сделает недействительными любые вычисления, основанные на фиксированной скорости., @Edgar Bonet

Спасибо за этот подробный ответ. Проект давно завершен (я не могу вспомнить, какой обходной путь я в итоге использовал), но я определенно посмотрю на него в следующий раз, когда буду играть с сервоприводами RC!, @Tom Auger

@EdgarBonet отчасти да, отчасти зависит от сервоприводов, но при типичных нагрузках, когда сервопривод на самом деле не перегружен, он должен вести себя аналогично тому, что указано в таблице данных, когда он перегружен, вы можете вместо этого измерить его ( также упомянул об этом), но я также специально добавил SAFETYMARGIN в код для конкретной вещи, это позволяет вам установить запас безопасности или точно настроить код, чтобы он работал хорошо. в большинстве случаев, когда сервопривод должен двигаться, а все остальное должно остановиться, это вполне контролируемый процесс, поэтому меньше ожиданий странного поведения., @TeD van Loon

но действительно, когда сервопривод постоянно находится под разной нагрузкой, он может иметь немного разные скорости, поэтому вам понадобится одна из тех аппаратных модификаций, таких как метод ServoMotorWireReadWires, который я добавил ниже кода, но программное обеспечение почти всегда является компромиссом по сравнению с оборудованием. но пока сервопривод не перегружен или близок к этому, он обычно должен быть примерно на скорости, указанной в техническом описании, или выше. так как часто для скорости в даташите берут скорость под нагрузкой(хотя она разнится в зависимости от производителя и количества нагрузки. чтение проводов двигателя мгновенно показывает изменения., @TeD van Loon


-1

Да, обычно servo.read() бесполезен.

Сначала проверьте свою библиотеку с помощью такого скетча

void setup() {
  servo.attach(PIN_SERVO);
  Serial.begin(9600);

  Serial.println("start");
  servo.write(180);
  delay(1000);
  unsigned long start = millis(); 
  servo.write(0);
  unsigned long duration = millis() - start;
  if (duration <= 1) 
     Serial.println("servo.write() returns immediately");
  else {
     Serial.print("servo.write() takes ");
     Serial.print(duration);
     Serial.println(" ms for a 180° move");
  }
}

Если он возвращается немедленно, вам нужно каким-то образом измерить фактическую скорость и в конечном итоге следовать комментарию Крисла.

,