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:
- 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.
- 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.