Forum: Mikrocontroller und Digitale Elektronik Anfängerprblem mit atmega8


von Janik J. (wvwi)


Lesenswert?

Hallo ertsmal an alle hier ;)

also ich habe gerade mit Mikrocontrollern angefangen also verzeiht mir 
wenn die Frage etwas "doof" ist.

Ich bin gerade dabei mich mit externen Interrupts zu befassen jedoch 
habe ich ein komisches Problem und zwar habe ich zwei LEDs, eine davon 
blinkt durchgehend die andere soll beim auslösen des Interrupts einmal 
blinken.

Soweit so gut jedoch bleibt das Programm am Ende der Interruptroutine 
hängen.

Das komische daran ist das dies nur passiert wenn ein "mdelay" im code 
des Interrupts oder in der Hauptschleife enthalten ist.

Hier mal mein Code (ist in Delphi mit AVRco geschrieben):
1
program Interrupt;
2
3
{$NOSHADOW}
4
{ $WG}                     {global Warnings off}
5
6
Device = mega8, VCC=5;
7
{ $BOOTRST $00C00}         {Reset Jump to $00C00}
8
9
Define_Fuses
10
//  Override_Fuses;
11
  NoteBook   = A;
12
  COMport    = USB;
13
  LockBits0 = [];
14
  FuseBits0  = [];
15
  FuseBits1  = [];
16
17
Import SysTick;
18
19
From System Import ;
20
21
22
Define
23
  ProcClock      = 1000000;       {Hertz}
24
  SysTick        = 10;             {msec}
25
  StackSize      = $0064, iData;
26
  FrameSize      = $0064, iData;
27
28
Implementation
29
30
{$IDATA}
31
32
{--------------------------------------------------------------}
33
{ Type Declarations }
34
35
type
36
37
38
{--------------------------------------------------------------}
39
{ Const Declarations }
40
41
{--------------------------------------------------------------}
42
{ Var Declarations }
43
{$IDATA}
44
45
46
{--------------------------------------------------------------}
47
{ functions }
48
Interrupt Int0;
49
  begin
50
   DisableInts;
51
   PortB := 00000001;
52
   mDelay (150);
53
   PortB := 00000000; //HIER "HÄNGT" ER SICH AUF
54
   EnableInts;
55
  end;
56
57
  procedure InitPorts;
58
begin
59
 DDRB := %00000011;//Datenrichtungsregister 
60
 DDRD := %00000001;
61
 
62
 PortD := %11111111; //pullups 
63
end;
64
65
66
{--------------------------------------------------------------}
67
{ Main Program }
68
{$IDATA}
69
70
begin
71
72
  InitPorts;
73
  EnableInts;
74
75
  GICR :=%01000000; //Interrupt einschalten
76
  MCUCR :=%00000100; //reagiert auf alle Wechsel
77
78
  loop
79
      PortB := %00000010;
80
      mDelay (200);
81
      PortB := %00000000;
82
      mDelay (200);
83
84
  endloop;
85
end Interrupt.

Hoffe mir kann jemand helfen.

MfG.

von Matthias L. (Gast)


Lesenswert?

Ohne jetzt Basic zu verstehen, aber ein PRoblem dürfte sein, dass du den 
PORTB im main und im Interrupt vollständig beschreibst. Du solltest nur 
das eine Bit für die LED beschreiben.

In C würd ich das so machen:
statt:
      PortB := %00000010;
      mDelay (200);
      PortB := %00000000;
      mDelay (200);
wird
PORTB |=   (1<<PB1);
mDelay(..);
PORTB &= ~(1<<PB1);

und im Int nimmst du (..PB0).

Aber wie das im Basic ist...

von Karl H. (kbuchegg)


Lesenswert?

Lass mal das
  DisableInts
und
  EnableInts

aus der Interrupt Routine raus. Beim AVR sind die Interrupts automatisch 
während der Abarbeitung einer Interrupt Routine gesperrt und das 
solltest du auch berücksichtigen. Das vorzeitige Freigeben der 
Interrupts während gerade eine ISR bearbeitet wird, ist problematisch, 
weil du unter Umständen in einer Endlosrekursion landest.

