Atomic bitfield change macro

Page: 12 > Showing page 1 of 2
Author
zardoz1
Super Member
  • Total Posts : 1852
  • Reward points : 0
  • Joined: 2005/07/09 08:03:28
  • Location: 's-Hertogenbosch, The Netherlands
  • Status: offline
2008/10/18 09:22:48 (permalink)
0

Atomic bitfield change macro

Many people are struggling with the Read-Modify-Write problem when using PIC's. This problem exists when accessing individual bits in an SFR both from an ISR and main code or, in case of using an RTOS, from ISR's and/or multiple threads.

For PIC24/30/33, individual bits are no problem since these controllers offer atomic bit set/clear/change. The problem here still is when using C, that you can not be sure that bit operations at C level are actually translated to the atomic operations.

The problem is bigger when wanting to manipulate a bitfield consisting of more than one bit. If this needs to be done in an SFR, and other bits are used from other ISR's or threads, operations must be atomic to be sure everything goes fine.

Atomic bit field manipulations can however be realized using exclusive or, since the xor operation at machine level is atomic again. For this purpose I created a macro that I would like to share:



static void __attribute__((always_inline)) atomic_xor(unsigned int mask, volatile unsigned int* pVar)
{
__asm__ __volatile__(" xor %1, [%2], [%0] " : "=r"(pVar) : "r"(mask), "0"(pVar) );
}
#define onesMask(u,l) (((~0 << (l)) ^ (~0 << (u))) | (1 << (u)) | (1 << (l)))
#define setBitField_atomic(r,v,u,l) atomic_xor((((v)<<( (u)>(l)?(l):(u) )) ^ (r)) & onesMask(u,l), &r)



The macro is setBitField_atomic. Macro onesMask and the inline functions are helpers.

Using setBitField_atomic you can change a number of consecutive bits without the RMW problem existing since the actual manipulation of the bits is done with a single write operation from the inline function by the xor present there.

The arguments to the macro are:

r: Register, for instance LATD
v: value to set the bitfield to, can be a variable or a constant
u: upper bit of the bitfield, numbered from 0..15
l: lower bit of the bitfield, numbered from 0..15

The macro is resistant to specifying a higher value for the l bit than the u bit, so for l and u you just pass the lowest and the highest bitnumber of the field and the macro does the rest.

Of course, the bitfield this macro is used with should be manipulated by a single ISR or thread only but this is obvious. The macro takes care that the other bits in the SFR are not changed so these can be used by other ISR's or threads without the risk of corruption. So it can not be used to disable interrupts by manipulating the SR register since this typically involves the same bits to be manipulated from multiple threads or ISR's. But hey, who wants to disable interrupts anyway when AVIX is there to offer all interrupt integration you might ever need without disabling interrupts at all.
post edited by zardoz1 - 2008/10/18 10:24:00


AVIX
the PIC32 & dsPIC/PIC24 RTOS with:
- Zero Latency Interrupts
- The best performance!
- Integrated Power Management
Download here: http://www.avix-rt.com/
#1

