1

I know there are already many questions why volatile is used when it comes to interrupts.

But explanations like this doesn't help to picture what really happens:

Specifically, it directs the compiler to load the variable from RAM and not from a storage register, which is a temporary memory location where program variables are stored and manipulated. Under certain conditions, the value for a variable stored in registers can be inaccurate.

Above is from the official Arduino website:

And below is the basic Atmega328 architecture:

enter image description here

Is it possible to explain the use/advantage of volatile by comparing it to the usual declaration way considering the above architecture?

I mean such way: If volatile int x is used, the CPU fetches this from this by this way ect. And if instead int x is used the instruction flow works like this so forth and so on.

Unfortunately I have difficulty to understand from plain two sentence explanations.

As you see my question is about after the compilation what do we observe as a benefit inside the hardware during the instruction flow.

  • Let's say you are running the code x++; x++; the compiled code will have to load variable x from the predefined memory location; place the value in a register; call the increment instruction; and write the register back to the memory location; and then the same steps for the next x++. The compiler will be smart and notices you write the register to memory, and the read it back from memory directly after, and will just remove these two steps entirely, to increase performance. It is however assuming the memory location doesn't change in between statements. – Gerben Jan 25 '17 at 20:01
  • Are general purpose registers used for fetching volatile variables from SRAM? – atmnt Jan 25 '17 at 20:03
  • The ALU can only work on registers, so you have to load the variable from SRAM to do anything with it. Sometimes local variable aren't even placed in SRAM, but only a register is used for them (for example the i variable in a for( i... loop) – Gerben Jan 25 '17 at 20:34
  • The compiler can't see whenever an interrupt is being called. So may basically make a constant out of a value that you wanted to change in the interrupt. You can draw your normale program loop, with an interrupt completely loose from that. Tell that the hardware calls this function, thus the compiler doesn't know if and when it's called. Show some example code and ask what a compiler could do with or without interrupts. – Paul Jan 27 '17 at 6:21
6

Short answer: no, volatile cannot be explained in terms of the figure you show in your question.

Longer answer: there is no direct link between the meaning of volatile and the architecture depicted in the figure. If you insist in linking them together, it is possible to find a very weak and indirect link as follows:

  1. Volatile is all about preventing the compiler from doing some optimizations when translating your program to assembly. Looking at the generated assembly can help understanding the effect of volatile.

  2. When learning the assembly language, the figure you show is helpful in understanding the assembly instructions themselves.

As you see, the link is indirect, mostly because we are talking about different abstraction levels. Also, the link is weak because the figure describes specifically the AVR architecture, whereas volatile applies to any architectures for which there is a C compiler.

To illustrate the first point above, here is an example program which shows the effect of volatile:

volatile bool volatile_flag;
bool plain_flag;

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

void loop()
{
    Serial.print("Waiting for volatile flag... ");
    while (!volatile_flag) ; /* wait */
    Serial.println("done.");

    Serial.print("Waiting for plain flag... ");
    while (!plain_flag) ; /* wait */
    Serial.println("done.");
}

As such, the program is not very interesting, it will just hang on the first loop. But you can imagine there being some ISR that can set the flags when some external event happens. Then the wait loops would start to make sense, as they wait for the external event.

Here is the disassembly of the loop() function:

    Serial.print("Waiting for volatile flag... ");
  ce:   60 e0           ldi r22, 0x00   ; 0
  d0:   71 e0           ldi r23, 0x01   ; 1
  d2:   82 e5           ldi r24, 0x52   ; 82
  d4:   91 e0           ldi r25, 0x01   ; 1
  d6:   0e 94 b3 02     call    0x566   ; 0x566 <Print::print(char const*)>
    while (!volatile_flag) ; /* wait */
  da:   80 91 51 01     lds r24, 0x0151
  de:   88 23           and r24, r24
  e0:   e1 f3           breq    .-8         ; 0xda <loop+0xc>
    Serial.println("done.");
  e2:   6e e1           ldi r22, 0x1E   ; 30
  e4:   71 e0           ldi r23, 0x01   ; 1
  e6:   82 e5           ldi r24, 0x52   ; 82
  e8:   91 e0           ldi r25, 0x01   ; 1
  ea:   0e 94 d0 02     call    0x5a0   ; 0x5a0 <Print::println(char const*)>
    Serial.print("Waiting for plain flag... ");
  ee:   64 e2           ldi r22, 0x24   ; 36
  f0:   71 e0           ldi r23, 0x01   ; 1
  f2:   82 e5           ldi r24, 0x52   ; 82
  f4:   91 e0           ldi r25, 0x01   ; 1
  f6:   0e 94 b3 02     call    0x566   ; 0x566 <Print::print(char const*)>
    while (!plain_flag) ; /* wait */
  fa:   80 91 50 01     lds r24, 0x0150
  fe:   81 11           cpse    r24, r1
 100:   01 c0           rjmp    .+2         ; 0x104 <loop+0x36>
 102:   ff cf           rjmp    .-2         ; 0x102 <loop+0x34>
    Serial.println("done.");
 104:   6e e1           ldi r22, 0x1E   ; 30
 106:   71 e0           ldi r23, 0x01   ; 1
 108:   82 e5           ldi r24, 0x52   ; 82
 10a:   91 e0           ldi r25, 0x01   ; 1
 10c:   0c 94 d0 02     jmp 0x5a0   ; 0x5a0 <Print::println(char const*)>

