1
\$\begingroup\$

I'm programming an ATtiny10, i have two spare pins, one of which is the external interrupt pin on PB2, and i want to write the inverse of that signal to PB1. I want to spare a component, since i have already the uC and some pins.

So far, the best option i could come up with, is:

PORTB = (PORTB & ~(1<<PB1)) | ((~PINB & (1<<PB2))>>1)

Used in the IRQ routine associated with the change of INT0.
Basically clearing the PB1 bit, inverting PINB, masking it and shifting to the right by one place.

The other option is using an if statement and branch the code, which doesn't really satisfy me.

Is there an easier way to do this, that i cannot see?

\$\endgroup\$
  • \$\begingroup\$ The first one-liner you've shown may look messy, but I would consider it the best way. Just change the rightmost >> 1 to >> PB2, so that it actually uses the pin defs instead of hardcode. \$\endgroup\$ – Vicente Cunha Sep 5 '18 at 12:34
  • \$\begingroup\$ @VicenteCunha i'm shifting by the position difference between PB1 and PB2, not by PB2. I could shift by (PB2-PB1) \$\endgroup\$ – valerio_new Sep 5 '18 at 13:00
5
\$\begingroup\$

In assembly code it would just be a matter of testing a single bit and then setting another bit to 1 or 0, if you write C code close enough to that assembly code, then that might be what the compiler will turn it into.

Compilers are usually very clever. Perhaps your code is already being converted to that assembly code, or maybe even something that is more efficient.

If I were to personally do it then I wouldn't care because you are over-optimizing a non-existent problem. But for the sake of being pedantic, then I'd look into the compiled assembly code and verify that it is what I believe is the most efficient code. Or I would just write some C code that hopefully makes the compiler turn it into what I think is the most efficient code.

if(PINB&(1<<PB2)){//This is essentially a bit test instruction
  PORTB&=~(1<<PB1);//This is essentially a bit clear instruction
}else{
  PORTB|=(1<<PB1);//This is essentially a bit set instruction
}

The bottom line is that it probably doesn't matter. The total number of clocks spent on this minuscule part of your code might go from 10 to maybe 6, or maybe it already was 6 because the compiler is smart. All in all, a couple of clocks here, some there, doesn't really matter.


Here's the test that OP performed, might be interesting for future readers:

  26:blink-prescaler-register.c ****   PORTB = (PORTB & ~(1<<PB1)) | (((PINB ^ (1<<PB2)) & (1<<PB2))>>1);
 107                    .loc 1 26 0
 108 0010 62B1              in r22,0x2
 109 0012 50B1              in r21,0
 110 0014 5095              com r21
 111 0016 5470              andi r21,lo8(4)
 112 0018 452F              mov r20,r21
 113 001a 50E0              ldi r21,0
 114 001c 5595              asr r21
 115 001e 4795              ror r20
 116 0020 562F              mov r21,r22
 117 0022 5D7F              andi r21,lo8(-3)
 118 0024 452B              or r20,r21
 119 0026 42B9              out 0x2,r20


