Toggle state of output pin

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi I often see the following line used to toggle an output pin

(assuming bit is the pin to toggle)

 

PORTB  ^= (1<<bit);

 

 

or similar

 

reading the data-sheets this is actually achievable in hardware simply by writing a 1 to the necessary bit in the PIN register

 

PINB = (1<<bit);

 

which compiles to a single instruction.

 

Does the compiler make this optimization?

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

Does the compiler make this optimization?

No and it must NOT because PORTB is a volatile target so the RMW operation requires it to implement an RMW.

 

The reason people use ^ with PORT rather than just writing the bit in PIn is that it's only "modern" AVRs (post 2006) that have that feature. On older AVRs you really did have to do an RMW.

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

Wow, Quick answer (faster that I was able to test it myself)

 

I did not know about the 2006 modification, I had (incorrectly) assumed that it was a feature that many had simply overlooked.

 

Last Edited: Fri. Sep 25, 2015 - 12:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

Does the compiler make this optimization?

No and it must NOT because PORTB is a volatile target so the RMW operation requires it to implement an RMW.

My recollection is that we have had this discussion before and that I was on the other side of it.

avr-gcc routinely issues sbi and cbi instructions of utility, but dubious legality.

The aforementioned optimization is in the same category.

Sometimes issuing an sbi or cbi instruction changes the semantics.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

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

This is a different situation here which adds a very significant factor to the argument. With ^= you are not only changing the RMW characteristic of the statement, you are also changing the actual register involved. I think that this would be more likely to preclude any generalized optimization.

Regards,
Steve A.

The Board helps those that help themselves.

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

skeeve wrote:
My recollection is that we have had this discussion before and that I was on the other side of it.

 

avr-gcc routinely issues sbi and cbi instructions of utility, but dubious legality.

The aforementioned optimization is in the same category.

Sometimes issuing an sbi or cbi instruction changes the semantics.

While this is an OT from the most-recent question regarding the possibility of optimising PORT ^= into PIN =, I continue to wonder about the legality of optimising a RMW of a volatile byte-sized entity i.e. PORT |= (1<<BIT) into the writing of a single bit-sized entity i.e. sbi PORT,BIT.

 

On the one hand, I agree that in the strictest sense this breaks the 'letter of the standard' w.r.t. the intended purpose of volatile.  As a volatile, a PORT read must not be eliminated.  The standard would seem to stipulate that PORT |= (1<<BIT) should be compiled as:

  in r,PORT
  ori r,1<<BIT
  out PORT,r

... the optimisation in question results instead in:

  sbi PORT,BIT

This has several effects.  The beneficial effects are:

  • shorter (1 word instead of 3)
  • faster (2 cycles instead of 3)
  • fewer registers consumed (0 instead of 1)
  • inherently atomic

 

It has two questionable side effects:

  • PORT is never read
  • PORT is not written (except for one bit)

 

Examining this in the specific context of an AVR's PORTx I/O registers, there are >>no<< deleterious effect associated with the side effects.  The behaviour of the output buffers is unaffected, except for the exact timing.  Since machine timing is not a purview of the C standard, this is not a violation.

 

Let's find an example where the side effects >> would<< result in differential behaviour between the strict form (in/or/out) and the optimised form (sbi):

  TIFR0 |= (1<<TOV0);

That's an interesting one.  The TIFR0 register lies within the lower half of I/O space, and is reachable by sbi/cbi.  If the compiler were to follow the letter of the standard, this would result in:

  in r,TIFR0
  ori r,1<<TOV
  out TIFR0,r

The consequence of this would be to clear not just TOV, but >>any and all<< flags currently set in TIFR0 at the moment the in instruction is executed.  Would this be the intention of the programmer?  Probably not.  In fact it would belie a misunderstanding on his part w.r.t. the functioning of the hardware vis a vis the procedure for clearing of interrupt flags (something we've seen here many times).

 

However, the compiler generates this:

  sbi TIFR0,TOV0

This is both 'right' and 'wrong'.  It is 'right' in that it likely reflects the programmer's intent, even though that intent was incorrectly expressed.  It is 'wrong' both in the sense that it violates the letter of the standard, and it is also a latent bug in the programmer's code.  If later the code is ported to a device where the TIFR0 register is no longer reachable by sbi/cbi, the behaviour will be different.  The same might be true if updating the code for a different, non-sbi-reachable register on the same device.  This may be a moot point, as I cannot recall a device which has an upper-half I/O space register (or SRAM-mapped-only register) which contains more than one interrupt flag.  All such registers appear to be in lower I/O space.

 

However, the issue would be similar when attempting RMW operations on registers like ADCSRA.  On the ATmega48/88/168/328, that is out of reach of sbi/cbi, but on the ATtiny25/45/85 it >>is<< within reach.  That means that an expression like:

  ADCSRA |= (1<<ADATE);

On the ATmega48/88/168/328:

  in r,ADCSRA
  ori r,1<<ADATE
  out ADCSRA,r

On the ATtiny25/45/85:

  sbi ADCSRA,ADATE

... resulting in different behaviour w.r.t. ADIF, depending upon the target, based solely on the address of ADCSRA on each target.  Putting aside for the moment the utility of the above expression, it is nevertheless worrisome.

 

Another worrisome example (on post-2006 hardware):

  PIN |= (1<<BIT);

Letter-of-the-standard:

  in r,PIN
  ori r,(1<<BIT)
  out PIN,r

Optimised:

  sbi PIN,BIT

Again, hardware behaviour very different.  Again, programmer's understanding of the hardware called into question.  Again, a possibly hard-to-find latent bug.

 

This analysis has only considered the 'letter of the standard'.  Whatever else they may be, standards are human documents.  By following them the hope is to arrive at an implementation that is true to the intention of the designers of the standard.  As human documents, however,  the need to interpret is never far away.  Whereas volatile may have been originally intended to provide a mechanism for multiple threads to have predictable read and write access to shared variables in memory, that intention seems to be insufficient to the task of describing and managing prescribed and proscribed read and write access to hardware resources like I/O ports and peripheral configuration registers on 'physical computing' platforms like the AVR and other microcontrollers.

 

It would seem like the standard is due for an update in this regard.  Perhaps some new keywords to help fully describe a programmers intent w.r.t. appropriate access to hardware resource.  Maybe something as simple as volatile_r, volatile_w, and volatile_rw.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

A one instruction output toggle is useful for generating square waves. Lets assume we use GPIOR0 as a generate tone Boolean, and GPIOR1 as the number of nops in the loop. I bet one of you guys that has most of the avr opcode cycles committed to grey matter could tell us the freq range for GPIOR1 values of 0 to 255 given avr freq. I guess the fastest is the most interesting, so there is a test of the generating Boolean, the toggle, the decrement and the test for zero which is one instruction, but I don't know if its 1 or 2 cycles, so 4 cycles? a 5th of a usec at 20mHz? 200KHz? Useful for a hydrophone maybe? 

Imagecraft compiler user

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

PIN registers are what I had in mind, but the interrupt flag registers have the same problem.

So far as I am aware, there is no correct standard C construct that might generate a useful sbi instruction on a PIN register.

By "correct", I mean that the semantics of C promise approximately what one wants.

An sbi macro guaranteed to generate an sbi instruction would seem to be in order.

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles