Setzen Sie den Pin auf High in der Funktion anstelle des Hauptausgangs, nicht auf die volle Spannung?

Ich habe eine Funktion, die eine LED einschaltet, indem PB1 auf HIGH gesetzt wird. Das Licht der LED ist kaum sichtbar. Wenn ich PB1 in meiner Hauptfunktion auf HIGH stelle, ist das Licht der LED so hell, wie es sein sollte.

Es macht für mich keinen Sinn, da es nur einen Wert ändert, der entweder 0 oder 1 ist. Ich muss etwas sehr Offensichtliches übersehen, aber was könnte es sein?

Hier einige Hintergrundinformationen:

  • Die LED ist mit einem Widerstand in Reihe geschaltet, damit sie nicht kurzgeschlossen wird
  • Ich habe die zwei verschiedenen angehängten Codes mehrmals ausprobiert, um sicherzustellen, dass das Problem jedes Mal reproduziert wird
  • Ich habe versucht, einen anderen Pin zu verwenden, um auszuschließen, dass mit diesem bestimmten Pin etwas nicht stimmt

Bearbeiten: Weitere Informationen und Tags hinzugefügt

  • Chip: ATmega328-PU
  • Programmierer: AVRisp MKII

Hier ist der Code mit dem Pin-Set in main:

#include <avr/io.h>

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

    PORTB |= (1 << PB1); 

    while(1) {
    }

    return 0;
}

Und hier ist der Code, wo der Pin in einer Funktion gesetzt wird:

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

Hier ist das Makefile:

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

Sicherungseinstellung:

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

Eingabe der Sicherungseinstellung in einen Sicherungsrechner, um einzelne Bits anzuzeigen:

Sicherungsrechner

Demontage des Einstellstiftes im Wesentlichen:

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>

Demontage Einstellstift in Funktion:

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>
Nur eine Randbemerkung: Sie sollten #defines verwenden, um die LED-Ausgänge umzubenennen und „magische Zahlen“ zu vermeiden, da dies zu einem saubereren und besser lesbaren Code führt.
Ja, oder eine Aufzählung für mehrere zusammengehörige Nummern verwenden. Die Idee ist, Zahlen einen konstanten Namen zu geben, damit: 1) Wenn Sie die Zahl ändern möchten, es einen zentralen Ort gibt, an dem Sie sie ändern können, 2) Sie eine ausführliche Beschreibung dessen haben, was die Zahl darstellt, anstatt nur den Wert was nicht sehr aussagekräftig ist.
Wie sind deine Sicherungseinstellungen? Ist der Watchdog aktiviert? Wenn Sie beide Versionen avr-objdumpen, was ist der Unterschied? Funktioniert die Version mit der Funktion, wenn die Funktion inline ist?
@ Joby Taffey: Ich habe die Sicherungseinstellungen und Dumps in der Frage hinzugefügt. Das einzige, was ich geändert habe, ist, CKDIV8 zu deaktivieren.
Ich habe keine Erfahrung mit der AVR-Codierung selbst, nur Arduino-Erfahrung. Bei Arduino ist der Standard-Pin-Modus INPUT, der die Pins auf hohe Impedanz setzt - LEDs können leuchten (das Schreiben eines HIGH aktiviert einen Pullup-Widerstand), aber sehr schwach. Wenn sie in den OUTPUT-Modus geschaltet werden, können die Pins viel mehr Strom liefern. Könnte etwas damit zusammenhängendes Problem sein? Ehrlich gesagt verstehe ich nicht, wie ich den Code lese, aber ich dachte, ich würde es erwähnen, da es mich gebissen hat, als ich neu in der Arduino-Umgebung war.
@rzetterberg zum Spaß solltest du DDRB |= (1 << PB1); vor PORTB |= (1 << PB1); in der Funktion turn_led_on und prüfen Sie, ob sie sich genauso verhält (ohne das Schlüsselwort static)
@exscape Eigentlich hast du Recht. Wenn ich die Anweisung ausschließe, die den Port auf Ausgabe setzt, ist das Verhalten genau das gleiche. Guter Fang :)
@vicatcu Ich habe versucht, sie beide ohne statisches Schlüsselwort in die Funktion einzufügen, und es funktioniert gut. Es ist, als ob das Einstellen des DDRB verloren geht, wenn es nicht nur über dem Einstellen von PORTB liegt.

Antworten (3)

Wow, das ist ziemlich verrückt. Diese Programme sind fast identisch. Nur zum einfacheren Vergleich hat das erste Programm mit der Zuweisung in maindie Assembly (mit Kommentaren):

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);`

Das zweite Programm mit der Zuweisung in einer Funktion hat die Assembly:

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);`

