Можете ли вы запустить несколько ISR одновременно без потоков? - PullRequest
0 голосов
/ 28 января 2020

Я анализировал некоторый код, используемый для одновременного управления несколькими шаговыми двигателями, используя прерывание таймера сравнения, но по тому, как написан код, я не могу сказать, действительно ли код запускает отдельные прерывания для одновременного запуска или выполняет ли он шаг двигатели по одному шагу за раз и циклически проходя через каждый двигатель как oop, пока все они не достигнут желаемого положения. Мой главный вопрос заключается в том, что, поскольку они используют один и тот же регистр сравнения выходных данных для запуска следующего прерывания, не может быть нескольких isr, поскольку они не могут, кроме разных значений в одной и той же ячейке памяти, корректны? И если да, верно ли мое предположение о том, что оно шагает поочередно?

  #define X_DIR_PIN          55
  #define X_STEP_PIN         54
  #define X_ENABLE_PIN       38

  #define Y_DIR_PIN          61
  #define Y_STEP_PIN         60
  #define Y_ENABLE_PIN       56

  #define Z_DIR_PIN          48
  #define Z_STEP_PIN         46
  #define Z_ENABLE_PIN       62

  #define A_DIR_PIN          28
  #define A_STEP_PIN         26
  #define A_ENABLE_PIN       24

  #define B_DIR_PIN          34
  #define B_STEP_PIN         36
  #define B_ENABLE_PIN       30

#define C_DIR_PIN          32
#define C_STEP_PIN         47
#define C_ENABLE_PIN       45

#define X_STEP_HIGH             PORTF |=  0b00000001;
#define X_STEP_LOW              PORTF &= ~0b00000001;

#define Y_STEP_HIGH             PORTF |=  0b01000000;
#define Y_STEP_LOW              PORTF &= ~0b01000000;

#define Z_STEP_HIGH             PORTL |=  0b00001000;
#define Z_STEP_LOW              PORTL &= ~0b00001000;

#define A_STEP_HIGH             PORTA |=  0b00010000;
#define A_STEP_LOW              PORTA &= ~0b00010000;

#define B_STEP_HIGH             PORTC |=  0b00000010;
#define B_STEP_LOW              PORTC &= ~0b00000010;

#define C_STEP_HIGH             PORTL |=  0b00000100;
#define C_STEP_LOW              PORTL &= ~0b00000100;

#define TIMER1_INTERRUPTS_ON    TIMSK1 |=  (1 << OCIE1A);
#define TIMER1_INTERRUPTS_OFF   TIMSK1 &= ~(1 << OCIE1A);

struct stepperInfo {
  // externally defined parameters
  float acceleration;
  volatile unsigned int minStepInterval;   // ie. max speed, smaller is faster
  void (*dirFunc)(int);
  void (*stepFunc)();

  // derived parameters
  unsigned int c0;                // step interval for first step, determines acceleration
  long stepPosition;              // current position of stepper (total of all movements taken so far)

  // per movement variables (only changed once per movement)
  volatile int dir;                        // current direction of movement, used to keep track of position
  volatile unsigned int totalSteps;        // number of steps requested for current movement
  volatile bool movementDone = false;      // true if the current movement has been completed (used by main program to wait for completion)
  volatile unsigned int rampUpStepCount;   // number of steps taken to reach either max speed, or half-way to the goal (will be zero until this number is known)

  // per iteration variables (potentially changed every interrupt)
  volatile unsigned int n;                 // index in acceleration curve, used to calculate next interval
  volatile float d;                        // current interval length
  volatile unsigned long di;               // above variable truncated
  volatile unsigned int stepCount;         // number of steps completed in current movement
};

void xStep() {
  X_STEP_HIGH
  X_STEP_LOW
}
void xDir(int dir) {
  digitalWrite(X_DIR_PIN, dir);
}

void yStep() {
  Y_STEP_HIGH
  Y_STEP_LOW
}
void yDir(int dir) {
  digitalWrite(Y_DIR_PIN, dir);
}

void zStep() {
  Z_STEP_HIGH
  Z_STEP_LOW
}
void zDir(int dir) {
  digitalWrite(Z_DIR_PIN, dir);
}

void aStep() {
  A_STEP_HIGH
  A_STEP_LOW
}
void aDir(int dir) {
  digitalWrite(A_DIR_PIN, dir);
}

