Не корректно считываются данные с оптического энкодера

Daminski

✩✩✩✩✩✩✩
26 Май 2021
23
1
Пытаюсь посчитать количество полных оборотов мотор-редуктора с помощью прерываний. Прикрепил на выходной вал тонкую полоску из металла, она проходит через оптопрерыватель. Пин прерывания 21 физически соединил с пином 31, от которого поступает сигнал с оптопрерывателя. За 2 секунды мотор успевает сделать 3-4 оборота. Но в сериал порте показывает не корректные значения. То оно считает верно, то зависает на 1-м обороте. Не могу понять в чём проблема.
Вот что мне пишет в сериал порте:

14:09:45.175 -> Кнопка нажата, мотор стартанул
14:09:45.409 -> Кол-во оборотов: 1
14:09:46.012 -> Кол-во оборотов: 2
14:09:46.616 -> Кол-во оборотов: 3
14:09:47.177 -> Мотор отключился, прошло 2 сек.
14:09:50.800 -> Кнопка нажата, мотор стартанул
14:09:51.448 -> Кол-во оборотов: 1
14:09:52.846 -> Мотор отключился, прошло 2 сек.
14:09:56.100 -> Кнопка нажата, мотор стартанул
14:09:56.520 -> Кол-во оборотов: 1
14:09:57.124 -> Кол-во оборотов: 2
14:09:57.726 -> Кол-во оборотов: 3
14:09:58.146 -> Мотор отключился, прошло 2 сек.
14:10:01.074 -> Кнопка нажата, мотор стартанул
14:10:01.260 -> Кол-во оборотов: 1
14:10:01.864 -> Кол-во оборотов: 2
14:10:03.116 -> Мотор отключился, прошло 2 сек.


А вот сам код программы:

C++:
#define INTERRUPT_PIN 21    /*пин прерывания*/
#define OPTICAL_ENCODER 31  /*сигнальный пин оптопрерывателя*/
/*DIGITAL PINS USABLE FOR INTERRUPTS: 2, 3, 18, 19, 20, 21
 (pins 20 & 21 are not available to use for interrupts while
 they are used for I2C communication)*/


int MoveUP = 6; 
int Motor_Action_Pin = 27; /*пин включения драйвера двигателя*/
bool Button;               /*pin 33*/
bool Action = 0;           /*переменная запуска действия - вращения мотора*/

long Step = 0;             /*счётчик оборотов*/
long Timer = 0;
unsigned long Motor_Timer = 0;

void setup() {

   pinMode(MoveUP, OUTPUT);
   pinMode(27, OUTPUT);
   pinMode(31, INPUT);
   pinMode(33, INPUT);
   digitalWrite(Motor_Action_Pin, HIGH);

   Serial.begin(9600);
   attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), Read_Enc, LOW);
}

void loop() {
    
  Button = digitalRead(33);
    
  if(Button == 1 && Action == 0 && millis() - Timer >= 100)
   {
     Action = 1;
     Step = 0;
     analogWrite(MoveUP, 50);   
     Serial.println("Кнопка нажата, мотор стартанул");
     Timer = millis();
     Motor_Timer = millis();
   }
  if (Action == 1 && millis() - Motor_Timer >= 2000)
   {
    Action = 0;
    analogWrite(MoveUP, LOW);
    Serial.println("Мотор отключился, прошло 2 сек.");
   }
}


void Read_Enc() {

  if (digitalRead(OPTICAL_ENCODER))
   {
    Step++;
    Serial.print("Кол-во оборотов: ");
    Serial.println(Step);
   }
}
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,272
1,303
Москва
А зачем сразу выключать ? мотор же разгоняется и тормозит. сделайте несколько циклов. потом смотрите.
А то вполне может быть что не успевает набрать обороты.
И вот ЭТО
Serial.print("Кол-во оборотов: ");
убирайте из прерывания. В нем надо только считать тики. а выводить надо вне прерывания.
 

Nikanor

★★✩✩✩✩✩
1 Окт 2020
178
51
а обработка
digitalRead(OPTICAL_ENCODER)
в прерывании нафига ?
полная чушь.
надо книжки почитать, что такое прерывание и с чем его едят.
 

Daminski

✩✩✩✩✩✩✩
26 Май 2021
23
1
Кажется я разобрался. Нужно было поменять в строчке
"attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), Read_Enc, LOW);"
режим с "LOW" на "RISING" и оно стало корректно считать, ура!

Serial.print("Кол-во оборотов: ");
убирайте из прерывания. В нем надо только считать тики. а выводить надо вне прерывания.
Да, наверное уберу. Хотя и с ним тоже тики успевает считать :)
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,272
1,303
Москва
Вот когда то на заре делал код для проверки скорости DC-моторов:
Мотор подключался к вал, который вращал маховик с отверстием. Отверстие проходило через оптический прерыватель/клнцевик, который вызывал прерывание на 2-ом пине.
Из особенностей было автоматический стар и финиш. 60 000 оборотов в минуту точно считал.
Если делать на кнопках, то в преывании можно оставить только Cntr++;
Но как сейчас понимаю я не решал проблему набора мотором максимальных оборотов, а просто измерял их кол-во от начала движения до конца.
C++:
volatile  unsigned long Cntr=0;
volatile unsigned long Tm1=0,Tm2=0,OldTm2=0;
boolean isEnd=false,isStart=false;

void potch()
{   
  if (not isStart)
  {
    Tm1=micros();
    isStart=true;
    Cntr=0;   
  }
 Cntr++;
 Tm2=micros();
}

void setup() {
  pinMode(2,INPUT); 
  Serial.begin(9600); 
  Serial.println("Start..");
  attachInterrupt(0, potch, FALLING);
}

void loop() {
 
  if (isStart)
  {
  if ((micros()-Tm2)>2000000)
  {
   detachInterrupt(0);
   Serial.println("End of work");
   //Serial.println(Cntr);   
   isStart=false;
   //delay(5000);     
   isEnd=true;
  }
  else
  {
  Serial.println(Cntr);
  delay(500);   
  }
  }
 
  if (isEnd)
  {
    Serial.println("Results");   
    Serial.print("Start time ");   
    Serial.println(Tm1/1000000.0);   
    Serial.print("End time ");   
    Serial.println(Tm2/1000000.0);   
    Serial.print("All time ");   
    Serial.println((Tm2-Tm1)/1000000.0);   
    Serial.print("Rotates ");   
    Serial.println(Cntr);   
    unsigned long rpm=(unsigned long)Cntr*60000000.0/(Tm2-Tm1);
    Serial.print("RPM ");   
    Serial.println(rpm);   
    isEnd=false;
    isStart=false;   
    delay(5000);
    Serial.println("----------------------------------------------------------------");
    Serial.println("Start..");
    attachInterrupt(0, potch, FALLING);
  }
}
 

Daminski

✩✩✩✩✩✩✩
26 Май 2021
23
1
Спойлер: Сам код:
Спасибо, интересный код. Я так понял micros() может намного точнее посчитать временные промежутки между срабатываниями оптического энкодера.

У меня ещё параллельно возник вопрос, возможно не совсем по теме, но тоже связан с энкодером, только уже магнитным, прикреплённым сзади на моторе. Как можно сделать торможение двигателем? А то, например после остановки мотор по инерции ещё может сделать лишние 1-3 оборота
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,272
1,303
Москва