is it okay to use analgoWrite() in an ISR?

Status
Not open for further replies.

owhite

Active member
Hey folks,

I'm been using code similar to what's below to run a brushless motor, but it involves sending to analogWrites to each of the three leads of a motor. I'd like to do be able to control velocity in a very precise way for a balancing robot, and to be able to change velocity outside of the main loop. Is it okay to use analogWrite() in an ISR? I'lll be making various tests using and oscilloscope but I thought I'd ask for everyone's opinion as well.

thanks.

owen

Code:
#define ENABLE_PIN    11
#define LED_PIN       13 

// LED 
boolean blinkOn = false;
uint32_t blinkDelta = 0;
uint32_t blinkInterval = 50;
uint32_t blinkNow;

unsigned long loopTimer;

// motor numbers
#define L_Motor 0
#define R_Motor 1

// motor pole angle, 0->255 overflow to loop after >>8 shift
uint16_t R_MotorStep = 0;
uint16_t L_MotorStep = 0;

uint16_t MotorPower = 120;
uint16_t idl_motor_power = 80;

// speed of motors, -127 to 127
int16_t R_Speed = 0;
int16_t L_Speed = 0;

volatile uint16_t _freqCounter = 0;
volatile uint16_t _R_MotorStep = 0;
volatile uint16_t _L_MotorStep = 0;
volatile uint16_t _R_Speed = 0;
volatile uint16_t _L_Speed = 0;
volatile uint16_t _power;

byte motorPins[2][3]={
  {0, 2, 1}, // M0
  {5, 9, 6}  // M1
};

int8_t pwmSinMotor[] = {0,  5,  10, 16, 21, 27, 32, 37, 43, 48, 53, 59, 64, 69, 74, 79, 84, 89, 94, 99, 104,  109, 
			111,  112,  114,  115,  116,  118,  119,  120,  121,  122,  123,  123,  124,  125,  125,  126,  126,  126,  127,  
			127,  127,  127,  127,  127,  126,  126,  126,  125,  125,  124,  123,  123,  122,  121,  120,  119,  118,  116,  
			115,  114,  112,  111,  110,  112,  113,  114,  116,  117,  118,  119,  120,  121,  122,  123,  124,  124,  125,  
			125,  126,  126,  126,  127,  127,  127,  127,  127,  126,  126,  126,  125,  125,  124,  124,  123,  122,  121,  
			120,  119,  118,  117,  116,  114,  113,  112,  110,  106,  101,  97, 92, 87, 82, 77, 72, 66, 61, 56, 51, 45, 40, 
			35, 29, 24, 18, 13, 8,  2,  -2, -8, -13,  -18,  -24,  -29,  -35,  -40,  -45,  -51,  -56,  -61,  -66,  -72,  -77,  
			-82,  -87,  -92,  -97,  -101, -106, -110, -112, -113, -114, -116, -117, -118, -119, -120, -121, -122, -123, -124, 
			-124, -125, -125, -126, -126, -126, -127, -127, -127, -127, -127, -126, -126, -126, -125, -125, -124, -124, -123, 
			-122, -121, -120, -119, -118, -117, -116, -114, -113, -112, -110, -111, -112, -114, -115, -116, -118, -119, -120, 
			-121, -122, -123, -123, -124, -125, -125, -126, -126, -126, -127, -127, -127, -127, -127, -127, -126, -126, -126, 
			-125, -125, -124, -123, -123, -122, -121, -120, -119, -118, -116, -115, -114, -112, -111, -109, -104, -99,  -94,  
			-89,  -84,  -79,  -74,  -69,  -64,  -59,  -53,  -48,  -43,  -37,  -32,  -27,  -21,  -16,  -10,  -5, 0};

IntervalTimer ISR_function;

void setup() {
  // motor inits
  initMotors();
  setMotorState(true);

  ISR_function.begin(tickLoop, 2); 
  loopTimer = micros() + 4000;
}


void loop() {
  noInterrupts();
  R_MotorStep = _R_MotorStep;
  L_MotorStep = _L_MotorStep;
  _R_Speed = R_Speed;
  _L_Speed = L_Speed;
  _power = MotorPower;
  interrupts();

  R_MotorStep++;
}

