¿Configurar el pin en función alta en lugar de la salida principal, no de voltaje completo?

Tengo una función que enciende un LED configurando PB1 en ALTO. La luz del LED es apenas visible. Si configuro PB1 en ALTO en mi función principal, la luz del LED es tan brillante como debería ser.

No tiene sentido para mí, ya que solo está cambiando un valor, que es 0 o 1. Debo estar omitiendo algo extremadamente obvio, pero ¿qué podría ser?

Aquí hay alguna información de fondo:

  • El LED está conectado en serie con una resistencia para que no se cortocircuite.
  • He probado los dos códigos diferentes adjuntos varias veces para asegurarme de que el problema se reproduzca cada vez.
  • He intentado usar un pin diferente para descartar que haya algún problema con ese pin específico

Editar: se agregó más información y etiquetas

  • Microprocesador: ATmega328-PU
  • Programador: AVRisp MKII

Aquí está el código con el pin establecido en main:

#include <avr/io.h>

int main(void) {    
    DDRB |= (1 << PB1);

    PORTB |= (1 << PB1); 

    while(1) {
    }

    return 0;
}

Y aquí está el código donde se establece el pin en una función:

#include <avr/io.h>

void turn_on_led();

int main(void) {
    DDRB |= (1 << PB1);

    turn_on_led();

    while(1) {
    }

    return 0;
}

void turn_on_led()
{
    PORTB |= (1 << PB1); 
}

Aquí está el archivo MAKE:

main: 
    avr-gcc -g -Os -Wall -mmcu=atmega328 -c ../src/example.c

elf:
    avr-gcc example.o -o example.elf    

hex: 
    avr-objcopy -O ihex example.elf example.hex 

dump: 
    avr-objdump -h -S example.o > example.lst   

upload:
    avrdude -p m328 -c avrispmkII -P usb -U flash:w:example.hex

clean:
    rm -f *.o
    rm -f *.hex
    rm -f *.lst

Configuración de fusibles:

avrdude: Device signature = 0x1e9514
avrdude: safemode: lfuse reads as E2
avrdude: safemode: hfuse reads as D9
avrdude: safemode: efuse reads as 7

Entrada de configuración de fusibles en una calculadora de fusibles para mostrar bits individuales:

Calculadora de fusibles

Desmontaje del pasador de ajuste en la tubería principal:

example.o:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000006  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  0000003a  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  0000003a  2**0
                  ALLOC
  3 .debug_abbrev 0000004e  00000000  00000000  0000003a  2**0
                  CONTENTS, READONLY, DEBUGGING
  4 .debug_info   00000082  00000000  00000000  00000088  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  5 .debug_line   000000c2  00000000  00000000  0000010a  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  6 .debug_frame  00000020  00000000  00000000  000001cc  2**2
                  CONTENTS, RELOC, READONLY, DEBUGGING
  7 .debug_pubnames 0000001b  00000000  00000000  000001ec  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  8 .debug_pubtypes 0000001e  00000000  00000000  00000207  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  9 .debug_aranges 00000020  00000000  00000000  00000225  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
 10 .debug_str    000000d9  00000000  00000000  00000245  2**0
                  CONTENTS, READONLY, DEBUGGING

Disassembly of section .text:

00000000 <main>:
#include <avr/io.h>

int main(void) {
    DDRB |= (1 << PB0);
   0:   20 9a           sbi 0x04, 0 ; 4

    PORTB |= (1 << PB0); 
   2:   28 9a           sbi 0x05, 0 ; 5
   4:   00 c0           rjmp    .+0         ; 0x6 <__zero_reg__+0x5>

Desmontaje del pasador de ajuste en función:

example.o:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         0000000c  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  00000040  2**0
                  ALLOC
  3 .debug_abbrev 00000061  00000000  00000000  00000040  2**0
                  CONTENTS, READONLY, DEBUGGING
  4 .debug_info   00000096  00000000  00000000  000000a1  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  5 .debug_line   000000dc  00000000  00000000  00000137  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  6 .debug_frame  00000030  00000000  00000000  00000214  2**2
                  CONTENTS, RELOC, READONLY, DEBUGGING
  7 .debug_pubnames 0000002b  00000000  00000000  00000244  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  8 .debug_pubtypes 0000001e  00000000  00000000  0000026f  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
  9 .debug_aranges 00000020  00000000  00000000  0000028d  2**0
                  CONTENTS, RELOC, READONLY, DEBUGGING
 10 .debug_str    000000e5  00000000  00000000  000002ad  2**0
                  CONTENTS, READONLY, DEBUGGING

Disassembly of section .text:

00000000 <turn_on_led>:
    return 0;
}

