Forum: Mikrocontroller und Digitale Elektronik Microcontroller ATtiny44A ohne delay keine Ausführung


von Nico S. (embedded_engineering)


Lesenswert?

Hey Leute,
ich bin momentan am Programmieren mit der Atmel Studio IDE und bin auf 
ein unerklärliches Problem gestoßen.

Mein Programm verfügt über eine Interrupt Funktion, welche soweit super 
funktioniert. In der ISR werden zwei LEDs gemultiplext und je nachdem 
welche an ist, wird die Farbe (sind RGB LEDs) entsprechend ausgegeben.

In der Endlosschleife habe ich dann die Farben entsprechend definiert:

right_color = 1;
left_color = 2;

Mache ich das so, passiert NICHTS. Die LEDs leuchten nicht.
Füge ich aber ein _delay_us(1) hinzu (egal welcher delay-Wert), dann 
funktioniert es einwandfrei.

Die Frage ist jetzt, warum es ohne delay nicht funktioniert?

Ist doch etwas blöd wenn ich einen Systemtakt von 8 MHz habe, aber durch 
die 1µS dann nur noch theoretisch 1 MHz habe.

Aktuell fällt das nicht so ins Gewicht mit der Geschwindigkeit, aber 
später habe ich Projekte geplant bei der ich Daten im ns Bereich 
übertrage. Also direkt mit dem Systemtakt spiele. Da wäre also ein delay 
unerwünscht.



Sollte etwas unklar sein, würde ich mich über Fragen freuen ;).



HIER DER CODE:




#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>



uint8_t multiplex[2];
uint8_t color[4];

uint8_t multiplex_value = 0;
uint8_t color_value = 0;

uint8_t right_color = 0;
uint8_t left_color = 0;

uint8_t wert = 1;



void init_timer_0 (void)
{
  TCCR0A = 0x00;
  TCCR0B = (0<<CS00) + (1<<CS01) + (0<<CS02);
  TIMSK0 = (1<<TOIE0);
  TCNT0 = 0;
}



int main(void)            //void setup
{
  DDRA = 0xFF;

  multiplex[0] = 0b00010000;
  multiplex[1] = 0b00000000;

  color[0] = 0b00000000;
  color[1] = 0b00100000;
  color[2] = 0b01000000;
  color[3] = 0b10000000;

  init_timer_0();
  sei();


    while (1)            // void Loop
    {
    if (wert == 1)
    {
      left_color = 1;
      right_color = 2;
      wert = 0;
      _delay_us(1);
    }
    }
}


ISR(TIM0_OVF_vect)
{
  // TCNT0 = 0;

  if (multiplex_value == 0)
  {
    PORTA = (color[left_color]) | (multiplex[0]);
  }
  else
  {
    PORTA = (color[right_color]) | (multiplex[1]);
  }


  multiplex_value ++;
  if (multiplex_value == 2)
  {
    multiplex_value = 0;
  }

}

von Georg (Gast)


Lesenswert?

Nico S. schrieb:
> Sollte etwas unklar sein

Wann wird wert wieder 1?

Wann bekommt der Timer seinen Startwert?

Georg

von turgut (Gast)


Lesenswert?

https://www.mikrocontroller.net/articles/Interrupt#Volatile_Variablen

Warum es dann mit delay geht? Weis auch nicht.

von S. Landolt (Gast)


Lesenswert?

Mal mit -O0, also ohne Optimierung, übersetzen lassen.

von Stefan F. (Gast)


Lesenswert?

Merke: Variablen, die innerhalb einer ISR gelesen und außerhalb 
beschrieben werden (oder umgekehrt) muss man als "volatile" deklarieren.

Der Compiler bemüht sich darum, die Zugriffe auf das RAM zu reduzieren 
und stattdessen CPU Register zu benutzen auf die er schneller zugreifen 
kann. Das Register wird erst ins RAM geschrieben, wenn es unbedingt 
nötig ist. Zum Beispiel wenn nicht genug Register frei sind oder wenn 
eine Funktion aufgerufen wird, die aus dem RAM lesen wird.

Da im gesamten Programm die ISR nirgends aufgerufen wird, hält der 
Compiler es nicht für notwendig, den aktuellen Wert ins RAM zu 
schreiben.

Für dich ist klar, dass die ISR ausgeführt wird, weil du weisst, wie der 
Timer mit Interrupt-Handlern zusammen arbeitet. Der Compiler kennt das 
Konzept "Interrupt-Handler" aber leider nicht. Deswegen musst du da 
manuell nachhelfen.

Lösung:
1
volatile uint8_t right_color = 0;
2
volatile uint8_t left_color = 0;

multiplex_value muss nicht volatile sein, weil diese nur in der ISR 
geänder und gelesen wird.

Georg schrieb:
> Wann wird wert wieder 1?

Gar nicht, spielt auch keine Rolle. Ich denke er hat das Programm hier 
minimiert um das Problem darzustellen. Daher sieht dieser Teil sinnlos 
aus.

Georg schrieb:
> Wann bekommt der Timer seinen Startwert?

Er läuft irgendwann über und ab dann im vorgesehenen Intervall.

turgut schrieb:
> Warum es dann mit delay geht? Weis auch nicht.

Die Delay Funktion löst das Problem zufällig, weil sie CPU Register 
benötigt. Der Compiler muss das Register das die Variable 
zwischengespeichert hat, ins RAM schreiben, um das Register für die 
Delay Funktion frei zu bekommen.

S. Landolt schrieb:
> Mal mit -O0, also ohne Optimierung, übersetzen lassen.

Dadurch wird die Code-Optimierung deaktiviert. Variablen werden dann bei 
jedem Zugriff wirklich aus dem RAM gelesen und ins RAM geschrieben. Das 
Programm wird dadurch etwas größer und langsamer.

Bitte beachten, dass die delay Funktionen nicht mehr richtig 
funktionieren, wenn man die Optimierung deaktiviert. Siehe 
https://www.nongnu.org/avr-libc/user-manual/group__util__delay.html

Bei AVR liegen die 32 Register tatsächlich auch im RAM. Auf diese kann 
er aber schneller zugreifen, als auf alle anderen RAM-Zellen.

von Ralph S. (jjflash)


Lesenswert?

Hm, ich habe mir das jetzt mal knapp durchgesehen ohne alles 
nachvollziehen zu können, weil ich gerade keine Controllerhardware zur 
Verfügung habe, aber:

Zum einen solltest du deinen Code besser kommentieren denn, hier muß man 
nachforschen was du eingestellt hast:

  TCCR0B = (0<<CS00) + (1<<CS01) + (0<<CS02);

Im Code hätte dann stehen können:

  TCCR0B |= (1<<CS01);            // CS02= 0, CS01= 1, CS00= 0 ==> 
Timerclock = F_CPU / 8

Dein Systemtakt ist also 1 MHz (wenn, wie du sagst, dein Controller mit 
8MHz läuft)!! (Wäre auch nicht schlecht, das im Kommentar zu 
hinterlegen).


  TIMSK0 = (1<<TOIE0);

Hätte einen Kommentar vertragen, dass du den Timer als Overflow 
verwendest.

Weil du keine Wertezuweisungen machst und Timer0 ein 8-Bit Timer ist, 
wird der Timer alle 256 Takte überlaufen, also wird deine ISR alle 256 
µs aufgerufen.

Wenn du also in deiner ISR einen Wechsel auf Leuchtdioden anzeigst, die 
alle 256 µs ändert, wirst du davon nichts sehen können.

Zudem:

    if (wert == 1)
    {
      left_color = 1;
      right_color = 2;
      wert = 0;
      _delay_us(1);
    }

Wird in deiner Schleife genau ein einziges mal (beim ersten mal) 
ausgeführt werden, weil du in der globalen Variable wert hier den 
Zahlenwert 1 zuweist, danach wird der Inhalt zu 0 und nirgendwo wieder 
zu 1.

Also dürfte auch dein _delay_us(1) keine Auswirkungen haben.

Deine Zuweisung an die LED ist hierbei schon extrem gewöhnungsbedürftig 
und grundsätzlich bin ich der Meinung dürfte dein Teil eigentlich gar 
nichts machen, ich kann mir jetzt nur noch vorstellen, dass du irgendwo 
in der Hardware noch etwas hast, das einen Reset oder ähnliches 
ausführt.

Warum gehst du nicht her (wie alle anderen auch) und machst eine 
"saubere" Zuweisung, an welchen Port deine RGB-LED angeschlossen ist. 
Ich habe mir deinen Code jetzt ein paar mal angesehen und für mich sieht 
es so aus, als ob die LED an PA7, PA6 und PA5 angeschlossen ist.

Warum benennst du das nicht so?
1
#define LED_PORT  PORTA
2
#define LED_DDR   DDRA
3
#define LED_R     PA7
4
#define LED_G     PA6
5
#define LED_B     PA5
6
7
#define INIT_LED  (LED_DDR |= (1 << LED_R) | (1 << LED_B) | (1 << LED_G))
8
#define LEDSET_R  LED_PORT |= (1 << LED_R)
9
#define LEDCLR_R  LED_PORT &= ~(1 << LED_R)
10
11
// setzen und loeschen fuer gruenen und blauen Anteil im gleichen Stil

