Arduino Inline Assembly Tutorial (Bit Ops)

Introduction

Bit operations are the foundation upon which all microcontroller programming builds. A good understanding of bit functions is essential for efficient arduino programming in general, and specifically for inline assembly programming. Its that simple.

It really is simple too. Bit manipulation basically comes down to the capacity to set, clear and toggle bits inside of a byte. The operations which perform these manipulations are termed bitwise operations (AND, OR, XOR, NOT, and shifts).

I don’t plan to cover the very basics here. If you don’t already have at least a fundamental understanding of bit operations, I suggest starting here.

Truth in a Table

Having said that, I find truth tables are helpful in understanding each bitwise operation. In a truth table, a 1 bit stands for true, and a 0 stands for false. Here are the truth tables for AND, OR and XOR operations:

In addition to AND, OR and XOR, there is also the NOT operation, which inverts the bit; 1 becomes 0, and 0 becomes 1.

SO CAN I

I use the mnemonic “SO CAN I” to help remember basic bit functions. “SO”, or “Set a bit with Or”, and “CAN 1”, for “Clear a bit with And Not 1”. It works for me!

Setting the Stage

It’s time to twiddle some bits. We will start by setting one bit inside of a byte. For example, let’s set bit 0 in byte-sized variable “foo” (storing the result back into foo), which requires the use of the OR operation. We OR (“SO”) between the variable that we want to change and a constant. The constant is referred to as a BIT MASK, or simply the MASK. The mask is used to identify the bit (or bits) that we want to affect. In our example, we just want to set bit 0, so our mask is simply 1, or 0b00000001 (binary). Note, that 1 occupies the right-most bit position, or bit #0. Using the binary form of the number (i.e. 0b00000001) for the mask is helpful because it provides a pictorial representation of the affected bits.

The ORI instruction is the mnemonic for Logical OR with Immediate. ORI performs a logical OR between the contents of a register and a constant and places the result back into the register. The OR instruction is simply a logical OR between the contents of two registers.

Finally, if you don’t like my “SO CAN I” mnemonic, you can use the SBR instruction, which is the mnemonic for Set Bit in Register. However, the assembler often replaces the SBR instruction with an ORI instruction.

 
//Set bit to immediate value
//C language equivalent: b = b | 0x01;
volatile uint8_t b = 0;

asm (
  "ori %0, %1 \n"
  :"+r" (b) : "M" (0x01)
);

If you are already comfortable using the typical C notation to define multiple bits, like so:

(1<<3) | (1<<2) | (1<<1) | (1<<0)

You can continue to use this technique in your inline assembly code for constant expressions. For example:

//corresponds to “ori %0, 15 \n”
volatile uint8_t b = 0;

asm (
  "ori %0, (1<<3) | (1<<2) | (1<<1) | (1<<0) \n"
  :"+r" (b) 
);

Clear a Register, And Not

To clear bit 0 in variable foo requires 2 bit operations (“CAN 1”), both the AND, and NOT operations. To clear a bit, the bit mask must first be NOT’d (inverted) and then AND’d with the variable. The first hurdle here, is there is no ‘NOT’ instruction. Did I just state a “double negative“? However, we realize when EORing something with a one, that bit flips. So to get the inverse bits (or to perform a NOT operation), we EOR it with a byte filled with 1’s (0b11111111, or 255):

volatile uint8_t foo = 0xff, bit = 1;

asm (
  "eor %1, %2 \n"
  "and %0, %1 \n"
  :"+r" (foo) : "r" (bit), "r" (0xff)
);

Of course if we wanted to perform the NOT operation with a constant value, we can use the C language negation operator ‘~’ (or tilde) with the ANDI instruction, like so:

volatile uint8_t foo = 0xff;

asm (
  "andi %0, %1 \n"
  :"+r" (foo) : "i" (~1)
);

Getting One’s Bits Inverted

When I said, there is no NOT instruction (was that a double negative again?), I was only partially correct. While there is no instruction termed “NOT”, there is a COM instruction which performs a “one’s-COMplement of the register. One’s complementing a number is the same as inverting it’s bits.