void initMotors() {
  for (int8_t x = 0; x < 2; x++) {
    pinMode(motorPins[x][0], OUTPUT);
    pinMode(motorPins[x][1], OUTPUT);
    pinMode(motorPins[x][2], OUTPUT);
    analogWriteFrequency(motorPins[x][0], 32000); 
    analogWriteFrequency(motorPins[x][1], 32000); 
    analogWriteFrequency(motorPins[x][2], 32000); 
  }
  pinMode(ENABLE_PIN, OUTPUT);
  digitalWrite(ENABLE_PIN, LOW);
}

void setMotorState(boolean flag) {
  if (flag) { digitalWrite(ENABLE_PIN, HIGH); } // enable 
  else { digitalWrite(ENABLE_PIN, LOW); } // disable
}

void tickLoop() {
  _freqCounter++;

  if (_freqCounter > _R_Speed) {
    _freqCounter = 0;

    _R_MotorStep++;

    volatile uint16_t pwm_a, pwm_b, pwm_c;
    volatile uint8_t x, y, z;
    volatile uint8_t posStep = (uint8_t)(_R_MotorStep >> 8);

    pwm_a = pwmSinMotor[(uint8_t)posStep];
    pwm_b = pwmSinMotor[(uint8_t)(posStep + 85)];
    pwm_c = pwmSinMotor[(uint8_t)(posStep + 170)];

    pwm_a = _power * pwm_a; // scales motor power
    pwm_a = pwm_a >> 8;
    pwm_a += 128;
    x = pwm_a;

    pwm_b = _power * pwm_b;
    pwm_b = pwm_b >> 8;
    pwm_b += 128;
    y = pwm_b;

    pwm_c = _power * pwm_c;
    pwm_c = pwm_c >> 8;
    pwm_c += 128;
    z = pwm_c;

    analogWrite(motorPins[0][0], x);
    analogWrite(motorPins[0][1], y);
    analogWrite(motorPins[0][2], z);
  }
}
 
Setting a few registers isn't time consuming, so analogWrite() is perfectly fine to call in an ISR. The things to
avoid are delaying significantly or calling some subsystem that itself relies on interrupts to progress, such as
Serial.
 
For what it is worth, I think it should be fine, as it mostly figures out what hardware register to write to, convert your value to the appropriate value for that register and write it out... Nothing that should be time consuming nor anything that has delays in it... But than again I have been wrong before ;)

Also in this case I don't think it maters, but code is different for T4 versus T3.x... So might help to know which Teensy.
 
Thank you people. I thought I read somewhere that analogWrites that set PWM are based on interrupts, so I thought maybe I was creating code that might be working at cross-purposes.
 
Sometimes if there isn't hardware, an interrupt might be used - so use a hardware PWM pin. An example is the Arduino Servo library which does this,
so you'd probably have issues trying to use that from inside another ISR.
 
I would answer with a qualified "yes". The usage you have shown should be safe, and most normal uses of analogWrite inside an interrupt should work.

But there are not-so-normal cases that may not work. Using analogWriteFrequency() on the same pin while the interrupt might occur is likely to cause trouble. Likewise, you could expect problems if you use analogWrite() to the same pin from both the main program and your interrupt, or from multiple interrupts running at different interrupt priority levels.

Your program appears to use analogWriteFrequency() only during setup() and before the IntervalTimer interrupt is turned on, so I believe this should be fine. Looks like you're also making use of volatile and temporarily disabling interrupts while accessing a group of shared variables, which is good. The use of volatile on local scope variables inside the interrupt isn't necessary, but isn't harmful either, other than running slightly slower.
 
Thanks. So just to confirm - it sounds like I can create local variables that are not volatile in the ISR function, correct?

And, I will be sure not to make any calls to the pins used in ISR/analogWrites outside of the ISR.
 
Yes, local variables with simple types (eg, not pointers) within the interrupt function do not need to be volatile.

But using volatile isn't really harmful. It just makes your code run slower, because the compiler can't do most optimizations.
 
Status
Not open for further replies.
Back
Top