bclose

44 – Bitwise operations

Objectives

 

 

    • Get to know how to represent graphic characters in a dot matrix.
    • How to code these characters.
    • Introduce the basic bitwise operations AND, OR and XOR
    • Introduce the left shift and right shift operations.

 

BILL OF MATERIALS

Arduino UNO  Arduino UNO or equivalent and relaxed mental state

 

DEFINING A CHARACTER

 

If you remember, in chapter 31, we defined one of these arrays to show the numbers on the display, something like this:

byte Digit[10][8] =
  {  
     { 1,1,1,1,1,1,0,0 },    // 0
     { 0,1,1,0,0,0,0,0 },    // 1
     { 1,1,0,1,1,0,1,0 },    // 2
     { 1,1,1,1,0,0,1,0 },    // 3
     { 0,0,1,0,0,1,1,0 },    // 4   
     { 1,0,1,1,0,1,1,0 },    // 5
     { 1,0,1,1,1,1,1,0 },    // 6
     { 1,1,1,0,0,0,0,0 },    // 7
     { 1,1,1,1,1,1,1,0 },    // 8
     { 1,1,1,0,0,1,1,0 }     // 9
   };

We defined the segments we wanted to turn up setting them as 1 or 0 inside one byte. That means that one array takes up 80 bytes (you can check it using with the sizeof() function, sizeof(Digits)).

Of course, this is valid for some BCD (Binary Coded Decimal) displays that have 8 segments for each digit. Suppose we wanted to use a slightly more sophisticated display, such as a 8 × 8 LED dot matrix, which could independently turn the LEDs up (we will see it in the next chapter). In this case, we will have to define a 8 × 8 dot matrix, to draw an specific letter.

If we defined the characters as we did in chapter 31, each letter would take up 8 x 8 = 64 bytes. So, if we defined the complete alphabet, using uppercase and lowercase letters, numbers, some symbols such as +, -, /, *, punctuation marks and others, it would right away grow up to 128 characters. All these characters, taking up 64 bytes each, give us the beautiful amount of 8,192 bytes.

If you remember, when we spoke of available memory for variables, we said we only had 2k, but we do need 8K for these arrays, so this is not the right way to do things.

Fortunately, we are not the first ones facing this problem (what a surprise!). There was an heroic era when buying 8k of RAM for your computer cost an arm and a leg. So, the solution is bitwise encoding and operate with them later.

And this leads us directly to the objectives of this chapter.

DEFINING 8×8 CHARACTER ARRAYS

 

Suppose we wanted to draw the letters of the word PROMETEC on a 8 × 8 display. We could try to draw in a grid something like this:

bits_p

For R:

bits_r

For O:

bits_o

Notice that I also write the value in binary, and I have also added a last column in hexadecimal because it is more convenient to operate.

 
  • As we said the last time we talked about it, the hexadecimal system was widely used because it was very easy to convert numbers from hexadecimal to binary and vice versa. For those of you interested, it is as easy as picking bits 4 by 4 and convert them directly to hexadecimal using the following table:
Binary to hexadecimal

 

A compact way to write these values is:

byte P[] = { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x40 };
byte R[] = { 0x78, 0x44, 0x44, 0x78, 0x70, 0x58, 0x4C, 0x46 };
byte O[] = { 0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C };

Notice that all we have done is to copy the above numbers in hexadecimal (although it is the same if you write them in binary or decimal). Thus, using only 8 bytes we code the bits needed to draw each letter.

The problem now will be to use the bits when needed, which will take us straight to the next point.

BITWISE OPERATIONS

 

Let’s start with the letter P and the array that defines it graphically.

byte P[] = { 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x40 };

We write the array in hexadecimal, for convenience. But the idea is that the first row of the letter P is contained in the first element of the array, 0x78. Each of the bits that draw their first row are there, contained in a single bit, not 8. And the same with the rest of the rows of the drawing.

If we did this to define a complete set of characters, we would use:

128 characters x 8 bytes = 1024 bytes

Of course, it fits us and we still have another 1k of memory for other variables. The problem is that we will have to unpack the bits to turn up each dot of the display, but we will see that C ++ provides the means to do that.

Let’s start first with the AND function at bitwise level. Its symbol is the operator & (do not mistake it for &&, which is the logical operator). Let’s see how it works with the following example:

int a =  92;       // in binary: 0000000001011100
int b = 101;       // in binary: 0000000001100101
int c = a & b;     // result:    0000000001000100,    68 in decimal.

AND works at bitwise level, if both are 1, the result is 1. Otherwise the result is 0. Notice that it performs the bitwise operation in binary.

Let’s see the OR function. Its symbol is |:

int a =  92;        // in binary: 0000000001011100
int b = 101;        // in binary: 0000000001100101
int c = a | b;      // result:    0000000001111101,  125 in decimal.

OR is works at bitwise level as follows: the result is 1 if any bit or both are 1, and zero if both are 0.

