[TUT] [C] Bit manipulation (AKA "Programming 101")

225 posts / 0 new
Last post
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The following text is a slightly reformatted version of a post made by Eric quite a while ago. It has been referred to by so many users (it's now the post for bit-manipulations), and has helped so many that I have reposted it here so it is more easily found.

Programming 101 - By Eric Weddington

To really understand what's going, it's best to learn C languages bit operators and about truth tables.

    | bit OR & bit AND
    ~ bit NOT
    ^ bit EXLUSIVE OR (XOR)
    << bit LEFT SHIFT
    >> bit RIGHT SHIFT

These operators work on bits and not logical values. Take two 8 bit bytes, combine with any of these operators, and you will get another 8 bit byte according to the operator's function. These operators work on the individual bits inside the byte.

A truth table helps to explain each operation. In a truth table, a 1 bit stands for true, and a 0 stands for false.

The OR operation truth table:

    0 OR 0 = 0 0 OR 1 = 1
    1 OR 0 = 1
    1 OR 1 = 1

The AND operation truth table:

    0 AND 0 = 0 0 AND 1 = 0
    1 AND 0 = 0
    1 AND 1 = 1

The XOR operation truth table:

    0 XOR 0 = 0 0 XOR 1 = 1
    1 XOR 0 = 1
    1 XOR 1 = 0

The NOT operator inverts the sense of the bit, so a 1 becomes a 0, and a 0 becomes a 1.

So let's say I have a byte foo that is initialized to 0:

unsigned char foo = 0;

To set bit 0 in foo and then store the result back into foo:

foo = foo | 0x01;

The OR operation is used between the variable that we want to change and a constant which is called a BIT MASK or simply the MASK. The mask is used to identify the bit that we want changed.

Remember that we write the constants in hexadecimal because it's shorter than writing it in binary. It is assumed that the reader knows how to convert back and forth between hex and binary. ;-)

Usually, though the statement is made shorter in real programming practice to take advantage of C's compound assignment:

foo |= 0x01;

This is equivalent to the statement above.

To clear bit 0 in foo requires 2 bit operators:

foo = foo & ~0x01;

This uses the AND operator and the NOT operator. Why do we use the NOT operator? Most programmers find it easier to specify a mask wherein the bit that they are interested in changing, is set. However, this kind of mask can only be used in setting a bit (using the OR operator). To clear a bit, the mask must be inverted and then ANDed with the variable in question. It is up to the reader to do the math to show why this works in clearing the desired bit.

Again, the statement is made shorter with a compound assignment:

foo &= ~0x01;

To see if a bit is set or clear just requires the AND operator, but with no assignment. To see if bit 7 is set in the variable foo:

if(foo & 0x80)
{
}

The condition will be zero if the bit is clear, and the condition will be non-zero if the bit is set. NOTE! The condition will be NON-ZERO when the bit is set. But the condition will not NECESSARILY BE ONE. It is left to the reader to calculate the value of the condition to understand why this is the case.

There is another useful tool that is not often seen, and that is when you want to flip a bit, but you don't know and you don't care what state the bit is currently in. Then you would use the XOR operator:

foo = foo ^ 0x01;

Or the shorter statement:

foo ^= 0x01;

A lot of times the bit mask is built up dynamically in other statements and stored in a variable to be used in the assignment statement:

foo |= bar;

Sometimes, a programmer wants to specify the bit NUMBER that they want to change and not the bit MASK. The bit number always starts at 0 and increases by 1 for each bit. An 8 bit byte has bit numbers 0-7 inclusive. The way to build a bit mask with only a bit number is to LEFT SHIFT a bit by the bit number. To build a bit mask that has bit number 2 set:

(0x01 << 2)

To build a bit mask that has bit number 7 set:

(0x01 << 7)

To build a bit mask that has bit number 0 set:

(0x01 << 0)

Which ends up shifting the constant 0 bytes to the left, leaving it at 0x01.

MACROS

Because there are a number of programmers who don't seem to have a familiarity with bit flipping (because they weren't taught it at school, or they don't need to know it because of working on PCs), most programmers usually write macros for all of these operations. Also, it provides a fast way of understanding what is happening when reading the code, or it provides additional functionality.

Below is a set of macros that works with ANSI C to do bit operations:

#define bit_get(p,m) ((p) & (m))
#define bit_set(p,m) ((p) |= (m))
#define bit_clear(p,m) ((p) &= ~(m))
#define bit_flip(p,m) ((p) ^= (m))
#define bit_write(c,p,m) (c ? bit_set(p,m) : bit_clear(p,m))
#define BIT(x) (0x01 << (x))
#define LONGBIT(x) ((unsigned long)0x00000001 << (x))

To set a bit:

bit_set(foo, 0x01);

To set bit number 5:

bit_set(foo, BIT(5));

To clear bit number 6 with a bit mask:

bit_clear(foo, 0x40);

To flip bit number 0:

bit_flip(foo, BIT(0));

To check bit number 3:

if(bit_get(foo, BIT(3)))
{
}

To set or clear a bit based on bit number 4:

if(bit_get(foo, BIT(4)))
{
    bit_set(bar, BIT(0));
}
else
{
    bit_clear(bar, BIT(0));
}

To do it with a macro:

bit_write(bit_get(foo, BIT(4)), bar, BIT(0));

If you are using an unsigned long (32 bit) variable foo, and have to change a bit, use the macro LONGBIT which creates un unsigned long mask. Otherwise, using the BIT() macro, the compiler will truncate the value to 16-bits.[/]

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This was also the basis for excellent an article in Circuit Cellar http://www.dtweed.com/circuitcellar/caj00180.htm#3120
Unfortunately I think you have to pay to get a copy, but it is really worth it.

Smiley

FREE TUTORIAL: 'Quick Start Guide for Using the WinAVR C Compiler with ATMEL's AVR Butterfly' AVAILABLE AT: http://www.smileymicros.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

this is also touched on in "AVR035: Efficient C Coding for AVR" http://www.atmel.com/dyn/resources/prod_documents/doc1497.pdf

I based a few macros off of that app note, and created avr035.h:

#ifndef _AVR035_H_
#define _AVR035_H_

// from AVR035: Efficient C Coding for AVR

#define SETBIT(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define CLEARBIT(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
#define FLIPBIT(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))
#define CHECKBIT(ADDRESS,BIT) (ADDRESS & (1<<BIT))

#define SETBITMASK(x,y) (x |= (y))
#define CLEARBITMASK(x,y) (x &= (~y))
#define FLIPBITMASK(x,y) (x ^= (y))
#define CHECKBITMASK(x,y) (x & (y))

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A recent thread had a very nice solution which extends on the basic bit-manipulation macros. IIRC it went something along the lines of:

Defines:

#ifndef _AVR035_H_
#define _AVR035_H_

// from AVR035: Efficient C Coding for AVR

#define SETBIT(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define CLEARBIT(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
#define FLIPBIT(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))
#define CHECKBIT(ADDRESS,BIT) (ADDRESS & (1<<BIT))

#define SETBITMASK(x,y) (x |= (y))
#define CLEARBITMASK(x,y) (x &= (~y))
#define FLIPBITMASK(x,y) (x ^= (y))
#define CHECKBITMASK(x,y) (x & (y))

#define VARFROMCOMB(x, y) x
#define BITFROMCOMB(x, y) y

#define C_SETBIT(comb) SETBIT(VARFROMCOMB(comb), BITFROMCOMB(comb))
#define C_CLEARBIT(comb) CLEARBIT(VARFROMCOMB(comb), BITFROMCOMB(comb))
#define C_FLIPBIT(comb) FLIPBIT(VARFROMCOMB(comb), BITFROMCOMB(comb))
#define C_CHECKBIT(comb) CHECKBIT(VARFROMCOMB(comb), BITFROMCOMB(comb))

#endif

Use:

#define Status_LED  PORTA, 3

C_SETBIT(Status_LED);
C_CLEARBIT(Status_LED);

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As an additional item to check if a bit is clear:

if(~(foo) & 0x80)
{
}

Ben
-Using IAR (& ocasionally CodeVision)
0.7734
1101111011000000110111101101

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

BenG wrote:
As an additional item to check if a bit is clear:

if(~(foo) & 0x80)
{
}

My 1st choice would be for the following which, IMHO, is easier to "read":
if ( ( foo & 0x80 ) == 0 )
{
  ...
}

should result in the same compiler generated code.

Don

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

With regards to the above i'd use

if(!CHECKBITMASK(foo,0x80))

or

if(!CHECKBIT(foo,7))

But i agree Don's code is more readable

/Bingo

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Another approach:

I like it to access bit variables like any other variables and then I can write:

if(i == 1)
if(i == 0)
i = 0;
i = 1;

which looks easy readable for me.

This can easy be done by casting a portbit as a member of a bit field.

On the attachment there is the definition of the macro SBIT.

Following an example code:

#include 
#include "sbit.h"


#define KEY0            SBIT( PINB, 0 )
#define KEY0_PULLUP     SBIT( PORTB, 0 )

#define LED0            SBIT( PORTB, 1 )
#define LED0_DDR        SBIT( DDRB, 1 )


int main( void )
{
  LED0_DDR = 1;         // output
  KEY0_PULLUP = 1;      // pull up on

  for(;;){

    if( KEY0 == 0 )     // if key pressed (low)
      LED0 = 0;         // LED on (low)
    else
      LED0 = 1;         // LED off (high)
  }
}

Naturally this macro can also be used for internal flag variables, not only for IO registers.

Peter

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
 TCCR1B |= (1 << CS10); 

You might want to explain what this means. I didn't realize you could actually left shift a bit to a specific place using this actual name of the bit. It drove me crazy because the timer tutorial was directing me here and your tutorial makes no mention of this.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That means, that nr 1 is shifted left as many times as it is needed to reach bit named CS10. Where does the compiler know that CS10 is that? well you give him the AVR's name and it's smart enough to know such things thanks to some smart programmers on the GCC side ;)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think this tutorial is trying to be as generic as possible. Not all the AVR C compilers have all the bit names defined in the header files for each AVR part so

TCCR1B |= (1 << CS10);

won't necessarily work on all compilers.

There's no "magic" to it anyway, a typical part definition file that does include the bit positions simply has something like (for mega16):

#define CS10    0
#define CS11    1
#define CS12    2
#define WGM12   3
#define WGM13   4
#define ICES1   6
#define ICNC1   7

For those C compilers that don't have the bit definitions in their .h collection the AVR Studio file \Program Files\Atmel\AVR Tools\AvrStudio4\xmlconvert.exe will probably prove useful to generate .h files from Atmel's own XML part definition files:

C:\Program Files\Atmel\AVR Tools\AvrStudio4>xmlconvert
xmlconvert: No source file specified

Usage: xmlconvert [-f output-format] [-o outdir] [-1nbclV] infile ...
        Output formats: a[vrasm] | g[cc] | i[ar] | c[c] (generic c)
Options:
        -1  = Don't generate AVRASM2 #pragma's
        -n  = Don't warn about bad names
        -b  = use DISPLAY_BITS attribute to limit bit definitions
        -c  = Add some definitions for compatibility with old files
        -l  = Produce linker file (IAR only)
        -q  = Allow linked register quadruple (32-bit)
        -V  = print xmlconvert version number

Cliff

 

Pages