Author Topic: Tip for Toggling an ATMega IO pin  (Read 8405 times)

0 Members and 1 Guest are viewing this topic.

Offline WebbotTopic starter

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 2,136
  • Helpful? 109
Tip for Toggling an ATMega IO pin
« on: June 13, 2010, 07:48:12 PM »
Most code I've ever seen does something like:

Code: [Select]
PORTA ^= (1 << bitno);
Which is kind of fine except that it expands, in assembler pseudo code, to something like:
Code: [Select]
1. tempRegister = read PORTA
2. tempRegister ^= (1<<bitno)
3. write PORTA(tempRegister)

The problem here is that step 3 is rewriting the whole register with contents based on what was read at step 1. So if an interrupt occurs at around step 2 and that interrupt routine changes PORTA then when it finishes then step 3 will splat its change.
You could make it more atomic by surrounding the whole thing with a disable then re-enable interrupts.


But I just discovered a good tip in the datasheets which results in smaller code, and is atomic without needing to disable interrupts....
Each PORT register has a matching PIN register. The PIN register is read only in so far as you cannot change its contents BUT if you set a bit in it (using the sbi command) then it toggles the corresponding output pin!!!
So just use:-
Code: [Select]
sbi(PINA, bitno);
Quick, simple, and much smaller code !!!

Sorry - I know its very small and simple - but I love finding these little gems.  ::)


Webbot Home: http://webbot.org.uk/
WebbotLib online docs: http://webbot.org.uk/WebbotLibDocs
If your in the neighbourhood: http://www.hovinghamspa.co.uk

Offline Razor Concepts

  • Supreme Robot
  • *****
  • Posts: 1,856
  • Helpful? 53
Re: Tip for Toggling an ATMega IO pin
« Reply #1 on: June 13, 2010, 08:33:29 PM »
Isn't sbi/cbi depricated? I kind of remember reading about that somewhere.

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #2 on: June 13, 2010, 08:51:01 PM »
Googling finds this:

"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."

Offline guncha

  • Jr. Member
  • **
  • Posts: 40
  • Helpful? 0
Re: Tip for Toggling an ATMega IO pin
« Reply #3 on: June 18, 2010, 02:58:02 AM »
That's a great tip, Webbot! Is it one cycle?

Offline WebbotTopic starter

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 2,136
  • Helpful? 109
Re: Tip for Toggling an ATMega IO pin
« Reply #4 on: June 19, 2010, 07:04:46 AM »
The C code
PINB |= _BV(PB1);
will be turned into the assembler instruction:
sbi(PINB,1)
which takes 2 cycles to execute to set the bit. The manual doesn't mention if further clock cycles are then needed internally to perform the toggle - so I presume it happens immediately. Either way its faster than: read PINB, toggle bit, write PINB.
« Last Edit: June 21, 2010, 12:49:56 PM by Webbot »
Webbot Home: http://webbot.org.uk/
WebbotLib online docs: http://webbot.org.uk/WebbotLibDocs
If your in the neighbourhood: http://www.hovinghamspa.co.uk

Offline voyager2

  • Supreme Robot
  • *****
  • Posts: 459
  • Helpful? 6
  • Behold! The Impossible Triangle of robotics!
Re: Tip for Toggling an ATMega IO pin
« Reply #5 on: June 19, 2010, 07:52:19 AM »
Hi All,
I don't really understand any of them.
Here is the non-existent, easy to understand version...
Code: [Select]
A2D,6 {change to} Digital_I/O 8   
:) ;) :D ;D
And Admin said "Let there be robots!"
And it was good.

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #6 on: August 18, 2010, 08:55:59 AM »
Alright guys, I have been curious about this and have done some tests. Here's the setup. An Atmega 328P flashed with the Arduino IDE and 16Mhz ceramic resonator, a single output pin watched by a gigasample per second o-scope.

Pseudo code for the test was

for(ever){
pin Low;
pin High;
}


The following were the different methods, and the resulting frequency of the square wave produced.