Und in einer ISR zu warten ist generell eine schlechte Idee (sofern das 
warten länger als ein paar µs dauert).

Die einfache Zuweisung an den Port ist ebenfalls problematisch, weil du 
damit auch die andere LED beeinflusst.

  PortB := 0;

setzt nun mal alle Portpins auf 0 und nicht nur den einen, an dem 
deine LED hängt, die blinken soll.


Ich hoffe mal du findest hier noch jemanden der besser Delphi, speziell 
auf dem AVR, kann als ich. Viele werden es nicht sein.

von Janik J. (wvwi)


Lesenswert?

Danke erstmal für die Antworten.
Das mit den DisableInts und EnableInts habe ich getestet jedoch ohne 
Erfolg.

Wenn man die Interruptroutine so ändert funtioniert alles:
1
Interrupt Int0;
2
  begin
3
   DisableInts;
4
   PortB := 00000001;
5
   EnableInts;
6
  end;

Jedoch kann ich mir nicht erlären warum ein normales delay sowas 
auslöst.

PS: Es funktioniert ja auch wenn man die Delays nur in der Hauptschleife 
entfernt.

MfG

von holger (Gast)


Lesenswert?

>Ich hoffe mal du findest hier noch jemanden der besser Delphi, speziell
>auf dem AVR, kann als ich. Viele werden es nicht sein.

Gibts irgendwo auch Fortran für AVR? SCNR;)

von Achim M. (minifloat)


Lesenswert?

holger schrieb:
> Fortran für AVR

Es gibt Forth. Das kann man mit einem speziellen Bootloader drauf 
klatschen und dann über die Serielle arbeiten.
mfg mf

PS: Ich tu mir das nicht an.

von Janik J. (wvwi)


Lesenswert?

Mir ist gerade etwas eingefallen und ich wollte mal hören ob das 
wirklich der Auslöser sein könnte und zwar wird der Interrupt durch 
einen Taster ausgelöst.

Diese Taster ist jedoch nicht entprellt, könnte es sein der der 
Interrupt dadurch "zu oft" in sehr kurzer Zeit aufgerufen wird und das 
Hängen daduch verursacht wird ?

Aber auf der anderen Seite sollten weitere Interrupts ja am Anfang der 
Interroptroutine deaktivirt werden.

Naja vill. hat ja noch jemand ne Idee und falls es wirklich mit den 
Delays zu tun hat dann ist das ja Programmiersprachenunabhängig denn 
Delays gibt es ja nicht nur in Delphi ;)

MfG.

von Karl H. (kbuchegg)


Lesenswert?

Kai J, schrieb:

> Diese Taster ist jedoch nicht entprellt, könnte es sein der der
> Interrupt dadurch "zu oft" in sehr kurzer Zeit aufgerufen wird und das
> Hängen daduch verursacht wird ?

Nein.
Deine BLinksequenz wird kräftig durcheinander kommen, aber hängen wird 
er nicht.

Wie kommst du überhaupt darauf, dass er hängt?

(Was ich mir allerdings vorstellen kann: das die Funktion mdelay nicht 
reentrant ist. D.h. das ein Aufruf der Funktion mdelay aus dem Interrupt 
heraus nicht so gut ist, wenn das Hauptprogramm ebenfalls gerade im 
mdelay ist.)

> Aber auf der anderen Seite sollten weitere Interrupts ja am Anfang der
> Interroptroutine deaktivirt werden.

Noch mal:
Lass das weg. Du schiesst dir nur ins eigene Knie, wenn du innerhalb 
einer ISR selber die Interrupts wieder freigibst. Der AVR hat eine 
spezielle Return Instruktion, die das macht und die vom Compiler ans 
Ende der Interrupt Routine gesetzt wird.

> Delays zu tun hat dann ist das ja Programmiersprachenunabhängig denn
> Delays gibt es ja nicht nur in Delphi ;)

Die Frage ist: wie ist die Funktionalität von mdelay implementiert? Ist 
die Prozedur reentrant oder nicht.

von Oldmax (Gast)


Lesenswert?

