This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Accessing a shared volatile variable

Genius 3300 points

1. Need to access a volatile shared variable between main & interrupt. Trying to write code irrespective of 8,16 or 32 bit machine.

2. I have a code as below. But problem is even I have a lock varible i.e main_read, it is not atomic again.
For example, it will break into number of assembly instruction depending on 8,16 or 32.
Again if in between these instruction, interrupt comes, problem will arise.


3. Current I do like this:

volatile uint8_t vol_var,vol_var_copy;   /* took two variable & one as its copy */


void main_code(void)
{
    main_read = 1;
    data_read = vol_var;
    main_read = 0;
   
    if(1 == main_interrupted)
    {
        data_read = vol_var_copy;
        main_interrupted = 0;
    }
}


void isr(void)
{
    if(0 == main_read)
    {
        vol_var = value;
    }
    else
    {
        main_interrupted = 1;
        vol_var_copy = value;
    }
}

  • Volatile is NOT atomic!

    The standard requires only the following
    All variable writes in the code must occur
    All variable reads in the code must occur
    There must be no writes other than those in the code
    There must be no reads other than those in the code
    The order of reads and writes must be the same as the code

    This last point is a little subtle, involving concepts like sequence points to fully understand. It is generally sufficient to understand that if you perform each access as a single statement the order requirement will exist.

    It does not forbid access via volatile from taking multiple statements.
    It does not require any particular access method. For instance the compiler is free to access a word as a series of bytes, or access a byte as a word and then mask it. It is allowed to be even more creative.

    If you need atomic access you need to enforce that yourself.

    Robert

    I did have an unfortunate experience with a commercial RTOS that assumed access to a particular size of variable was atomic. It turns out this assumption only held true when optimization was turned on.
  • > If you need atomic access you need to enforce that yourself.

    Half true. The particular core/MCU must implement atomic operations (i.e. machine instructions), and you probably need to resort to assembler to enforce such an operation. The Cortex M has macros for this purpose (in the CMSIS headers, though ...)

  • Hi,

    To me seems the o/p has some problems with semaphores - best to do a Google search with the keywords "dijkstra semaphores".

    Also, the code, as posted is some kind of deadly embrace - (not working) See this:

    Vindhyachal Takniki said:
    main_read = 1; data_read = vol_var; main_read = 0;

    If main_read is 0, then there is no chance to go through other code lines.

    Take care the semaphore is set in one part of the code and cleared in another part. There are a second backward semaphore, which should not be there.

    Also, this may be related to producer(interrupt routine) - consumer(main-code) operations which depends on time/execution time.

  • f. m. said:
    resort to assembler to enforce such an operation.

    Which is "doing it yourself"

    Atomic is outside of the scope of C and C++

    Robert

  • As said, the CMSIS headers define (C language) macros for those instructions (core_cmFunc.h, core_cmInstr.h).
    Unfortunately, CMSIS doesn't enjoy great support by TI/CCS ...
  • I'd call that doing it yourself too, in the context of what the language supports.

    I thought CMSIS defined the interface not the implementation for things like semaphores.

    Robert
  • That's correct, semaphores and the like are typical OS metaphers.

    However, if I remember correctly, the latest CMSIS version(s) come with an RTOS API. (I never checked what it actually implements....)

  • @Petrei, there is no dead code here. Let me explain here.

    1. volatile uint8_t vol_var,vol_var_copy; /* in interrupt two varibale are taken in case one of them changes during mainline accessing */
    uint8_t data_read; /* mainline var which read the value of one of above two variables */

    2. Below code set a var main_read before reading volatle var & then clears main_read after reading.
    This flag is used to make sure that in case interrupt has occur.
    next main_interrupted is checked, it got set if interrupt happens in case of mainline reading. If yes then variable is read again from copy of volatile variable.
    Below function run continuously.

    void main_code(void)
    {
    main_read = 1;
    data_read = vol_var;
    main_read = 0;

    if(1 == main_interrupted)
    {
    data_read = vol_var_copy;
    main_interrupted = 0;
    }
    }


    3. This is isr. Before changinf the volatile, it check if main_read is reset, means if main is not reading, then it update the variable, otherwise it set the main_interrupt var & change data in its copy.

    void isr(void)
    {
    if(0 == main_read)
    {
    vol_var = value;
    }
    else
    {
    main_interrupted = 1;
    vol_var_copy = value;
    }
    }


    4. One caveat is, the time between two mainline reading should be greater than isr time.

    5. Another problem is var like main_read, it themselves break down into three aseembly instructions, so they itself are not atomic.

    6. How to make sure that shared variable always get read properly, without have to disable that interrupt. I use superloop method, not any rtos.
  • I said you are in some *kind* of non-working code: while the vol_var may change a lot, their copy may be not upadated properly, (why do you need a copy? what will you do if vol_var_copy never gets updated?, due to interrupt timing) so you may have more trouble working with bad values. The interrupt may occur any time, so it still exists the possibility of bad working, supposing there is some other code in your project. Try to vary the interrupt time and see if still working. Don't base your design on a sigle case/value.
    Otherwise, to protect values , you may try to use Exclusives. This is accomplished by using .asm instructions LDREX and STREX. This makes it possible to read a value from memory (byte, half, word, or bit), modify it, and then write it back; if another task or an
    ISR has written it between the load and store, the store will not happen and you will be told that via a register. So, you use a spin-lock model with no extra test-and-set location. Exclusives can be used for FIFOs (e.g. from ISR to main/tasks or main/tasks to ISRs) as non-locking and non-blocking models, as well as for many other shared resource.
    In CMSIS package you can find out some macros to play with that, as nothed by other posters here.
    As for other micros, 8- or 16-bits, the "atomic" problem still remain unsolved, may depend on micro, what I have used up to now do not have that.

  • Vindhyachal Takniki said:
    if(1 == main_interrupted)
    {
    data_read = vol_var_copy;
    main_interrupted = 0;
    }

    Vindhyachal Takniki said:
    void isr(void)
    {
    if(0 == main_read)
    {
    vol_var = value;
    }
    else
    {
    main_interrupted = 1;
    vol_var_copy = value;
    }
    }

    That, in general, cannot work. You cannot be sure of the order in which those two writes take place. In the worst case a write can be interrupted part way through.

    Writing a variable from interrupt and main code can only be done if you protect the writes so only a single process can do so at once and you ensure that reads are likewise not interrupted1. Yes, that means you will need to disable that interrupt.

    What you want is a non-blocking algorithm2. For that only a single process writes to a variable, the other reads. There is such an algorithm for FIFOs and that may be what you need. However, for things like timers where all you are interested in is the latest value the usual approach is multiple reads. If the value changes between reads you know there was an interrupt and you try again. Simple and robust.

    Robert

    1 - It is possible in some cases to make multiple writers work w/o blocking but the amount of effort required to prove that there is no problem and the high risks of getting it wrong usually makes other approaches superior. I'm not sure I've ever done it and really it's asking for trouble unnecessarily

    2 - I suggest spending a little time looking up non-blocking algorithms. It'll be worth your while understanding what they are doing and will provide references you are going to need later.

  • Hello Vindhyachal,

    Going through the post and seeing a similar issue earlier, what I do is to set a flag in the main code which allows the ISR to access only if it is cleared.

    ISR
    {
    if(main_flag == 0)
    {
    }
    }

    main
    {
    while
    {
    main_flag = 1;
    .. do some operation
    main_flag = 0;
    }
    }

    Regards
    Amit