Forum: Mikrocontroller und Digitale Elektronik Programm bleibt in ISR hängen


von Daniel (Gast)


Lesenswert?

Hallo,

ich habe nun schon sämtliche Beiträge zu diesem Thema durchforstet, bin 
aber leider noch nicht fündig geworden. Ich bin noch ein Neuling im 
Programmieren mit C.

Anbei seht Ihr mein Programm. Was es tun soll: Eine PWM wird durch 
Timer1 erstellt und die ISR mit der Frequenz der PWM synchronisiert. In 
der ISR wird der Wert eines Potis ausgelesen, mit dem die PWM gesteuert 
werden kann. Dieser Teil funktioniert auch einwandfrei (getestet an 
einer LED). Darüberhinaus soll der eingelesene Sollwert in der ISR über 
ein Display ausgegeben werden.

Im Main-Programm seht ihr Timer_Konfigurierung(); . In diesem Programm 
wird die ISR aktiviert mit sei(); . Das Programm lcd_init(); ist in 
einem anderen Quellcode hinterlegt, welches das LCD-Display 
initialisiert. Diese Programm habe ich mit #include "LCD.h" 
eingebunden.´

Was ist mein Problem: Ich vermute, dass das Programm in der ISR hängen 
bleibt, da die LCD-Initialisierung nicht ausgeführt wird. Kommentiere 
ich Timer_Konfigurierung(); oder lediglich das sei(); aus, funktioniert 
die LCD-Initialisierung. In der while-Schleife wird sollwert_anzeige 
umgewandelt und auf dem Display positioniert und angezeigt. Dieser Teil 
funktioniert auch tadellos, sobald ich das Programm Sollwert_einlesen(); 
in der while-Schleife aufrufe. Doch ich möchte eben den Sollwert in der 
ISR einlesen und nicht in der while-Schleife.

Könnt Ihr mir weiterhelfen? Vielen Dank schonmal!

Gruß,
Daniel

PS: Der Part Istwert_einlesen(); spielt hier noch keine Rolle; deswegen 
auskommentiert.



//////////////////////////////////////////////////////////////////////// 
//////////////////////
//Sollwertanzeige über Display
//////////////////////////////////////////////////////////////////////// 
//////////////////////

/////////////define////////////

#define F_CPU 3686400
#define Sollwert_Wandler 0

////////////include///////////
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdio.h>
#include <stdlib.h>
#include "LCD.h"

////////////uint_Global////////////
uint16_t sollwert_adc = 0;
uint8_t sollwert_anzeige = 0;
uint8_t istwert_adc = 0;
uint8_t istwert_anzeige = 0;

//////////Displayausgabe//////////
char SW_char [1];
char IW_char [1];

void Sollwert_einlesen(void);


//////////ADC konfigurieren/////////////
void ADC_Konfigurierung(void)

{
  ADMUX = 0x40;
  //Bit7=0 (Refs1), Bit6=1 (Refs0) -> Referenz ist Vcc-Board
  //Bit5=0 (ADLAR) -> right adjust 10bit
  //Bit4=0 reserviert
  //Bit3=0 (Mux3), Bit2=0 (Mux2), Bit1=0 (Mux1), Bit0=0 (Mux0) -> PortC0 
als ADC Eingang

  ADCSRA = 0xC9;
  //Bit7=1 (ADEN) -> Freigabe ADC
  //Bit6=1 (ADSC) -> Starten einer Einzelwandlung
  //Bit5=0 (ADFR) -> Dauerwandlung; 0 weil zeitdiskret (PWM)
  //Bit4=0 (ADIF) -> wird automatisch nach der Wandlung auf 1 gesetzt
  //Bit3=1 (ADIE) -> Interrupt wird nach Beendigung ausgeloest
  //Bit2=0 (ADPS2), Bit1=0 (ADPS1), Bit0=1 (ADPS0) -> Prescaler 2
}



/////Interrupt Service Routine, PWM////////////
ISR(TIMER1_OVF_vect)

{
  Sollwert_einlesen();
  //  Istwert_einlesen();
  OCR1A = sollwert_adc;
}




///////////Timer/Counter 1 konfigurieren////////////
void Timer_Konfigurierung (void)