Dann kannst du in deinem Programm, egal ob in der main oder in deiner 
ISR deine LED deutlich besser leßbar steuern.

von Stefan F. (Gast)


Lesenswert?

Ralph, lies mal meinen Beitrag.

von Ralph S. (jjflash)


Lesenswert?

Ich habe angefangen zu schreiben, da gabs den Kommentar von Stefan noch 
nicht...

Grundsätzlich sollte man Stefans Aussage noch ergänzen (weil man, ohne 
böse zu nahe zu treten, vllt. annehmen mag, dass der Threadersteller 
über volatile-Variable nicht so wirklich bescheid weiß):

Eines der Gründe warum man Variable volatile macht ist der, dass der 
Compiler, selbst dann wenn er annimmt, dass er eine Variable gar nicht 
benötigt, nicht wegoptimieren darf.

Innerhalb einer ISR (und hier weiß der Compiler nicht, dass diese 
Funktion von Hardware, in deinem Falle von einem Timer, aufgerufen wird 
und deshalb "meint" der Compiler, dass dieser Code nie ausgeführt wird 
und optimiert diese weg).

Volatile verhindert dieses.

von Stefan F. (Gast)


Lesenswert?

Der Compiler optimiert die ISR nicht weg, weil das Makro "ISR" das 
Schlüsselwort "volatile" enthält.

volatile bewirkt zwei Dinge: Der Compiler darf es (Variable, Funktion) 
nicht weg optimieren und er darf die Werte nicht cachen, muss also jeden 
Schreib- und Lese-Zugriff direkt so ausführen, wie es im Quelltext 
steht.

von S. Landolt (Gast)


Lesenswert?

Okay, ich hätte bei meinem Assembler bleiben sollen, schustermässig 
betrachtet. Trotzdem fühle ich mich an Meys Klempner erinnert: Immer, 
wenn er Wasser zapfe, sammle Erdgas sich im Napfe, und klingle zufällig 
das Telephon, ergäbe sich manch heftige Detonation. Der wusste noch 
nicht, dass _delay einiges reparieren kann.

von Ralph S. (jjflash)


Lesenswert?

Stefan F. schrieb:
> volatile bewirkt zwei Dinge: Der Compiler darf es (Variable, Funktion)
> nicht weg optimieren und er darf die Werte nicht cachen, muss also jeden
> Schreib- und Lese-Zugriff direkt so ausführen, wie es im Quelltext
> steht.

... ich habe nichts anderes geschrieben

von Georg G. (df2au)


Lesenswert?

S. Landolt schrieb:
> Der wusste noch
> nicht, dass _delay einiges reparieren kann.

Das hat mit _delay() nichts zu tun. Für den Compiler gilt, dass jede 
Funktion für die eigenen Daten allein verantwortlich ist. Wenn dann ein 
Unterprogramm aufgerufen wird, muss der Compiler davon ausgehen, dass 
alle Register verändert werden und muss daher die noch benötigten Werte 
im RAM ablegen. Und damit sind sie auch für die ISR greifbar und 
aktuell. Statt _delay() kannst du eine beliebige externe Routine nehmen, 
über die zur Compile Zeit nichts bekannt ist.
Der einfachere Weg ist, wie oben geschrieben, das Schlüsselwort 
volatile.

von Wolfgang (Gast)


Lesenswert?

Ralph S. schrieb:
> TCCR0B = (0<<CS00) + (1<<CS01) + (0<<CS02);
>
> Im Code hätte dann stehen können:
>
>   TCCR0B |= (1<<CS01);            // CS02= 0, CS01= 1, CS00= 0 ==>

Das geht i.A. schief, weil nicht sicher gestellt ist, dass TCCR0B 
gelösch ist.

Insbesondere macht diese Formulierung die Sache nicht klarer, weil die 
Anzahl der Schiebebefehle (CS01) vom zu setzenden Wert abhängt und wegen 
der mitzuschleppenden Kommentare fehlerträchtig ist.

Die Table 11-9 im Datenblatt stellet die drei Clock-Select Bits 
gemeinsam dar und so kann man sie dann sinnvollerweise bei der 
Konfiguration auch benutzen, also einfach
1
TCCR0B = (0b010<<CS00)
oder der Klarheit wegen sogar
1
#define CS CS00
2
...
3
TCCR0B = (0b010<<CS)

von Ralph S. (jjflash)


Lesenswert?

Wolfgang schrieb:
> Das geht i.A. schief, weil nicht sicher gestellt ist, dass TCCR0B
> gelösch ist.

Zum einen:

Das geht deshalb NICHT schief, weil im Datenblatt Seite 83 steht:

Initial Value = 0

D.h. mit Einschalten der Betriebsspannung ist das Register TCCR0B 
gelöscht !!

Zum anderen hatte ich noch überlegt gehabt, etwas in dieser Art zu 
schreiben:

TCCR0B &= ~((1<<CS00) | (1<<CS02));

Um die 2 Bits ganz sicher zu löschen. Andererseits gehe ich allerdings 
davon aus, dass man weiß, ob man dieses Register schon einmal während 
seines Programmes angefasst hat oder nicht und zum anderen wird der 
Timer i.a.R. nur einmal eingestellt, beim Initialisieren beim 
Programmstart.

Wolfgang schrieb:
> Die Table 11-9 im Datenblatt stellet die drei Clock-Select Bits
> gemeinsam dar und so kann man sie dann sinnvollerweise bei der
> Konfiguration auch benutzen, also einfachTCCR0B = (0b010<<CS00)oder der
> Klarheit wegen sogar#define CS CS00
> ...
> TCCR0B = (0b010<<CS)

Viele Wege führen nach ROM!

Man kann ja auch Namen vergeben wie: Timer1_Prescaler_Clock_Selectbits 
und dann auch noch Timer1_Prescaler_Clock_Selectbits_Position

Nur, wer will das ausschreiben. Grundsätzlich wichtig ist, dass man das 
kommentiert (egal wo) und vor allen Dingen dann, wenn man den Code 
jemandem zeigt, der darin einen Fehler finden soll.

von Wolfgang (Gast)


Lesenswert?

Ralph S. schrieb:
> Das geht deshalb NICHT schief, weil im Datenblatt Seite 83 steht:
>
> Initial Value = 0

i.A. geht es genau schief, weil der Initial Value nur nach einem Reset 
im Register steht. Genau nur nach einem Reset funktioniert es also.

Ralph S. schrieb:
> Nur, wer will das ausschreiben. Grundsätzlich wichtig ist, dass man das
> kommentiert (egal wo)

Statt Kommentaren ist es immer zuverlässiger, den Code von sich aus so 
zu schreiben, dass man ihn schon möglichst du versteht. Ein "CS02= 0, 
CS01= 1, CS00= 0" als Kommentar hilft da wenig, aussagekräftig und 
weiterführend wäre ein "clkIO / 8".

von Stefan F. (Gast)


Lesenswert?

Wolfgang schrieb:
> Das geht i.A. schief, weil nicht sicher gestellt ist, dass TCCR0B
> gelösch ist.

Der Zustand nach einem Reset ist sicher. Er ist unmissverständlich 
spezifiziert.

Wolfgang schrieb:
> i.A. geht es genau schief, weil der Initial Value nur nach einem Reset
> im Register steht. Genau nur nach einem Reset funktioniert es also.

Die Initialisierung wird ja auch nur genau einmal nach dem Reset 
ausgeführt. Stell Dich nicht so an!

> Ein "CS02= 0, CS01= 1, CS00= 0" als Kommentar hilft da wenig,
> aussagekräftig und weiterführend wäre ein "clkIO / 8".

Das ist auch eher mein Stil, würde ich ebenfalls so weiter empfehlen.

von Thomas E. (thomase)


Lesenswert?

Stefan F. schrieb:
> Der Zustand nach einem Reset **ist** sicher. Er ist unmissverständlich
> spezifiziert.

Ach was. Du bist nur nicht paranoid geung.

von Ralph S. (jjflash)


Lesenswert?

Wolfgang schrieb:
> Ein "CS02= 0,
> CS01= 1, CS00= 0" als Kommentar hilft da wenig, aussagekräftig und
> weiterführend wäre ein "clkIO / 8".

// CS02= 0, CS01= 1, CS00= 0 ==>
Timerclock = F_CPU / 8

Und was steht da? Der Zeilenumbruch im Forum hat das F_CPU / 8 in die 
nächste Zeile verschoben... unglaublich ...

Mich würde eher interessieren, was der TO daraus alles gemacht hat, 
anstelle über Kleinigkeiten zu diskutieren wie etwas gemacht wird und 
wie übersichtlich etwas ist... von Leuten, die wissen wie es geht und 
von denen eben jeder seinen eigenen Stil hat.

