operación a nivel de bits AVR: hacer un NO con un microcontrolador

1

Estoy programando un ATtiny10, tengo dos pines de repuesto, uno de los cuales es el pin de interrupción externo en PB2, y quiero escribir la inversa de esa señal en PB1. Quiero ahorrar un componente, ya que ya tengo la unidad de control y algunos pines.

Hasta ahora, la mejor opción que podría encontrar es:

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

Se utiliza en la rutina IRQ asociada con el cambio de INT0.
Básicamente borrando el bit PB1, invirtiendo PINB, enmascarándolo y moviéndolo hacia la derecha en un lugar.

La otra opción es usar una sentencia if y derivar el código, lo que realmente no me satisface.

¿Hay una manera más fácil de hacer esto que no pueda ver?

    
pregunta valerio_new

2 respuestas

5

En el código de ensamblaje solo sería cuestión de probar un solo bit y luego establecer otro bit en 1 o 0, si escribes el código C lo suficientemente cerca de ese código de ensamblador, entonces podría ser lo que el compilador convertirá en.

Los compiladores son generalmente muy inteligentes. Quizás su código ya se está convirtiendo a ese código de ensamblaje, o tal vez incluso algo que sea más eficiente.

Si tuviera que hacerlo personalmente, no me importaría porque está optimizando excesivamente un problema inexistente. Pero por el simple hecho de ser pedante, vería el código de ensamblaje compilado y verificaría que es el código más eficiente. O simplemente escribiría un código C que espero haga que el compilador lo convierta en lo que pienso es el código más eficiente.

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
}

La conclusión es que probablemente no importa. El número total de relojes gastados en esta parte minúscula de su código puede ir de 10 a quizás 6, o quizás ya era 6 porque el compilador es inteligente. En general, un par de relojes aquí, algunos allí, en realidad no importan.

Aquí está la prueba que realizó OP, que podría ser interesante para futuros lectores:

  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:

En cuanto a la instrucción, mi versión es aproximadamente la mitad. Supongo que el compilador no fue lo suficientemente inteligente en este caso particular.

También, a partir de pruebas OP, retraso de propagación:

La pista azul es la señal de entrada, asumiendo que 5.1V * (0.6) = 3.06 V para el alto voltaje de retención (disparador a 3.04V debido a la configuración del alcance).

La expresión que propuso OP es la CH1 (amarilla, etiquetada), mientras que la blanca es el tiempo de respuesta con la instrucción IF.

La diferencia de tiempo entre los dos es de 1.64 uS, aproximadamente 13 ciclos de reloj si el oscilador interno de uC está calibrado correctamente. Así que con la expresión el retraso de propagación es casi el doble.

    
respondido por el Harry Svensson
2

Evitar un if que puede resultar en una rama no debe ser su prioridad, especialmente en AVR donde las ramas son 1 o 2 ciclos (¡dependiendo del valor de la condición!)

En este caso, su solución probablemente usaría un registro y una operación aritmética, una reescritura a una sentencia if como la respuesta de Harry Svensson usaría solo operaciones de bifurcación de bits y de configuración / borrado de bits que no cambian el registro de estado SREG ).

En ambos casos, si realmente desea un tiempo de ejecución mínimo, podría considerar usar una interrupción "desnuda" ( ISR_NAKED ) para evitar que el compilador almacene un montón de registros y los haga aparecer al regresar.

Cuando no utiliza uso registros (sugerencia de pista) no es necesario guardarlos y su ISR puede ser muy pequeño. Solo asegúrese de que su código generado no use realmente ningún registro como el que tiene previsto al inspeccionar el código generado después de la compilación, o escriba el ISR en ensamblador.

#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)
{}

compila con avr-gcc -S -O3 bitwise_not.c -mmcu=attiny10 a:

    .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 la optimización agresiva de duplicar el epílogo)

#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)
{}

compila a (usando el mismo comando)

    .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"
    
respondido por el Pelle

Lea otras preguntas en las etiquetas