Exclusive OR output port

22 posts / 0 new
Last post
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the pic world I often apply ExlusiveOR to an output port to "toggle" a pin. Doing it this way means I don't need to use a constant bit number for the pin. Handy for generic routines that don't know the pin that is being used and can be passed a bit mask.

To do this in AVR I seem to need to EOR a register and then OUT the register, am I doing it right, or have I missed a sneaky quicker way?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In many newer AVRs (those released since about 2004) there is an unexpected 2nd function in the PINx registers.

Traditionally, the PINx register has been a read-only register, used to read the current logical state of the I/O pin's input buffer.

In newer devices, an unrelated write function has been added to the PINx register - writing a '1' to any bit in the register will have the effect of toggling the corresponding bit in the related PORTx register. Any '0's written to the register will have no effect.

You can access this feature using the bit-access SBI/CBI instructions if the PINx register is in range (with some pretty critical caveats on some devices!) or by sending a full byte to the register, with the bits you want to toggle set as '1's and the bits you want to stay the same cleared as '0's.

In the XMEGA series of AVRs, this feature has been made more distinct by the addition of unique OUTTGLx registers.

You'd have to check the data sheet for the particular AVR device you're working with to determine whether or not this feature is implemented in your device.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is how I've done it in the past:

	in	R16,Portb ; Read current port state
	ldi	R17,0b00000100 ; Setup bit 2 to toggle 
	eor	R16,R17 ; Do the magic stuff
	out	Portb,R16 ; Toggle the pin 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is what AVR-gcc does with PORTB^= (1<<LED1) for the ATTiny85:

276:              PORTB^= (1<<LED1);
+000000D6:   B388        IN      R24,0x18         In from I/O location
+000000D7:   258A        EOR     R24,R10          Exclusive OR
+000000D8:   BB88        OUT     0x18,R24         Out to I/O location
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Same thing, except their R10 needs to be set up with the mask.
If you have plenty of spare regs, this can remain setup so only needs to be initialised once.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To be clear, if you've got an AVR with the alternate functionality of the PINx register, the following code:

PORTB ^= (1<<LED1);

which would typically be compiled to something like:

IN Rx, PORTB
LDI Ry, (1<<LED1)
EOR Rx, Ry
OUT PORTB, Rx

for a total of 4 words and 4 clock cycles, can be replaced with this totally functionally equivalent code:

PINB = (1<<LED1);

which would typically be compiled into something like:

LDI Rx, (1<<LED1)
OUT PINB, Rx

for at total of 2 words and 2 clock cycles. The average savings, in terms of both code size and execution time, is 50%.

The PINx formulation also happens to be more likely to be interrupt-safe, if you happen to have any interrupts in the system that might also be attempting to twiddle other bits in PORTx at the same time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey, the '85s have it! From the datasheet:

Quote:
10.2.2 Toggling the Pin
Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn.
Note that the SBI instruction can be used to toggle one single bit in a port

Now my program does
277:      		PINB=(1<<LED1);
+000000D6:   BAA6        OUT     0x16,R10         Out to I/O location

or

278:      		PINB|=(1<<LED1);
+000000D6:   9AB0        SBI     0x16,0           Set bit in I/O register
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
PINB|=(1<<LED1); 

You need to be very careful with that. If the PIN register is not reachable by the SBI command, you will possibly toggle more than only one pin.

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for that info lfmorrison.
I didn't know that about the newer devices. Learn something new every day!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sternst wrote:

PINB|=(1<<LED1); 

You need to be very careful with that. If the PIN register is not reachable by the SBI command, you will possibly toggle more than only one pin.

Since SBI was a read-modify-write command, it works not as wanted on the PINx register.

The PORTx-bit, which was selected, toggle.
But all other PORTx-bits are cleared :!:

And on using the CBI command, only the selected bit was not cleared.

A similiar behaviour was already known on the ADCSRA register:

ADSRA |=(1<<ADSC); 

This compile also to the SBI command, which start a new conversion and clears the ADIF bit at the same time.

Peter

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Works great, my boss is a happy man, thanks!

Pages