Just discovered something very useful about the AVR....

Hi all,

At one time or another, most of us have needed to toggle an output pin on and off. Usually, it's done by writing a high or low to the output pin.

But, did you know that writing to an INPUT toggles the output? Check out this code example:

DDRB |= _BV(6); // enable PORTB, bit 6 as an output

while (1) {
    PINB |= _BV(6); // toggle port b, bit 6
    delay (100); // just a delay
}

I've searched Google for this info and didn't run into anything about it. But, I noticed it in an AVR datasheet, tried it and for sure it works!!!!!!

See page 75 of the ATMega328p datasheet:

Three I/O memory address locations are allocated for each port, one each for the Data Register – PORTx, Data Direction Register – DDRx, and the Port Input Pins – PINx. The Port Input Pins I/O location is read only, while the Data Register and the Data Direction Register are read/write. However, writing a logic one to a bit in the PINx Register, will result in a toggle in the corresponding bit in the Data Register. In addition, the Pull-up Disable – PUD bit in MCUCR disables the pull-up function for all pins in all ports when set.

Think about it... a few lines of code to add an Arduino function. Along with digitalWrite() and digitalRead() we could also have something like digitalToggle()!

For what it's worth......

    PINB |= _BV(6); // toggle port b, bit 6

The comment does not match the code. I suspect you meant this...

    PINB = _BV(6); // toggle port b, bit 6

I meant what I typed... either one works. I usually try to use OR or AND instead of "EQUALS" to avoid disturbing other pins.

For example, to turn off the ADC, you can do [b]ADCSRA = 0x00;[/b] but I prefer to use [b]ADCSRA &= ~_BV(ADEN);[/b] even though both do exactly the same thing.

(edit to add): Here's one of the ISR's in my Sony camera remote:

ISR (TIMER0_COMPA_vect) // IR carrier generator
{
    if (count--) {
        IO_PIN |= (IR_BIT && enable); // toggle IR LED drive pin

    } else {
        enable = 0; // insure LEDS stay off
        busy = 0; // flag ISR is done
    }
}

Delta_G:
Yes, I've done that before. The really cool thing is that it gets the whole thing done in one clock cycle. It's definitely the fastest way to toggle a pin.

You don't have to or it in either. Just write

PINB = _BV(6);

and it will only affect the one pin where you write the 1. Writing 0 to one of those bits has no effect. That's how you do it in one clock cycle.

See my comment to Coding Badly above.

Ugh. I am getting really tired of explaining this.

PINB starts with a value of 0b11111111. Let's expand your expression...

PINB |= _BV(6); // toggle port b, bit 6

PINB = PINB | _BV(6);

PINB = 0b11111111 | _BV(6);

PINB = 0b11111111;

You just wrote a 1 to all eight pins of the port which toggles all eight outputs. Oops.

either one works

One snippet matches the comment and toggles a single pin by writing a 1 to that position. Exactly as describe in the datasheet.

One snippet does not match the comment and toggles an arbitrary number of pins by writing a 1 to bit 6 and writing a 1 to any other bits of PINB that are also set. Exactly as described in the datasheet.

In either case, both "work" as described in the datasheet.

Which of those matches your comment?

I usually try to use OR or AND instead of "EQUALS" to avoid disturbing other pins.

That is not how writing to PIN works. Writing 0 has no affect. Writing 1 toggles that pin.

To summarize:

PINx are not ordinary registers. They do not store state. If you read PINx you read the live actual state of the
pins. If you write PINx you activate the output toggle on those pins for which you write a 1, a completely unrelated
operation to reading the inputs.

The hardware diagram in the datasheet shows all the hardware attached to every digital pin, including
internal pull-ups, de-glitch circuit, toggle circuit, input/output select, sleep logic. Its figure 13.2 on my
copy.

[quote author=Coding Badly date=1484422966 link=msg=3084493]
Ugh. I am getting really tired of explaining this.

[me too][/quote]

Please try this code (tested on a Mega2560)

int main (void)
{
    uint8_t n;

    init(); // call Arduino init

    DDRB = 0xFF; // all PORTB bits output
    PORTB = 0x00; // all PORTB bits low

    // make sure that the LED works
    n = 5;
    while (n--) {
        PORTB |= _BV(7); // led on
        delay (100);
        PORTB &= ~_BV(7); // led off
        delay (100);
    }

    // if Coding Badly is right, the "pin 13" LED (PB7) should blink
    while (1) {
        PINB |= _BV(6); // toggle bit 6
        delay (250);
    }
}

Guess what? The LED test blinks, then nothing else aside from pin 12 toggling as it should (i.e. using "|=" is indeed valid).

If I set PORTB, bit 7 high after the test, then the LED stays on solid and bit 6 (pin 12) toggles.

Likewise if I set PORTB to 0xFF (all bits high), the LED stays on and bit 6 toggles.

    // if Coding Badly is right, the "pin 13" LED (PB7) should blink
    while (1) {
        PINB |= _BV(6); // toggle bit 6
        delay (250);
    }

That's not what CB meant.

On the next iteration PINB has value 0, is ORed with _BV(6) => only bit 6 is toggled. Other bits remain 0 and the LED should be off.

Likewise if I set PORTB to 0xFF (all bits high), the LED stays on and bit 6 toggles.

Yes, in fact only bit 6 is toggled and the others are left untouched regardless of the value, which was a bit unexpected at first (but there an explanation). Try this, this works as described above (except that two bits are toggled instead of one):

 while (1) {
        PINB |= _BV(6)|_BV(3);
        delay (250);
    }

The explanation? Hint: it does not work the same on all pins (or PINs).

oqibidipo:

    // if Coding Badly is right, the "pin 13" LED (PB7) should blink

while (1) {
       PINB |= _BV(6); // toggle bit 6
       delay (250);
   }



That's not what CB meant.

On the next iteration PINB has value 0, is ORed with _BV(6) => only bit 6 is toggled. Other bits remain 0 and the LED should be off.

Yes, in fact only bit 6 is toggled and the others are left untouched regardless of the value, which was a bit unexpected at first (but there an explanation). Try this, this works as described above (except that two bits are toggled instead of one):


while (1) {
       PINB |= _BV(6)|_BV(3);
       delay (250);
   }




The explanation? Hint: it does not work the same on all pins (or PINs).

Well, you have me confused. The ONLY things that could happen by OR-ing to a PINx register are:

  • It would work as Atmel says it works
  • Pins other than the desired one(s) would toggle
  • Pins other than the desired one(s) would be set high
  • Pins other than the desired one(s) would be set low

Do we agree so far?

If so, then any one of those cases can be easily tested for. And, guess what? I already did test them and I found, not to my surprise, that it works as Atmel says it works.

If I am missing something, please spell it out for me 'cause I'm not seeing it (nor is a "hint" helping).

oqibidipo:
Try this, this works as described above (except that two bits are toggled instead of one):

 while (1) {

PINB |= _BV(6)|_BV(3);
       delay (250);
   }




The explanation? Hint: it does not work the same on all pins (or PINs).

Tried it. Bit 3 is toggling the MEGA2560 MISO pin, but strangely the waveform is only 4 volts P-P instead of 5.......

I'm not seeing the point here. I assumed that OR-ing two different bits at the same time would toggle both pins at the same time (however I never stated so, nor did I try it before now).

If someone asked me if two (or more) pins would toggle at once when done this way, I would have replied "probably they will". And indeed they do.

Okay, here is the explanation: compiler optimization.

On AVR you can set or clear individual bits of I/O registers 0...31 with a single instruction:

    SBI ioreg, bit    set bit
    CBI ioreg, bit    clear bit

On ATmega2560 ports A to G are within the range.

PINB is register 0x03 and the compiler optimizes

    PINB |= _BV(6);

to

    SBI 0x03, 6

which leaves the other bits untouched, whilst

    PINB |= _BV(6)|_BV(3);

becomes

    IN  r24, 0x03
    ORI r24, 0x48
    OUT 0x03, r24

which toggles all bits that were ones before.

