This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

volatile keyword

The application note suggests to wrap a timer tick variable in a function which disables all interrupts, reads and copies the variable into a temp, re-enables interrupts, then returns the temp.

Can the variable be accessed directly outside of the ISR without disabling interrupts if the timer tick variable is declared as "volatile?"

  • Can the variable be accessed directly outside of the ISR without disabling interrupts if the timer tick variable is declared as "volatile?"
    yes, if it is a char. If it is an int, interrupts must be disabled to read it.

    Erik

  • Really? Why? I'm not very familiar with 'volatile' but from what I've read so far, it did not say it had to be a char (or 8 bits).

    Will you be so kind as to direct me towards a reference for this? I would really appreciate it.

  • This has nothing to do with volatile, it has to do with the '51 being an 8 bit device.

    Example:
    a counter is incremented in the ISR and read in main. The count now is 0x1ff

    Main read low byte to a register = 0xff
    INTERRUPT HAPPEN HERE
    ISR increment counter to 0x200
    interrupt exit
    main read high byte 0x02
    

    As you see, in this scenario main would read 0x2ff instead of 0x200. Reversing the read of high and low bytes changes, but does not cure, the problem.

    Erik

  • "I'm not very familiar with 'volatile'"

    See K&R.

  • IMHO, "volatile" really _should_ play a role here.

    From the point-of-view of the C language definition, any object whose value may change for reasons not explicitly part of the C program itself (e.g. a counter/timer) should still work as expected, *if* it's declared "volatile". It's in the responsibility of the compiler to ensure that it does.

    For the sake of the 8051, this means that a large fraction of the SFRs should implicitly be considered volatile. If a variable larger than 1 byte is declared "volatile", the compiler would have to turn off interrupts while accessing it automatically. That's one of the effects of the "volatile" qualifier.


  • In general, you can't expect a compiler to solve the problem of needing atomic access to a value that's more than a single bus transfer wide. While masking interrupts is sufficient in the example above, since software changes the value, consider a free-running 16-bit counter in hardware. Interrupts have nothing to do with it, and volatile doesn't help. The problem is simply that it takes two bus cycles to read the value, which may straddle a rollover. You'd need hardware to latch a single consistent 16-bit value before you started to transfer it into the processor.

    Even for all-software cases, you have the problems that you might not want to mask all interrupts just to mask the one with which you have a synchronization issue. And if you have custom interrupt controller logic, the compiler can't know which bits to set. In general, the programmer is going to have to solve the problem anyway.

    When it comes to the definition of volatile as "any object whose value may change for reasons not explicitly part of the C program itself", keep in mind that the "volatility" is seen from a small, local, code-generator/optimizer-ish view of "the program", rather than any sort of whole-source analysis. "volatile" helps let the compiler know it has to generate a physical read and not just use a value that may be cached in a register from a couple of lines ago. You still need to declare "volatile" values that are shared between two threads of execution, or interrupt handlers, even though the code may be all part of "the C program", or even the same file, all visible to the compiler.

    For example:

    U8 counter; // should be "volatile"
    
    void TimerTick (void) interrupt 0
        {
        counter++;
        }
    
    void MainRoutine (void)
        {
        for (;;) printf ("%d\n", counter);
    
        }
    

    could legally produce an endless stream of the same value instead of an incrementing pattern, if the optimizer hoists the read of "counter" outside the loop with the printf(). The error in this case has nothing to do with the interrupt interrupting MainRoutine(), but is simply that the compiler needs to know that "counter" is volatile, so that it knows not to optimize away the multiple reads. The compiler isn't required to detect the potential volatility just due to use of the global in two different routines; it has to be told.

  • Yes it can be. You can access it even with interrupts enabled using the following trick:

      temp = count;
      if (temp != count) temp = count;
    

    count must be declared volatile.
    If the interrupt has changed the count while accessing it, it is read again.

  • shouldn't that be:
    temp = count;
    while(temp != count) temp = count;
    ?

  • Yes, you are right. This is needed if count changes at random time interval (like EXT0 interrupt).

    If count changes/increments at regular time intervals (based on a timer interrupt) then you don't need a while loop.

  • From K&R:

    "The purpose of volatile is to force an implementation to suppress optimisation that could otherwise occur"

    So there you are: its sole purpose is to control optimisation - not to guarantee atomic accesses, nor anything else.


  • The hidden assumption with this second fix is that the counter variable changes at a much slower rate than you can sample with the assign-test-assign sequence. For typical slow timer ticks and their rollover, this may often be true.

    Note that if you write a loop, you'd better be sure that the update frequency of the counter doesn't alias with your time to execute the loop, or your code will get stuck.

    In general, the second assignment has all the potential problems of the first; if the value can change while you do the first assignment, what stops it from changing while you do the second? In the case of the timer tick, the reason it "stops" changing is because the tick rate is slow compared to the sampling rate.

    Masking interrupts also assures that the counter can't change in the original instance, because it's the interrupt routine that changes it. Stopping the interrupt stops the counter. Compare with a free-running 16-bit hardware timer; it will change even though there's no interrupt, so masking interrupts in this case wouldn't help.

    Then consider something like another processor updating shared memory, or a 16-bit A/D converter sampling fluctuating values. In this case, there's no guarantee that the value doesn't change during the second assignment any more than in the first; adding the test and re-read doesn't make the code any more correct. A double assignment isn't a general solution to the problem of atomic access to a value wider than your data bus.

    Ultimately, you have to be sure the value you're trying to read doesn't change while you read it over multiple bus cycles and/or instructions. The method for ensuring that lack of change will depend on exactly what you're trying to read and how it gets updated. You might be able to detect bogus values and re-read, you might have to mask interrupts, you might have to synchronize with another processor, you might even need hardware to latch a value for you to keep it constant while you read it.