VERSUS:
  27:blink-prescaler-register.c ****   if(PORTB&(1<<PB2)){//This is essentially a bit test instruction
  95                    .loc 1 27 0
  96 000a 129B              sbis 0x2,2
  97 000c 00C0              rjmp .L5
  28:blink-prescaler-register.c ****   PORTB&=~(1<<PB1);//This is essentially a bit clear instruction
  98                    .loc 1 28 0
  99 000e 1198              cbi 0x2,1
 100 0010 00C0              rjmp .L4
 101                .L5:
  29:blink-prescaler-register.c ****   }else{
  30:blink-prescaler-register.c ****   PORTB|=(1<<PB1);//This is essentially a bit set instruction
 102                    .loc 1 30 0
 103 0012 119A              sbi 0x2,1
 104                .L4:

Instruction wise, my version is about half. I suppose the compiler wasn't smart enough in this particular case.


Also, from OP tests, propagation delay:

The blue track is the input signal, assuming 5.1V*(0.6) = 3.06 V for the high treshold voltage (trigger at 3.04V because of scope settings).

The expression that OP proposed is the CH1 (yellow, labeled), while the white one is the response time with the IF statement.

The difference in time between the two is 1.64 uS, about 13 clock cycles if the uC internal oscillator is calibrated correctly. So with the expression the propagation delay is almost double.

propagation delay

\$\endgroup\$
  • 2
    \$\begingroup\$ > "you are over-optimizing a non-existent problem" Yes, i'm definitely doing that. But it is for learning's sake \$\endgroup\$ – valerio_new Sep 5 '18 at 12:58
  • 1
    \$\begingroup\$ And yes, you were right. Here is the assembly: hastebin.com/isofoqesux.m Plus, in my version in pushes and pops 5 register, while in yours only has to store and retrive 3. So yes, definitely more efficient \$\endgroup\$ – valerio_new Sep 5 '18 at 13:05
  • 1
    \$\begingroup\$ I've done some more testing with the scope: valerionappi.it/wp-content/uploads/2018/09/… The blue track is the input signal, assuming 5.1V*(0.6) = 3.06 V for the high treshold voltage (trigger at 3.04V because of scope settings). The expression that i proposed is the CH1 (yellow, labeled), while the white one is the response time with the IF statement. The difference in time between the two is 1.64 uS, about 13 clock cycles if the uC internal osc is calibrated correctly. So with the expression the propagation delay is almost double \$\endgroup\$ – valerio_new Sep 5 '18 at 18:52
  • \$\begingroup\$ Often, you tell compilers how clever they need to be, and what they're optimizing for -- execution time, memory,.... Free versions of some compilers are often limited in optimization levels or options. While this difference is dramatic here, without compiler info and calling flags, it's hard to put too much weight on it. \$\endgroup\$ – Scott Seidman Sep 6 '18 at 16:09
  • \$\begingroup\$ @ScottSeidman you are right. I'm using avr-gcc with -Os for optimization. I should try with a more up to date version maybe \$\endgroup\$ – valerio_new Sep 6 '18 at 17:15
2
\$\begingroup\$

Avoiding an if that may result in a branch should not be you priority, especially on AVR where branches are 1 or 2 cycles (depending on the value of the condition!)

In this case, your solution would probably use a register and arithmetic operation, a rewrite to an if statement like Harry Svensson's answer would use only bit-branch and bit-set/clear operations, that do not change the status register (SREG).

In both cases, if you really want minimal execution time, you could consider using a "naked" interrupt (ISR_NAKED) to prevent the compiler from storing a bunch of registers and popping them upon return.

When you don't use registers (hint hint) there is no need to save them and your ISR can be very small indeed. Just make sure your generated code does not actually use any registers like you intend by inspecting the generated code after recompile, or write the ISR in assembly.

#include <avr/interrupt.h>
ISR(PCINT0_vect)
{
    if(PINB&(1<<PB2)){//This is essentially a bit test instruction
        PORTB&=~(1<<PB1);//This is essentially a bit clear instruction
    }else{
        PORTB|=(1<<PB1);//This is essentially a bit set instruction
    }
}

int main(int argc, char** argv)
{}

compiles with avr-gcc -S -O3 bitwise_not.c -mmcu=attiny10 to:

    .file   "bitwise_not.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__CCP__ = 0x3c
__tmp_reg__ = 16
__zero_reg__ = 17
    .text
.global __vector_2
    .type   __vector_2, @function
__vector_2:
    push r17
    push r16
    in r16,__SREG__
    push r16
    ldi __zero_reg__,0
/* prologue: Signal */
/* frame size = 0 */
/* stack size = 3 */
.L__stack_usage = 3
    sbic 0,2
    rjmp .L5
    sbi 0x2,1
/* epilogue start */
    pop r16
    out __SREG__,r16
    pop r16
    pop r17
    reti
.L5:
    cbi 0x2,1
/* epilogue start */
    pop r16
    out __SREG__,r16
    pop r16
    pop r17
    reti
    .size   __vector_2, .-__vector_2
    .section    .text.startup,"ax",@progbits
.global main
    .type   main, @function
main:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    ldi r24,0
    ldi r25,0
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 5.4.0"

(note the aggressive optimization of duplicating the epilogue)

#include <avr/interrupt.h>
ISR(PCINT0_vect, ISR_NAKED)
{
    if(PINB&(1<<PB2)){//This is essentially a bit test instruction
        PORTB&=~(1<<PB1);//This is essentially a bit clear instruction
    }else{
        PORTB|=(1<<PB1);//This is essentially a bit set instruction
    }
    reti(); //Must be explicitly called when using ISR_NAKED
}


int main(int argc, char** argv)
{}

compiles to (using the same command)

    .file   "bitwise_not.c"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__CCP__ = 0x3c
__tmp_reg__ = 16
__zero_reg__ = 17
    .text
.global __vector_2
    .type   __vector_2, @function
__vector_2:
/* prologue: naked */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    sbis 0,2
    rjmp .L2
    cbi 0x2,1
    rjmp .L3
.L2:
    sbi 0x2,1
.L3:
/* #APP */
 ;  9 "bitwise_not.c" 1
    reti
 ;  0 "" 2
/* epilogue start */
/* #NOAPP */
    .size   __vector_2, .-__vector_2
    .section    .text.startup,"ax",@progbits
.global main
    .type   main, @function
main:
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
    ldi r24,0
    ldi r25,0
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 5.4.0"
\$\endgroup\$
  • \$\begingroup\$ As much as I hate using if statements, in this case I believe it is the most optimal. I'd be glad to see if you could show it be done better without an if statement. \$\endgroup\$ – Harry Svensson Sep 6 '18 at 5:34
  • \$\begingroup\$ Just curious, why do you hate if? Especially on AVR where there is no branch prediction, hardly any pipelines to flush an definitely no speculative execution it will usually translate to a single branch or skip instruction that executes in 1 or 2 cycles with no side effects \$\endgroup\$ – Pelle Sep 6 '18 at 20:41
  • \$\begingroup\$ Well, I'm coming from a PC background, so what you just named are things I've learned to avoid. - Sorry for not being specific. In this particular case on an AVR I agree, an if is not that hurtful. \$\endgroup\$ – Harry Svensson Sep 7 '18 at 4:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.