void bStep() {
  B_STEP_HIGH
  B_STEP_LOW
}
void bDir(int dir) {
  digitalWrite(B_DIR_PIN, dir);
}

void cStep() {
  C_STEP_HIGH
  C_STEP_LOW
}
void cDir(int dir) {
  digitalWrite(C_DIR_PIN, dir);
}

void resetStepperInfo( stepperInfo& si ) {
  si.n = 0;
  si.d = 0;
  si.di = 0;
  si.stepCount = 0;
  si.rampUpStepCount = 0;
  si.totalSteps = 0;
  si.stepPosition = 0;
  si.movementDone = false;
}

#define NUM_STEPPERS 6

volatile stepperInfo steppers[NUM_STEPPERS];

void setup() {

  pinMode(X_STEP_PIN,   OUTPUT);
  pinMode(X_DIR_PIN,    OUTPUT);
  pinMode(X_ENABLE_PIN, OUTPUT);

  pinMode(Y_STEP_PIN,   OUTPUT);
  pinMode(Y_DIR_PIN,    OUTPUT);
  pinMode(Y_ENABLE_PIN, OUTPUT);

  pinMode(Z_STEP_PIN,   OUTPUT);
  pinMode(Z_DIR_PIN,    OUTPUT);
  pinMode(Z_ENABLE_PIN, OUTPUT);

  pinMode(A_STEP_PIN,   OUTPUT);
  pinMode(A_DIR_PIN,    OUTPUT);
  pinMode(A_ENABLE_PIN, OUTPUT);

  pinMode(B_STEP_PIN,   OUTPUT);
  pinMode(B_DIR_PIN,    OUTPUT);
  pinMode(B_ENABLE_PIN, OUTPUT);

  pinMode(C_STEP_PIN,   OUTPUT);
  pinMode(C_DIR_PIN,    OUTPUT);
  pinMode(C_ENABLE_PIN, OUTPUT);

  digitalWrite(X_ENABLE_PIN, LOW);
  digitalWrite(Y_ENABLE_PIN, LOW);
  digitalWrite(Z_ENABLE_PIN, LOW);
  digitalWrite(A_ENABLE_PIN, LOW);
  digitalWrite(B_ENABLE_PIN, LOW);
  digitalWrite(C_ENABLE_PIN, LOW);

  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 1000;                             // compare value
  TCCR1B |= (1 << WGM12);                   // CTC mode
  TCCR1B |= ((1 << CS11) | (1 << CS10));    // 64 prescaler
  interrupts();

  steppers[0].dirFunc = bDir;
  steppers[0].stepFunc = bStep;
  steppers[0].acceleration = 1000;
  steppers[0].minStepInterval = 50;

  steppers[1].dirFunc = aDir;
  steppers[1].stepFunc = aStep;
  steppers[1].acceleration = 4000;
  steppers[1].minStepInterval = 50;

  steppers[2].dirFunc = cDir;
  steppers[2].stepFunc = cStep;
  steppers[2].acceleration = 1000;
  steppers[2].minStepInterval = 50;

  steppers[3].dirFunc = xDir;
  steppers[3].stepFunc = xStep;
  steppers[3].acceleration = 1000;
  steppers[3].minStepInterval = 250;

  steppers[4].dirFunc = yDir;
  steppers[4].stepFunc = yStep;
  steppers[4].acceleration = 1000;
  steppers[4].minStepInterval = 50;

  steppers[5].dirFunc = zDir;
  steppers[5].stepFunc = zStep;
  steppers[5].acceleration = 1000;
  steppers[5].minStepInterval = 450;
}

void resetStepper(volatile stepperInfo& si) {
  si.c0 = si.acceleration;
  si.d = si.c0;
  si.di = si.d;
  si.stepCount = 0;
  si.n = 0;
  si.rampUpStepCount = 0;
  si.movementDone = false;
}

volatile byte remainingSteppersFlag = 0;

void prepareMovement(int whichMotor, int steps) {
  volatile stepperInfo& si = steppers[whichMotor];
  si.dirFunc( steps < 0 ? HIGH : LOW );
  si.dir = steps > 0 ? 1 : -1;
  si.totalSteps = abs(steps);
  resetStepper(si);
  remainingSteppersFlag |= (1 << whichMotor);
}

volatile byte nextStepperFlag = 0;

volatile int ind = 0;
volatile unsigned int intervals[100];

