someguy
someguy

Reputation: 89

bitwise operation confusion in tutorials

how is:

GIMSK |= (1 << PCIE); 
PCMSK |= (1 << PCINT4); 

equal to (I can use the above or the below in my setup of my program, both work and activate pin 4), the GIMSK and the PCMSK are for some reason equal to each other, I am trying to learn why.

GIMSK = 0b00100000; 
PCMSK = 0b00010000; 

first: https://thewanderingengineer.com/2014/08/11/pin-change-interrupts-on-attiny85/

second: https://embeddedthoughts.com/2016/06/06/attiny85-introduction-to-pin-change-and-timer-interrupts/

data: http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2586-AVR-8-bit-Microcontroller-ATtiny25-ATtiny45-ATtiny85_Datasheet.pdf

The sheet says PCIE is 0b00100000 in the bit mask, so somehow |= (1 << PCIE) equals that? I don't get it if PCIE is supposed to be that, doing a shift would change that value.. how and why would you use that instead of binary? I would guess it changes it but obviously, somehow it doesn't. I've asked this on several different places nobody has an answer so I came here. Hopefully someone can explain.

I'm new to C, I just learned bitwise operations today to try to figure out what is going on here, my code does work with either or but I want to know why! Thanks.

Upvotes: 0

Views: 574

Answers (2)

Andy T
Andy T

Reputation: 272