digitalWrite(PIN, LOW);
digitalWrite(PIN, HIGH);

138 Khz

cbi(PINB,0);
sbi(PINB,0);

1.33 Mhz

PINB |= _BV(0);
PINB &= ~(_BV(0));

1.33 Mhz

PORTB = CLR(PORTB, 0); 
PORTB = SET(PORTB, 0);

1.6 Mhz


PORTB |= _BV(0);
PORTB &= ~(_BV(0));

2.7Mhz


and the macros define:
Code: [Select]
#define SET(x,y) (x|=(1<<y))
#define CLR(x,y) (x&=(~(1<<y)))
#define CHK(x,y) (x & (1<<y))
#define TOG(x,y) (x^=(1<<y))

#define _BV(bit) (1 << (bit))

#define sbi(port,bit) (port)|=(1<<(bit))
#define cbi(port,bit) (port)&=~(1<<(bit))

It seems that _BV with the port register is the fastest way to flip a pin, which to me says it's the least amount of instructions. Or did i miss something?

« Last Edit: August 18, 2010, 08:57:18 AM by madsci1016 »

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #7 on: August 18, 2010, 09:17:47 AM »
Err, I missed one

cbi(PORTB,0);         
sbi(PORTB,0);

is also 2.7Mhz, but the wave form is different then _BV method. Longer high then low.

Offline WebbotTopic starter

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 2,136
  • Helpful? 109
Re: Tip for Toggling an ATMega IO pin
« Reply #8 on: August 18, 2010, 09:19:51 AM »
In your second example:
Code: [Select]
cbi(PINB,0);
sbi(PINB,0);

You only need to set the bit to make it toggle. So you can just write:
Code: [Select]
sbi(PINB,0);You never need to clear it as the hardware does that for you.

Being slightly pedantic then the examples you've given are not really a 'toggle' operation. You are setting the pin to a know state ie 'high' or 'low'. Whereas the toggle method I mention above will toggle it to its opposite state. So in pseudo-code this is more like:
if( pin_is_currently_high()){
   set_it_low();
}else{
   set_it_high();
}
Webbot Home: http://webbot.org.uk/
WebbotLib online docs: http://webbot.org.uk/WebbotLibDocs
If your in the neighbourhood: http://www.hovinghamspa.co.uk

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #9 on: August 18, 2010, 09:26:32 AM »

You only need to set the bit to make it toggle. So you can just write:
Code: [Select]
sbi(PINB,0);You never need to clear it as the hardware does that for you.


Tested. Still not as fast as _BV, but is an improvement at 2Mhz.

So,

Code: [Select]
PORTB |= _BV(0);       
PORTB &= ~(_BV(0));

is still fastest at 2.7 Mhz,

while

Code: [Select]
sbi(PINB,0);
clocks at 2 Mhz.

I agree with webbot though, I think I like sbi better when speed is not an issue, as it's much easier on the eyes.
« Last Edit: August 18, 2010, 09:31:34 AM by madsci1016 »

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #10 on: August 18, 2010, 09:34:42 AM »
You are setting the pin to a know state ie 'high' or 'low'. Whereas the toggle method I mention above will toggle it to its opposite state.


Ahh, I see. Well, hopefully my tests shed light on the fastest way to set a pin state then.

I was comparing apples to oranges, here's a better test:

sbi(PINB,0); is 2Mhz

PORTB ^= (_BV(0)); is 1.5Mhz

That should both be apples, and verifies Webbot in every way.

I did this test because i'm looking to get into LED multiplexing, and needed to know the fastest way to set pins, and to clock out data. So for me, the _BV by PORT method will be the fastest way to clock a signal, assuming I keep track of the last state of the pin.
« Last Edit: August 18, 2010, 09:56:55 AM by madsci1016 »

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #11 on: August 18, 2010, 10:26:05 AM »
And, to be fair, the 'Arduino' way of flipping a pin

digitalWrite(PIN, !digitalRead(PIN));

clocks in at a whopping  66 Khz!

yuck.