Port H is outside the range of IN and OUT instructions, and is accessed like SRAM:

    PINH |= _BV(6);  // PH6 = pin 9
 =>
    LDS r24, 0x0100
    ORI r24, 0x40
    STS 0x0100, r24

No surprises there.

oqibidipo:
Okay, here is the explanation: compiler optimization.

becomes

    IN  r24, 0x03

ORI r24, 0x48
   OUT 0x03, r24




**which toggles all bits that were ones before.**
.

The output pin(s) toggle regardless of whether they were previously high or low.

Look at this:

// test on Arduino MEGA2560R3
void to_bits (char *str, uint8_t value)
{
    uint8_t b = 8;
    while (b--) {
        if (value & _BV(b)) {
            *str++ = '1'; // ascii one
        } else {
            *str++ = '0'; // ascii zero
        }
    }
    *str = 0; // null terminate string
}

int main (void)
{
    uint8_t i, j, n;
    char buf1[128]; // text buffer
    char buf2[16]; // binary string buffer
    char buf3[16]; // ditto

    init (); // arduino init
    Serial.begin (115200);

    DDRB = 0xFF; // all PORTB bits output
    PORTB = 0x00; // all PORTB bits low

    // make sure that the LED works
    n = 5;
    while (n--) {
        PORTB |= _BV(7); // led on
        delay (100);
        PORTB &= ~_BV(7); // led off
        delay (100);
    }

    PORTB = 0x00; // init all PORTB bits low

    i = 10; // init iteration count

    while (i--) {
        n = PINB; // read PINB
        PINB |= _BV(6) | _BV(3); // toggle bit 6 and bit 3
        j = PINB; // read PINB
        to_bits(buf2, n); // make binary string
        to_bits(buf3, j); // ditto
        sprintf (buf1, "Iter: %3u, Before OR: %s, After OR: %s\n", i, buf2, buf3);
        Serial.print (buf1); // display it
        delay (250);
    }

    while (1); // hang here
}

Resulting output:

** **Iter:   9, Before OR: 00000000, After OR: 00000000 Iter:   8, Before OR: 01001000, After OR: 01001000 Iter:   7, Before OR: 00000000, After OR: 00000000 Iter:   6, Before OR: 01001000, After OR: 01001000 Iter:   5, Before OR: 00000000, After OR: 00000000 Iter:   4, Before OR: 01001000, After OR: 01001000 Iter:   3, Before OR: 00000000, After OR: 00000000 Iter:   2, Before OR: 01001000, After OR: 01001000 Iter:   1, Before OR: 00000000, After OR: 00000000 Iter:   0, Before OR: 01001000, After OR: 01001000** **

Now see something amazing.... set the port pins all high (PORTB=0xFF) and we get THIS!

** **Iter:   9, Before OR: 11111111, After OR: 11111111 Iter:   8, Before OR: 00000000, After OR: 00000000 Iter:   7, Before OR: 01001000, After OR: 01001000 Iter:   6, Before OR: 00000000, After OR: 00000000 Iter:   5, Before OR: 01001000, After OR: 01001000 Iter:   4, Before OR: 00000000, After OR: 00000000 Iter:   3, Before OR: 01001000, After OR: 01001000 Iter:   2, Before OR: 00000000, After OR: 00000000 Iter:   1, Before OR: 01001000, After OR: 01001000 Iter:   0, Before OR: 00000000, After OR: 00000000** **

Notice how the 0xFF (11111111) got switched back to 00000000? Obviously there's some internal logic in the chip to (surprise!) make the thing work just as Atmel says it works.

Are we still disputing something?

@oqibidipo provided a beautiful detailed explanation. Anyone else reading this thread has the information they need to make a decision. So I no longer care.

Krupski:
Now see something amazing.... set the port pins all high (PORTB=0xFF) and we get THIS!

** **Iter:   9, Before OR: 11111111, After OR: 11111111 Iter:   8, Before OR: 00000000, After OR: 00000000 Iter:   7, Before OR: 01001000, After OR: 01001000 Iter:   6, Before OR: 00000000, After OR: 00000000 Iter:   5, Before OR: 01001000, After OR: 01001000 Iter:   4, Before OR: 00000000, After OR: 00000000 Iter:   3, Before OR: 01001000, After OR: 01001000 Iter:   2, Before OR: 00000000, After OR: 00000000 Iter:   1, Before OR: 01001000, After OR: 01001000 Iter:   0, Before OR: 00000000, After OR: 00000000** **