void turn_on_led()
{
    PORTB |= (1 << PB1); 
   0:   29 9a           sbi 0x05, 1 ; 5
}
   2:   08 95           ret

00000004 <main>:
#include <avr/io.h>

void turn_on_led();

int main(void) {
    DDRB |= (1 << PB1);
   4:   21 9a           sbi 0x04, 1 ; 4

    turn_on_led();
   6:   0e 94 00 00     call    0   ; 0x0 <turn_on_led>
   a:   00 c0           rjmp    .+0         ; 0xc <main+0x8>
Solo una nota al margen: debe usar #defines para cambiar el nombre de las salidas de LED y evitar 'números mágicos', ya que da como resultado un código más limpio y legible.
Sí, o usar una enumeración para varios números relacionados. La idea es dar a los números un nombre constante para que: 1) Cuando desee cambiar el número, haya un lugar central donde pueda cambiarlo, 2) Tenga una descripción detallada de lo que representa el número, en lugar de solo el valor. que no es muy descriptivo.
¿Cuáles son sus configuraciones de fusibles? ¿Está habilitado el perro guardián? Si avr-objdump ambas versiones, ¿cuál es la diferencia? ¿Funciona la versión con la función si la función está en línea?
@Joby Taffey: Agregué la configuración de fusibles y volcados en la pregunta. Lo único que he cambiado es deshabilitar CKDIV8.
No tengo experiencia con la codificación AVR en sí, solo experiencia con Arduino. Con Arduino, el modo de pin predeterminado es INPUT, que establece los pines en alta impedancia: los LED pueden encenderse (escribir un ALTO habilita una resistencia de extracción), pero muy débilmente. Al cambiarlos al modo de SALIDA, los pines pueden generar mucha más corriente. ¿Podría ser algo relacionado con esto el problema? Honestamente, no veo cómo al leer el código, pero pensé en mencionarlo, ya que me mordió cuando era nuevo en el entorno Arduino.
@rzetterberg por diversión deberías poner DDRB |= (1 << PB1); antes de PORTB |= (1 << PB1); en la función turn_led_on y ver si se comporta igual (sin la palabra clave estática)
@exscape En realidad, tienes razón. Si excluyo la instrucción que establece el puerto para salida, el comportamiento es exactamente el mismo. Buena atrapada :)
@vicatcu Traté de ponerlos a ambos en la función sin palabra clave estática y funciona bien. Es como si la configuración de DDRB se perdiera cuando no está justo por encima de la configuración de PORTB.

Respuestas (3)

Vaya, eso es bastante loco. Esos programas son casi idénticos. Solo para facilitar la comparación, el primer programa, con la asignación en main, tiene el ensamblado (con comentarios):

0:   20 9a           sbi 0x04, 0 ; Set Bit of IO for DDRB
2:   28 9a           sbi 0x05, 0 ; Set Bit of IO for PORTB
4:   00 c0           rjmp    .+0 ; Relative JuMP of 0 bytes functions as `while(1);`

El segundo programa, con la asignación en una función, tiene el ensamblado:

0:   28 9a           sbi 0x05, 0 ; Set Bit of IO for PORTB
2:   08 95           ret         ; Return to the address on the call stack 
4:   20 9a           sbi 0x04, 0 ; Set Bit of IO for DDRB
6:   0e 94 00 00     call      0 ; Call the function at address 0 (at top)
a:   00 c0           rjmp    .+0 ; Relative JuMP of 0 bytes functions as `while(1);`

El tercer programa realiza una optimización interesante: elimina completamente la llamada a la función, la alinea y hace que este programa sea idéntico al primero. Probablemente podría obtener un efecto idéntico con inline void turn_on_led(). En aras de la integridad, el conjunto es:

0:   20 9a           sbi 0x04, 0 ; Set Bit of IO for DDRB
2:   28 9a           sbi 0x05, 0 ; Set Bit of IO for PORTB
4:   00 c0           rjmp    .+0 ; Relative JuMP of 0 bytes functions as `while(1);`

La tabla de vectores de interrupción

Las direcciones 0a ason direcciones dentro de la .textsección, no en la memoria del programa. La sección .text comienza en el desplazamiento 0x34, según la File off[set]directiva:

Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000006  00000000  00000000  00000034  2**0