Short Version

  1. Break apart the compound expression from x |= y to x = x | y
  2. Load integer '1' which is just binary `[0000 0001] and shift it to the spot we want.
  3. Variable is defined in a header, or elsewhere which says where that bit is for compatibility. New card? Just get a new definition file- this is done automatically- usually). PCIE and PCINT4 are defined somewhere, look for it if needed, but this is supposed to handle those details for you.

  4. Yet- we know is PCINT4 is 4 and PCIE is 5 respectively because of the second set- where we see a shifted '1'- 5 spaces for PCIE and 4 spaces for PCINT4. That's why they seem equivalent. Because they are literally equivalent- once you evaluate the expression you know that 0b means that what follows is binary (which might have been obvious). But they are NOT equivalent. Not exactly- see below--

    4-So if you "OR" a register, it overrides whatever is there if TRUE and forces it to be what True and leaves everything else alone. We shift that 1 to show the bit we want, then we choose the operant that will have the effect we want. Look at the table for others.**

But we get GIMSK = GIMSK OR 0b00100000 and PCMSK = PCMSK OR 0b00010000; Which is similar, but not exactly the same thing.

The devil is in the details, see below for

Detailed Explanation.

AKA someone better read this, took me forever.

GIMSK |= (1 << PCIE); 
PCMSK |= (1 << PCINT4); 

equal to (I can use the above or the below in my setup of my program, both work and activate pin 4), the GIMSK and the PCMSK are for some reason equal to each other, I am trying to learn why.

 GIMSK = 0b00100000; 
 PCMSK = 0b00010000;

Let's take it lexicographically by the token to start, so we are all talking about the same thing.

GIMSK |= (1 << PCIE);

Starting at the first bit. Pick it apart-

 GIMSK- variable or 'id'
 |=   - operator  and assignment combo bitwise OR and =
 1    - the integer 1
 <<   - shift operation
 PCIE - Variable and ID

Of course, this makes it harder to explain. What is |=? I'm certain that led to confusion on this question for some. The better-known one is +=. So if I have a variable x, and I always add to itself, really any time you are counting, etc. the variable is on both sides of the equation. Like this:

 x = x + 1  ;this is so common though, that in C, it was shortened to +=
 x += 1     ; now its written like this.  It takes some time

so programmers are lazy, and if something 'cool' pops up in one language, it usually spreads to the others, so most languages allow this now. It does make a difference if you write += or =+, at least in Java. Won't hit that though.

 y  = y * 2; it works for other types of operands
 y  *= 2;     Now we take y, multiply by 2 and assign back to y.

Now, let's look at 'C' style bitwise operators- most languages have adopted similar notation though there are exceptions.

Usually in C you use two symbols to compare '&&" or '||' or '==' Well, that's because the single operator compares bits. This got one symbol because it's much more natural on a computer and much more common. Not to us anymore, we are abstracted away from it by layers of software.

So we have: ** Good Source for more info

  • Bitwise AND (&)
  • Bitwise OR (|)
  • Bitwise XOR (^)
  • Bitwise NOT (~)

And we can also make this compond (Click for more info) Basically, they compare some variable on the right with the left and assign it back to the right. Like this x = x* y => x *= y. Likewise we have x &= y, x |= y and, x ^= y

So for the above- let's unwrap it first- write it out longhand to make it easier to understand-

   GIMSK |= (1 << PCIE)
   GIMSK =  GIMSK | (1 << PCIE)  #OK! much easier to understand if your new.
          #NOW we can lexigraphically analyze this
   VarA {assignment} VarA OR ( 1 {Operator} VarB )

         #Ignore the assignment side, for now, practice order of Operations
         #Start with Parenthetical Exp.

   1 {Operator} VarB  

         #It turns out this is defined.  
         #OP Didnt know but computer does. = 5 in this case.
         #so 1, shift left 5.  To bitwise shift, need bits
   1 => 0b00000001  << 5 = 0b00100000 
          # shift left is really multiplied by 2 in base 10, divide by 2 in shift right. Beware Right Shift, esp in float.

So now we have: GIMSK = GIMSK OR 0b00100000

GIMSK |= (1 << PCIE); PCMSK |= (1 << PCINT4);

GIMSK = 0b00100000; PCMSK = 0b00010000; Which is just what you already said. More or less. The 2nd operations are not equivalent though as I mentioned above in the short answer. Thats covered at the end.

This is Assembly format, GIMSK is an 8-bit register. We created a bitmask, by moving a 1 to the register we want to effect, and putting a 0 in the bits we want to leave alone. The |= means we will compare the two the save it back to the same register. That's it. OR 1 will always turn it on. Which is what we want.

Think about what we want to do to start. We want to set a boolean value to HIGH or TRUE, or 1, however, you put it. We say "Lets set the register bit that we specify if your value (0/1) OR my value (1) is 1." Well, we know our value is 1 because that's what we put. So when you bit-wise OR (is that a verb?), you are writing a value on the basis of one of 2 values being a 1. It either writes a one or leaves a one, unless you send 0 and it sends 0 it stays off.

It says "I think this should be on. If any other process thinks this should be on, leave it even if I don't need it so (it sends a 0)" It's worth thinking through on paper, and thinking through each of the operands. Make a colored table, that's how I got to understand them. Not of the operands will flip the values whatever it is. AND checks the value for you, it leaves the register the same- Operating on each reg will have this effect. I used to have a cheat sheet, I would have loved to include, but I lost it- but it summarized for my dumb brain the behavior of each operand.

REMEMBER- we can not operate on a single bit. This is a critical bit you need to understand. You cant change just one bit. If I have 0010 0010 and I want to say, hey computer, change byte 6. You cant! You have to load the whole word or byte into a register or at least half (16bits in MIPS, 8 in ATMEL 16bit controllers**), and operate on the whole thing. You can't operate directly from memory (Ram, SSD, L2 Cache- way too far away). There's no such thing as popping a single bit into a register to change it, though there are tricks to make new bytes (8bit) in the shape you want. Want just the 6th bit, well {0100 000} -with AND, will get it for you. Then you can shift right, or divide by 2^6, etc. We will get back to this. First- the actions of the Comparators if you care to learn more:

*this chip is 8 bit. Doubt they have half read. Bit

Logical Operators v. Registers

 Register(b)      Me          OR   NOR  XOR   AND   NAND  N   XNOR
     1            0           1     0    1     0     1    0     0
     0            0           0     1    0     0     1    1     1
     1            1           1     0    0     1     0    0     1
     0            1           1     0    1     0     1    1     0

So the above looks at the single bit we want to effect. The left 2 columns are all possible scenarios (just 4), where we show the bit of the register in that byte. I keep saying byte. A register is, as I mentioned 16 or 32 bits usually, so I really mean Word. I have just organized this example around a hypothetical 8 bit machine. edit- this is an 8 bit chip, 32 registers. one of which is this one

Now! What do we want to do? We Want to change 1 byte, which represents a boolean value, but we don't want to mess with the rest! If you OR across all 8 bytes- do this on paper- it leaves the values in there already alone. Perfect! That's what we want.

Whatever they are set to, they stay, if it's 1, OR leaves it a 1, if it's 0, it stays.

Ah, I should mention why.

Its because you start with 0000 0001 (1) so everything is 0, except the 1st bit. Why did we start with 0000 0001? because you told it to. See here syntax Arduino Doc Bitwise Ops

So, without reviewing Binary, 1 in binary is 0000 0001 It should be noted, that in a computer, it can't tell that 11001100 isn't 11,001,100 (eleven million), or if its 204 (binary) or even 285,217,024 (if it was HEX), or 2,359,872 (in Octal). The compiler and computer 'know' we always think base 10, but the computer never does, just base 2 or compressed for easy human reading, into octal (2^2,) or hex (2^4) eg, each 'character' is 2 bits or 4 bits. 0x0A is 0b1010. And right there is where I am getting at. We indicate the values are not base 10, with a prefix. 0b***** is binary. 0x**** is hex. And I can never remember Octal- no one uses it anyway.

So!

see here if needed: Another practical book I wrote on Bitwise Ops, that covers basic Binary a little. Then you shift that bit by the PIN NUMBER I don't know the right term, and this is certainly not it, but you say the register you want to effect is the 5th register. Ok.

    #take a 1,  
    0000 0001 = $temp
    #shift it 5 spots "<<" , where 5 is the PCIE 'bit' value spot number. 
    1<<5 = 32   
    #binary  equals 32. 

You could replace either GIMSK value with 32 and it would be fine, again equivalent, or 0x020

     # 0010 0000
     # Then OR this with whats in the register now:
      1010 1010 (made up number, a mix of ones and 0s)
      0010 0000 (Our Value)
          OR=>
      1010 1010 Result.  

Note how we left the other bits alone, and only changed what we wanted! Effective bit mask!

Now, why does it say PCIE, and whatever the other one is. Its because somewhere, when you compile, there is a file that assigns values to those variables. This allows the code to be compatible across several different chip designs. The ATMEGA and the ATTINY do not have the same interrupt pin. Though it likely goes to the same internal register.

#Take it bit by bit,  no Pun intended

GIMSK |= (1 << PCIE);   
PCMSK |= (1 << PCINT4); 

GIMSK = 0b00100000; 
PCMSK = 0b00010000; 

Again, starting at the first bit from above.

 GIMSK- some variable
 |=   - bitwise OR
 1    - the integer 1
 `<`<   - shift operation
 PCIE - another var

So all you are doing is taking a base 10 integer- 1, which we know equals 0b0000 0001, then we are pushing that 1 (now in binary to the spot indicated by PCIE or PCINT4. So the latter 2 are just simply variables that hold the bit number, so if it changes, the code doesn't break.

From the latter 2 lines, we infer that PCIE is 5 and PCINT4 is 4. GIMSK is now equal to 32 and the other 16. Shifting << and >> has the effect of multiplying or diving by 2. Although, shifting down is risky for reasons I won't get into, but if you need to multiply a number by 2, for a computer, it's much faster to shift left by 1 bit than it is to go through the multiplayer.

We talked about the OR already. It sets a 1, if there's not one, otherwise it leaves the other bits alone because they are 0.

Equivalent or Not??

GIMSK |= (1 << PCIE);

GIMSK = 0b00100000;
GIMSK = GIMSK OR 0b00100000

PCMSK |= (1 << PCINT4);

PCMSK = 0b00010000; PCMSK = PCMSK OR 0b00010000

So evaluating the 1st expression in each set gets the 3rd equation. But notice they look a little different. They are not equivalent statements, though, as you say they may work. It depends on what those other bits are.

 PCMSK = 0b00010000;  #This sets the PCMSK register to be exactly
 => PCMSK = `0|0|0|1|0|0|0|0
 #While
 PCMSK = PCMSK OR 0b00010000; #  yields   PCMSK = `?|?|?|1|?|?|?|?`
 #Obviously,    
 GIMSK = 0b00001000; # This sets the GIMSK register to be exactly
 =>  GIMSK = `0|0|0|0|1|0|0|0`  
 While`GIMSK = GIMSK OR 0b00001000;  # yields   
 GIMSK = ` ?|?|?|?|1|?|?|? `

while the OR statement leaves the other bits alone, if they were set by something else, and just changes the 5 (or 4th bit) as the case my be. The OR statement is probably the better statement. If you found the latter statement suggested in a reputable place though, it's probably fine.

Conclusion

So that's it. It's much easier than you thought probably now that the different bits make sense. I wrote this though with the hope it'll give some lasting insight rather than just a quick answer. Although in truth- it was complicated. There are a LOT of computer science concepts buried in those 2 statements, that if you're not in the know, might as well be hieroglyphics.

All this makes much more sense if you dive into how a computer works.

Check out Chapter 2 and 3 of Computer Organization and Design (5th ed) Patterson and Hennesy. It's the standard. If this is for fun, you can skim it. But the computer has Registered, of a defined width- 8, 16, 32. and 64 or even 128 (rarely e.g. x86 AVX- Intel x86). but usually 32 bit. These are the bits of data in hand, what the processor actually touches. The processor can only operate on registers. So everything, under the hood, will end up back there.

Now using interrupts correctly is a whole other topic. I again recommend the same book- Ch 5 and Appendix A7

Note- My assembly class was in MIPS. I've never specifically studied this microcontroller. If I get some of the architecture wrong, forgive me.

Upvotes: 0

0___________
0___________

Reputation: 67476

It is equal because all other bits of those registers were 0 before the OR operation

1u << x shifts one by x positions left. As a result you have the number with all bits except x cleared

Upvotes: 1

Related Questions