Zumindest gehe ich davon aus, dass die, die antworten, wissen wie es 
geht !

von Nico S. (embedded_engineering)


Lesenswert?

Danke für die schnellen Antworten!
Bin neu hier im Forum und in anderen Foren hat es ewig gedauert bis 
immerhin eine Antwort kam.


Stefan, ich habe mir mal deinen Beitrag ausführlich durchgelesen. Da ich 
neu im Atmel Studio Bereich bin kannte ich die genaue Funktion dahinter 
nicht.

Ich werde es gleich mal so ausprobieren wie du gesagt hast.


> Wann wird wert wieder 1?
Wie Stefan sagte, hat das keine Rolle gespielt. Ich habe es vorher ohne 
die if-Bedingung gemacht und dachte mir (klingt paradox), dass wenn ich 
ohne ein delay die ganze Zeit die Variablen "beschreibe", dass der 
microcontroller sich irgendwo in der Art "aufhängt" und deshalb nichts 
an den LEDs passiert.

Deshalb dachte ich mir, ich führe das nur einmal aus, um das "Aufhängen" 
zu verhindern. Aber das hat auch nichts gebracht :D.


Ich werde es in den nächsten Stunden mit volatile austesten und euch 
dann bescheid geben!

Danke nochmals für die Antworten!

von Nico S. (embedded_engineering)


Lesenswert?

Servus Leute,

habe es eben gerade ausprobiert ^^.


Mit der Funktion volatile funktioniert es einwandfrei ^^.

Danke nochmal!



Der Code sieht jetzt wie folgt aus:


#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>



uint8_t multiplex[2];
uint8_t color[4];

uint8_t multiplex_value = 0;
uint8_t color_value = 0;

volatile uint8_t right_color = 0;
volatile uint8_t left_color = 0;

uint8_t wert1 = 1;
uint8_t wert2 = 1;




void init_timer_0 (void)
{
  TCCR0A = 0x00;
  TCCR0B = (0<<CS00) + (1<<CS01) + (0<<CS02);
  TIMSK0 = (1<<TOIE0);
  TCNT0 = 0;
}



int main(void)            //void setup
{
  DDRA = 0xFF;

  multiplex[0] = 0b00010000;
  multiplex[1] = 0b00000000;

  color[0] = 0b00000000;
  color[1] = 0b00100000;
  color[2] = 0b01000000;
  color[3] = 0b10000000;

  init_timer_0();
  sei();



    while (wert1 == wert2)            // void Loop
    {
    left_color = 2;
    right_color = 3;
    }
}


ISR(TIM0_OVF_vect)
{
  if (multiplex_value == 0)
  {
    PORTA = (multiplex[0]) | (color[left_color]);
  }
  else
  {
    PORTA = (multiplex[1]) | (color[right_color]);
  }


  multiplex_value++;
  if (multiplex_value == 2)
  {
    multiplex_value = 0;
  }
}

von Wolfgang (Gast)


Lesenswert?

Ralph S. schrieb:
> Und was steht da? Der Zeilenumbruch im Forum hat das F_CPU / 8 in die
> nächste Zeile verschoben... unglaublich ...

Das Problem sitzt in diesem Fall vor dem Rechner.

Nicht ohne Grund sollte man Code oder fest formatierten Text immer in 
passende Tags einschließen, eben genau um sicher zu stellen, dass er 
nicht durch die automatische Formatierung verändert wird.
Probier's mal mit "C"- bzw. "Pre"-Tags

von Falk B. (falk)


Lesenswert?

Nico S. schrieb:
> Servus Leute,
>
> habe es eben gerade ausprobiert ^^.
>
> Mit der Funktion volatile funktioniert es einwandfrei ^^.

Das ist keine Funktion sondern ein type qualifier.

https://de.wikipedia.org/wiki/Volatile_(Informatik)

https://en.cppreference.com/w/c/language/volatile

von embedded_engineering (Gast)


Lesenswert?

Wolfgang, ich weiß zwar nicht genau was das Problem jetzt sein soll, 
aber meins wäre auf jeden Fall geklärt.

Das einzige Problem war das fehlende "volatile" gewesen. Der Rest des 
Codes funktioniert einwandfrei. Die CPU Frequenz wurde nicht im Code, 
sondern in der Toolchain festgelegt. Das funktioniert auch super.

Kommentare habe ich in dem Code nicht eingefügt, da es nur ein kleines 
Funktions-Code ist, um meine Hardware auf Funktion zu prüfen.

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.