Das dritte Programm führt eine interessante Optimierung durch - es entfernt den Funktionsaufruf vollständig, fügt ihn ein und macht dieses Programm mit dem ersten identisch. Sie könnten wahrscheinlich einen identischen Effekt mit erzielen inline void turn_on_led(). Der Vollständigkeit halber ist die Baugruppe:

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);`

Die Interrupt-Vektortabelle

Die Adressen 0bis asind Adressen innerhalb des .textAbschnitts, nicht im Programmspeicher. Der .text-Abschnitt beginnt bei Offset 0x34 gemäß der File off[set]Direktive:

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

Was sich wirklich an den Adressen 0-34 befindet, ist (normalerweise) die Interrupt-Vektortabelle. Wenn Sie das BOOTRSTFlag ausgewählt hätten, wäre es am Start des Bootloaders, aber Sie haben es nicht getan, also ist es nicht. Das erste Element in der Unterbrechungsvektortabelle teilt dem Prozessor mit, wohin er beim Zurücksetzen oder Hochfahren gehen soll.

Diese Stelle sollte die erste Anweisung mainfür diese Programme sein ( 0für den ersten Fall, 4für den zweiten Fall), aber es ist möglich, dass sie 0im zweiten Programm standardmäßig adressiert wird, was den Ausgang hoch setzen würde, während das Datenrichtungsregister noch gesetzt war zur Eingabe gemäß den Standardeinstellungen. Dies würde den schwachen Pullup einschalten, und die spätere Änderung des Datenrichtungsregisters hätte keine Wirkung.

Ich würde vermuten, dass dies passiert. Um zu testen, ob Ihr Problem darin besteht, dass Ihre Ausgabe nur den schwachen Pullup verwendet, können Sie die DDRB-Zuweisung vollständig aus beiden Programmen entfernen. Das Ergebnis sollte eine schwach leuchtende LED sein. Wenn es nicht die gleiche Helligkeit ist, dann ist dies nicht Ihr Problem. Wenn es die gleiche Helligkeit ist, würde ich vermuten, dass dies tatsächlich Ihr Problem ist.

Alternative Erklärungen, die wahrscheinlich nicht das Problem sind (könnten aber in anderen Situationen auftreten)

Brauchen Sie eine Verzögerung?

Ein weiteres Problem könnte die Erwähnung in Abschnitt 11.2 des Datenblatts „Ports as General Digital I/O“ sein, dass:

Wie in Abbildung 11-2 gezeigt, bilden das PINxn-Registerbit und das vorhergehende Latch einen Synchronisierer. Dies ist erforderlich, um Metastabilität zu vermeiden, wenn der physische Pin den Wert nahe der Flanke des internen Takts ändert, führt aber auch zu einer Verzögerung. Abbildung 11-3 zeigt ein Zeitdiagramm der Synchronisation beim Lesen eines extern angelegten Pinwerts.

Daher gibt es in jedem Beispiel des Lesens eines Pins eine Nulloperation, während der diese Synchronisationsverzögerung berücksichtigt wird. Verwenden Sie __no_operation();für diesen Effekt in C. Diese Notwendigkeit einer Verzögerung ist bei der Programmierung eingebetteter Systeme sehr verbreitet; Es ist billiger, den Programmierer eine Verzögerung in seinen Code einbauen zu lassen, als einige Dinge in einem einzigen Taktzyklus passieren zu lassen.

In Ihrem ersten Programm haben Sie keine solche Verzögerung. In Ihrem zweiten Programm haben Sie eine Verzögerung. Das sollte dazu führen, dass das erste Programm nicht funktioniert, aber das zweite Programm sollte funktionieren. Dies ist nicht der Fall, und es gibt keine solche Verzögerung an den Ausgängen, daher bezweifle ich, dass dies das Problem ist.

Versehentliches PWM

Ihre Assembly zeigt, dass dies nicht der Fall ist, aber ein häufiger Fehler besteht darin, die while(1). Dies kann dazu führen, dass der Prozessor sofort nach dem Einschalten der LED zurückgesetzt wird. Während der Prozessor sich selbst zurücksetzt und das DDRB-Register gesetzt wird, ist die LED aus. Dann ist es kurz an und der Reset beginnt von vorne. Dies bildet bei einem Unfall ein rudimentäres PWM-System , wodurch die LED schwach erscheint.

Sie haben jedoch eine while(1)(beachten Sie, dass dies for(;;)ein beliebtes Äquivalent ist), und es wird in der Assembly als angezeigt rjmp .+0, sodass dies nicht Ihr Problem zu sein scheint . Ich bin ein wenig verwirrt von der 0, rjmpändert den Programmzähler in PC + k + 1. Normalerweise verwenden wir dafür Labels, wenn wir in Assembler schreiben, und dies sollte daher a kvon -1 ausgeben, aber es scheint vernünftig, darauf zu vertrauen, dass der Compiler tut hier das Richtige.

Schauen wir uns jedoch die Codierung genauer an. Der Hex-Code für die Anweisung ist 00 c0. Gemäß dem AVR Instruction Set-Handbuch ist der Opcode für rjmp 1100 kkkk kkkk kkkk, oder, in Hex, 0xCK KK, wobei die Verkettung von Kk ist, unser relativer Sprung. Der AVR, den wir verwenden, ist Little-Endian, also ist, 00 C0wie im Programm zu sehen, ein relativer Sprung (C) zu einer Position 0 Bytes entfernt.

Gemäß der Operationsbeschreibung führt dies die Operation PC < – PC + 0 + 1 durch oder erhöht den Programmzähler über diese Adresse hinaus. Es kann jedoch sein, dass dies nicht die korrekte Interpretation dieser Operation ist, ich habe bei der Arbeit mit dieser Anweisung immer Labels verwendet, daher wurde die tatsächlich verwendete Zahl vom Assembler abstrahiert.

Den Compiler beschuldigen, die Zeilen falsch interpretiert zu haben

while(1) {
} 

ist aber ziemlich extrem. Ich glaube nicht, dass dies das Problem ist.

Hoffe das hilft!

Vielen Dank, dass Sie sich die Zeit genommen haben, die verschiedenen Möglichkeiten zu erklären! Ich recherchiere etwas über das, was Sie angesprochen haben, und morgen werde ich die verschiedenen Szenarien testen, um das Problem zu lokalisieren :)
Ich habe versucht, die Einstellung von PB1 auf Ausgabe zu entfernen, und es verhält sich genau gleich. Also irgendwie ist DDRB in meinem ersten Programm nie eingestellt und der Pin arbeitet tatsächlich im Eingabemodus mit dem Pull-up-Widerstand auf: SI schickte eine Mail an avr-ggc-list in der Hoffnung, einige Informationen darüber zu erhalten, warum der Compiler dies tun würde. Hier verfügbar: listen.gnu.org/archive/html/avr-gcc-list/2012-03/msg00000.html

Nachdem ich die Informationen gelesen hatte, die Kevin in seiner Antwort bereitgestellt hatte, las ich ein Dokument mit dem Titel „AVR32006: Erste Schritte mit GCC für AVR32“. In diesem Dokument gab es einen bestimmten Abschnitt, der mich darüber nachdenken ließ, was Kevin über die Vektortabelle gesagt hat.

Der Verknüpfungsprozess benötigt Informationen über Code und Datenspeicherort. Die Verwendung eines Linker-Skripts bietet dies. Wenn Sie angeben, für welches Gerät Sie Code kompilieren, indem Sie das ?-mpart=? Option in avr32-gcc, wird ein Standard-Linker-Skript für dieses Gerät verwendet.

Also habe ich versucht, das mmcuFlag zum Verknüpfungsbefehl hinzuzufügen, sodass es geändert wurde von:

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

Zu:

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

Dies stellte sich als Problem heraus, der Vektortisch war nicht richtig eingerichtet und viele andere Teile fehlten.

Hier ist die Demontage nach dem Hinzufügen der mmcuFlagge:

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>
Ich fragte mich, was dieser Schalter tat. Danke für die komplette Nachverfolgung. +1
Ich habe Folgendes gefunden: „Es ist wichtig, beim Linken den MCU-Typ anzugeben. Der Compiler verwendet die Option -mmcu, um Startdateien und Laufzeitbibliotheken auszuwählen, die miteinander verknüpft werden. Wenn diese Option nicht angegeben ist, verwendet der Compiler die Standardeinstellungen in die 8515-Prozessorumgebung, was Sie sicherlich nicht wollten." hier: nongnu.org/avr-libc/user-manual/group__demo__project.html

Ich stelle mir vor, dass Ihr Problem darin besteht, dass der Stapel nicht richtig konfiguriert ist, sodass die callAnweisung fehlschlägt, wenn versucht wird, die Rücksendeadresse zu speichern. Dies führt wahrscheinlich zu einem Prozessor-Reset, was zu einem versehentlichen PWM führt, wie von Kevin beschrieben, mit der Ausnahme, dass DDRB von PWM betroffen ist und PORTB überhaupt nie eingestellt ist.