Electronic – AVR bitwise operation: making a NOT with a microcontroller

avrprogramming

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?

Best Answer

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