Notice how the 0xFF (11111111) got switched back to 00000000? Obviously there's some internal logic in the chip to (surprise!) make the thing work just as Atmel says it works.

Yes, and oqibidipo* already explained why.

|= is supposed to be a read-modify-write statement. As you have noticed, there are times where the compiler (incorrectly) optimizes it into just a bit set or bit clear instruction. This works just fine when writing to the pin just changes a memory address, but it does not work when the bits in the register are strobes. A strobe is a bit that executes some function when it is written to. The toggle functionality of the PINx registers is a strobe. Another example of a strobe is the ADEN (ADC Enable) bit in ADCSRA (ADC Special Control Register A), which initiates an ADC conversion.

In this case, the |= statement is not being optimized into a bit set, but it keeps its intended "read-modify-write" semantics. Apply this to the situation where the entire PORTB starts out as 1.

PINB |= _BV(6) | _BV(3);

  1. Since all the pins are physically HIGH (due to being outputs), PINB reads back as 0xFF. This is ORed with 0x48, then 0xFF is written back to PINB. All the pins on PORTB are toggled, and PORTB is now 0x00.

  2. Since the pins are LOW not, PINB reads back 0x00. This is ORed with 0x48, and then 0x48 is written back to PINB. Bits 3 and 6 are toggled in PORTB, so it's now 0x48.

  3. Since pins 3 and 6 are now HIGH, PINB reads back as 0x48. This is ORed with 0x48 (which makes no change), and 0x48 is written back to PINB. This toggles bits 3 and 6 in PORTB, and it is now 0x00.

Loop back to #2 and repeat. Does this make sense now?

TL;DR, the discrepancy that Krupski has noted is caused by a compiler optimization incorrectly changing the semantics of the |= operator in some circumstances.

  • Could you have chosen a worse name to try and spell?

Reference: avr-libc: <avr/sfr_defs.h>: Special function registers

Porting programs that use the deprecated sbi/cbi macros

Access to the AVR single bit set and clear instructions are provided via the standard C bit manipulation commands. The sbi and cbi macros are no longer directly supported. sbi (sfr,bit) can be replaced by sfr |= _BV(bit) .

i.e.: sbi(PORTB, PB1); is now PORTB |= _BV(PB1);

This actually is more flexible than having sbi directly, as the optimizer will use a hardware sbi if appropriate, or a read/or/write operation if not appropriate. You do not need to keep track of which registers sbi/cbi will operate on.

Likewise, cbi (sfr,bit) is now sfr &= ~(_BV(bit));

Time for a puzzle.

The snippet of code...

    DDRB = 0xFF; // all PORTB bits output
...
        n = PINB; // read PINB
        PINB |= _BV(6) | _BV(3); // toggle bit 6 and bit 3
        j = PINB; // read PINB

        to_bits(buf2, n); // make binary string
        to_bits(buf3, j); // ditto

        sprintf (buf1, "Iter: %3u, Before OR: %s, After OR: %s\n", i, buf2, buf3);

        Serial.print (buf1); // display it

The result...

Iter:   9, Before OR: 11111111, After OR: 11111111

Before the OR, all ones. After the OR, all ones.

How is that possible? How could the toggle fail? Is there a bug in @Krupski's code? Is there something wrong with @Krupski's processor? Did @Krupski make a wiring mistake?

Karma for the first person who correctly answers.

Your port is programmed as an INPUT! :smiley:

BTW. You did actually toggle something there. You toggled the state of the pull-ups on those pins.

The snippet from post #12 which includes this...

    DDRB = 0xFF; // all PORTB bits output

(Post #17 updated.)

Arr, you've got me stumped now. :slight_smile:

Just had another thought. It's not the 2 clock delay on the input synchronizing latches is it?

Does that "j = PINB" read back get done before the new port value makes it though the synchronizer?