Offline WebbotTopic starter

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 2,136
  • Helpful? 109
Re: Tip for Toggling an ATMega IO pin
« Reply #12 on: August 18, 2010, 12:19:09 PM »
The main reason I like the toggle method is its quite a common requirement to send a pulse.
So with my 'new find' you can do:
Code: [Select]
sbi(PINB,0);  // Toggle
delay
sbi(PINB,0); // Toggle back

So if the pin is currently high then it send a 'low pulse', and if it is already low then it send a 'high pulse'.

But yep its horse for courses - but good data from madsci
Webbot Home: http://webbot.org.uk/
WebbotLib online docs: http://webbot.org.uk/WebbotLibDocs
If your in the neighbourhood: http://www.hovinghamspa.co.uk

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #13 on: August 18, 2010, 12:55:03 PM »
I wrote up a article with all this data, aimed at Arduino users to try to get them off the slow Arduino functions.

http://www.billporter.info/?p=308

Offline WebbotTopic starter

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 2,136
  • Helpful? 109
Re: Tip for Toggling an ATMega IO pin
« Reply #14 on: August 18, 2010, 04:22:49 PM »
The more I think about it then the more I'd change in the testing - just to be a pain  ;D

My new method:
Code: [Select]
sbi(PINB,0);Only creates half of a pulse - whereas your other examples create a full pulse.

So you could emulate a full pulse by using:
Code: [Select]
sbi(PINB,0);
sbi(PINB,0);
This would be a better comparison to your other metrics.


The reason this is significant is that your compiler should turn:
Code: [Select]
while(1){
 sbi(PINB,0);
}

Into assembler pseudo code  like:
1. sbi PORTB,0
2. rjmp 1

Where the 'sbi' requires 2 cycles, but the jump requires 3 cycles. So the program is spending 3/5 of its time just performing the while loop and only 2/5 toggling the pin.

So, with hindsight, I would write each toggle command as a macro or inline function and then make the while loop use the macro like 50 times over and over. This then makes the overhead of the loop become insignificant compared to its contents (whereas at the moment it is very significant).
« Last Edit: August 18, 2010, 06:46:05 PM by Webbot »
Webbot Home: http://webbot.org.uk/
WebbotLib online docs: http://webbot.org.uk/WebbotLibDocs
If your in the neighbourhood: http://www.hovinghamspa.co.uk

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #15 on: August 18, 2010, 05:41:44 PM »
I'll test it at work tomorrow.

I've got one for you though,

Why does

Code: [Select]
PORTB &= ~(_BV(0));
 PORTB &= ~(_BV(1));
 PORTB &= ~(_BV(2));

work as advertised but

Code: [Select]
PORTB &= ~(_BV(0)|_BV(1)|_BV(2));

or

Code: [Select]
PORTB &= 0xF8;
cause random resets and sporadic results? Is there something with GCC that doesn't like setting more then one bit in a register?

macro is #define _BV(bit) (1<<(bit))
   

EDIT:

Could it be something with PB7 sharing a crystal pin?
« Last Edit: August 18, 2010, 07:03:33 PM by madsci1016 »

Offline WebbotTopic starter

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 2,136
  • Helpful? 109
Re: Tip for Toggling an ATMega IO pin
« Reply #16 on: August 18, 2010, 07:03:17 PM »
Hmm thats a strange one - should work.
The compiler currently tries to detect a single bit change on a port and then changes it into the assembler commands sbi and cbi. These can only ever work on a single bit in the native instruction set.
So a C comand that tries to change multiple bits at once (as per your example) will probably end up using a memory read/write or io commands to do the job.
Must confess that I haven't seen your problem in practice but it would be good to see an example that doesn't work along with the 'lss' listing file from the compiler of the assembler commands, and any cmd line compiler options especially the optimisation flags.