The important thing to notice is how the compiler implemented the waiting loops. The first one is essentially

0:  lds r24, volatile_flag  ; load the flag from RAM
    tst r24                 ; test it
    breq 0b                 ; if zero, loop back

whereas the second loop is:

    lds r24, plain_flag     ; load the flag from RAM
    cpse r24, r1            ; if zero, skip the following jump
    rjmp 1f                 ; jump over the next loop
0:  rjmp 0b                 ; infinite loop
1:

You may appreciate the first loop has been translated to assembly in a very literal way, whereas in the second loop the compiler optimized away all but the first read of the variable. That could be translated back to C as

if (!plain_flag) {
    for (;;) ;  // hang in an infinite loop
}

The compiler assumed the flag cannot be changed by some external event, so there is no point in reading it repeatedly. The volatile keyword prevents the compiler from making such assumption.

And how is all this related to your figure you may ask? Well, if you happened to not understand the assembly code above, you could check the AVR Instruction Set Manual to see what each instruction does, and that's when the figure becomes helpful.

  • I believe the back translation to C you provided is going to be the clearest explanation of volatile you can provide: any further simplification is not going to have any value. – Roberto Lo Giacco Jan 26 '17 at 0:54
  • @RobertoLoGiacco: Agree, but reading the generated assembly is the only (contrived) way I found to link volatile with the figure, and the OP kind of insisted on finding such a link. I reworded the answer to make this clearer. – Edgar Bonet Jan 26 '17 at 9:03
2

Let me explain it like this. Modern compilers optimize. They do this to make things run faster. People would complain if they didn't, or switch to another compile that does optimize.

Here's an analogy. Let's say you are wondering how many cats your brother has. You call him up on the phone, and he says "two".

A week later your friend asks how many cats your brother has. You say "I'll call him up and ask him.".

Your friend says "Didn't you do that last week? How many did he have then?"

You reply, "Yeah, that's right. He must still have two cats, right? I've just saved the expense of a phone call!"

Not making the phone call is an optimization - you save time and money by not doing it. However what if your brother now has another cat? Or he gave one away? The information about the number of cats is out of date.

If your brother is a cat dealer, or frequently changes his cat configuration you might say "the number of cats my brother has is volatile - I'll have to call him up again to make sure of the number."

So, if something happens behind your back (like in your brother's house - or in a computer program in an ISR) then you need to check again each time. However if you are sure that things haven't changed (eg. in your house you would know if you got a new cat) then you can optimize by not checking each time.

I agree with Gerben's comment on this page. This is nothing to do with the compiler architecture - any more than the number of cats you have depends on the way the house is built.

0

volatile varaibles are used in Shared variables, because the flow can change whit compiler otimizations. It's important to when you use timer's interrupt in sample.

  • Im looking for a pictorial explanation using illustrated flow chart I provided. How the variable is stored and fetched and processed in both volatile and other cases. – atmnt Jan 25 '17 at 19:54
  • volatile variables are the same as variables, the diference is in the code otimization. A volatile variable is ever consistent. – rodrigo Jan 25 '17 at 20:00
  • Im not asking what you write about. – atmnt Jan 25 '17 at 20:01
  • 1
    Volatile is a compiler only thing. It not part of the micro controller, so it doesn't exist in the diagram. – Gerben Jan 25 '17 at 20:36
  • The mocrocontroles use a non-volatile program memory. The Atmel328 has a Harvard architecture, so have a memory block for instrutions and one block for data, both are non-volatile. If what you will is persist data betwhen a shutdown, you need use the EEPROM memory. – rodrigo Jan 26 '17 at 3:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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