{

  OCR1A = 0x00;

  TCCR1A = 0xF1;
  //Bit7=1 (COM1A1), Bit6=1 (COM1A0), Bit5=1 (COM1B1), Bit4=1 (COM1B0) 
-> Set OC1A/OC1B on compare match
  //Bit3=0 (FOC1A), Bit2=0 (FOC1B) -> no force output compare
  //Bit1=0, Bit0=1 -> Fast PWM 8 bit

  TCCR1B = 0x09;
  //Bit7=0 (ICNC1), Bit6=0 (ICES1) -> noise canceller inactive
  //Bit5=0 (-) reserviert
  //Bit4=0 (WGM13), Bit3=1 (WGM12) -> Fast PWM 8 bit
  //Bit2=0 (CS12), Bit1=0 (CS11), Bit0=1 (CS10) -> Prescaler 1

  TIMSK |= 0x04;
  //Bit0=1 (TOIE0), Bit1=0,
  //Bit2=1 (TOIE1) Interrupt wird aktiviert
  //Bit3=0 (OCIE1B), Bit4=0 (OCIE1A), Bit5=0 (TICIE1), Bit6=0 (TOIE2), 
Bit7=0 (OCIE2)

  sei();  //Freigabe aller Interrupts

}


////////////Sollwert einlesen/////////////
void Sollwert_einlesen (void)

{
  sollwert_adc = 0;

  ADMUX = 0x40;
  DDRB = 0x02;    //Port B als Ausgaenge -> B1 abgreifen (OC1A)
  DDRC = 0x00;    //Port C wird komplett als Eingang deklariert 
Poti-Signal (Port C-0)
  _delay_ms(50);
  ADCSRA |= 0x40;    //Wandlung starten
  while (ADCSRA & 0x40);
  sollwert_adc= ADC/4;
  ADMUX =0x41;
  sollwert_anzeige=sollwert_adc*0.35;

}

////////////Istwert einlesen/////////
/*
void Istwert_einlesen (void)

{
  ADMUX = 0x41;
  DDRB = 0xFF;                /////Dient der Kontrolle nicht 
löschen!//Port B als Ausgaenge -> B1 abgreifen (OC1A)            (nicht 
notwendig)
  DDRC = 0x00;                /////Dient der Kontrolle nicht 
löschen!//Port C wird komplett als Eingang deklariert Poti-Signal (Port 
C-0) (nicht notwenidg)

  _delay_ms(50);                //erforderliches Delay

  ADCSRA |= 0x40;                //Wandlung starten
  while (ADCSRA & 0x40);            //Schleife, bis Wandlung 
abgeschlossen ist

  istwert_adc= ADC;

  ADMUX= 0x40;                // Register zurücksetzen
  //ADMUX &= ~ 0x41;  (eventuelle besser, da bit 0x40 komplett 
ausgeschaltet wird.)

  istwert_anzeige=sollwert_adc*90/256;    // Wert umrechnen für das 
Anzeigemodul
}
*/


///////////Hauptprogramm//////////////////////

int main (void)
{

  DDRB = 0xFF;    //Port B als Ausgaenge -> B1 abgreifen (OC1A)
  DDRC = 0x00;    //Port C wird komplett als Eingang deklariert 
Poti-Signal (Port C-0)

  _delay_ms(100);

  ADC_Konfigurierung();

  Timer_Konfigurierung();

  lcd_init();



  while(1)
  {
    char2ascii(sollwert_anzeige,SW_char);
    set_cursor(14,1);
    lcd_text(SW_char);
  }
}

von Frank (Gast)


Lesenswert?

Du rufst in der ISR die Sollwert-Einlesen auf und in der wartest du 50ms 
in einem delay?
Böse!

von OLED (Gast)


Lesenswert?

> uint16_t sollwert_adc = 0;
> uint8_t sollwert_anzeige = 0;

>   sollwert_anzeige=sollwert_adc*0.35;


sieht auch seltsam aus ... (ohne tiefer zu analysieren, was da gemacht 
wird)

von Daniel (Gast)


Lesenswert?

> uint16_t sollwert_adc = 0;
> uint8_t sollwert_anzeige = 0;

Hier definiere ich Variablen für den ADC.



>   sollwert_anzeige=sollwert_adc*0.35;

Hier wird der Wert sollwert_anzeige normiert auf den Bereich 0 ... 89. 
Das wird genau so benötigt, da der Sollwert zur Ansteuerung einer 
Drosselklappe dient. Deren Winkel reicht von 0 ... 89 °.

von Frank (Gast)


Lesenswert?