This allows us to rewrite our initial example for clearing a bit:

volatile uint8_t foo = 0xff, bit = 1;

asm (
  "com %1     \n"
  "and %0, %1 \n"
  :"+r" (foo) : "r" (bit)
);

Making Flippy Flop

Another handy tool flips or toggles a byte on and off, even if you don’t know and/or don’t care what state the bit is currently. This can easily be accomplished by using the XOR operator:

//C language equivalent: foo = foo ^ 0x01;
volatile uint8_t foo = 1;

asm (
  "ldi r16, 1  \n"
  "eor %0, r16 \n"
  : "+r" (foo) : : "r16"
);

Reading, Modifying and Writing

We previously covered how to set and clear bits within PORT registers using the SBI/CBI instructions (here). But, can we have a general method for setting and/or clearing several bits on a Port without altering the others? Since Port registers contain bits controlling several pins (typically 0-7), simply writing to the Port without regard to the other bits would overwrite their status. This is the essence of the Read-Modify-Write strategy we briefly talked about previously. The R-M-W technique properly preserves the unaffected bits of register.

Suppose we wanted to set digital pins #11 and #13 (Port B bits 3 & 5). Our mask becomes 0b00101000, 40, or 0x28 hexadecimal. The process is to first read the port, then modify the byte (set our bits with an OR operation), and finally write back to the Port. Conversely, if we EOR and AND the bits, we clear them.

Turn on multiple bits:

//Set multiple bits on a port
volatile uint8_t BitMask = 0b00101000;

asm (
  "in r18, %0  \n"
  "or r18, %1  \n"
  "out %0, r18 \n"
  : : "I" (_SFR_IO_ADDR(PORTB)), "r" (BitMask) : "r18"
);
 

Turn off multiple bits:

//Clear multiple bits on a port
volatile uint8_t foo = 0xff, bit = 1;

asm (
  "in r18, %0  \n"
  "eor %1, %2  \n"
  "and r18, %1 \n"
  "out %0, r18 \n"
  : : "I" (_SFR_IO_ADDR(PORTB)), "r" (BitMask) , "r" (0xff) : "r18"
);

Remember the restrictions on using IN/OUT? These two instructions only work with the Standard IO Registers 0x00 through 0x3f. If we wanted to use the Read-Modify-Write technique on IO Registers above 0x3f, we need to substitute LDS and STS for IN and OUT.

Not So Exclusive Or

BTW, EOR has several other useful functions. For example, an EOR of itself is the same as CLR (clear), or an LDI with 0 (Load Immediate). Each instruction impacts the Status Register too:

"eor r16, r16 \n" //clears SREG S, V and N and sets Z bit
"clr r16 \n" //clears SREG S, V and N and sets Z bit
"ldi r16, 0 \n" //does not effect the SREG

An EOR can also be used to compare two registers to determine if they are the same. For example:

EOR r0, r1

This will perform an Exclusive OR of registers 0 and 1, putting the result in r0. If both registers had the same value, r0 will contain zero. This will form the basis for a following tutorial on branching.

Shifty Bit(s)

A confusing issue with setting or clearing bits arises from the difference between the bit number(s) and the bit mask. The bit number and the bit mask are not the same. For example, the bit position #0 requires a mask of 0b00000001 (or 0x01), and position #1 uses a mask of 0b00000010 (or 0x02), followed by 0x04 for bit #3, and so on.

Bit : Mask [bit position]
  0 : 1    [ 0b00000001 ]
  1 : 2    [ 0b00000010 ]
  2 : 4    [ 0b00000100 ]
  3 : 8    [ 0b00001000 ]
  4 : 16   [ 0b00010000 ]
  5 : 32   [ 0b00100000 ]
  6 : 64   [ 0b01000000 ]
  7 : 128  [ 0b10000000 ] 

A Practical Bit

