1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice

Defining and using Bit Flags in C

Discussion in 'Code Repository' started by misterT, Apr 11, 2014.

  1. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,695
    Likes:
    368
    Location:
    Finland
    There are many ways to access single bits in C. This first one is my favorite method.

    First, lets define some macros to help manipulate single bits:
    Code (C):
    /* Bit Operation macros */
    #define sbi(b,n) ((b) |= (1<<(n)))          /* Set bit number n in byte b */
    #define cbi(b,n) ((b) &= (~(1<<(n))))       /* Clear bit number n in byte b   */
    #define fbi(b,n) ((b) ^= (1<<(n)))          /* Flip bit number n in byte b    */
    #define rbi(b,n) ((b) & (1<<(n)))           /* Read bit number n in byte b    */

    /* Examples */
    volatile uint8_t flags;

    sbi(flags,0); /* set bit 0 in 'flags' */
    cbi(flags,3); /* clear bit 3 in 'flags' */

    /* Test whether bit 0 is set */
    if(rbi(flags,0)) { /*...*/ }
    That allready looks pretty handy, but you still need to remember where each flag is located and what the purpose of the flag is.
    With couple of more macros and defines we can hide this little detail from the user and make our code more readable, flexible and more safe.

    Code (C):
    #define set_flag(combo)   sbi(combo)
    #define clear_flag(combo) cbi(combo)
    #define read_flag(combo)  rbi(combo)

    /* Define your flags in the format: (variable),(bit_number) */
    #define FLAG_SRAM         (sram_flags),(0)
    #define FLAG_REFRESH      (sram_flags),(1)
    #define FLAG_REGISTER         (GPIOR0),(0)
    #define FLAG_ERROR            (GPIOR0),(1)
    #define FLAG_INTERRUPT        (GPIOR0),(3)


    /* Now our code starts to look very readable, making comments almost useless and redundant */
    volatile uint8_t sram_flags;

    set_flag(FLAG_SRAM)     /* set sram flag */
    clear_flag(FLAG_ERROR); /* clear error flag */

    /* Test whether error flag is set */
    if(read_flag(FLAG_ERROR)) { /*...*/ }

    /* You can make those if-statements even more readable by defining */
    #define flag_is_set(combo)    rbi(combo)
    #define flag_is_clear(combo)  (!rbi(combo))

    if(flag_is_set(FLAG_SRAM)) { /*...*/ }
    if(flag_is_clear(FLAG_SRAM)) { /*...*/ } /* That is almost like reading plain english. */
    The above method gives you the freedom to locate individual flags where ever you want.. even in a general purpose register for efficient access.
    It also lets you change the flag location without modifying every line of code where you access the bit.


    One other method is to use structs and bitfields, but this is not as flexible as the previous method.
    Code (C):
    /* Define structure with 8 one bit bitfields */
    struct bits
    {
        uint8_t bit0:1;
        uint8_t bit1:1;
        uint8_t bit2:1;
        uint8_t bit3:1;
        uint8_t bit4:1;
        uint8_t bit5:1;
        uint8_t bit6:1;
        uint8_t bit7:1;
    };

    /* Now, we could use this structure straight as a variable like this */
    volatile struct bits flags;

    flags.bit0 = 1; /* set bit 0 in 'flags' */
    flags.bit3 = 0; /* clear bit 3 in 'flags' */

    /* Test whether bit 0 is set */
    if(flags.bit0) { /*...*/ }


    /* Or, we could map the structure to some register or variable for extra flexibility and performanse */
    #define flags_bit0  (((volatile struct bits*)&flags)->bit0)
    #define flags_bit1  (((volatile struct bits*)&flags)->bit1)
    #define flags_bit2  (((volatile struct bits*)&flags)->bit2)
    #define flags_bit3  (((volatile struct bits*)&flags)->bit3)
    #define flags_bit4  (((volatile struct bits*)&flags)->bit4)
    #define flags_bit5  (((volatile struct bits*)&flags)->bit5)
    #define flags_bit6  (((volatile struct bits*)&flags)->bit6)
    #define flags_bit7  (((volatile struct bits*)(0x1E))->bit7) /* This bit is mapped in register (memory address 0x1E) for extra performance */

    volatile uint8_t flags;

    flags_bit0 = 1; /* set bit 0 in 'flags' */
    flags_bit3 = 0; /* clear bit 3 in 'flags' */

    /* Test whether bit 0 is set */
    if(flags_bit0) { /*...*/ }
    I tested these methods with 8 bit AVR (GCC) and all the methods produced exactly same results in simple test cases. Register variables of course were much more efficient than flags in sram.
     
    Last edited: Apr 11, 2014
    • Informative Informative x 1
  2. NorthGuy

    NorthGuy Well-Known Member

    Joined:
    Sep 8, 2013
    Messages:
    1,218
    Likes:
    206
    Location:
    Northern Canada
    I think the traditional approach with masks is more flexible. You can set/clear/toggle several bits at a time.

    If you wish you can define macros with bit numbers:

    Code (C):
    #define BIT0 0x0001
    #define BIT1 0x0002
    #define BIT2 0x0004
    Then you can operate several bits at a time:

    Code (C):
    x ^= BIT0 | BIT2 | BIT11;
    Compare to:

    Code (C):
    fbi(x, 0);
    fbi(x, 2);
    fbi(x,11);
    You can also store a combination of flags in a variable, for example:

    Code (C):
    data_bits = BIT0 | BIT11 | some_additional_flags; // the variable now holds the set of flags
     
    ...
     
    x |= data_bits; // and we can set all these flags
    x &= ~data_bits; // or perhaps reset them late
    Or, if you want it less cryptic:

    Code (C):
    #define SET(x,b) x|=b
    #define RESET(x,b) x&=~(b)
    #define TOGGLE(x,b) x^=b
     
    SET(x,data_bits);
    TOGGLE(x,BIT2);
    But, certainly, there are many ways to skin a cat.
     
    • Informative Informative x 1
  3. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,695
    Likes:
    368
    Location:
    Finland
    True, good addition to the topic.

    I was only considering situation where bits are used as flags.. that is: to flag out single event or condition. I assumed that only one bit at a time is modified. In that situation it is nice to name those flags and just use them. No need to know where they are actually located etc.. could be a variable in sram, could be a register.. All you need is the "name" of the flag. (and you can easily map a single bit anywhere you want)

    When you change the state of one bit only the compiler is able to use the efficient SBI or CLI instruction. When you write many bits at a time then the compiler may not optimize properly.. don't know if any compiler is smart enough to use two SBI instructions when you say something like: x |= 0x03

    Edit: Actually, if compiler uses two SBI instructions to do "x |= 0x03" it would be (horribly) wrong.
    If simultaneous update is not important, then setting those bits one at a time can be more efficient. It can also be less efficient.. haha.
     
    Last edited: Apr 11, 2014
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,695
    Likes:
    368
    Location:
    Finland

    I did some tests.. Setting multiple bits in SRAM variable and in register variable.

    Code (ASM):

    ;/* SRAM variables */

    ;/* Setting 3 bits in sram at the same time */
        ;sram |= BIT0 | BIT1 | BIT2;
    00000052  LDS R24,0x0100        Load direct from data space
    00000054  ORI R24,0x07          Logical OR with immediate
    00000055  STS 0x0100,R24        Store direct to data space

    ;/* Setting 3 bits in sram one at a time */
        ;set_flag(SRAM0);
    00000057  LDS R24,0x0100        Load direct from data space
    00000059  ORI R24,0x01          Logical OR with immediate
    0000005A  STS 0x0100,R24        Store direct to data space
        ;set_flag(SRAM1);
    0000005C  LDS R24,0x0100        Load direct from data space
    0000005E  ORI R24,0x02          Logical OR with immediate
    0000005F  STS 0x0100,R24        Store direct to data space
        ;set_flag(SRAM2);
    00000061  LDS R24,0x0100        Load direct from data space
    00000063  ORI R24,0x04          Logical OR with immediate
    00000064  STS 0x0100,R24        Store direct to data space


    ;/* Register variables */

    ;/* Setting 3 bits in a register at the same time */
        ;GPIOR0 |= BIT0 | BIT1 | BIT2;
    00000066  IN R24,0x1E         In from I/O location
    00000067  ORI R24,0x07        Logical OR with immediate
    00000068  OUT 0x1E,R24        Out to I/O location

    ;/* Setting 3 bits in a register one at a time */
        ;set_flag(REG0);
    00000069  SBI 0x1E,0        Set bit in I/O register
        ;set_flag(REG1);
    0000006A  SBI 0x1E,1        Set bit in I/O register
        ;set_flag(REG2);
    0000006B  SBI 0x1E,2        Set bit in I/O register
     
    This is SRAM flag vs. register flag when you have those efficient instructions (SBI, CBI, SBIC, SBIS) available.
    Code (ASM):

        ;if(read_flag(REG0)) { clear_flag(REG0); }
    00000052  SBIC 0x1E,0        Skip if bit in I/O register cleared
    00000053  CBI 0x1E,0         Clear bit in I/O register


        ;if(read_flag(SRAM0)) { clear_flag(SRAM0); }
    00000052  LDS R24,0x0100        Load direct from data space
    00000054  SBRS R24,0            Skip if bit in register set
    00000055  RJMP PC+0x0006        Relative jump
    00000056  LDS R24,0x0100        Load direct from data space
    00000058  ANDI R24,0xFE         Logical AND with immediate
    00000059  STS 0x0100,R24        Store direct to data space
     
     
    Last edited: Apr 11, 2014
  6. NorthGuy

    NorthGuy Well-Known Member

    Joined:
    Sep 8, 2013
    Messages:
    1,218
    Likes:
    206
    Location:
    Northern Canada
    With SRAM, it could've optimized three flag sets into one. I actually expected the code to be exact the same in both cases. I guess it depends on the compiler.

    With GPIO, the possibility to optimize depends on the number of bits being set. If you set 1 or 2 bits, then it's better to use SBI. If you need to set 3 bits, it's three instructions either way, but SBI is still preferable because it doesn't use any working register. If you need to set 4 or more bits, ORI is better because it's only 3 instructions. I wonder if it would figure out to use two SBI instructions instead of ORI if you would ask it to set exactly 2 bits, e.g. "GPIOR0 |= 3"?
     
    • Agree Agree x 1
  7. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,695
    Likes:
    368
    Location:
    Finland
    In AVR the SBI etc. instructions are only available to memory locations 0xFF and below. So it does depend on the hardware and the compiler.

    If you mean that it could combine the individual flag sets into one ORI.. I think that would be wrong to optimize like that. What if you intend to set the flags one after the other.. not all at once.
     
  8. NorthGuy

    NorthGuy Well-Known Member

    Joined:
    Sep 8, 2013
    Messages:
    1,218
    Likes:
    206
    Location:
    Northern Canada
    Yes, you're right. With I/O it could be important to set flags in some order. For example, in PICs you often must set all bits in a module register before you set the "enable" bit, otherwise it gets enabled with old bits.

    However, with RAM locations these two should be equivalent:

    Code (C):
    x |= 7;
    Code (C):
    x |= 1;
    x |= 2;
    x |= 4;
     
  9. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,695
    Likes:
    368
    Location:
    Finland
    I think it is wrong for a compiler to make assumptions about the order you intend to do things. And those two examples above are different.

    Should this:
    Code (C):
    x |= 1;
    x |= 2;
    x |= 4;
    Compile to exactly same asm instructions as this:
    Code (C):
    x |= 1;
    x |= 4;
    x |= 2;
     
    I think not. No matter where x is located.
     
    Last edited: Apr 11, 2014
  10. NorthGuy

    NorthGuy Well-Known Member

    Joined:
    Sep 8, 2013
    Messages:
    1,218
    Likes:
    206
    Location:
    Northern Canada
    Lot of optimization is done that way. This only matters if if these variables may be accessed from other threads or ISRs. If that's a problem, then these variables can be defined as volatile, which tells the compiler to avoid this sort of optimization.

    For example:

    Code (C):
    for (i = 0; i < n; i++) {
       x[i] = a + b;
    }
    This could be optimized as:

    Code (C):
    temp = a + b;
    for (i = 0; i < n; i++) {
       x[i] = temp;
    }
    This makes the code faster without any side effects. Of course, if "a" or "b" is volatile (e.g. an SFR, or a variable modifiable from an ISR), such optimization cannot be done. But otherwise, you won't even notice uless you look at the disassembly.

    Moreover, it's perfectly perfect for the compiler to cast away "i" completely and use some fast way to fill the array with a fixed value.
     
  11. NorthGuy

    NorthGuy Well-Known Member

    Joined:
    Sep 8, 2013
    Messages:
    1,218
    Likes:
    206
    Location:
    Northern Canada
    Here's an example with GNU C

    Code (C):
    #include <stdio.h>
    int x = 0;
     
    void p1() {
      x |= 7;
    }
     
    void p2() {
      x |= 1;
      x |= 2;
      x |= 4;
    }
     
    void main() {
     
    p1();
    p2();
     
    printf("%d",x);
     
    }
    Code (text):
    #cc b.c -o b -O2 --save-temps
    Code (asm):
        .file    "b.c"
        .text
        .p2align 4,,15
    .globl p1
        .type    p1, @function
    p1:
        pushl    %ebp
        movl    %esp, %ebp
        orl    $7, x
        popl    %ebp
        ret
        .size    p1, .-p1
        .p2align 4,,15
    .globl p2
        .type    p2, @function
    p2:
        pushl    %ebp
        movl    %esp, %ebp
        orl    $7, x
        popl    %ebp
        ret
        .size    p2, .-p2
        .section    .rodata
    .LC0:
        .string    "%d"
        .text
        .p2align 4,,15
    .globl main
        .type    main, @function
    main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl    -4(%ecx)
        pushl    %ebp
        movl    %esp, %ebp
        pushl    %ecx
        subl    $12, %esp
        movl    x, %eax
        orl    $7, %eax
        movl    %eax, x
        pushl    %eax
        pushl    $.LC0
        call    printf
        addl    $16, %esp
        movl    -4(%ebp), %ecx
        leave
        leal    -4(%ecx), %esp
        ret
        .size    main, .-main
    .globl x
        .bss
        .align 4
        .type    x, @object
        .size    x, 4
    x:
        .zero    4
        .ident    "GCC: (GNU) 4.5.0"
        .section    .note.GNU-stack,"",@progbits
     
    As you can see, they both came out the same.
     
  12. misterT

    misterT Well-Known Member Most Helpful Member

    Joined:
    Apr 19, 2010
    Messages:
    2,695
    Likes:
    368
    Location:
    Finland
    Of course. I assume that the code is used in time critical embedded application.. This is electronics forum. If I code for PC I do not bother with bit flags..
     
    Last edited: Apr 11, 2014
  13. Ian Rogers

    Ian Rogers Super Moderator Most Helpful Member

    Joined:
    Mar 28, 2011
    Messages:
    8,933
    Likes:
    877
    Location:
    Rochdale UK
    ONLINE
    I wish to add... Pics have separate access registers...

    There only several locations where bit access is allowed on the humble pic... Each range is different. The only way to "bit access" a normal ram location, is via high level code...

    If I create a union to access bit flags, it has to be created with a bit access register! Bit manipulation is a different thing.

    RB0 = 1; is bit access..

    char x;
    if(x & 0x1)... is bit manipulation...
     
    • Like Like x 1

Share This Page