Lo que realmente está en las direcciones 0-34 es (generalmente) la tabla de vectores de interrupción. Si hubiera seleccionado la BOOTRSTbandera, estaría al comienzo del cargador de arranque, pero no lo hizo, así que no lo está. El primer elemento en la tabla de vectores de interrupción le dice al procesador a dónde ir para reiniciar o arrancar.

Esta ubicación debería ser la primera instrucción de mainestos programas ( 0para el primer caso, 4para el segundo caso), pero es posible que tenga una dirección predeterminada 0en el segundo programa, lo que establecería la salida alta mientras el registro de dirección de datos aún estaba configurado. para ingresar según los valores predeterminados. Esto activaría el pullup débil y el cambio del registro de dirección de datos más adelante no tendría ningún efecto.

Supongo que esto es lo que está pasando. Para probar si su problema es que su salida solo usa el pullup débil, puede eliminar la asignación DDRB por completo de cualquiera de los programas. El resultado debería ser un LED tenuemente iluminado. Si no es el mismo brillo, entonces este no es tu problema. Si es el mismo brillo, supongo que este es, de hecho, su problema.

Explicaciones alternativas, probablemente no sean el problema (pero podrían serlo en otras situaciones)

¿Necesitas un retraso?

Otro contratiempo podría ser la mención en la sección 11.2 de la hoja de datos , "Puertos como E/S digital general" que:

Como se muestra en la Figura 11-2, el bit de registro PINxn y el pestillo anterior constituyen un sincronizador. Esto es necesario para evitar la metaestabilidad si el pin físico cambia de valor cerca del borde del reloj interno, pero también introduce un retraso. La Figura 11-3 muestra un diagrama de tiempo de la sincronización cuando se lee un valor de pin aplicado externamente.

Por lo tanto, en cada ejemplo de lectura de un pin, hay una operación nula durante la cual se tiene en cuenta este retraso del sincronizador. Use __no_operation();en C para este efecto. Esta necesidad de retraso es muy común en la programación de sistemas integrados; es más barato hacer que el programador ponga un retraso en su código que hacer que algunas cosas sucedan en un solo ciclo de reloj.

En su primer programa, no tiene ese retraso. En su segundo programa, tiene un retraso. Esto debería causar que el primer programa no funcione, pero el segundo programa debería funcionar. Esto no es lo que está sucediendo, y no hay tal retraso en las salidas, por lo que dudo que este sea el problema.

PWM accidental

Su ensamblaje demuestra que este no es el caso, pero un error común es olvidar el archivo while(1). Esto puede hacer que el procesador se reinicie inmediatamente después de encender el LED. Mientras el procesador se reinicia y se configura el registro DDRB, el LED está apagado. Luego, se enciende brevemente y el reinicio comienza de nuevo. Esto forma un sistema PWM rudimentario por accidente , lo que hace que el LED parezca tenue.

Sin embargo, tiene un while(1)(tenga en cuenta que for(;;)es un equivalente popular) y aparece en el ensamblaje como rjmp .+0, por lo que este no parece ser su problema. Estoy un poco confundido por 0, rjmpcambia el contador del programa a PC + k + 1. Por lo general, usamos etiquetas para esto cuando escribimos en ensamblador y, por lo tanto, debería generar un resultado kde -1, pero parece razonable confiar en que el compilador está haciendo lo correcto aquí.

Sin embargo, echemos un mejor vistazo a la codificación. El código hexadecimal de la instrucción es 00 c0. De acuerdo con el manual del conjunto de instrucciones AVR , el código de operación para rjmp es 1100 kkkk kkkk kkkko, en hexadecimal, 0xCK KK, donde la concatenación de Kes k, nuestro salto relativo. El AVR que estamos usando es little-endian, por lo que, 00 C0como se ve en el programa, es un salto relativo (C) a una posición a 0 bytes de distancia.

Según la descripción de la operación, esta realizará la operación PC <- PC + 0 + 1, o avanzará el contador del programa más allá de esta dirección. Sin embargo, puede ser que esta no sea la interpretación correcta de esta operación, siempre he usado etiquetas al trabajar con esta instrucción, por lo que el ensamblador ha abstraído el número real utilizado.

Acusar al compilador de malinterpretar las líneas

while(1) {
} 

es bastante extremo, sin embargo. No creo que este sea el problema.

¡Espero que esto ayude!