Wenn die Timerinterrupts während der init des LCD schon kommen, könnte 
es da evtl auch zu Problemen führen.
Da die aber nicht angehängt ist, kann man das so genau nicht sagen.

von Daniel (Gast)


Lesenswert?

Hier mal der Quellcode für das LCD-Display:

/////////////////////////////////////////////////////////////
//Konfiguration des LCD-Displays
/////////////////////////////////////////////////////////////

#define  F_CPU  3686400
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "LCD.h"








  /////////////////////////////////////////////////////////////
  //Programm: Initialisierung
  /////////////////////////////////////////////////////////////

  void lcd_init(void)
  {

  char deg = 0xDF;  //°-Zeichen aus Tabelle aus Datenblatt
  DDRD = 0xFF;    //Port D als Ausgang
  PORTD = 0x00;    //Port D 0 setzen
  _delay_ms(30);    //wait more than 15 ms after 'Power On'


  //dreimaliger Reset
  PORTD = 0x30;    //Reset: DB4=1, DB5=1, RS=0, E=0
  PORTD = 0x38;    //Reset: DB4=1, DB5=1, RS=0, E=1
  PORTD = 0x30;    //Reset: DB4=1, DB5=1, RS=0, E=0
  _delay_ms(10);    //wait more than 4.1 ms

  PORTD = 0x38;    //Reset: DB4=1, DB5=1, RS=0, E=1
  PORTD = 0x30;    //Reset: DB4=1, DB5=1, RS=0, E=0
  _delay_ms(1);    //wait more than 100 ns

  PORTD = 0x38;    //Reset: DB4=1, DB5=1, RS=0, E=1
  PORTD = 0x30;    //Reset: DB4=1, DB5=1, RS=0, E=0
  _delay_ms(10);    //zusätzliches Puffer


  //4bit Modus aktivieren
  PORTD = 0x28;    //4bit Modus, 2 line display, 5x7 dots
  PORTD = 0x20;    //Enable
  _delay_ms(5);


  //LCD 2x16 konfig
  lcd_cmd(0x28);    //4bit Modus, 2 line display, 5x7 dots
  lcd_cmd(0x08);    //Display off
  lcd_cmd(0x01);    //Display clear


  //set Entry Mode
  lcd_cmd(0x06);    //Cursor Decrease
  lcd_cmd(0x0C);    //Display on, Cursor off, Blinking off


  //Cursor to first digit (Cursor Home)
  cursor_home(0x02);



  //LCD-Anzeige
  lcd_text("Initialisieren");
  set_cursor(1,2);
  lcd_text(".");
  _delay_ms(500);
  set_cursor(2,2);
  lcd_text(".");
  _delay_ms(500);
  set_cursor(3,2);
  lcd_text(".");
  _delay_ms(500);
  set_cursor(4,2);
  lcd_text(".");
  _delay_ms(500);
  set_cursor(5,2);
  lcd_text(".");
  _delay_ms(500);
  set_cursor(6,2);
  lcd_text(".");
  _delay_ms(500);
  lcd_cmd(0x01);            //Display clear
  _delay_ms(5);
  lcd_text("Sollwert:");        //Anzeigen: 'Sollwert:'
  set_cursor(16,1);
  lcd_text(&deg);
  set_cursor(1,2);
  lcd_text("Istwert:");        //Anzeigen: 'Istwert:'
  set_cursor(16,2);
  lcd_text(&deg);

  //Ende Initialisierung
}









/////////////////////////////////////////////////////////////
//Programm: Text senden
/////////////////////////////////////////////////////////////

void lcd_text(char* pText)
{
  char j = 0;            // Zählvariable für Zeichen

  while(pText[j]!=0)        // Ausgabe bis 0-Zeichen als Ende-Kennung
  {
    unsigned char send = pText[j];  //Sendevariable definieren
    send &= 0xF0;          //Bit4 bis 7 maskieren
    send |= 0x04;          //RS=1 (senden), E=0
    PORTD = send;          //Signalausgabe
    send |= 0x08;          //RS=1, E=1
    PORTD = send;          //Signalausgabe
    send &= 0xF7;          //Bit4 bis 7 maskieren und E=0
    PORTD = send;          //Signalausgabe

    send = pText[j]<<4;        //Bit0 bis 3 nach links schieben
    send |= 0x04;          //RS=1, E=0
    PORTD = send;          //Signalausgabe
    send |= 0x08;          //RS=1, E=1
    PORTD = send;          //Signalausgabe
    send &= 0xF7;          //Bit4 bis 7 maskieren und E=0
    PORTD = send;          //Signalausgabe

    _delay_ms(5);          //Puffer
    j++;
  }
}





