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);
}
}
> 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)
> 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 °.
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.
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.
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.
> 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.
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?
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.
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?
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!
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?
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.
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?
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.
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.
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.
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.
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.
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.
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
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.
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
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.
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.
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!
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.
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.