¡Gracias por tomarse el tiempo para explicar las diferentes posibilidades! Investigo un poco sobre lo que mencionaste y mañana probaré los diferentes escenarios para identificar el problema :)
Intenté eliminar la configuración de PB1 para la salida y se comporta exactamente igual. Entonces, de alguna manera, DDRB nunca se configura en mi primer programa y el pin está actuando en modo de entrada con la resistencia de extracción activada: SI envió un correo a avr-ggc-list con la esperanza de obtener información sobre por qué el compilador haría esto. Disponible aquí: Lists.gnu.org/archive/html/avr-gcc-list/2012-03/msg00000.html

Después de leer la información que Kevin proporcionó en su respuesta, leí un documento llamado "AVR32006: Primeros pasos con GCC para AVR32". En este documento había una sección en particular que me hizo pensar en lo que dijo Kevin sobre la tabla de vectores.

El proceso de vinculación necesita información sobre el código y la ubicación de la memoria de datos. El uso de un script de enlace proporciona esto. Si especifica para qué dispositivo está compilando el código mediante el uso de ?-mpart=? opción en avr32-gcc, se utilizará un script de enlace predeterminado para ese dispositivo.

Así que intenté agregar la mmcubandera al comando de vinculación, para que se cambiara de:

elf:
        avr-gcc example.o -o example.elf

A:

elf:
        avr-gcc -mmcu=atmega328 example.o -o example.elf

Este resultó ser el problema, la tabla de vectores no estaba configurada correctamente y faltaban muchas otras partes.

Aquí está el desmontaje después de agregar la mmcubandera:

example.elf:     file format elf32-avr

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000090  00000000  00000000  00000054  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .stab         000006cc  00000000  00000000  000000e4  2**2
                  CONTENTS, READONLY, DEBUGGING
  2 .stabstr      00000081  00000000  00000000  000007b0  2**0
                  CONTENTS, READONLY, DEBUGGING

Disassembly of section .text:

00000000 <__vectors>:
   0:   0c 94 34 00     jmp     0x68    ; 0x68 <__ctors_end>
   4:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
   8:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
   c:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  10:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  14:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  18:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  1c:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  20:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  24:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  28:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  2c:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  30:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  34:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  38:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  3c:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  40:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  44:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  48:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  4c:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  50:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  54:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  58:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  5c:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  60:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>
  64:   0c 94 3e 00     jmp     0x7c    ; 0x7c <__bad_interrupt>

00000068 <__ctors_end>:
  68:   11 24           eor     r1, r1
  6a:   1f be           out     0x3f, r1        ; 63
  6c:   cf ef           ldi     r28, 0xFF       ; 255
  6e:   d8 e0           ldi     r29, 0x08       ; 8
  70:   de bf           out     0x3e, r29       ; 62
  72:   cd bf           out     0x3d, r28       ; 61
  74:   0e 94 42 00     call    0x84    ; 0x84 <main>
  78:   0c 94 46 00     jmp     0x8c    ; 0x8c <_exit>

0000007c <__bad_interrupt>:
  7c:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>

00000080 <turn_on_pb>:
        return 0;
}

void turn_on_pb(void)
{
        PORTB |= (1 << PB0);
  80:   28 9a           sbi     0x05, 0 ; 5
}
  82:   08 95           ret

00000084 <main>:

void turn_on_pb(void);

int main(void)
{
        DDRB |= (1 << PB0);
  84:   20 9a           sbi     0x04, 0 ; 4
        turn_on_pb();
  86:   0e 94 40 00     call    0x80    ; 0x80 <turn_on_pb>
  8a:   ff cf           rjmp    .-2             ; 0x8a <main+0x6>

0000008c <_exit>:
  8c:   f8 94           cli

0000008e <__stop_program>:
  8e:   ff cf           rjmp    .-2             ; 0x8e <__stop_program>
Me preguntaba qué hacía ese interruptor. Gracias por el seguimiento completo. +1
Encontré esto: "Es importante especificar el tipo de MCU al vincular. El compilador usa la opción -mmcu para elegir los archivos de inicio y las bibliotecas en tiempo de ejecución que se vinculan entre sí. Si no se especifica esta opción, el compilador tiene los valores predeterminados al entorno del procesador 8515, que sin duda es lo que no quería". aquí: nongnu.org/avr-libc/user-manual/group__demo__project.html

Me imagino que su problema es que la pila no está configurada correctamente, por lo que la callinstrucción falla cuando intenta guardar la dirección de retorno. Esto probablemente da como resultado un reinicio del procesador, lo que resulta en un PWM accidental como lo describe Kevin, excepto que es DDRB el que se ve afectado por el PWM y PORTB nunca se configura en absoluto.