/////////////////////////////////////////////////////////////
//Programm: Kommando ans Display
/////////////////////////////////////////////////////////////

void lcd_cmd(unsigned char cmd)
{
  unsigned char send = cmd;  //Anlegen der Sendevariable zum 
Zwischenspeicher

  PORTD = 0x00;      //RS=0, E=0
  PORTD = 0x08;      //RS=0, E=1
  send = cmd & 0xF0;    //Bit 4 bis 7 extrahieren
  send |= 0x08;      //in Sendevariable E=1
  PORTD = send;      //Signalausgabe
  PORTD &= 0xF7;      //RS=1, E=0

  PORTD = 0x08;      //RS=0, E=1
  send = cmd<<4;      //Bit 0 bis 3 nach links schieben
  send |= 0x08;      //in Sendevariable E=1
  PORTD = send;      //Signalausgabe
  PORTD &= 0xF7;      //RS=1, E=0

  _delay_ms(5);      //zusätzliches Puffer
}




/////////////////////////////////////////////////////////////
//Programm: Umrechnung einer Zahl in eine Zeichenkette
/////////////////////////////////////////////////////////////

void char2ascii(unsigned char zahl,char* text)
{
  char ziffer = 0;    //Variable für Ziffer

  //Zehnerstelle ermitteln
  while(zahl>=10)
  {
    ziffer++;      //Zehnerstelle inkrementieren
    zahl -=10;      //Zahl um 10 erniedrigen
  }

  text[0] = ziffer + 0x30;  //Umrechnung Ziffer in ASCII-Zeichen
  text[1] = zahl + 0x30;    //Umrechnung Zahl in ASCII-Zeichen
  text[2] = 0;        //Ende der Zeichenkette
}



/////////////////////////////////////////////////////////////
//Programm: Cursor Home
/////////////////////////////////////////////////////////////
void cursor_home(unsigned char c_home)
{
  lcd_cmd(c_home);  //Cursor move to first digit
  _delay_ms(5);    //Puffer
}



/////////////////////////////////////////////////////////////
//Programm: Cursor positionieren
/////////////////////////////////////////////////////////////
void set_cursor(int Spalte, int Zeile)
{
  if((Spalte <= 16) & (Zeile <= 2))
  {
    if(Zeile <= 1)
    Zeile = 0x00;          //DD RAM -> erste Zeile
    else
    Zeile = 0x40;          //DD RAM -> zweite Zeile
    char send = (Spalte-1) | Zeile;
    send |= 0x80;          //Write to DD RAM -> Cursor setzen
    lcd_cmd(send);
  }
}

von N. G. (newgeneration) Benutzerseite


Lesenswert?

dein problem wird sein, dasss du in jeder ISR 50ms wartest. Gaaaanz 
schlecht. Nicht nur vom Stil her, sondern es wird auch dein Problem 
verursachen.
Wie immer die Faustregel: nur sehr kurze Dinge in der ISR tun, alles 
andere im normalen Programm. Extrem gesagt: setzte ein Flag und werte es 
in der Hauptschleife aus.

von Max H. (hartl192)


Lesenswert?

Daniel schrieb:
> //////////Displayausgabe//////////
> char SW_char [1];
> char IW_char [1];
> ...
>     char2ascii(sollwert_anzeige,SW_char);

Daniel schrieb:
> void char2ascii(unsigned char zahl,char* text)
> {
>   char ziffer = 0;    //Variable für Ziffer
>
>   //Zehnerstelle ermitteln
>   while(zahl>=10)
>   {
>     ziffer++;      //Zehnerstelle inkrementieren
>     zahl -=10;      //Zahl um 10 erniedrigen
>   }
>
>   text[0] = ziffer + 0x30;  //Umrechnung Ziffer in ASCII-Zeichen
>   text[1] = zahl + 0x30;    //Umrechnung Zahl in ASCII-Zeichen
>   text[2] = 0;        //Ende der Zeichenkette
> }
Das schreibst du über die Array Grenzen hinaus

: Bearbeitet durch User
von Wolfgang (Gast)


Lesenswert?

Daniel schrieb:
> sei();  //Freigabe aller Interrupts