void setNextInterruptInterval() {

  bool movementComplete = true;

  unsigned int mind = 999999;
  for (int i = 0; i < NUM_STEPPERS; i++) {
    if ( ((1 << i) & remainingSteppersFlag) && steppers[i].di < mind ) {
      mind = steppers[i].di;
    }
  }

  nextStepperFlag = 0;
  for (int i = 0; i < NUM_STEPPERS; i++) {
    if ( ! steppers[i].movementDone )
      movementComplete = false;

    if ( ((1 << i) & remainingSteppersFlag) && steppers[i].di == mind )
      nextStepperFlag |= (1 << i);
  }

  if ( remainingSteppersFlag == 0 ) {
    OCR1A = 65500;
  }

  OCR1A = mind;
}

ISR(TIMER1_COMPA_vect)
{
  unsigned int tmpCtr = OCR1A;

  OCR1A = 65500;

  for (int i = 0; i < NUM_STEPPERS; i++) {

    if ( ! ((1 << i) & remainingSteppersFlag) )
      continue;

    if ( ! (nextStepperFlag & (1 << i)) ) {
      steppers[i].di -= tmpCtr;
      continue;
    }

    volatile stepperInfo& s = steppers[i];

    if ( s.stepCount < s.totalSteps ) {
      s.stepFunc();
      s.stepCount++;
      s.stepPosition += s.dir;
      if ( s.stepCount >= s.totalSteps ) {
        s.movementDone = true;
        remainingSteppersFlag &= ~(1 << i);
      }
    }

    if ( s.rampUpStepCount == 0 ) {
      s.n++;
      s.d = s.d - (2 * s.d) / (4 * s.n + 1);
      if ( s.d <= s.minStepInterval ) {
        s.d = s.minStepInterval;
        s.rampUpStepCount = s.stepCount;
      }
      if ( s.stepCount >= s.totalSteps / 2 ) {
        s.rampUpStepCount = s.stepCount;
      }
    }
    else if ( s.stepCount >= s.totalSteps - s.rampUpStepCount ) {
      s.d = (s.d * (4 * s.n + 1)) / (4 * s.n + 1 - 2);
      s.n--;
    }

    s.di = s.d; // integer
  }

  setNextInterruptInterval();

  TCNT1  = 0;
}

void runAndWait() {
  setNextInterruptInterval();
  while ( remainingSteppersFlag );
}

