Как независимо запускать позиционные и непрерывные сервоприводы с помощью millis()?

Я настраиваю 3 сервопривода (2 сервопривода положения и 1 сервопривод непрерывного вращения на 360градусов) с помощью Arduino. но мне не удалось запустить их самостоятельно. Он бежит один за другим. Я хочу запустить его в цикле независимо друг от друга. Вот код, который я использую для запуска позиционного сервопривода и сервопривода CR (360). позиции сервопривода по умолчанию равны 90. *Сервопривод CR 360 должен работать независимо 90-60-120-90 (в цикле) *Сервопривод Pos должен работать один за другим (в цикле)

Для запуска позиционных сервоприводов (наклон/панорамирование):

//сервопривод наклона
    for (pos = 90; pos >= 60; pos -= 1)     //90 ~ 60
  {
    tiltservo.write(pos);
    delay(30);
  }

  for (pos = 60; pos <= 120; pos += 1)      //60 ~ 120
  {
    tiltservo.write(pos);
    delay(30);
  }

  for (pos = 120; pos >= 90; pos -= 1)       //120 ~ 90
  {
    tiltservo.write(pos);
    delay(30);
  }

//сервопривод панорамирования
for (pos = 90; pos >= 60; pos -= 1)          //90 ~ 60
  {        
    panservo.write(pos);
    delay(30);
  }

  for (pos = 60; pos <= 120; pos += 1)       //60 ~ 120
  {        
    panservo.write(pos);
    delay(30);
  }

  for (pos = 120; pos >= 90; pos -= 1)       //120 ~ 90
  {        
    panservo.write(pos);
    delay(30);
  }

Для запуска сервопривода CR 360:

 legservo.writeMicroseconds(1600);      //CW
 delay(5000);                           //run for 5s
 legservo.writeMicroseconds(1300);      //CCW
 delay(5000);                           //run for 5s 

Попробовал это для независимого запуска сервоприводов CR, но как поставить некоторое ожидание (3s-4s) между FW и RW.

if (millisNow - lastMillis >= 10000) 
{ 
lastMillis = millisNow; 
if (crservoState == 0) 
 { 
   legservo1.writeMicroseconds(2150); //FW
   crservoState = 1; 
 } 
  else 
 { 
   legservo1.writeMicroseconds(850);  //RW
   crservoState = 0; 
 } 
} 

Пробный код после предложений:

uint32_t milliSNow = millis();

  if (milliSNow - lastTimeServoRotate >= 1000)
  {
    if (crservoState == 0)
    {
      if (milliSNow - lastTimeServoRotate >= 10000)
      {
        legservo1.writeMicroseconds(2150);    //FW run
        crservoState = 2;
        lastTimeServoRotate = milliSNow;
      }
    }

    if (crservoState == 1)
    {
      if (milliSNow - lastTimeServoRotate >= 10000)
      {
        legservo1.writeMicroseconds(850);     //RW run
        crservoState = 3;
        lastTimeServoRotate = milliSNow;
      }
    }

    if (crservoState == 2)
    {
      if (milliSNow - lastTimeServoRotate >= 3000)
      {
        legservo1.writeMicroseconds(1500);    //FW STOP
        crservoState = 1;
        lastTimeServoRotate = milliSNow;
      }
    }

    if (crservoState == 3)
    {
      if (milliSNow - lastTimeServoRotate >= 3000)
      {
        legservo1.writeMicroseconds(1500);     //RW STOP
        crservoState = 0;
        lastTimeServoRotate = milliSNow;
      }
    }
  }

, 👍2

Обсуждение

https://arduinoprosto.ru/q/83977/how-to-run-2-different-servo-continuous-rotation-and-regular-servo-in-parallel/83984#83984, @ArduinoFan

@ArduinoFan спасибо, что поделился ссылкой, я пробовал это, но это для управления сервоприводами того же типа, в моем случае мне также нужно настроить позиционный сервопривод и CR, поэтому его работа становится смешанной. во время выполнения панорамирования/наклона один за другим с непрерывно работающим сервоприводом CR CW/CCW со временем ожидания 2 с. любая помощь будет признательна. Спасибо., @Emlinux

Последний фрагмент кода, который вы опубликовали, должен работать, хотя и с 10-секундными (а не 3-4-секундными) паузами между FW и RW. Если это не работает, у вас есть проблема в другом месте вашего кода. Может быть, вы делитесь переменной "lastMillis" с кодом, обрабатывающим позиционные сервоприводы?, @Edgar Bonet