Optimisation generally gets a bad press. Yes the compiler does have bugs - which are fairly predictable once you get to understand its madness and are easy to work around. eg the entire WebbotLib is compiled with optimisation '-Os' and this hasn't created any reported bugs.
Webbot Home: http://webbot.org.uk/
WebbotLib online docs: http://webbot.org.uk/WebbotLibDocs
If your in the neighbourhood: http://www.hovinghamspa.co.uk

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #17 on: August 18, 2010, 07:05:08 PM »
EDIT:

Could it be something with PB7 sharing a crystal pin?

My spidey senses tell me it has to do with this. Thoughts?

Looks like this is the case. Quick test on another port and no funny business. My guess is the the PORTB &= first reads the register before writing to it, and the crystal pin could have changed. Am i getting warmer?

Grr, nevermind. Man this is irritating.
« Last Edit: August 18, 2010, 07:15:04 PM by madsci1016 »

Offline WebbotTopic starter

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 2,136
  • Helpful? 109
Re: Tip for Toggling an ATMega IO pin
« Reply #18 on: August 18, 2010, 07:54:55 PM »
Ah yes that could be bad.

I imagine that changing the fuse bits to say that you have an external crystal probably reserves PB7 for the crystal.

ie
Code: [Select]
PORTB &= ~(_BV(0));
 PORTB &= ~(_BV(1));
 PORTB &= ~(_BV(2));
will generate 3 individual assembler 'cbi' instructions to clear individual bits

but
Code: [Select]
PORTB &= ~(_BV(0)|_BV(1)|_BV(2));
Clears multiple bits in one go which the 'cbi' instruction cannot do. So instead the compiler will generate:
Code: [Select]
uint8_t temp = PORTB;
temp &= ~(_BV(0)|_BV(1)|_BV(2));
PORTB = temp;
So this will overwrite PB7 on the write with its old value from the read.

It still surprises me though. ATmel are normally good in hardware in making bits read-only in this sort of event.
I'll check the datasheet - presume its still an ATMega328P ?
Webbot Home: http://webbot.org.uk/
WebbotLib online docs: http://webbot.org.uk/WebbotLibDocs
If your in the neighbourhood: http://www.hovinghamspa.co.uk

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #19 on: August 18, 2010, 08:38:04 PM »
I'll check the datasheet - presume its still an ATMega328P ?


Yes, but now i'm getting the same results with port D, so i don't know what this is.

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #20 on: August 19, 2010, 12:15:07 PM »
What can I say Webbot, when you're right, you're right.

Retested in such a way to account for the delay of the for loop itself, shaved 2 cycles off the sbi time. sbi() to flip a pin is just as fast as PORTD |= _BV to set a pin.

Full results on my website.

http://www.billporter.info/?p=308

 Also realized i was using the CLR() and SET() Macros wrong as well, and they turn out to be just as fast as sbi() and PORTB |=.
« Last Edit: August 19, 2010, 12:21:08 PM by madsci1016 »

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #21 on: August 19, 2010, 12:33:00 PM »
Wooo!! Made it on Hack-A-Day it seems!

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #22 on: August 24, 2010, 08:00:29 AM »
Webbot, where did you learn of the PIN trick?

Seems there's some haters calling me out on my blog.

http://www.billporter.info/?p=308&cpage=2#comment-280

Offline WebbotTopic starter

  • Expert Roboticist
  • Supreme Robot
  • *****
  • Posts: 2,136
  • Helpful? 109
Re: Tip for Toggling an ATMega IO pin
« Reply #23 on: August 24, 2010, 08:13:25 AM »
Look at the full datasheet for the ATMega328P, ATMega640 or ATMega168 and look at section 13.2.2

Quote
13.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.
Webbot Home: http://webbot.org.uk/
WebbotLib online docs: http://webbot.org.uk/WebbotLibDocs
If your in the neighbourhood: http://www.hovinghamspa.co.uk

Offline madsci1016

  • Contest Winner
  • Supreme Robot
  • ****
  • Posts: 1,450
  • Helpful? 43
Re: Tip for Toggling an ATMega IO pin
« Reply #24 on: August 24, 2010, 08:22:18 AM »
Thanks.

 


Get Your Ad Here