X
For example:
int A = 1;
if(A == 1) {... do something};
the compiler might look at that and say "I know A will always be 1 so don't compile the == part. Now consider what would happen if an interrupt occurred and the ISR contained the line "A = 2;" , the main program would have no knowledge that A could be changed elsewhere and after returning from the ISR, A would hold the wrong value. Declaring it as volatile prevents the instruction being ignored by the compiler and also tells it not to assume A still holds 1. It actually makes code that stores A in memory instead of a register and reads it again before using it.
Depending on the compiler, they generally will compile each function (including the ISRs) separately. That is because normally the code within a function is executed independently of other functions. Even when a function calls another function, the compiler has no understanding of that the called function may do and so ensures that all of the context that it needs is maintained across that call. (Think about when you call a library function or a function in a separately compiled module - where you make the call you only know the declared function header.)
When it comes to ISRs, the compiler has absolutely no way of knowing when the ISR will be triggered and therefor can't save any context across that call. (That is why the ISR must generally do the context saving and restoring itself - at least to save whatever internal registers it uses.)
In your case where you see the ISR change the value if 'A', it has no idea if that variable has been copied into a register somewhere else and so it will read the value from the variable address and write the value back to the address.
Now, in your 'normal' code, you set the value of the variable and then start to use it. The compiler 'knows" this and so puts the value of the variable in a register for easier access. If your ISR is then called, it will save that register value, update the variable but then restore the register. Now back in your main code, it has no real idea that the ISR has just been executed and so thinks that the register value is still 'valid' and starts to use that. Of course it is the value the variable had BEFORE the ISR updated it.
Using 'volatile' gets around many of these issues in that it tells the compiler to never assume a register stored value is still correct. (Of course if the ISR is called just after the variable is read then is will still be the 'old' value - and tis can be a particular problem when you are dealing with (say) 32-bit values in a 16-bit machine where two successive operations are required to access the variable - if the ISR occurs between those successive operations then you could get an invalid result. This is often referred to as non-atomic operations.)
As for setting a flag i the ISR and then processing it in the main loop, that is generally a good approach. However if you have some operation that is very quick - say moving the next audio sample to a DAC for your MP3 player or whatever - then you can do that directly in the ISR. The key think to consider is to keep the execution of ISRs as short as possible.
If the design of your application is such that you simply don't have enough processing time between interrupts to do all of the work triggered by that interrupt then you have an architectural problem, and either need to try to move more into the hardware modules (DMA etc.), increase the clock speed or use another MCU that might have additional cores.
Susan
Optimizing the function might happen, it depends on the compiler and how vigorously it does the optimization. Certainly if the "Sum()" function was used in another part of the program it wouldn't remove it as the 'a' and 'b' might have different values in that instance.
Conceptual point: if you put a breakpoint in the ISR it will always show you the ISR was called - otherwise it wouldn't 'break'!
Remember , as Susan pointed out, the ISR can be called at any time without the main() knowing when it might be and when the ISR ends, it goes back to main() without main() knowing it ever happened. It's rather like the program flow freezes, the MCU does something else then the program thaws out. The program flow doesn't know the ISR code was ever executed or whether any values were changed by it. That's why some kind of 'flag' or indicator is used, the ISR sets it and then the main() code can check it and reset it.
You are thinking of the ISR as a subroutine which is wrong. The ISR is a program in itself but it shares resources with your main program. You call a subroutine and may pass parameters to it and get a result back from it. An ISR isn't called, it is triggered by some event, either internally like from a timer or externally from a signal on a pin. An ISR never returns a value but it can change values that are stored in memory. Perhaps this will explain better:
1. You are watching a recorded movie on TV.
2. Your phone rings unexpectedly.
3. You hit the 'pause' button to freeze the movie.
4. You answer the phone call.
5. Your call finishes and you hang up.
6. You press 'play' and continue watching the movie.
What was unpredictable was when the phone call arrived, you didn't plan it and it could have happened anywhere during the movie, however you did something, talked or typed a message on the phone then resumed watching TV and didn't miss any of the movie.
Answering the phone call is like the ISR, it was an interruption to your viewing but didn't stop you seeing the whole movie.
Going back to the 'volatile' issue, the ISR uses the same registers in the MCU as in your main program and for your main program to resume after the ISR it needs to have the same values in the registers restored. If the registers were changed by the ISR code and you just returned with them as they are, the chances are the main program would crash or misbehave, especially if things like return addresses were changed. So the ISR has to save the MCU register values at its start and restore them just before ending. The problem is this can also apply to whole variables, the solution is to declare variables that might be changed by the ISR as volatile so the compiler is warned to generate extra code to check the value is still valid. You only have to use volatile on variables shared with the ISR and that might have their values changed within the ISR.
Hi,
"A" always becomes updated, independent of using volatile or not.
But A does not become checked in case of missing "volatile"
You need get the step from C to assembler.
You write C code, but the compiler generates Assembler code.
When writing C code you don´t see what happens in detail:
"A" is a variable stored in SRAM.
But in order to be checked ( ... == 1) "A" needs to be transferred to a microcontroller_Register first.
So the assembler pseudo code may be looks like this: (without "volatile")
1: transfer SRAM variable "A" to register1
2: compare register1 with 1
3: if equal jump to "do something"
4: jump to line 2
It is faster in processing, but if "A" changes it will have no effect, because A is only once transferred to a register, then never again.
The assembler pseudo code may be looks like this: (with "volatile")
1: transfer SRAM variable "A" to register1
2: compare register1 with 1
3: if equal jump to "do something"
4: jump to line 1
It is slower in processing, because "A" has to be transferred to a register on every loop.
Klaus
Hi,
the debugger does exactly the same as the real hardware. It uses the same code.
But if you look at the SRAM cell of "A" you will see the actual "A" value.
Klaus
while(m == 1)
{}
move the value of variable m into internal register A
label:
if A != 1 goto done
goto label
done:
carry on
label:
move the value of variable 'm' to internal register A
if A != 1 then go to done
go to label
done:
In some ways the debugger is working the same way as the ISR.
When the operation is transferred to the debugger at the breakpoint, the debugger will go to the variable location to read its value. Therefore it WILL read the updated value from the ISR.
However that is missing the point.
Now imagine what happens if the ISR - which alters the variable 'm' - is triggered anytime after the 'label:' in the above pseudo code. The ISR *may* alter the 'A' register but, as has been mentioned before, the ISR will always save the context at the start and restore it at the end. Therefore the 'A' register will be unaffected by the execution of the ISR.
Now if you hit a breakpoint, the same sort of thing happens as when the ISR is triggered. The debugger will 'save the context' because it shouldn't change anything (that the user has not done in the debugger) when you continue the program execution. Therefore it will read the value of 'm' from memory becsaue that is all it knows. However if you look at the code that the compiler generated for the 'while' loop, it will show you the register (that we have called 'A') that it assigned that value to in this instance. If you use the debugger to look at that register, you will see the value that the 'while' loop is using which will be the one read before entering the 'while' loop'.
PS.In some ways the debugger is working the same way as the ISR.
When the operation is transferred to the debugger at the breakpoint, the debugger will go to the variable location to read its value. Therefore it WILL read the updated value from the ISR.
However that is missing the point.
Take the following code:
Code:while(m == 1) {}
Lets imagine that the MCU has an internal register that we will call A. What the compiler will generate is code such as:
Code:move the value of variable m into internal register A label: if A != 1 goto done goto label done: carry on
It does this because accessing an internal register is faster than reading from memory.
The compiler thinks it can do this because:
1) nothing in the 'while' loop affects the value of the variable 'm'
2) nothing in the while loop means that it has to use the 'A' register for something else
Now imagine what happens if the ISR - which alters the variable 'm' - is triggered anytime after the 'label:' in the above pseudo code. The ISR *may* alter the 'A' register but, as has been mentioned before, the ISR will always save the context at the start and restore it at the end. Therefore the 'A' register will be unaffected by the execution of the ISR.
However the ISR is seeing the variable 'm' for the first time and so reads it from the memory. It updates it and, because it has no further use for the value in any internal register, it will write the value back to memory.
If you declare the variable 'm' to be volatile, the compiler is required to generate the code as:
Code:label: move the value of variable 'm' to internal register A if A != 1 then go to done go to label done:
Why does it still move the value into the internal register? Probably because many CPUs - and certainly all RISC CPUs but many CISC ones as well - perform all operations on internal registers (except moving between registers and memory). Therefore it needs the value in an internal register to perform the comparison.
There is still a vulnerable point between the 'move' and the 'if' pseudo code statements above, and if the ISR is triggered between these then the 'if' wil still see the old value. However, when it loop is executed for the next time, it will pick up the new value of 'm' and use that.
Now if you hit a breakpoint, the same sort of thing happens as when the ISR is triggered. The debugger will 'save the context' because it shouldn't change anything (that the user has not done in the debugger) when you continue the program execution. Therefore it will read the value of 'm' from memory becsaue that is all it knows. However if you look at the code that the compiler generated for the 'while' loop, it will show you the register (that we have called 'A') that it assigned that value to in this instance. If you use the debugger to look at that register, you will see the value that the 'while' loop is using which will be the one read before entering the 'while' loop'.
All of this goes to show that you do need a good understanding of how the code is generated by the compiler, how ISRs work and how debuggers operate.
Susan
That´s the point.So the memory values aren't used in comparison rather the values in the registers.
As @KlausST has already said - yesSo the memory values aren't used in comparison rather the values in the registers.
No.And so when we exit the ISR function and go back to the while comparison does it mean that the memory is overwritten with the old A value ?
volatile uint16_t buzzer = 0; (indeed no need to be volatile unless this variable is being READ in main.)
//ISR:
void Isr10ms()
{
if (buzzer > 0) // performs automatic switch ON and OFF after: (buzzer-1)*10ms
{
buzzer = ON; // pseudo code set the port.buzzer.pin = 1
if (--buzzer == 0) // counts down once every 10ms
{
buzzer = OFF; // if after count down it now it is zero, it switches OFF the buzzer
}
}
}
****
//main
void main()
{
do_init;
while(1)
{
do_someting;
if (new_key_pressed) buzzer = 10;
do_something else;
}
}
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?