0

I'm needing to pass variables from my timerIsr() function to my loop() function. I have little experience with the Arduino language, I'm mostly familiar with Python so this has been quite difficult for me.

Essentially I'm creating a PID controller to maintain RPM of a fan motor if friction is applied to it. I'm implementing the PID control in the loop() function but I need the rotation variable information from my timerIsr() function. I've tried to return the variable info but it doesn't match up to the actual fan speed. I think it has something to do with difference delay/timer times operating at different instances but I'm unsure.

Here's what I have so far

#include "TimerOne.h"
#include <PID_v1.h>

int counter=0;

const int IN1 = 11;
const int IN2 = 10;
volatile float pot=0;
const int POT = 0;
const int ENA = 6;

double Setpoint, Input, Output;
double Kp=2, Ki=5, Kd=1;

PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);


void docount()  // counts from the speed sensor
{
  counter++;  // increase +1 the counter value
} 

void timerIsr()
{
  Timer1.detachInterrupt();  //stop the timer
  Serial.print("Motor Speed: "); 
  int rotation = (counter / 30);  // divide by number of holes in Disc
  Serial.print(rotation,DEC);  
  Serial.println(" Rotation per second"); 
  counter=0;  //  reset counter to zero
  Timer1.attachInterrupt( timerIsr );  //enable the timer
  Serial.print("pot = ");
  Serial.print(pot);
  Serial.print("\n");

  //return rotation;
}

void setup() 
{
  Serial.begin(9600);

  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT); 

  Timer1.initialize(1000000); // set timer for 1sec
  attachInterrupt(0, docount, FALLING);  // increase counter when speed sensor pin goes High
  Timer1.attachInterrupt( timerIsr ); // enable the timer


  myPID.SetMode(AUTOMATIC);
} 

void loop()
{
  //Input = timerIsr();
  pot=analogRead(POT)/4.01569;
  //Setpoint = sp;

  //Serial.print(Input);

  analogWrite(ENA, pot);
  digitalWrite(10, HIGH);  // set rotation of motor to Clockwise
  digitalWrite(11, LOW);

  delay (250);
}

As you can see I've commented some stuff out that's related to the PID library. Right now I'm just trying to pass the rotation variable to the loop() function.

Is it possible to update global variables at different times so any function can access them?

1 Answer 1

2

Short answer: Yes, it is possible to update global variables from interrupt routines, but it comes also with problems.

Long answer:

First you have to understand, how interrupts work. You are using a timer interrupt and an external (pin) interrupt. The compiled program lies in the flash of the chip. At the start there are some special addresses for the different interrupts. If the hardware then registers a condition for an interrupt, it will stop the execution of the main code (the code, that was executed, when the interrupt condition occured), saving the current state and jumping to the corresponding address, where it get's send to the ISR (interrupt service routine), that you defined (in your case the function docount()). After the execution of this program the microcontroller will return to the execution of the main code (by restoring the previously saved state and jumping there). An ISR can only have a return type of void (no return value), because when it returns, there is no special code to receive and use this value. The interrupt may occur at different times every time.

Instead it is common to write global variables in the ISR. For this you have to consider some points:

  1. Global variables, that may be changed during an ISR, has to be declared (additionally to the type) as volatile. This means, that the compiler cannot optimize this variable (for example to a local variable) and has to consider, that it may change anytime, so that he cannot use a cached value.
  2. The usage of the variable in the main code has to be atomic or secured against changing of the variable. If you use a variable in a calculation, it may take many cycles for it (especially if you have a big datatype or some expensive calculations as with float). If the interrupt occures in this time, the value might be changed in the middle of calculation, making the result a bunch of garbage. If the calculation only takes 1 statement cycle, is cannot be interrupted and is called atomic. Since this is rarely possible, you can enclose the corresponding code into securing code. For the Arduino platform, you can use the noInterrupts() and interrupts() functions, to turn off all interrupts, do the calculation and turning the interrupts on again. Be sure do turn the interrupts off only for a short time, so that you don't miss interrupts. Often this is only used to copy the variables value into a local variable (local working copy), which then can safely used for calculations, while the global variable may still change.

Besides that, you also have to think about, what code you write into an ISR. An ISR cannot be interrupted by another interrupt. So functions, that depend on interrupts to work, will not behave like intended. The delay() function for example will simply block all code execution, since the return of the millis() function will not change (it depends on the timer0 interrupt). In your Timer1 ISR, you use Serial.print(). This function can be used, but only until the Serial libraries buffer is filled. The actual communication will take place, after the ISR returns, because it also depends on interrupts. When you try to send more, than fits in the buffer (64 bytes), you will loose data. Currently you should be below that, but you should have this in mind, when coding.


I don't think, that you really need a timer interrupt for calculating and outputting the data every second. Here I would use a non-blocking coding style in the loop() function, like you can see in the BlinkWithoutDelay example, that comes with the Arduino IDE. This is better than using delay(), which is just busy waiting. You save a timestamp with the millis() function and check the time difference. That saves Timer1 for other purposes and also refactors your main code for being extendable and non-blocking, if you want to add more functions.


As I read from this example from the PID library, you need to call myPID.Compute() once in a while to do the PID calculations with the new values. Since these only change every second, you should call this function directly after the calculation of Input.


All in all you need something like this (I didn't include all the code, just the important part, so that you should be able to understand the principle):

unsigned long timestamp;
#define INTERVAL 1000

void loop(){
    if(millis() - timestamp >= INTERVAL){
        timestamp += INTERVAL;
        noInterrupts();
        int counter_copy = counter;
        interrupts();
        // Do the calculations with counter_copy and same the result to `Input`
        // You can put the code from your timerisr here (without the Timer1 code)
        myPID.Compute();
    }
}

Your pot code then should also be enclosed in such an if statement, to prevent, that the PWM get's restarted again and again very fast. If a reaction time of 1s is ok for you, you can put this code in the if statement above. If you want faster reaction times, you can add a second if statement like this to the loop() function (including it's own global timestamp variable and interval value).

The digitalWrite() calls for the direction can be put into the setup, since they don't ever change in your code.

4
  • You mean if (millis() - timestamp >= INTERVAL). Commented Apr 28, 2019 at 20:19
  • Oh, yes, you are right. I corrected that. Thank you.
    – chrisl
    Commented Apr 28, 2019 at 20:32
  • So what you're saying is I can pretty much condense all of my interrupts down to this loop by just using timestamps? I'm trying to digest all of this information and apply it, sorry.
    – igomez
    Commented Apr 29, 2019 at 0:13
  • No, not all. You still have the external interrupt, which you attach with attachInterrupt() and the docount() function. This makes sense, because you don't want to miss any of the pulses. I left that out from my code, since it is exactly the same, as in your code. I ditched only the Timer1 interrupt. I think the accuracy of the 1s interval is not that important, that you really need an own hardware timer for it. If you use non-blocking code the loop() run very fast, so that should be sufficient.
    – chrisl
    Commented Apr 29, 2019 at 7:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.