Warum tust du das, bevor du mit den Initialisierungen, insbesondere vom 
LCD fertig bist. Damit zerschießt du dir dort jegliches Timing.

von Daniel (Gast)


Lesenswert?

> dein problem wird sein, dasss du in jeder ISR 50ms wartest.


Verstehe ich! Aber wäre das der Fehler, würde das mit dem Dimmen der LED 
auch nicht funktionieren, oder?


Danke Max H. Ist mir gar nicht aufgefallen, hab ich angepasst! Ändert 
aber leider nichts am eigentlichen Problem.

von Daniel (Gast)


Lesenswert?

Wolfgang schrieb:
> Warum tust du das, bevor du mit den Initialisierungen, insbesondere vom
>LCD fertig bist. Damit zerschießt du dir dort jegliches Timing.


Danke für den Tip! Habe es gerade mal ausprobiert und den Befehl sei(); 
in das Main-Programm hinter lcd_init(); gesetzt. Nun wird die 
Initialisierung durchgeführt aber kein Wert auf dem Display angezeigt. 
Das Dimmen der LED funktioniert immer noch. Ich denke, dass das Programm 
hier wieder in der ISR festhängt, oder?

von Frank (Gast)


Lesenswert?

Wie oft kommt dein Timer Interrupt?
Wenn er schneller als 50ms ist, hängst du zu 100% im Interrupt!

von Frank (Gast)


Lesenswert?

Sprich, nimm das delay aus der ISR!
Die PWM funktioniert, da du sie in der ISR setzt und der Rest die 
Hardware über nimmt.

Dein Main kommt aber nie dran.

von Daniel (Gast)


Lesenswert?

Klasse! Danke Frank!

Das Problem war folgendes: PWM-Frequenz: 14kHz -> T_p = 0,07 ms. Da die 
ISR mit der PWM synchronisiert ist, wurde die ISR alle 0,07 ms 
durchlaufen, konnte aber ihr beinhaltetes Programm, welches ein delay 
von 50ms beinhaltete, nicht schnell genug durchlaufen. Habe das delay 
auskommentiert. Jetzt funktioniert es!

Allerdings 'hinkt' die Anzeige der Potenziometerstellung hinterher. 
Sprich drehe ich am Poti, dauert es ca 4 sek bis der richtige Wert am 
Display angezeigt wird. Woran könnte das nun liegen?

von Stefan E. (sternst)


Lesenswert?

Daniel schrieb:
> Woran könnte das nun liegen?

Daran, dass du immer noch das gleiche Problem hast.
Quizfrage: wie lange dauert eine ADC-Wandlung?

von Frank (Gast)


Lesenswert?

Ich würde es so machen:
Du musst versuchen das warten auf das ADC Ergebnis in der ISR zu 
vermeiden.
Sprich: Timerinterrupt startet nur die ADC Messung, mehr nicht. Im ADC 
Interrupt setzt Du nun nur ein Flag, mehr nicht.

In der Main pollst du dieses Flag und setzt Deinen neuen PWM Wert und 
zählst einen Zähler hoch. Sobald der Zähler 750 anzeigt aktualisierst Du 
einmal Dein Display. Denn eine Updaterate mit mehr als 20Hz bringt dir 
eh nichts!

von Frank (Gast)


Lesenswert?

Frank schrieb:
> Im ADC Interrupt setzt Du nun nur ein Flag, mehr nicht.

Den Wert evtl auch noch sichern.

von Daniel (Gast)


Lesenswert?

Ich verstehe das leider noch nicht ganz. Wann wird nun der Sollwert 
eingelesen? Und wahrscheinlich verdonnern mich die ein oder anderen für 
folgende Frage; was ist ein Flag?

von Uwe (de0508)


Lesenswert?

Hallo Daniel,

das was die anderen Programmierer beschreiben, nennt sich 
Event-Orientierte Programmierung.

Ein Flag ist eine Event-Flag, der nur zwei Zustände kennt.
So etwas kann mit mit einer Variable vom Datentype boolean lösen.
Ist die Event Variable =false ist das Event /Ereignis noch nicht 
eingetroffen, im anderen Fall schon.

von Georg G. (df2au)


Lesenswert?

Daniel schrieb:
> sollwert_anzeige=sollwert_adc*0.35;

Diese Zeile ist auch etwas unglücklich. Versuch es mit einem 
Koeffizienten, der ohne Fließkomma auskommt.