There is also an XOR operator, which is one if any of them is a 1, but 0 if both are 1 or 0. That is, if they are not equal the result is 1, otherwise, if they are equal, the result is 0. The symbol is ^.

int x = 12;         // binary: 1100
int y = 10;         // binary: 1010
int z = x ^ y;      // binary: 0110, in decimal 6

Is this any use? The million dollar question.

Because, as always, it is very difficult to explain the solution to problems that you still have not faced, but calm down, we will see one soon.

 
  • So far we have used the Arduino gates one at a time, reading its value. But Arduino contains internal registers that can be read in blocks of 8 bits and each bit represents the status of a digital gate.
  • In fact, the Arduino IDE, reads the registers all in a row and then do bitwise operations, to show the result of a digitalRead() as HIGH or LOW. And this is slower dear friends, than doing it yourself, and there are times when improving the speed of your program can be critical.
  • I do not intend to face this issue yet (though it will come, sooner or later), but it is important that it rings a bell with you.

 

The case of representing characters by using dot matrix is one of these problems, in which these operations will be critical.

When we want to turn up a dot in 8 × 8 LED dot matrix, we will have extract one by one each bit of a byte, that represents which dot have to be turned up or not. To extract a particular bit of a byte of data, we will have to use this kind of operators and also use the bitwise operators.

So, let’s go:

The first is the right shift operator, >>, which moves the bits to the right an specified number of positions. And there exists also the left shift operator, <<. Here is an example:

int a = 5;       // binary: 00000101
int b = a << 3;  // binary: 00101000, 40 in decimal
int c = b >> 3;  // binary: 00000101, Back to the initial value 5

If we shift 00000101 three digits to the left, the result it is 00101000.

It is very similar to what happened using a shift register, where bits were shifted. By shifting to the left, the bits that enter from the right are 0s and those leaving are simply lost.

When you make a right shift, the outgoing bits on the right are lost, and those who enter from the left are 0s.

Well, armed with these tools let’s see how can we extract a particular bit of a given byte:

 
  • These operations, AND, OR, XOR, right shift and left Shift, work the same regardless of the type of the variable, ie works also with int, long, and others..
 

 

EXTRACTING SPECIFIC BITS

 

Suppose we have a number like this:

byte n = 0b00110110 ;   // Hex 0x36         DEC  54

And we want to get the bit 3, starting from the right (position 2). How can we do it?

Let’s start defining the position of the bits. So, from left to right the positions are:

pos = 7 6 5 4 3 2 1 0

We could start making a 3 bit right shift:

byte n = 0b00110110;
n >>3  = 0b00000110    // Bits come out to the right y zeros come in from the left 

PBut we are now interested only in the bit that is on the rightmost position and not the rest, so we will do:

byte result = n & 0b00000001 // Decimal 1

After doing a bitwise AND operation, we get rid of all positions that contain zeros and only if the least significant bit of n is 1, the result is 1, otherwise it will be 0.

We can write a general purpose function to get a specific bit of a byte:

bool GetBit(byte N, int pos)
   {                            // pos = 7 6 5 4 3 2 1 0
       int b = N >> pos;        // Shift bits
       b = b & 1;               // Gets only the last bit
       return b;
   }

This function will return TRUE or FALSE depending on whether the bit is 1 or 0, and we will have a chance to try. But just for fun, here it is a little program that will print the consecutive bits of a given number, n, that is defined in the first line:

Sketch 44.1
    byte n = 0b01110110 ;

    void setup()
       {      
            Serial.begin(9600);  
       }

    void loop()
       {    
            for ( int k=7; k>=0 ; k--)
                 {   byte m = GetBit( n, k);
                     Serial.print(m);
                     Serial.print(", ");
                 }
              Serial.println();
       }

    bool GetBit( byte N, int pos)
        {   // pos = 7 6 5 4 3 2 1 0
            int b = N >> pos ;                  // Shift bits
            b = b & 1 ;                         // Gets only the last bit
            return b ;
        }

By the way, it is a good time to mention that our Serial.print() function accepts modifiers to specify whether we want the result to be formatted in binary, decimal or hexadecimal.

Sketch 44.2
int n = 415 ;

void setup()
   {
        Serial.begin(9600);
   }
void loop()
   {
        Serial.print(n, DEC);
        Serial.print(",    ");
        Serial.print(n, BIN);
        Serial.print(",    ");
        Serial.print(n, HEX);
        Serial.print(",    ");
        Serial.println(n, OCT);
   }

And so I hope to avoid you some headache converting formats.

 

SUMMARY

 

 

    • We have seen how to represent graphical characters in a dot grid y how to compactly code this information bit by bit.
    • We have introduced the basic bitwise operations: AND, OR and XOR.
    • We have worked on some bit shifting examples to the right and to the left.
    • We have defined a general purpose function, GetBit(), that allows us to extract a particular bit from a byte. It would be very easy to upgrade it in order to work with words of bigger sizes.
    • We described the way to use the Serial.print() function using modifiers to print the output in decimal, hexadecimal, binary or octal.
 

 

 

No Comments

Give a Reply