Here are two simple yet practical examples of AND’ing and OR’ing. The difference between an ASCII upper and lower case character of the alphabet is simply the status of bit #5. If bit #5 is set, the character is lower case, but when bit #5 is clear the character is upper case. We can use this knowledge to write two simple inline functions (noting of course, that these functions are already included in the “ctype.h” header file of the standard C library):

inline char _tolower(char c) {
  asm (
    "ori %0, 0x20 \n" //set bit 5, ‘A’ becomes ‘a’
    : "=r" (c)
  );
  return c;
}

inline char _toupper(char c) {
  asm (
    "andi %0, ~0x20 \n" //clear bit 5, ‘a’ becomes ‘A’
    : "=r" (c)
  );
  return c;
}

Shifting Into Gear

As you can see, there’s much to cover on the topic of bit manipulation. Bit operations are used throughout embedded programming in general, and arduino programming in particular. We’ve just skimmed the basics of setting, clearing and inverting bits. Our next tutorial will continue with bit shift operations.

Reference

AVR 8-bit Instruction Set
AVR-GCC Inline Assembler Cookbook
Extended Asm – Assembler Instructions with C Expression Operands
AVR Bit and Bit-Test Instructions

Also available as a book, with greatly expanded coverage!

BookCover
[click on the image]

About Jim Eli

µC experimenter
This entry was posted in Uncategorized and tagged , , , , , . Bookmark the permalink.

3 Responses to Arduino Inline Assembly Tutorial (Bit Ops)

  1. juvar1 says:

    For some reason your examples are not working on my Arduino IDE. I tried direct copy and paste but error is always the same “impossible constraint in ‘asm'”. Do you have any idea how to get it working? Thank you.

    • Jim Eli says:

      Fearing that something had changed in the IDE (1.8.3), I just successfully compiled every example on the page, including using the _SFR_IO_ADDR MACRO. What Arduino board are you using, these examples are for 8-bit AVR based boards?


      void setup() {
      volatile uint8_t b = 0;
      volatile uint8_t foo = 0xff, bit = 1;
      volatile uint8_t BitMask = 0b00101000;

      asm (
      "ori %0, %1 \n"
      :"+r" (b) : "M" (0x01)
      );

      asm (
      "ori %0, (1<<3) | (1<<2) | (1<<1) | (1<<0) \n"
      :"+r" (b)
      );

      asm (
      "eor %1, %2 \n"
      "and %0, %1 \n"
      :"+r" (foo) : "r" (bit), "r" (0xff)
      );

      asm (
      "andi %0, %1 \n"
      :"+r" (foo) : "i" (~1)
      );

      asm (
      "com %1 \n"
      "and %0, %1 \n"
      :"+r" (foo) : "r" (bit)
      );

      asm (
      "ldi r16, 1 \n"
      "eor %0, r16 \n"
      : "+r" (foo) : : "r16"
      );

      asm (
      "in r18, %0 \n"
      "or r18, %1 \n"
      "out %0, r18 \n"
      : : "I" (_SFR_IO_ADDR(PORTB)), "r" (BitMask) : "r18"
      );

      asm (
      "in r18, %0 \n"
      "eor %1, %2 \n"
      "and r18, %1 \n"
      "out %0, r18 \n"
      : : "I" (_SFR_IO_ADDR(PORTB)), "r" (BitMask) , "r" (0xff) : "r18"
      );
      }

      inline char _tolower(char c) {
      asm (
      "ori %0, 0x20 \n" //set bit 5, ‘A’ becomes ‘a’
      : "=r" (c)
      );
      return c;
      }

      inline char _toupper(char c) {
      asm (
      "andi %0, ~0x20 \n" //clear bit 5, ‘a’ becomes ‘A’
      : "=r" (c)
      );
      return c;
      }

      void loop() { }

      Sketch uses 540 bytes (1%) of program storage space. Maximum is 32256 bytes.
      Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.
      
      • juvar1 says:

        Yes, that worked. Thanks! I don’t know what was wrong previously. Probably i had some typing error. Now I can continue to learn more assembly.

Leave a comment