Hi
Ich weiß nicht, wer mit Delphi BAsic verbindet... das ist PASCAL.
Leider sibd meine Erfahrungen mit PASCAL und µC nicht besonders 
erfolgreich und so bin ich bei Assembler geblieben. Daher kann ich dir 
hier auch nicht weiterhelfen, jedenfalls nicht mit Code....
Gruß oldmax

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Kai J, schrieb:

>
1
>    PortB := 00000001;
2
>    [...]
3
>    PortB := 00000000; //HIER "HÄNGT" ER SICH AUF
4
>

Unabhängig von Deinem Problem: Hast Du da oben die Prozentzeichen vor 
den Ziffernkolonnen vergessen? Ich kann zwar kein Delphi, konnte aber 
aus dem Kontext herauslesen, dass Binärzahlen offenbar mit einem 
Prozentzeichen gekennzeichnet werden. Da oben stehen aber Dezimalzahhlen 
(in C wären es Oktalzahlen). Du hast Glück: 00000001 und 00000000 haben 
in beiden Zahlensystemen denselben Wert. Trotzdem würde ich diese 
Unschönheit korrigieren. Wechselst Du später den Pin, hättest Du hier 
einen dicken Fehler ("warum klappt das nur an PB0?").

Zu Deinem Problem: Da kann ich nur Karl Heinz zustimmen: Innerhalb von 
Interrupt-Routinen Finger weg von DisableInts und EnableInts. Damit 
bringst Du den µC nur durcheinander. Innerhalb von Interrupts sind 
weitere Interrupts automatisch gesperrt. Du rennst daher mit beiden 
Aufrufen offene Türen ein, wobei der Schuss nach hinten losgeht.

Wie auch schon Karl Heinz sagte: es könnte tatsächlich so sein, dass 
mdelay nicht reentrant ist. Ich selbst tippe auch darauf. Deshalb: Nicht 
machen.

Gruß,

Frank

von Ralph B. (rkhb)


Lesenswert?

Kai J, schrieb:
> Naja vill. hat ja noch jemand ne Idee und falls es wirklich mit den
> Delays zu tun hat dann ist das ja Programmiersprachenunabhängig denn
> Delays gibt es ja nicht nur in Delphi ;)
>
> MfG.

Es handelt sich um einen Bug in diesem Compiler: mDelay wird als 
Funktion in den Code eingebunden. Wenn der Interrupt während eines 
mDelay passiert, dann wird mDelay noch einmal mit neuen Werten 
aufgerufen. mDelay selbst sichert keine Register, verändert aber so 
einige davon, und die Interruptroutine "vergisst", r20/r21 
(_ACCDLO,_ACCDHI) wiederherzustellen. Damit kommt der Delay in der 
Hauptroutine kräftig durcheinander. Warte ein gehöriges Weilchen, und es 
fängt wieder an zu blinken. Ist wenigstens Deine ProcClock richtig 
eingestellt?

Lasse DisableInts und EnableInts auf jeden Fall weg! Dadurch werden 
nämlich Interrupts freigegeben, bevor der Interrupt den Stack aufräumen 
kann. Soooviel Stack hast Du in einem AVR nicht. Das Sperren und 
Freigeben der Interrupts geschieht automatisch.

BTW: Delphi ist ein Pascal-Dialekt von der Firma Borland. Es ist 
eleganter - und wie Du siehst weniger verwirrend -, wenn Du hier 
schlicht von Pascal redest.

viele grüße
ralph

von Janik J. (wvwi)


Lesenswert?

Frank M. schrieb:
>
> Unabhängig von Deinem Problem: Hast Du da oben die Prozentzeichen vor
> den Ziffernkolonnen vergessen? Ich kann zwar kein Delphi, konnte aber
> aus dem Kontext herauslesen, dass Binärzahlen offenbar mit einem
> Prozentzeichen gekennzeichnet werden.

Ja stimmt, hab ich wohl beim ganzen rumprobieren übersehen, macht aber 
leider keinen Unterschied.


@ Ralph

du hast den nagel aufn Kopf getroffen ;). Es ist wirklich so das wenn 
man einige zeit wartet das dann die Hauptschleife wieder normal 
funktioniert.

Jetzt bräuchte ich nur noch ein Tipp oder eine Lösung wie ichs trotzdem 
zum laufen bekomme.

PS: Von den DisableInts und EnableInts hab ich mich verabschiedet.

