Exclusive OR output port

Last post
22 posts / 0 new
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!

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

danni wrote:
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 data sheet has a different opinion:
Quote:
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.

Stefan Ernst

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

Peter--I don't think I agree with you on the PINx SBI to toggle a port bit. I've used it in many apps over the last few years without noticing any effects on other port bits.

Your ADSRA example, though, can indeed cause the side-effects on those registers that have an embedded xxIF.

Lee

You can put lipstick on a pig, but it is still a pig.

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

danni wrote:
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

Note, firstly, that I did not recommend the

|=

formulation. I recommended the direct assignment

=

formulation. There is no possibility of read-modify-write atomicity problems with that formulation because there is no read-modify-write operation going on there. The OP couldn't have used the SBI method because he claimed he wanted a generic function that takes a variable btimask as an argument, and SBI cannot operate on a runtime variable bit. Anyway, the direct assignment to PINx is still smaller and faster than the OP's initial Exclusive OR method, thus preferable.

Note, secondly, that you're right, there is a bug in the SBI and CBI instructions on some generations of the AVR core - it actually reads the entire register in, modifies a single bit, then writes the entire register out again. This has potentially undesirable effects in clearing some status flag bits, and it might potentially have undesirable effects on toggling unintended bits in a PIN register. That's why I had said in my original reply that the SBI instruction might have some severe caveats.

However, many of the AVRs that have been released since about the same timeframe as the introduction of the alternate PIN register function, around 2004, have fixed the bug in the SBI and CBI instructions so that they truly operate only on the single bit specified in the instruction. There ought to be a footnote at the end of each AVR data sheet's register summary section that will indicate whether or not the bug is present in that particular part.

Of course, it would be an extremely trivial experiment to test to see whether or not the bug is relevant on any given part. Write this code:

.ORG 0
LDI R16, 0xFF
OUT DDRA, R16
OUT PORTA, R16
NOP
NOP
NOP
SBI PINA, PINA0
Loop:
RJMP Loop

And see what happens to port A.

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

theusch wrote:
Peter--I don't think I agree with you on the PINx SBI to toggle a port bit. I've used it in many apps over the last few years without noticing any effects on other port bits.

Sorry. :oops:
It was only my assumption, that it works similar to registers with interrupt flags.

I have it never used until now.

Peter

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

lfmorrison wrote:

The OP couldn't have used the SBI method because he claimed he wanted a generic function that takes a variable btimask as an argument, and SBI cannot operate on a runtime variable bit. Anyway, the direct assignment to PINx is still smaller and faster than the OP's initial Exclusive OR method, thus preferable.
I had to use |= to get C to generate the SBI. It turned out to be the same number of instructions as the OUT since LED1=1 was already in a register. Is there some clean way to force the SBI in C?
Quote:
However, many of the AVRs that have been released since about the same timeframe as the introduction of the alternate PIN register function, around 2004, have fixed the bug in the SBI and CBI instructions so that they truly operate only on the single bit specified in the instruction. There ought to be a footnote at the end of each AVR data sheet's register summary section that will indicate whether or not the bug is present in that particular part.

A compiler warning would be nice too, might save a lot of hair-pulling.

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

Quote:

Is there some clean way to force the SBI in C?

hat surely depends on your C compiler? I happen to know (with optimisation enabled) that GCC will always use SBI/CBI when it can (I/O 0x00..0x1F, single bit changing) but maybe not all C compilers do this?

 

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

In CodeVision:

PINB.0 = 1;

would do it.

Regards,
Steve A.

The Board helps those that help themselves.

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

As would PINB |= 1; -- >>if<< PINB is down low.

Even I :twisted: am starting to get away from the CV bit notation, as the new generation has few registers down low and not all ports on large chips.

You can put lipstick on a pig, but it is still a pig.

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

Quote:

Even I [Twisted Evil] am starting to get away from the CV bit notation

What is the world coming to.. :wink:

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington]

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

Somewhere in the post saying PINB|=something.
The pinb command is for checking only not for writing and is accessed directly by SBIS and SBIC command for checking the status of a certain bit or copied to a register for comparing for a desired value.Note in avrstudio that if you change the value of a bit in PORTx register in the next cycle the PINx register follows.

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

Quote:

The pinb command is for checking only not for writing

Not true for newer AVRs, and the specific purpose of the PINx write is to give modern AVR models the feature of an easy port pin toggle.

I suggest you re-read the first replay again (from Luke) and search AVR datasheets for "toggle".

Lee

You can put lipstick on a pig, but it is still a pig.