Btw: Dein Compiler gibt dir keine Warnung bei der Zeile aus?

von Rolf M. (rmagnus)


Lesenswert?

N. G. schrieb:
> dein problem wird sein, dasss du in jeder ISR 50ms wartest.

Und danach stößt er auch noch eine ADC-Wandlung an und wartet auf das 
Ergebnis.

von Frank (Gast)


Lesenswert?

Deine Timer ISR  triggert momentan schon deinen ADC.
Leider wartet sie dann in der Sollwert-einlesen() auf das Ergebnis. Das 
ist schlecht und führt im Prinzip zu einem ähnlichem Problem wie vorher 
das wait!
Die Interrupts überholen sich.

Deshalb versucht man die schnellen Dinge(wie die PWM anhand des ADCs zu 
stellen) von den langsamen Dingen (wie die LCD Ausgabe) zu entkoppeln.

Deshalb sollte der Timer die Messung des ADCs triggern. Evtl brauchst du 
da noch nicht mal eine Timer ISR. Schau mal im Datenblatt beim ADC ob 
der Timer den ADC direkt triggern kann. Ich meine das geht. Dadurch 
übernimmt das die Hardware und du sparst dir sogar noch die Zeit für den 
ISR Ein/Aussprung.

Dann brauchst du nur noch in der ADC ISR ein Flag* setzen, evtl das PWM 
Register neu setzen und den Wert des ADCs speichern. In der Main 
überprüfst du dann das Flag* und aktualisierst das LCD. (Oder nur wenn 
sich der Wert geändert hat).

Grüße Frank

*Flag = Bit z.B in einer globalen Variable.

von Daniel (Gast)


Lesenswert?

Georg G. schrieb:
> Btw: Dein Compiler gibt dir keine Warnung bei der Zeile aus?

Nein, werd ich aber im Folgenden berücksichtigen, danke!


Danke Frank für deine Erklärung! Den Sinn dahinter habe ich nun 
verstanden, doch leider fehlt mir die nötige Programmiererfahrung, um 
das eben mal einfach so umzusetzen. Könntest Du mir vielleicht auf die 
Sprünge helfen? Die Timer ISR würde ich gerne so beibehalten (Vorgabe), 
wenn das möglich ist.

von Frank (Gast)


Lesenswert?

Ich habe nur ein Handy zur Hand, also nein :-)
Du musst im wesentlichen folgendes machen:

*In der ADC Config den ISR für den ADC aktivieren (siehe Datenblatt oder 
Forumartikel)
*Die ADC ISR einfügen.
*Alles aus der Timer ISR raus.
*Das starten des ADC aus der Sollwert-Einlesen in die Timer ISR. Die 
While für das warten auf das Ende der Messung kannst du löschen.
*In der ADC ISR den Wert Formatieren,speichern und auf das PWM Register 
speichern. Zusätzlich ein flag setzen.

*In der Main überprüfen ob das flag gesetzt ist. Wenn ja löschen und 
einen Zähler hochzählen.
*Wenn der Zähler größer 700 ist und ein anderer Wert im ADC Wert steht 
als auf dem Display, dann die Anzeige aktualisieren.

von Georg G. (df2au)


Lesenswert?

Frank schrieb:
> In der ADC ISR den Wert Formatieren,speichern und auf das PWM Register
> speichern.

Das würde ich nicht machen. Er arbeitet mit Float. Das kostet viel 
Zeit.
Warum nicht nur den ADC auslesen und den Wert merken (als volatile!). 
Alles andere kann in Ruhe in der Hauptschleife erfolgen.

von Frank (Gast)


Lesenswert?

Georg G. schrieb:
> Frank schrieb:
> In der ADC ISR den Wert Formatieren,speichern und auf das PWM Register
> speichern.
>
> Das würde ich nicht machen. Er arbeitet mit Float. Das kostet viel Zeit.
> Warum nicht nur den ADC auslesen und den Wert merken (als volatile!).
> Alles andere kann in Ruhe in der Hauptschleife erfolgen.

Kann er. Ich weiß nur nicht ob er nach jeder ADC Messung das PWM 
Register aktualisieren möchte.
Das würde in der Main evtl nicht gehen.

Außerdem muss er nicht mit float arbeiten!
Ein ADC*7/80 würde auch reichen.

von Frank (Gast)


Lesenswert?

