FOR оператор цикла с условием IF

Последние несколько дней у меня были проблемы с итерацией цикла FOR в операторе IF.

У меня есть мотор-редуктор постоянного тока 12 В с магнитным энкодером, приводимым в действие драйвером двигателя L298N, который управляется Arduino. Большая часть кода работает, так как при нажатии кнопки 2 двигатель вращается по часовой стрелке; при нажатии кнопки 32 двигатель вращается против часовой стрелки.

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

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

#define enA 9
#define in1 6
#define in2 7

const int button1Pin = 8;
const int button2Pin = 12;
const int button3Pin = 13;

int button1State = 0;
int button2State = 0;
int button3State = 0;

int rotDirection = 0;

volatile long temp, counter = 0;

void setup() {
  Serial.begin (9600);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);

  attachInterrupt(0, ai0, RISING);
  attachInterrupt(1, ai1, RISING);

  pinMode(enA, OUTPUT);
  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);

  digitalWrite(in1, LOW);
  digitalWrite(in2, LOW);

  pinMode(button1Pin, INPUT);
  pinMode(button2Pin, INPUT);
  pinMode(button3Pin, INPUT);
}

void loop() {
  analogWrite(enA, 255);

  if( counter != temp ){
    Serial.println (counter);
    temp = counter;
  }

  button1State = digitalRead(button1Pin);
  button2State = digitalRead(button2Pin);
  button3State = digitalRead(button3Pin);

  if (button1State == HIGH) {
    for (counter = 0; counter < 2000; counter++) {
      clockwise();
    }
    for (counter = 2000; counter > 0; counter--) {
      counterclockwise();
    }
  }
  if (button2State == HIGH) {
    clockwise();
  }
  if (button3State == HIGH) {
    counterclockwise();
  }
}

void ai0() {
  if (digitalRead(3) == LOW) {
    counter++;
  } else {
    counter--;
  }
}

void ai1() {
  if (digitalRead(2) == LOW) {
    counter--;
  } else {
    counter++;
  }
}

void clockwise () {
  digitalWrite(in1, HIGH);
  digitalWrite(in2, LOW);
  rotDirection = 1;
  delay(20);
}

void counterclockwise () {
  digitalWrite(in1, LOW);
  digitalWrite(in2, HIGH);
  rotDirection = 0;
  delay(20);
}

Более простой код (без каких-либо других вводов кнопок), который позволял постоянно переключаться вперед и назад в обоих направлениях, можно увидеть здесь:

#define enA 9
#define in1 6
#define in2 7

const int button1Pin = 8;     
const int button2Pin = 12;
const int button3Pin = 13;

int button1State = 0;     
int button2State = 0;  
int button3State = 0;  

int rotDirection = 0;


volatile long temp, counter = 0; 

void setup() {
  
  Serial.begin (9600);
  pinMode(2, INPUT_PULLUP); 
  pinMode(3, INPUT_PULLUP); 
  attachInterrupt(0, ai0, RISING);
  attachInterrupt(1, ai1, RISING);
  
  pinMode(enA, OUTPUT);
  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);
  
  digitalWrite(in1, LOW);
  digitalWrite(in2, LOW);
  
  pinMode(button1Pin, INPUT);
  pinMode(button2Pin, INPUT);
  pinMode(button3Pin, INPUT);

}

void loop() {

  analogWrite(enA, 255);

  if( counter != temp ){
  Serial.println (counter);
  temp = counter;
  }

  button1State = digitalRead(button1Pin);
  button2State = digitalRead(button2Pin);
  button3State = digitalRead(button3Pin);

   for (counter =0; counter<2000; counter++) {
    digitalWrite(in1, HIGH);
    digitalWrite(in2, LOW);
    rotDirection = 1;   
    delay(20);
  }


  
  // If button is pressed - change rotation direction
  for (counter= 2000; counter>0; counter--) {
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    rotDirection = 0;
    delay(20);
  }
 
}

 void ai0() {
  if(digitalRead(3)==LOW) {
  counter++;}
  
  else{
  counter--;}
  }
   
  void ai1() {
  if(digitalRead(2)==LOW) {
  counter--;}
  
  else{
  counter++;}
  }

New Void Loop:

 if( counter != temp ){

Serial.println (counter); temp = counter; } long local_counter = counter;

  attachInterrupt(digitalPinToInterrupt(0), ai0, RISING); 
  attachInterrupt(digitalPinToInterrupt(1), ai1, RISING); 

  while(local_counter <2000){
  analogWrite(enA, 255);
  clockwise();
  
  detachInterrupt(digitalPinToInterrupt(0));
  detachInterrupt(digitalPinToInterrupt(1));
  
  local_counter = counter;
  
  attachInterrupt(digitalPinToInterrupt(0), ai0, RISING); 
  attachInterrupt(digitalPinToInterrupt(1), ai1, RISING);

}

