I have come accross a lot of examples using ways similar to the ones shown below to assign values to the pins.
PORTB |= (1<<PORTB2); //set bit 2
PORTB &= ~(1<<PORTB1); //clear bit 1
DDRD = DDRD | B11111100; //Sets up the pins for output
My question is why do we need to use bitwise operations to make assignements?
4 Answers 4
My question is why do we need to use bitwise operations to make assignements?
Because you're not assigning, you're modifying.
If you want to turn on just pin 3 (for example) you need to only control bit 3 of PORTB and leave the others alone.
That means you need to "or" the value of just bit 3 being on with the existing PORTB to turn the bit on, or AND it with the inverse (all pins on except bit 3) to turn it off.
To get the value of bit 3 you can remember that it's 4
, or you can just use 1<<3
to say "Bit 3" - or use the macro PORTB3
to make it more readable instead of just the number 3.
The operation is commonly known as "Read Modify Write" and in some low-end microcontrollers you have to take special care when doing it.
For example, if you want to control the output pin 3 and currently you have pins 2, 5 and 6 on and the rest of pins 0-7 off (those are the ones on port B on the Uno) the sequence may be like:
Turn on:
Calculate bit value 1<<3 => 0b00001000
Read PORTB => 0b01100100
OR the two values together => 0b01101100
Write to PORTB <= 0b01101100
Turn off:
Calculate bit value 1<<3 => 0b00001000
Invert it => 0b11110111
Read PORTB => 0b01101100
AND the two values together => 0b01100100
Write to PORTB <= 0b01100100
-
Could you put examples into your answer as you write (refer) to them? I have learned from your and &Gerbens answer, but it would make things easier to understand if reference and example are seen simultaneously. ThanksTomas– Tomas2020年10月11日 19:11:28 +00:00Commented Oct 11, 2020 at 19:11
-
@Tomas Does that help you?Majenko– Majenko2020年10月11日 19:21:02 +00:00Commented Oct 11, 2020 at 19:21
-
Thanks. That just become one of the posts to read when learning ArduinoTomas– Tomas2020年10月11日 20:10:03 +00:00Commented Oct 11, 2020 at 20:10
-
1Possibly worth noting that some chips (including the AVR) have specific "set bit" instructions, and at least gcc-avr will recognize this idiom and use the single instruction.chrylis -cautiouslyoptimistic-– chrylis -cautiouslyoptimistic-2020年10月12日 01:52:47 +00:00Commented Oct 12, 2020 at 1:52
If you want to change all pins on a port at once, you don't have to.
The point isn't just that you want to set/clear the pins you want, it's that you want to not change the state of the other pins. To do that, you need to modify only the bits for the pins to be changed, and leave all the other bits as they are. The only way to do this is with bitwise operators.
But if you do want to change all the pins at once, then you can perfectly well just set the port register to a value. Outputting a value on an 8-bit bus would be a classic case where you'd do this.
I do need to pick you up on one thing in your question though. In many cases, it is a bad idea to read the state of the port, modify the bits, and write the value back. The reason is that almost all microcontrollers use the same port address for setting the output state and reading the input state. So by reading back the port state, you aren't necessarily reading the actual outputs you set. Logic-high outputs are generally weaker than logic-low, so it's entirely possible for your connected electronics to pull down a logic-high. The sequence of faults for the bug goes:-
- Your program sets a bit (pin) to 1.
- Another component pulls that pin down to logic-low.
- The next time you read that port back to change another pin, the input for the pin reads 0.
- You modify the value for the pin you intended to change.
- You write the value back. Now not only has the intended pin changed state, but so has the pulled-down output.
- The other component stops pulling the pin down. But your micro keeps outputting 0 (logic-low) for that pin.
The solution is that you don't do bitwise operations directly on the register. Instead, you keep a "shadow" variable containing the state you want the outputs to be. You do all your bitwise operations on that shadow variable, and then you write the shadow variable directly to the register. And the bug goes away.
The registers are 8 bits in size. And in the above two cases you want to set/clear 1 bit, while leaving the others the same.
In the last example you want to set pins D2-D7 to output, while leaving pins D0 and D1 unchanged.
It makes code easier to write and re-use.
Let's say your program needs to do 3 unrelated things on different pins. Control a motor, flash a light, count some input pulses.
Since this bitwise technique allows you to read/change only the bits you need to, and leave the others alone, you can totally separate the code for the 3 tasks. It can even be written in separate files by separate people, and without knowing what other pins in the project are being used for. The code for one of the 3 tasks could be copy-pasted into a separate project, without needing to untangle it from project 1 nor make major changes to project 2. In software engineering lingo this is called making code "more maintainable" and also "modular."
PORTB |= (1<<PORTB2); //set bit 2 and keep the rest unchanged
andPORTB = (1<<PORTB2); //set bit 2 and clear everything else
.