void loop() {

  TIMER1_INTERRUPTS_ON

  for (int i = 0; i < 4; i++) {
    for (int k = 0; k < NUM_STEPPERS; k++) {
      prepareMovement( k,  200 );
      runAndWait();
    }
  }
  for (int i = 0; i < 4; i++) {
    for (int k = 0; k < NUM_STEPPERS; k++) {
      prepareMovement( k,  200 );
    }
    runAndWait();
  }

  for (int i = 0; i < NUM_STEPPERS; i++)
    prepareMovement( i, 400 );
  runAndWait();
  for (int i = 0; i < NUM_STEPPERS; i++)
    prepareMovement( i, -400 );
  runAndWait();
  for (int i = 0; i < NUM_STEPPERS; i++)
    prepareMovement( i, 200 );
  runAndWait();
  for (int i = 0; i < NUM_STEPPERS; i++)
    prepareMovement( i, -200 );
  runAndWait();
  for (int i = 0; i < NUM_STEPPERS; i++)
    prepareMovement( i, 600 );
  runAndWait();
  for (int i = 0; i < NUM_STEPPERS; i++)
    prepareMovement( i, -600 );
  runAndWait();

  while (true);

}```

Ответы [ 3 ]

2 голосов
/ 29 января 2020

Я настоятельно рекомендую изучать базовый c ассемблер при программировании MCU.

Основа прерываний заключается в том, что при входе в ISR типичный MCU автоматически устанавливает маску глобального прерывания. AVR не является исключением - оно происходит от «школы Motorola», в которой глобальная маска прерываний называется I и хранится в регистре кода состояния - AVR вызывает это STATUS. Это самый важный регистр во всем MCU, поскольку кроме обработки прерываний, здесь также сообщаются все результаты выполненных инструкций.

Регистр STATUS будет автоматически сохраняться в стеке всякий раз, когда запускается ISR, так что текущие вычисления могут продолжаться там, где они были после выполнения ISR. После сохранения регистра MCU автоматически очищает бит I и блокирует все остальные прерывания от запуска.

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

Даже с включенной глобальной маской прерывания аппаратное обеспечение, которое вызывает прерывания, может по-прежнему устанавливать флаги, которые будут запускать прерывание. Это означает, что как только ваш ISR завершит выполнение и маска прерываний отключена, другие ожидающие прерывания могут выполняться немедленно. Это делается в соответствии с некоторым расписанием приоритетов прерываний, указанным изготовителем и / или программистом MCU. Но вы не можете выполнять несколько прерываний одновременно.

В частности, в случае ШИМ, однако, может быть одно единственное прерывание, которое отмечает начало рабочего цикла, в случаях, когда несколько каналов ШИМ работают одновременно.

Относительно изменения разделяемой памяти от прерываний, это совсем другая история. Я бы рекомендовал прочитать это .

1 голос
/ 30 января 2020

loop() устанавливает целевую относительную позицию для каждого двигателя, затем ждет, пока все двигатели не достигнут своей целевой позиции. Выполнение последовательности таких шагов.

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

Важно отметить, что шаг импульса очень коротко, контроллер мотора генерирует правильные сигналы для катушек мотора для завершения шага. Шаговый импульс завершается задолго до того, как двигатель достигает конца шага (или даже начинает свой шаг), поэтому, хотя импульсы для каждого двигателя выдаются последовательно, поскольку это не занимает значительного времени по сравнению со временем шага, все двигатели могут шагать одновременно не последовательно.

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

Независимо от достоинств и недостатков Arduino Sketch setup() / loop() framework, эта конкретная реализация полностью его побеждает, никогда не повторяя loop() и не блокируя бесконечно в конце. Фреймворк ожидает, что loop() будет запущен до конца, и он будет вызывать его повторно (отсюда и его название - это тело исполнительной власти l oop).

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

void runNoWait() 
{
    setNextInterruptInterval();
}

void loop()
{
    static const int sequence[] = { 200,200,200,200,200,200,200,200,
                                    400,-400,200,-200,600,-600} ;
    static const sequence_steps = sizeof(sequence) / sizeof(*sequence) ;

    static sequence_index = 0 ;

    // If sequence not complete...
    if( sequence_index <  sequence_steps )
    { 
        // If all motor positions achieved...
        if( remainingSteppersFlag == 0x00 )
        {
            // For each motor set up next position
            for( int k = 0; k < NUM_STEPPERS; k++) 
            {
                prepareMovement( k, sequence[sequence_index] ) ;
            }

            // Start motors
            runNoWait() ;

            // Next step in sequence
            sequence_index++ ;
        }
    }

    // Do other (non-blocking) stuff here while 
    // simultaneously executing motor sequence
    ...
}

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

void allStop()
{
    remainingSteppersFlag = 0 ;
} 

void loop()
{
    ...

    // Stop sequence immediately on button press
    if( digitalRead(buttonPin) == LOW )
    {
        allStop() ;
        sequence_index = sequence_steps ;
    }

    ...
}


1 голос
/ 29 января 2020

Мой главный вопрос - [...] не может быть нескольких [...]?

Ваш код четко показывает один ISR. Если вызывается ISR, его прерывание отключается до тех пор, пока оно не вернется.

Хотя ISR может разрешить свое прерывание, но я не вижу инструкции для этого в вашем коде. Я делал это редко и на других микроконтроллерах, но это определенно возможно и приводит к рекурсивным вызовам того же ISR. Я не уверен насчет AVR.

На самом деле прерывание привязано к фиксированному адресу для его ISR. Для каждого прерывания может быть только один ISR. (Оставьте в стороне некоторые хитрые уловки, такие как банкирование памяти или векторы скачков в ОЗУ.)

И если да, то мое предположение о том, что он шагает по одному двигателю за раз, верно?

Не углубляясь в ваш источник, мне кажется, что при каждом запуске ISR включаются те двигатели, чье «время пришло». Посмотрите, например, steppers[*].di.

Зная, что ISR запускается сам по себе, вы можете исследовать источник и следить за его потоком.

Предположительно, ISR вызывается всякий раз, когда наступает время произвести следующий шаг, для каждого двигателя, который должен быть включен в это время. Подумайте о двух двигателях, работающих на 100 Гц и 200 Гц соответственно. ISR будет вызываться с частотой 200 Гц, но первый двигатель будет работать только при каждом втором вызове.

Таким образом, ответ скорее:

"Снаружи все двигатели работают параллельно каждый со своей скоростью. "

...