21 Replies Related Threads

    hg12345
    New Member
    • Total Posts : 10
    • Reward points : 0
    • Joined: 2007/09/10 02:01:10
    • Location: 0
    • Status: offline
    RE: Atomic bitfield change macro 2008/10/23 01:25:05 (permalink)
    0
    universal  fast & compact code:
     
    #define ChangeBitsHW(Dest,New,Mask)     asm volatile ("mov.w #%1,w1         \n" \
                                                                               ";disi #3                    \n   ;??????????"\
                                                                               "xor.w %2,WREG         \n" \
                                                                               "and.w w1,w0,w0        \n" \
                                                                               "xor.w %2                  \n" \
                                                                               : :  "a"(New) , "g"(Mask), "T"(Dest) : "w1" );
     
     
    #2
    secon
    Starting Member
    • Total Posts : 84
    • Reward points : 0
    • Joined: 2007/09/28 01:57:09
    • Location: 0
    • Status: offline
    RE: Atomic bitfield change macro 2008/10/23 02:42:16 (permalink)
    0
    universal  fast & compact code:

     
    I like zardoz's solution better since his macro calculates the mask automatically which is easier to read, does not disable interrupts and in the end the generated code is even more efficient than yours. Also your solution uses hard coded registers meaning the compiler has to deal with this leading to save and restore operations if those registers are in use. Zardoz's solution allows the compiler to select registers itself leading to more efficient code overall.
    #3
    A.R.T
    Super Member
    • Total Posts : 308
    • Reward points : 0
    • Joined: 2006/05/12 03:13:15
    • Location: Cambridge , UK
    • Status: offline
    RE: Atomic bitfield change macro 2008/10/23 02:58:54 (permalink)
    0
    when i look at the code produced from doing something like PORTDbits.RD4 = 1;
     
    i get  bclr.b 0x02da,#4 in the assembler. I'm assuming this is Atomic, so what is the need for these macros ?
    #4
    zardoz1
    Super Member
    • Total Posts : 1852
    • Reward points : 0
    • Joined: 2005/07/09 08:03:28
    • Location: 's-Hertogenbosch, The Netherlands
    • Status: offline
    RE: Atomic bitfield change macro 2008/10/23 07:32:40 (permalink)
    0
    ORIGINAL: atsoft

    when i look at the code produced from doing something like PORTDbits.RD4 = 1;

    i get  bclr.b 0x02da,#4 in the assembler. I'm assuming this is Atomic, so what is the need for these macros ?

     
    These macro's operate on more than a single bit. As long as a specific bitfield is used from a single ISR or single thread, the other bits are not influenced since the xor operation is atomic.


    AVIX
    the PIC32 & dsPIC/PIC24 RTOS with:
    - Zero Latency Interrupts
    - The best performance!
    - Integrated Power Management
    Download here: http://www.avix-rt.com/
    #5
    QKernel
    Super Member
    • Total Posts : 220
    • Reward points : 0
    • Joined: 2008/09/23 17:09:44
    • Location: Alberta Canada
    • Status: offline
    RE: Atomic bitfield change macro 2008/10/23 08:57:48 (permalink)
    0
    universal  fast & compact code:

     
    The disabling of interrupts is not complete.  DISI disables interrupts priority 0 to 6. Now this disables only 3 cycles so it is not a big issue but you actually don't want to disable interrupts at all.
     
     
    #6
    zardoz1
    Super Member
    • Total Posts : 1852
    • Reward points : 0
    • Joined: 2005/07/09 08:03:28
    • Location: 's-Hertogenbosch, The Netherlands
    • Status: offline
    RE: Atomic bitfield change macro 2008/10/23 09:10:55 (permalink)
    0
     but you actually don't want to disable interrupts at all.

     
    Which is exactly my point. I am developing a number of these macro's for use with my RTOS AVIX. These macro's should not disable interrupts since AVIX never does. For PIC32 this is easy since PIC32 contains atomic SET, CLR and INV registers. For PIC24/33 this takes more effort but still, since XOR is atomic it is possible.


    AVIX
    the PIC32 & dsPIC/PIC24 RTOS with:
    - Zero Latency Interrupts
    - The best performance!
    - Integrated Power Management
    Download here: http://www.avix-rt.com/
    #7
    BitWise
    Super Member
    • Total Posts : 1238
    • Reward points : 0
    • Joined: 2004/11/09 13:24:20
    • Location: UK
    • Status: offline
    RE: Atomic bitfield change macro 2008/11/16 04:39:53 (permalink)
    0
    Maybe I'm missing something but I can't see how Zardoz's code works under all cases. I can see that this part..

    static void __attribute__((always_inline)) atomic_xor(unsigned int mask, volatile unsigned int* pVar)
    {
    __asm__ __volatile__(" xor %1, [%2], [%0] " : "=r"(pVar) : "r"(mask), "0"(pVar) );
    }

    .. generates the actual bit changing instruction (equivalent to *pVar ^= mask) but it seems to me that at least one other instruction will have be generated by the expansion of the macro setBitField_atomic in order to calculate the mask. Embedded in the definition of setBitField_atomic is the operation '^(r)' which must read the current state of the target register and combine it with the masks (e.g. mask = ((value shifted) ^ current state) & bit mask) to calculate the bits to flipped in the subsequent xor.

    #define setBitField_atomic(r,v,u,l) atomic_xor((((v)<<( (u)>(l)?(l):(u) )) ^ (r)) & onesMask(u,l), &r)

    If an interrupt occurring after the read but before the execution of the xor changes the bit field then after the interrupt the field would not be changed as expected.

    So it looks like Zardoz's code generates almost the same sequence of instructions as shown in hg1234's post BUT without the disable interrupts instruction that guarantees correct execution.

    Throughout your life advance daily, becoming more skillful than yesterday, more skillful than today. This is never-ending.

    Yamamoto Tsunetomo (1659-1719)
    #8
    zardoz1
    Super Member
    • Total Posts : 1852
    • Reward points : 0
    • Joined: 2005/07/09 08:03:28
    • Location: 's-Hertogenbosch, The Netherlands
    • Status: offline
    RE: Atomic bitfield change macro 2008/11/16 10:32:21 (permalink)
    0
    If an interrupt occurring after the read but before the execution of the xor changes the bit field then after the interrupt the field would not be changed as expected.

     
    The idea behind it is that each thread only manipulates its own bitfield where thread can of course also be ISR.
     
    So for instance 'thread 1' has to use A15-A8 and 'thread 2' uses A7-A0'. In this case 'thread 1' is not supposed to access any of the bits A7-A0 and 'thread 2' is not supposed to access any of the bits A15-A8.
     
    Under this assumption (which is reasonable) the macro works flawless.
     
    It is true that in between reading and x'orring the value the other thread can kick in but this will only do an xor of the other threads bits with 0's meaning those bits are not changed.
     
    For instance, suppose thread 1 does:
     
    setBitField_atomic(LATA, 0x23, 15, 8);
     
    This will result in an xor of LATA where the lower bits (7-0) are guaranteed to be 0's and the upper bits will have the value required to change these bits in such a way the result in the upper byte is 0x23.


    AVIX
    the PIC32 & dsPIC/PIC24 RTOS with:
    - Zero Latency Interrupts
    - The best performance!
    - Integrated Power Management
    Download here: http://www.avix-rt.com/
    #9
    saj
    Junior Member
    • Total Posts : 114
    • Reward points : 0
    • Joined: 2008/06/20 03:13:30
    • Location: UK
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/03 11:30:21 (permalink)
    0
    Hi,

    Could someone explain to me how to use the macro and how to change the following code to be used with the macro:

    #define nRELAY_CS_1_PIN      PORTEbits.RE3
    #define nRELAY_CS_2_PIN      PORTEbits.RE4

    Say i want to set nRELAY_CS_1_PIN high , whats the process required?



    Regards,

    Saj
    #10
    DarioG
    Allmächtig.
    • Total Posts : 54081
    • Reward points : 0
    • Joined: 2006/02/25 08:58:22
    • Location: Oesterreich
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/03 12:25:58 (permalink)
    0
    duplicated in here
    http://forum.microchip.com/tm.aspx?m=387697

    I would not use PORTE, rather LATE. In both case, you can use

    nRELAY_CS_1_PIN = 1; // to set
    nRELAY_CS_1_PIN ^= 1; // to toggle


    GENOVA :D :D ! GODO
    #11
    aschen0866
    Super Member
    • Total Posts : 4398
    • Reward points : 0
    • Joined: 2006/01/08 22:18:32
    • Location: San Diego
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/03 12:40:04 (permalink)
    0

    ORIGINAL: hg12345

    universal  fast & compact code:

    #define ChangeBitsHW(Dest,New,Mask)     asm volatile ("mov.w #%1,w1         \n" \
                                                                              ";disi #3                    \n   ;??????????"\
                                                                              "xor.w %2,WREG         \n" \
                                                                              "and.w w1,w0,w0        \n" \
                                                                              "xor.w %2                  \n" \
                                                                              : :  "a"(New) , "g"(Mask), "T"(Dest) : "w1" );



    I don't see how this macro can be safe noticed that w1 and w0 might have already been used by the calling routine. Am I missing something with this inline assembly?
    #12
    zardoz1
    Super Member
    • Total Posts : 1852
    • Reward points : 0
    • Joined: 2005/07/09 08:03:28
    • Location: 's-Hertogenbosch, The Netherlands
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/03 13:12:03 (permalink)
    0
    ORIGINAL: saj

    Hi,

    Could someone explain to me how to use the macro and how to change the following code to be used with the macro:

    #define nRELAY_CS_1_PIN      PORTEbits.RE3
    #define nRELAY_CS_2_PIN      PORTEbits.RE4

    Say i want to set nRELAY_CS_1_PIN high , whats the process required?



    Regards,

    Saj

     
    You could use in the following way:

     #define RELAY_CS_1_PIN_SET setBitField_atomic(LATE, 1, 3, 3)
    #define RELAY_CS_1_PIN_CLR setBitField_atomic(LATE, 0, 3, 3)
    #define RELAY_CS_2_PIN_SET setBitField_atomic(LATE, 1, 4, 4)
    #define RELAY_CS_2_PIN_CLR setBitField_atomic(LATE, 0, 4, 4)

     
    Note once more this macro guarantees the operation to be atomic while with the regular statements you can not tell since there is no guarantee what the compiler will create and it might just as well create a read-modify-write sequence.
     
    The macro is especially usefull when setting multi-bit fields since in that case also it is guaranteed to be atomic, given the same bitfield is not shared by different 'threads'.
     


    AVIX
    the PIC32 & dsPIC/PIC24 RTOS with:
    - Zero Latency Interrupts
    - The best performance!
    - Integrated Power Management
    Download here: http://www.avix-rt.com/
    #13
    saj
    Junior Member
    • Total Posts : 114
    • Reward points : 0
    • Joined: 2008/06/20 03:13:30
    • Location: UK
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/04 03:41:56 (permalink)
    0
    ORIGINAL: DarioG

    duplicated in here
    http://forum.microchip.com/tm.aspx?m=387697

    I would not use PORTE, rather LATE. In both case, you can use

    nRELAY_CS_1_PIN = 1; // to set
    nRELAY_CS_1_PIN ^= 1; // to toggle


     
    DarioG,
     
    zardoz1 talks about the Read-Modify-Write ISR problem..... I'd prefer to use this method as you've described, would this affect the RMW issue?   Would it be safer to use zardoz1's method ?
     
    Thanks,
     
    Saj
    #14
    saj
    Junior Member
    • Total Posts : 114
    • Reward points : 0
    • Joined: 2008/06/20 03:13:30
    • Location: UK
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/04 03:43:34 (permalink)
    0
    ORIGINAL: zardoz1

    ORIGINAL: saj

    Hi,

    Could someone explain to me how to use the macro and how to change the following code to be used with the macro:

    #define nRELAY_CS_1_PIN      PORTEbits.RE3
    #define nRELAY_CS_2_PIN      PORTEbits.RE4

    Say i want to set nRELAY_CS_1_PIN high , whats the process required?



    Regards,

    Saj


    You could use in the following way:

    #define RELAY_CS_1_PIN_SET setBitField_atomic(LATE, 1, 3, 3)
    #define RELAY_CS_1_PIN_CLR setBitField_atomic(LATE, 0, 3, 3)
    #define RELAY_CS_2_PIN_SET setBitField_atomic(LATE, 1, 4, 4)
    #define RELAY_CS_2_PIN_CLR setBitField_atomic(LATE, 0, 4, 4)


    Note once more this macro guarantees the operation to be atomic while with the regular statements you can not tell since there is no guarantee what the compiler will create and it might just as well create a read-modify-write sequence.

    The macro is especially usefull when setting multi-bit fields since in that case also it is guaranteed to be atomic, given the same bitfield is not shared by different 'threads'.



    Thanks for the explanation! 
     
    I'm trying to understand what zardoz1 meant by the comment in his original post:
    " u: upper bit of the bitfield, numbered from 0..15 "
    " l: lower bit of the bitfield, numbered from 0..15 "
     
    So could you please explain using the macro with the following (pins are now on 14/15) -
     
    #define nRELAY_CS_1_PIN      PORTAbits.RA15
    #define nRELAY_CS_2_PIN      PORTCbits.RC14
     
    Is this correct ? :
     
    #define RELAY_CS_1_PIN_SET setBitField_atomic(LATA, 1, 15, 15)
    #define RELAY_CS_1_PIN_CLR setBitField_atomic(LATA, 0, 15, 15)

    #define RELAY_CS_2_PIN_SET setBitField_atomic(LATC, 1, 14, 14)
    #define RELAY_CS_2_PIN_CLR setBitField_atomic(LATC, 0, 14, 14)


    Saj
    post edited by saj - 2008/12/04 04:17:31
    #15
    zardoz1
    Super Member
    • Total Posts : 1852
    • Reward points : 0
    • Joined: 2005/07/09 08:03:28
    • Location: 's-Hertogenbosch, The Netherlands
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/04 05:09:39 (permalink)
    0
    Is this correct ? :

    #define RELAY_CS_1_PIN_SET setBitField_atomic(LATA, 1, 15, 15)
    #define RELAY_CS_1_PIN_CLR setBitField_atomic(LATA, 0, 15, 15)

    #define RELAY_CS_2_PIN_SET setBitField_atomic(LATC, 1, 14, 14)
    #define RELAY_CS_2_PIN_CLR setBitField_atomic(LATC, 0, 14, 14)

     
    This is correct indeed. the macro I presented is just an example how to atomically set bits and bitgroups to a desired value. Once more, the real strentgh of the macro is when changing multiple bits at once to a desired value. As an example, lets assume you want both relay pins to be set to whatever value at the same moment, so not setting one and some time later the other. Do note this is usefull quite often since when two pins are set directly after each other, there is no guarantee how many cycles this takes since one or more interrupts can occur in between (do mind that for a relay this probably is not such a big problem)
     
    So now an example for two relays at once:
     
     
    #define RELAY_CS_PIN_SET setBitField_atomic(LATA, 1, 15, 14)
     
    RELAY_CS_PIN_SET(0x0000); // Switch both relays off
    RELAY_CS_PIN_SET(0x0001); // Switch relay on pin 15 off and on pin 14 on
    RELAY_CS_PIN_SET(0x0002); // Switch relay on pin 15 on and pin 14 off
    RELAY_CS_PIN_SET(0x0003); // Switch both relays on
     
    And both pins are set at the exact same moment regardless their value before doing this.



    AVIX
    the PIC32 & dsPIC/PIC24 RTOS with:
    - Zero Latency Interrupts
    - The best performance!
    - Integrated Power Management
    Download here: http://www.avix-rt.com/
    #16
    saj
    Junior Member
    • Total Posts : 114
    • Reward points : 0
    • Joined: 2008/06/20 03:13:30
    • Location: UK
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/04 08:52:47 (permalink)
    0
    Great!  So basically a bitfield 1 turns on and 0 turns off, ie controlling 3 pins:
     
    #define RELAY_CS_PIN_SET setBitField_atomic(LATA, 1, 15, 13)
     
    RELAY_CS_PIN_SET(0x0000); // Switch all 3 relays off   (bits 000)
    RELAY_CS_PIN_SET(0x0001); // Pin 13 on, 14/15 off     (bits 001)
    RELAY_CS_PIN_SET(0x0002); // Pin 14 on, 13/15 off     (bits 010)
    RELAY_CS_PIN_SET(0x0004); // Pin 15 on, 13/14 off     (bits 100)
    RELAY_CS_PIN_SET(0x0003); // Pin 13/14 on, 15 off     (bits 011)
    RELAY_CS_PIN_SET(0x0007); // All pins on
     
    Thanks once again!
     
    Also if I call #define RELAY_CS_1_PIN_SET setBitField_atomic(LATA, 1, 15, 15)  will this turn off other pins? or does this only change the selected pin ?
     
     
    Saj
    #17
    DarioG
    Allmächtig.
    • Total Posts : 54081
    • Reward points : 0
    • Joined: 2006/02/25 08:58:22
    • Location: Oesterreich
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/04 12:58:29 (permalink)
    0
    ORIGINAL: saj

    DarioG,

    zardoz1 talks about the Read-Modify-Write ISR problem..... I'd prefer to use this method as you've described, would this affect the RMW issue?   Would it be safer to use zardoz1's method ?



    Hmmm, in particular circumstances Zardoz's code is better since it can avoid issues with simultaneous access to the pins.
    And, since it has no drawbacks, that method could be preferable in all cases.

    The above - the older one - is compatible with older PICs, though.

    GENOVA :D :D ! GODO
    #18
    zardoz1
    Super Member
    • Total Posts : 1852
    • Reward points : 0
    • Joined: 2005/07/09 08:03:28
    • Location: 's-Hertogenbosch, The Netherlands
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/04 15:17:24 (permalink)
    0
    Also if I call #define RELAY_CS_1_PIN_SET setBitField_atomic(LATA, 1, 15, 15)  will this turn off other pins? or does this only change the selected pin ?

     
    This macro will influence pin 15 only and leave all other pins in the state they are. This of course is the essence of this macro.
     
     


    AVIX
    the PIC32 & dsPIC/PIC24 RTOS with:
    - Zero Latency Interrupts
    - The best performance!
    - Integrated Power Management
    Download here: http://www.avix-rt.com/
    #19
    saj
    Junior Member
    • Total Posts : 114
    • Reward points : 0
    • Joined: 2008/06/20 03:13:30
    • Location: UK
    • Status: offline
    RE: Atomic bitfield change macro 2008/12/04 15:29:38 (permalink)
    0
    OK. Thanks all !
    #20
    Page: 12 > Showing page 1 of 2
    Jump to:
    © 2019 APG vNext Commercial Version 4.5