MfG.

von Karl H. (kbuchegg)


Lesenswert?

Kai J, schrieb:

> Jetzt bräuchte ich nur noch ein Tipp oder eine Lösung wie ichs trotzdem
> zum laufen bekomme.

Indem du eine Grundregel beherzigst:
In einer Interrupt Funktion wird nicht gewartet!


Überhaupt solltest du dir Funktionen wie mDelay wieder abschminken. Sie 
sind für Einsteiger verführerisch, sind aber der falsche Ansatz. Sie 
führen dazu, dass Programme lahm und unhandlich werden. Insbesondere 
wenn sie scheinbar mehrere Dinge gleichzeitig machen sollen, wie bei dir 
eben mehrere LED in unterschiedlichen Zeiten blinken zu lassen. Das bei 
dir die 2.te LED von einem Taster angestossen wird, ist dazu erst mal 
unerheblich.

Was du brauchst:
Du brauchst einen Timer als Zeitbasis. Das ist eine der Grundtechniken 
in allen Programmen, die irgendetwas mit Zeitsteuerung zu tun haben. 
Selbst dann, wenn es nur so etwas scheinbar banales wie das Blinken von 
2 LEDs ist. An diesen Timer koppelst du eine weitere Interrupt Funktion, 
die zb beim Timerüberlauf aufgerufen wird. Damit hast du deine 
Zeitbasis, es ist der regelmässige Aufruf eben dieser Funktion. Das muss 
dir genügen.
Man kann ja zb die Anzahl der Aufrufe mitzählen und nur wenn dieser 
Zähler bestimmte Werte hat, eine Aktion (bei dir das Umschalten der LED) 
ausführen.

von oldmax (Gast)


Lesenswert?

Hi
Pascal ist voll mit Stolpersteinen für einen Anfänger und ich möchte 
behaupten, Assembler ist da doch eher zu empfehlen. Vermutlich ist der 
Ansatz: "ich kann Delphi, also auch AVRco...."
Delphi ist objektorientiert und ereignisgesteuert... also, da ist ein µC 
doch weit weg von. Na ja, eine Ereignissteuerung mit Timer oder einem 
anderen Interrupt, das geht bestenfalls noch. Aber auch hier ist die 
Regel, der Interrupt erledigt nur das Notwendigste. Nimm dir deine 
Timer- ISR mal vor:
1
Interrupt Int0;
2
begin
3
   DisableInts;
4
   PortB := 00000001;
5
   mDelay (150);
6
   PortB := 00000000; //HIER "HÄNGT" ER SICH AUF
7
   EnableInts;
8
end;
Das mit Dis- und Enable ist bereits besprochen. Delais sind auch 
ausreichend behandelt, was bleibt ist die Portzuweisung.. reicht da 
nicht einfach nur das setzen eines Bits, welches von der Hauptschleife 
abgefragt wird.. sozusagen ein Flag.
1
Interrupt Int0;
2
begin
3
  Flag:=true;
4
end;
Nun kann in der Programmschleife dieses Flag behandelt werden:
1
   ...
2
   If Flag then 
3
   Begin
4
     If LED true then LED :=false else LED:=True;
5
     Flag:=False;
6
   end;
7
   Set_Out(LED);
8
   ...
Wobei hier eine weitere Procedure hinzugekommen ist, die die Ausgabe 
erledigt. Den Timer würde ich so initialisieren, das er im mSek.-Takt 
aufgerufen wird. Dann kannst du Bspw. durch Zählen der mSek. die 
gewünschte Zeit einstellen....
etwa so:
1
Interrupt Int0;
2
begin
3
  Inc(mSek)
4
  if mSek=1000 then 
5
  begin
6
    mSek:=0;
7
    Flag:=true;   { hier fügst du nun das gewünschte Zeitereignis ein}
8
    Inc(Sek);
9
    If Sek=60 then
10
    begin
11
      Inc(Min);
12
      .....
13
    end;
14
  end;
15
end;
Wie du siehst, ist es nun einfach, eine LED blinken zu lassen oder 
andere Zeitabhängige Programmaufgaben aufzurufen. Zur Not mußt du halt 
die Zähler etwas feiner einstellen.
Gruß oldmax

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.