, 👍0

Обсуждение

Почему вы увеличиваете/уменьшаете переменную счетчик в цикле for, когда вы уже делаете это внутри ISR? Это не имеет смысла. Кроме того, установив там переменную, вы потеряете некоторые шаги, которые двигатель мог бы сделать в положении 2000, @chrisl

И вам не нужно 2 прерывания, чтобы прочитать кодер. Одного достаточно. Один вход получает прерывание, другой определяет направление тика, @chrisl

Когда я помещаю итерацию FOR только в цикл void (без каких-либо других команд или запрошенных кнопок) , она работает правильно (или, по крайней мере, мне нравится) с уменьшением счетчика. В таком случае, не будет ли моя текущая проблема связана с тем, как команда FOR вложена в мой оператор IF?, @wickedhurricane

Вы разместили оба цикла for без остальных? Или только в одном направлении. Трудно сказать, что именно будет делать ваш код. Возможно, что проблема только с одним циклом for в направлении ccv. Пожалуйста, также предоставьте полный код для вашего рабочего тестового примера (где он действительно работал). В противном случае я могу только указать на проблемы, которые я вижу в вашем текущем коде, @chrisl

Извините, я добавил код с помощью команды just the FOR в цикле VOID в свой исходный пост, но не смог сделать это в правильном формате. Однако более простой код допускал правильное вращение вперед и назад., @wickedhurricane

Пожалуйста, сделайте правильный отступ в коде. Это трудно читать и понимать., @the busybee


1 ответ


0

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

  • Счетчик переменных в положении: Очевидно, вы хотите, чтобы счетчик переменных был вашей текущей позицией. В этом случае вы не должны изменять его без уважительной причины. Вы используете цикл for для перемещения на свою позицию. Двигатель вращается, и Arduino получает импульсы кодера через прерывания. И это именно то место, где необходимо изменить переменную положения. Вы дополнительно увеличиваете или уменьшаете его в цикле for. Это не имеет смысла, так как время выполнения цикла не имеет никакого отношения к фактической позиции. Вы можете решить эту проблему, просто удалив увеличение/уменьшение и первую настройку переменной в цикле for. Этот

      for(counter = 0; counter < 2000; counter++)
    

    становится

      for(; counter < 2000; )
    

    Хотя теперь это не столько цикл "для", сколько цикл "время". Так что вы можете вместо этого просто написать

      while(counter < 2000)
    

    Для вращения CCV соответственно.

  • Используя переменные, которые изменяются в ISR: Вам нужно быть осторожным с чтением переменной, которая изменяется в ISR. ISR может произойти в любое время при выполнении вашего основного кода. Arduino Uno нуждается в нескольких командах для обработки многобайтового типа переменной, например long, который имеет 4 байта. Таким образом, легко может произойти следующее: код начинает считывать переменную счетчика, чтобы проверить, меньше ли она 2000, и начинается с первого байта, затем со второго. Но сразу после обработки второго байта включается ISR, потому что произошел импульс. Затем ISR изменяет переменную счетчика, что означает, что третий и четвертый байты теперь могут отличаться. Основной код будет считывать эти байты с их новыми значениями. Итак, теперь у вас есть 2 байта от старого значения и 2 байта нового значения. Это может означать, что ваши данные могут быть искажены, и вы не можете быть уверены, правильно ли это.

    Для обработки многобайтовых переменных с помощью ISR необходимо поместить соответствующую часть основного кода в критический раздел. В критическом разделе прерывания отключены. Конечно, вы хотите, чтобы эти разделы были как можно короче. Поэтому в основном вы просто копируете значение из рассматриваемой переменной в локальную переменную, а затем используете ее в дальнейших вычислениях. Так что это выглядело бы примерно так:

      noInterrupts();
      long local_counter = counter;
      interrupts();
      while(local_counter < 2000){
          clockwise();
          noInterrupts();
          local_counter = counter;
          interrupts();
      }
    
  • Чтение поворотного энкодера: Обычный поворотный энкодер имеет два контакта, которые при вращении дадут вам сигнал, подобный этой ссылке на изображение википедии

Two square waves in quadrature.

Один импульс соответствует одному тику. Если вы используете 2 прерывания, каждое для одной строки, вы реагируете дважды на один и тот же импульс. Это означает, что вы получаете в два раза больше тиков. Вам действительно нужно прерывание только для одного контакта; другой тогда просто определяет направление вращения.


Помимо этих пунктов, вы можете попытаться вывести текущее значение счетчика внутри цикла for/while на последовательный монитор. Тогда вы сможете увидеть, что происходит внутри этого цикла.

,

Ваше объяснение помогает и очень ценится. Я начинаю с основ и переписываю свой цикл void с циклом while; однако двигатель постоянно вращается, не останавливаясь на 2000. Я опубликую новый цикл void в своем первоначальном посте., @wickedhurricane