Why the bitwise or assignment?

For Adafruit customers who seek help with microcontrollers

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
aqua_scummmm
 
Posts: 4
Joined: Thu Mar 05, 2009 11:42 am

Why the bitwise or assignment?

Post by aqua_scummmm »

I've seen assignment operators, notably more common in µC code, that use " |= ". I assume this is a bitwise OR assignment, much like a += or a *=.

I can see the use of it if you intend to use it as a bitwise function (such as here: http://imakeprojects.com/Projects/avr-tutorial/ ), but have also seen it used as an initial assignment (such as here: http://fourwalledcubicle.com/files/Chaser.c ). Why use it as an initial assignement? Isn't that just more work for the chip (checking both bytes and setting the OR value versus just setting a known value)? Is there any particular advantage?

I think I've seen it other places too, but I can't really google for what it sees as punctuation.

The_Don125
 
Posts: 373
Joined: Tue Mar 06, 2007 11:51 pm

Re: Why the bitwise or assignment?

Post by The_Don125 »

Coding clarity. Using the bitwise OR, you can see what is going on much easier than a direct assignment. Typically, I see it used for initializing register bits to 1.

So, say register reg_x controls the settings of function y, and upon microcontroller startup, it is guaranteed to have value 0x00. To get the functionality you want, the register needs to hold the value 0x33 (0b00110011). While

Code: Select all

reg_x = 0x33;
is certainly valid, that tells you nothing about what those bits in reg_x do. Now, if you use a bitwise or, such as:

Code: Select all

reg_x |= (1 << MUX2) | (1 << MUX1) | (1 << LALN) | (1 << EN);
You can see that, though they are in acronym form, the names of the bits seem a little more informative. Rather than a series of 1's and 0's, those digits now have names. If you really wanted to be robust in your commenting, you could cover all 8 bits, like:

Code: Select all

reg_x |= (0 << MUX4) | (0 << MUX5) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0) | (0 << (ACT)| (1 << LALN) | (1 << EN);
And know what the register does and is set to in a much simpler way than just assigning 0x33. Also, with the OR method, you no longer need to worry about if you have the right bit for the feature you want to enable, you just need to know the acronym.

edit:
What formerly inhabited this area was proved wrong, see my post below. Thanks for the catch oPossum
Last edited by The_Don125 on Fri Apr 03, 2009 3:42 am, edited 3 times in total.

aqua_scummmm
 
Posts: 4
Joined: Thu Mar 05, 2009 11:42 am

Re: Why the bitwise or assignment?

Post by aqua_scummmm »

Ok, awesome about it actually taking less time, now I have a tangible reason to use it.

Maybe someday I'll switch over to the way you coded it, but to me, it's easier to read the ones and zeroes with a comment line over it.

It does make sense, though, the way you wrote that.

User avatar
opossum
 
Posts: 636
Joined: Fri Oct 26, 2007 12:42 am

Re: Why the bitwise or assignment?

Post by opossum »

The_Don125 wrote:A direct register assignment of an 8-bit variable takes 2 cycles to complete, while using OR to turn on the bits you want and leaving the remainder unchanged only needs a single clock cycle.
For what microcontroller?

The LDI (Load Immediate) and ORI (Logical OR with Immediate) are both one word single cycle AVR instructions.

The_Don125
 
Posts: 373
Joined: Tue Mar 06, 2007 11:51 pm

Re: Why the bitwise or assignment?

Post by The_Don125 »

Ah, my mistake, looks like I read the data for LDS rather than LDI

And, in the interest of complete testing (actually procrastinating homework), I decided to see how the two tasks compile using AVR-GCC, Optimization level 3

Code: Select all

  PORTB |= 0x33;
  52:	88 b3       	in	r24, 0x18	; 24    one clock
  54:	83 63       	ori	r24, 0x33	; 51    one clock
  56:	88 bb       	out	0x18, r24	; 24    one clock
  58:	ff cf       	rjmp	.-2      	; 0x58 <main+0x6> two clocks

  PORTB = 0x33;
  52:	83 e3       	ldi	r24, 0x33	; 51    one clock
  54:	88 bb       	out	0x18, r24	; 24    one clock
  56:	ff cf       	rjmp	.-2      	; 0x56 <main+0x4> two clocks
Looks like I was way off. Not only is LDI only one clock cycle, but removing the OR actually removes an instruction from the compiled code!

Now to try optimization level s (I already tried optimizations 0 and 1, both of those need about 10 assembly instructions to do what was accomplished above in 3-4)

Code: Select all

  PORTB |= 0x33;
  52:	88 b3       	in	r24, 0x18	; 24
  54:	83 63       	ori	r24, 0x33	; 51
  56:	88 bb       	out	0x18, r24	; 24
  58:	ff cf       	rjmp	.-2      	; 0x58 <main+0x6>

  PORTB = 0x33;
  52:	83 e3       	ldi	r24, 0x33	; 51
  54:	88 bb       	out	0x18, r24	; 24
  56:	ff cf       	rjmp	.-2      	; 0x56 <main+0x4>
Same result. Optimization level 2 also gives the same assembly instructions.

User avatar
opossum
 
Posts: 636
Joined: Fri Oct 26, 2007 12:42 am

Re: Why the bitwise or assignment?

Post by opossum »

Try it with a variable (uint8_t) rather than a port. The code will be different. The AVR is non-orthogonal in a rather annoying way (if you are writing asm code at least).

The_Don125
 
Posts: 373
Joined: Tue Mar 06, 2007 11:51 pm

Re: Why the bitwise or assignment?

Post by The_Don125 »

Well, using this code:

Code: Select all

#include <avr/io.h>
int main(void)
{
uint8_t val;
  val = 0x33;
  val = val + 1;
  while(1);
}
Everything involving val got optimized into oblivion if I used any optimization level other than 0. I can see why though, it doesn't actually do anything.

Fixing the code so that it doesn't disappear, I now get the following at optimization 1 - s:

Code: Select all

uint8_t val;
  val |= 0x33;
  52:	83 e3       	ldi	r24, 0x33	; 51
  PORTB = val;
  54:	88 bb       	out	0x18, r24	; 24
  56:	ff cf       	rjmp	.-2      	; 0x56 <main+0x4>

uint8_t val;
  val = 0x33;
  PORTB = val;
  52:	83 e3       	ldi	r24, 0x33	; 51
  54:	88 bb       	out	0x18, r24	; 24
  56:	ff cf       	rjmp	.-2      	; 0x56 <main+0x4>
So they in fact compile down to the exact same assembly.

The code used was:

Code: Select all

#include <avr/io.h>
int main(void)
{
uint8_t val;
  val = 0x33;
  PORTB = val;
  while(1);
}

User avatar
opossum
 
Posts: 636
Joined: Fri Oct 26, 2007 12:42 am

Re: Why the bitwise or assignment?

Post by opossum »

A more realistic example.

Code: Select all

#include <stdint.h>
main()
{
        volatile uint8_t x[601];
        x[0]=1;
        x[100]=2;
        x[200]=3;
        x[300]=4;
        x[400]=5;
        x[500]=6;
        x[600]=7;
        x[0]|=8;
        x[100]|=9;
        x[200]&=10;
        x[300]&=11;
        x[400]|=12;
        x[500]|=15;
        x[600]&=16;
}
Last edited by opossum on Fri Apr 03, 2009 5:55 am, edited 2 times in total.

User avatar
opossum
 
Posts: 636
Joined: Fri Oct 26, 2007 12:42 am

Re: Why the bitwise or assignment?

Post by opossum »

The_Don125 wrote:Everything involving val got optimized into oblivion if I used any optimization level other than 0. I can see why though, it doesn't actually do anything.
Ports must be declared volatile for code to work as expected. val was optimized away because it was not declared volatile.

If the code is simple enough to keep all variables in registers, then the non-orthogonal nature will not show. A program of any significant complexity will force variables into RAM and the code for variables will then be different than for ports.

User avatar
westfw
 
Posts: 2010
Joined: Fri Apr 27, 2007 1:01 pm

Re: Why the bitwise or assignment?

Post by westfw »

Code: Select all

reg_x |= (0 << MUX4) | (0 << MUX5) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0) | (0 << (ACT)| (1 << LALN) | (1 << EN);
And know what the register does and is set to in a much simpler way than just assigning 0x33. Also, with the OR method, you no longer need to worry about if you have the right bit for the feature you want to enable, you just need to know the acronym.
This doesn't explain why you wouldn't just do:

Code: Select all

reg_x = (0 << MUX4) | (0 << MUX5) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0) | (0 << (ACT)| (1 << LALN) | (1 << EN);
I think the chaser.c example is pretty much coded wrong :-(

However, in may cases, a key thing to understand is that it's NOT necessarily an initial assignment when you're dealing with an IO register in a microcontroller. There may be other bits in the register that have other functions that you do not want to interfere with. So doing something like:

Code: Select all

ADCSRA |= 1<<ADSC;
will start an A-D conversion without changing the other bits in the A-D control register that were initialized "a long time ago." Essentially, the IO registers in a microcontroller need to be treated as global variables who have had their contents set to something that might be important by "other things" (possibly including power-on initialization.) (chaser.c is wrong because 1) it's setting ALL the bits in the DDR registers, and 2) because it's expecting |= to set things to zero. (SWITCHES_DDR |= 0b00000000; is a no-op; it doesn't do ANYTHING.)

Locked
Please be positive and constructive with your questions and comments.

Return to “Microcontrollers”