Vorausgesetzt der ADC Wert ist <=13 Bit!

von Daniel (Gast)


Angehängte Dateien:

Lesenswert?

Frank schrieb:
> *In der ADC Config den ISR für den ADC aktivieren (siehe Datenblatt oder
> Forumartikel)

Siehe Bild im Anhang.

Habe nun das ACSR = 0x13 gesetzt. Stimmt das? Wie implementiere ich das 
ADC ISR?

Arbeite nun nicht mehr mit Fließkommazahlen, sondern mit ...*10/28

von Wolfgang (Gast)


Lesenswert?

Frank schrieb:
> *In der ADC ISR den Wert Formatieren,speichern und auf das PWM Register
> speichern. Zusätzlich ein flag setzen.
>
> *In der Main überprüfen ob das flag gesetzt ist. Wenn ja löschen und
> einen Zähler hochzählen.

So wohl eher nicht.
*In der ADC ISR den Wert in einen Zwischenspeicher sichern und eine 
Signalflagge setzen.

*In der Main überprüfen, ob die Signalflagge gesetzt ist, wenn "ja", 
Wert aus dem Zwischenspeicher holen, formatieren, umrechnen und was 
sonst noch damit passieren soll. Signalflagge herunternehmen.

von Daniel (Gast)


Lesenswert?

Ich korrigiere mich; das sollte heißen: ACSR = 0x0B

von Frank (Gast)


Lesenswert?

Wolfgang schrieb:
> So wohl eher nicht.
> *In der ADC ISR den Wert in einen Zwischenspeicher sichern und eine
> Signalflagge setzen.
>
> *In der Main überprüfen, ob die Signalflagge gesetzt ist, wenn "ja",
> Wert aus dem Zwischenspeicher holen, formatieren, umrechnen und was
> sonst noch damit passieren soll. Signalflagge herunternehmen.

Was passiert wenn er in der Main gerade sein Display aktualisiert oder 
noch andere Dinge macht, dann wertet er das Flag evtl zu spät aus und 
verpasst ein paar ADC Werte -->OCR1A verpasst Werte.

Mit Formatierung meinte ich nur das umrechnen durch 7/80. Das kann er 
dann noch in der ISR auf sein OCR1A schreiben -->jeder wert landet auf 
dem PWM Register

von Rolf M. (rmagnus)


Lesenswert?

Daniel schrieb:
> Da die ISR mit der PWM synchronisiert ist, wurde die ISR alle 0,07 ms
> durchlaufen

Allerdings. Die ISR wird alle 256 Taktzyklen aufgerufen. Das ist schon 
sehr schnell. Da darf nichts mehr drin sein, das nicht unbedingt nötig 
ist, und ganz besonders darf da nicht gewartet werden.

von W.S. (Gast)


Lesenswert?

Daniel schrieb:
> Ich verstehe das leider noch nicht ganz. Wann wird nun der Sollwert
> eingelesen? Und wahrscheinlich verdonnern mich die ein oder anderen für
> folgende Frage; was ist ein Flag?

Du hast ne ganze Menge von Dingen noch nicht verstanden. Also: ein 
Interrupt ist ein Signal, was den normalen Ablauf des Programmes 
unterbricht, damit die CPU mal eben was ganz wichtiges tun kann. Aber 
das MUSS !!! so kurz wie möglich passieren und auf gar keinen Fall durch 
irgendwelche langatmigen Dinge wie das Starten des ADC oder gar das 
sture Warten in einer Delay-Routine unnötig verlängert werden, denn es 
gibt zwischenzeitlich noch ne Menge andere Arbeiten zu verrichten.

Und ein Flag ist schlichtweg ein Hinweis. Wie du den machst, ist 
eigentlich egal. Hier mal ein kleines Beispiel:

char iii, ggg;

Interrupt-Routine:
{ tu irgendwas;
  ++iii;
}

main:

 if (iii!=ggg)           // das != ist quasi das Flag
 { reagiere auf dieses Flag
   ggg = iii;
 }
 weiter in main...

Also, egal ob beim Inkrementieren iii überläuft, die Abfrage in main 
findet IMMER heraus, ob die Interrupt-Routine anzeigen will, daß es da 
einen Hinweis von ihr gibt.

W.S.

von Daniel (Gast)


Lesenswert?