@EdgarBonet Спасибо за ваш комментарий. Этот код работает для запуска CR таким образом.. 1. при запуске он запускается через 10 секунд 2. серво1 работает в течение 10 секунд FW, затем немедленно запускается в течение 10 секунд RW в цикле. но я хочу подождать / подождать 3 секунды после 10 секунд FW и 10 секунд RW каждый. 3. "lastMillis" используется только для сервопривода CR, он не конфликтует с переменной сервопривода POS., @Emlinux

Re “ _ Я хочу подождать / подождать 3 секунды после 10 секунд FW и 10 секунд RW, запускаемых каждый раз”: Этого нет в вашем фрагменте кода. Если вам нужны эти паузы, вы должны явно попросить сервопривод остановиться, и ваш цикл состоит из **четырех** фаз: FW, пауза, RW, пауза., @Edgar Bonet

@EdgarBonet Спасибо тебе. Я загрузил его как "Пробный код", не могли бы вы, пожалуйста, проверить его? так как он работает, но время выполнения fw/rw составляет 3 секунды вместо 10 секунд, а время удержания-10 секунд вместо 3 секунд, хотя я определил, что он будет работать в течение 10 секунд и удерживать/останавливать в течение 3 секунд. где я ошибаюсь? пожалуйста, предложите~, @Emlinux

@EdgarBonet Я только что перевернул (milliSNow - lastTimeServoRotate), проверил от 10000 до 3000, и он запустился. (milliSNow - lastTimeServoRotate), это проверяет прошедшее время. Но при запуске он ждет запуска в течение 10 секунд, даже если я установил его на проверку >= 1000 (~1 с), я его не получил. Пожалуйста, предложите., @Emlinux


1 ответ


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

4

Это описано в учебнике по Arduino "Мигание без задержки". Позвольте мне перефразировать основную часть примера кода здесь:

uint8_t ledState = LOW;
uint32_t lastTimeLedChanged = 0;

void loop() {
    uint32_t now = millis();
    if (now - lastTimeLedChanged >= 1000) {

        // Сохраните последний раз, когда мы обновляли светодиод.
        lastTimeLedChanged = now;

        // Вычислите новое состояние светодиода.
        if (ledState == LOW) {
            ledState = HIGH;
        } else {  // ledState is HIGH
            ledState = LOW;
        }

        // Установите индикатор в вычисленное состояние.
        digitalWrite(LED_BUILTIN, ledState);
    }
}

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

Для позиционных сервоприводов все еще можно применить тот же принцип, но есть одна загвоздка. В то время как и светодиод, и сервопривод CR переключаются между двумя состояниями, пара позиционных сервоприводов проходит через множество состояний (92 240, если я правильно посчитал). Таким образом, Вам понадобится:

  • дополнительные переменные для хранения текущего состояния сервопары
  • больше кода для вычисления нового желаемого состояния
  • больше кода для обновления состояния

Чтобы понять, как представить состояние, вам нужно посмотреть на ваш текущий код на основе функции delay (), посмотреть, сколько вызовов функции delay() выполняется в каждом полном цикле, и выяснить, как идентифицировать каждый из этих вызовов. Каждая из этих задержек соответствует одному состоянию вашей системы. Я бы сказал, что вам нужна одна переменная, чтобы знать, в каком из циклов for вы находитесь, и другая переменная, чтобы знать последнюю позицию, которую вы записали на сервопривод в этом цикле. Я буду называть каждый из оригиналов для зацикливает “фазу”. Существует два из шести таких этапов:

  • фаза 0: наклон: 90° → 60°
  • фаза 1: наклон: 60° → 120°
  • фаза 2: наклон: 120° → 90°
  • фаза 3: панорамирование: 90° → 60°
  • фаза 4: панорамирование: 60° → 120°
  • фаза 5: панорамирование: 120° → 90°

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

Затем вам нужно будет сохранить время последнего обновления, таким образом:

int phase = 0;  // в какой из шести фаз мы находимся
int pos = 90;   // последняя позиция, записанная на сервопривод
uint32_t lastTimeServoMoved = 0;

Как только у вас появятся правильные переменные для описания состояния, обновить его будет несложно. Если вы достигли конца фазы, переходите к следующей. Затем обновите позицию на один шаг. Таким образом:

void loop() {
    uint32_t now = millis();
    if (now - lastTimeServoMoved >= 30) {

        // Сохраните последний раз, когда мы обновляли позиционный сервопривод.
        lastTimeServoMoved = now;

        // Вычислите новое состояние.        
        if (phase == 0 && pos == 60)
            phase = 1;
        else if (phase == 1 && pos == 120)
            phase = 2;
        else if (phase == 2 && pos == 90)
            phase = 3;
        else if (phase == 3 && pos == 60)
            phase = 4;
        else if (phase == 4 && pos == 120)
            phase = 5;
        else if (phase == 5 && pos == 90)
            phase = 0;
        if (phase == 1 || phase == 4)
            pos += 1;
        else
            pos -= 1;

        // Установите сервопривод в вычисленное состояние.
        if (phase < 3)
            tiltservo.write(pos);
        else
            panservo.write(pos);
    }
}

Лично мне не нравится такой повторяющийся код. Если вас устраивают структуры данных, я бы рекомендовал использовать массив структур для хранения всей информации, описывающей действия, выполняемые на каждом этапе:

struct {
    Servo *servo;   // какой сервопривод перемещать
    int step;       // шаги, по которым он перемещается
    int final_pos;  // конечное угловое положение
} moves[] = {
    { &tiltservo, -1, 60 },   // tilt:  90 ->  60
    { &tiltservo, +1, 120 },  // tilt:  60 -> 120
    { &tiltservo, -1, 90 },   // tilt: 120 ->  90
    { &panservo,  -1, 60 },   // pan:   90 ->  60
    { &panservo,  +1, 120 },  // pan:   60 -> 120
    { &panservo,  -1, 90 }    // pan:  120 ->  90
};

Тогда фактический код становится проще:

void loop() {
    uint32_t now = millis();
    if (now - lastTimeServoMoved >= 30) {

        // Сохраните последний раз, когда мы обновляли позиционный сервопривод.
        lastTimeServoMoved = now;

        // Compute the new state.
        if (pos == moves[phase].final_pos) {
            phase +=1;
            if (phase == 6)
                phase = 0;
        }
        pos += moves[phase].step;

        // Установите сервопривод в вычисленное состояние.
        moves[phase].servo->write(pos);
    }
}
,

большое вам спасибо за подробное объяснение с примером. Это хорошо понимать. Я попробовал это, но сервопривод CR не ждет 5 секунд между движением CW и CCW. Фактическая операция должна быть: Сервопривод CR: (в цикле) запуск CW в течение 5 секунд, подождите 2 секунды, CCW для 5s Сервопривод Pos (панорамирование/наклон): (в цикле) 1-й сковородный ход с 90~60, 60~120, 120~90 тогда 2-й ход наклона от 90~60, 60~120, 120~90 Пожалуйста, предложите., @Emlinux

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

извините за недоразумение. Я соответствующим образом обновил его. Спасибо., @Emlinux

Спасибо за обновление решения. Я это понял. Хотя я не очень часто использовал структуру данных, но здесь хорошо использовать ее эффективно. Я предпочту учиться и использовать его таким образом. поскольку в комплект входит еще 1 leg_servo (CR 360), я попытался добавить его в цикл с помощью сервоприводов положения, подобных этому. но я не могу приостановить это на 2-3 секунды после запуска CW/CCW в течение 10 секунд. if (millisNow - lastMillis >= 10000) { lastMillis = millisNow; if (crservoState == 0) { legservo1.writeMicroseconds(2150); crservoState = 1; } else { legservo1.writeMicroseconds(850); crservoState = 0; } }, @Emlinux

Пожалуйста, предложите что-нибудь. Спасибо., @Emlinux

@Emlinux: Пожалуйста, попытайтесь понять _ общий подход_, проиллюстрированный этим ответом: разбиение цикла на фазы, определение отдельных шагов в рамках каждой фазы, выяснение того, как кодировать и обновлять состояние... Хотя код, который я показываю, специфичен для сервоприводов наклона и панорамирования, подход является общим, и, если вы его понимаете, вы можете применить его к сервоприводу CR. **Не спешите с написанием кода**: сначала подумайте и проанализируйте., @Edgar Bonet

да @Эдгар Бонет, я попробовал то же самое, поэтому я поделился несколькими строками кода в своем предыдущем посте. поскольку мне нужно запускать сервоприводы CR независимо от сервоприводов POS (панорамирование/наклон), я попытался запустить его независимо для 10 секунд FW и 10 секунд RW, и он работает, но я не уверен, как поместить некоторый wait_delay (например, 3 секунды) между FW и RW. Спасибо., @Emlinux

поскольку код, упомянутый в моих предыдущих комментариях, плохо виден, я поместил его в конец поста. пожалуйста, проверьте и направьте. Спасибо., @Emlinux