Kann mir jmd. vielleicht einen Anstoß mit Hilfe eines Quellcodes geben? 
Wie füge ich die ADC ISR ein? Wie rufe ich das Flag auf, etc.? Sehe ich 
das richtig, dass das Bit4 ACI im ACSR Register das Flag ist, das ich 
überprüfen muss? Fragen über Fragen, aber ich danke Euch für Eure Hilfe!

von Frank (Gast)


Lesenswert?

Sowas in der Art.
Ist nicht getestet und nicht vollständig.
1
#define ADC_FLAG  0x01
2
#define LCD_RATE  800
3
4
volatile uint16_t u16SWAdc = 0;
5
volatile uint16_t u16SWAdcAnz = 0;
6
volatile uint8_t  u8Flags = 0;
7
8
ISR(TIMER1_OVF_vect)
9
{
10
  ADCSRA |= 0x40;                // ADC-Wandlung starten
11
  //...evtl anderer Code
12
}
13
14
ISR(RICHTIGER ADC VEKTOR EINTRAGEN) //<-- Richtigen Vektor eintragen
15
{
16
  u8Flag | = ADC_FLAG;
17
  u16SWAdc = ADC/4;     //Wert fuer PWM berechnen
18
  OCR1A = u16SWAdc;     //& aktualisieren
19
20
  //...evtl anderer Code
21
}
22
23
///////////Hauptprogramm//////////////////////
24
25
int main (void)
26
{
27
  uint16_t counter = 0;
28
  uint16_t u16OldSWAdcAnz = 0; 
29
  //Initialiserung usw...
30
31
  while(1)
32
  {
33
    if(u8Flags & ADC_FLAG)            //ADC meldet Event
34
    {
35
     u8Flags &= ~ADC_FLAG;            //Flag loeschen
36
     counter++;                       //Zaehler fuer LCD erhoehen
37
                                      //Fuer kritische Dinge muesste dies
38
                                      //in der ISR erfolgen, da ansonsten
39
                                      //ein Event verpasst werden koennte
40
    }
41
42
    if(counter > LCD_RATE) //Nur alle LCD_RATE mal das Display aktualisieren
43
    {
44
      counter = 0;
45
46
      if(u16OldSWAdcAnz != u16SWAdcAnz)  //Und nur wenn sich was geaendert hat
47
      {
48
        u16OldSWAdcAnz = u16SWAdcAnz;  //Aktuellen Wert als alter Wert speichern fuer naechstes Mal
49
        char2ascii((u16SWAdc*7)/20,SW_char); //Berechnung nur durchfuehren
50
                                             //Wenn sie benoetigt wird
51
        set_cursor(14,1);
52
        lcd_text(SW_char);
53
      }
54
    }
55
  }
56
}

Grüße
Frank

von Daniel (Gast)


Lesenswert?

Vielen Dank Frank für deine Mühe!

Habe deinen Teil nun bei mir implementiert. Die Ansteuerung der PWM 
funktioniert (Kontrolle: Dimmen der LED). Die Display-Initialisierung 
funktioniert auch, doch leider wird kein Sollwert angezeigt.

von Frank (Gast)


Lesenswert?

Wie sieht jetzt der code aus?
Am besten so posten
1
[c]
2
//hier code
3
[/c]

Debugger vorhanden? Dann setzte mal ein paar Breakpoints an markanten 
Stellen!

Ansonsten mach mal in den verschiedenen If's eine Ausgabe auf das 
Display oder setze ein Portpin und lass für jedes if ne andere LED 
leuchten um zu sehen ob er überall rein geht.

von Frank (Gast)


Lesenswert?

Hab vergessen u16SWAdcAnz zu setzen!

Mach mal:
1
if(u16OldSWAdcAnz != u16SWAdc)  //Und nur wenn sich was geaendert hat
2
      {
3
        u16OldSWAdcAnz = u16SWAdc;  //Aktuellen Wert als alter Wert speichern fuer naechstes Mal
4
        char2ascii((u16SWAdc*7)/20,SW_char); //Berechnung nur durchfuehren
5
                                             //Wenn sie benoetigt wird
6
        set_cursor(14,1);
7
        lcd_text(SW_char);
8
      }

Dann braucht man u16SWAdcAnz garnicht mehr.

von Daniel (Gast)


Lesenswert?

Funktioniert nun alles einwandfrei! Perfekt, vielen Dank! =)

von Frank (Gast)


Lesenswert?

Na dann voll viel Erfolg mit den weiteren Schritten.

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.