PIC12F683 mit zwei Interrupts in C

Hallo zusammen,

zur Periodendauermessung habe ich beim 12F683 einen Pin für den Interrupt-on-Change (IOC) vorgesehen. Nebenher werden in einem anderen Interrupt Timer2-Überläufe registriert und gezählt. Dies läuft schon recht gut, und die Messung arbeitet. Um die Auflösung noch zu erhöhen, wäre es gut, einfach bei jedem IOC die Zählerstände von Timer2 abzufragen und jeweils vom letzten Wert übertragslos zu subtrahieren. Man erhält dann neben den oben genannten kummulierten Überläufen noch einen Wert, der 256 mal so klein ist. Damit hätte ich die Auflösung (theoretisch) auf 16 Bit gesteigert.

Leider stellt sich heraus, dass jegliche Timer2-Abfrage im INT_EXT mit dem INT_TIMER2 gekoppelt bzw. mit dem Timer2-Overflow synchronisiert ist, denn ich erhalte stets einen konstanten Wert beim Lesen. Beide Interrupts sollten aber völlig asynchron arbeiten können, wenn ich diese Zeitauflösung steigern möchte – und dann wären die Werte – da asynchron gelesen – ständig wechselnd.

Hat jemand eine Ahnung, was ich da falsch mache, oder geht das einfach nicht besser?

Ich habe mal den wichtigsten Code-Ausschnitt angefügt.

Ich weiß, es müsste auch noch eine andere Möglichkeit geben, und zwar Timer1 über den Gate-Pin zu triggern.

Gruß

Dieter

#int\_ext
void ext\_isr() 
 {
 if (input(PIN\_A4) && INT\_FLAG)
 {

 COUNTER\_LO = get\_timer2();
 COUNTER\_HI = 0;
 INT\_FLAG = FALSE;
 }

 if (!input(PIN\_A4) && !INT\_FLAG)
 {
 INT\_FLAG = TRUE;
 }
 }

#int\_timer2
void clock\_isr()
 {
 COUNTER\_HI++;
 }

main()
 {

 //Ur-Initialisierung (nach dem Einschalten)

 setup\_adc\_ports(NO\_ANALOGS);

 setup\_ccp1(CCP\_PWM);
 setup\_timer\_2(T2\_DIV\_BY\_1, PWM\_FACTOR, 1);

 enable\_interrupts(INT\_TIMER2);
 enable\_interrupts(INT\_EXT);
 enable\_interrupts(GLOBAL);

MOD: PRE-Tags eingefügt

Hallo Dieter,

es sollte funktionieren, da Timer2 ja lesbar ist. 3 Anmerkungen:

  1. ist PR2 auf 0xff (255) gesetzt? bzw. wie groß ist der Wert?
  2. Übertraglos solltest Du m.E. nicht subtrahieren. Du solltest m.E. den alten Wert vom neuen Wert abziehen und das auch mit übertrag.
  3. die Routinen get_timer2() ist korrekt impelentiert? und darf in Interrupts aufgerufen werden?

Ansonsten kann es sein, dass z.B. die Interruptbelastung so hoch ist, dass sich daher artefakte ergeben.

Gruß
achim

#int_ext
void ext_isr()
{
if (input(PIN_A4) && INT_FLAG)
{

COUNTER_LO = get_timer2();
COUNTER_HI = 0;
INT_FLAG = FALSE;
}

if (!input(PIN_A4) && !INT_FLAG)
{
INT_FLAG = TRUE;
}
}

#int_timer2
void clock_isr()
{
COUNTER_HI++;
}

main()
{

//Ur-Initialisierung (nach dem Einschalten)

setup_adc_ports(NO_ANALOGS);

setup_ccp1(CCP_PWM);
setup_timer_2(T2_DIV_BY_1, PWM_FACTOR, 1);

enable_interrupts(INT_TIMER2);
enable_interrupts(INT_EXT);
enable_interrupts(GLOBAL);

MOD: PRE-Tags eingefügt

Hallo Achim,

es sollte funktionieren, da Timer2 ja lesbar ist. 3
Anmerkungen:

  1. ist PR2 auf 0xff (255) gesetzt? bzw. wie groß ist der Wert?

Weiß ich nicht, müsste aber nach Startup so sein. Ich werde es aber mal so initialisieren.

  1. Übertraglos solltest Du m.E. nicht subtrahieren. Du
    solltest m.E. den alten Wert vom neuen Wert
    abziehen und das auch mit übertrag.

Stimmt, da hast Du Recht, denn diese „Zipfelchen“ vor dem ersten und nach dem letzten Timer-Overflow können ja auch mal länger sein als ein Intervall.

  1. die Routinen get_timer2() ist korrekt impelentiert? und
    darf in Interrupts aufgerufen werden?

Ja, das darf sein. Ich kann auch im Listfile nachschauen, wie es in Assembler aussieht.

Ansonsten kann es sein, dass z.B. die Interruptbelastung so
hoch ist, dass sich daher artefakte ergeben.

Ich habe noch ein paar Versuche angestellt, und ich bin immer mehr der Überzeugung, dass die Interrupt-on-Change (IOC) Routinen synchron gekoppelt sind mit den Timer2-Overflows. Wenn ich nämlich diesen Timer2-Wert auslese und auf dem Display darstelle, so ist dieser zunächst Null, aber wenn ich eine IOC-Flanke anlege, kommt z. B. immer der Wert 40. Er vergrößert sich, wenn ich vor der get-timer-Anweisung noch weitere Anweisungen einfüge. Das lässt fast nur den Schluss zu, dass ich an dem Wert einfach ein Maß für die Programmzyklenzahl seit dem Aufruf der Int-Routine habe. Und somit, dass der Zeitpunkt des Aufrufs der IOC-Routine zeitlich gekoppelt ist mit Timer2-Überläufen (also dem PWM-Takt).

Übrigens kann ich die reinen Überläufe von Timer2 auch dadurch erhalten, dass ich einfach Timer0 lese. Dann kann ich sogar auf die untere Int-Routine verzichten. Aber auch dann bringt mir das Ablesen von Timer2 nichts an Information. Das hat aber den Nachteil, dass keine Werte über 255 möglich sind, weil ja Timer0 8-Bit hat.

Diese IOCs sind wahrscheinlich doch eher nur etwas für Wakeup-Prozeduren.

Gruß

Dieter

#int_ext
void ext_isr()
{
if (input(PIN_A4) && INT_FLAG)
{

COUNTER_LO = get_timer2();
COUNTER_HI = 0;
INT_FLAG = FALSE;
}

if (!input(PIN_A4) && !INT_FLAG)
{
INT_FLAG = TRUE;
}
}

#int_timer2
void clock_isr()
{
COUNTER_HI++;
}

main()
{

//Ur-Initialisierung (nach dem Einschalten)

setup_adc_ports(NO_ANALOGS);

setup_ccp1(CCP_PWM);
setup_timer_2(T2_DIV_BY_1, PWM_FACTOR, 1);

enable_interrupts(INT_TIMER2);
enable_interrupts(INT_EXT);
enable_interrupts(GLOBAL);

MOD: PRE-Tags eingefügt

sollte eigentlich nicht so sein
hallo Dieter,

Ich habe noch ein paar Versuche angestellt, und ich bin immer
mehr der Überzeugung, dass die Interrupt-on-Change (IOC)
Routinen synchron gekoppelt sind mit den Timer2-Overflows.

Mit sicherheit nicht, es sei denn, dass es in den Errata vermerkt ist.

Wenn ich nämlich diesen Timer2-Wert auslese und auf dem
Display darstelle, so ist dieser zunächst Null, aber wenn ich
eine IOC-Flanke anlege, kommt z. B. immer der Wert 40. Er
vergrößert sich, wenn ich vor der get-timer-Anweisung noch
weitere Anweisungen einfüge. Das lässt fast nur den Schluss

dass mit Deiner Interruptbehandlung etwas nicht stimmt. Bist Du sicher, dass Du beide Interruptflags beider Interrupts korrekt setzt und löschst, oder liefert Dir dein Compiler dazu die Funktionalität, dann bitte dort nachschauen.

Diese IOCs sind wahrscheinlich doch eher nur etwas für
Wakeup-Prozeduren.

Ja, denn wie im Datenblatt zu lesen ist, können bei Aktivitäten auf den Ports auch mal Interrupts verloren gehen. Wenn man das beachtet, sollten sie aber auch für alles andere gut sein.

Gruß
achim

Hallo Achim,

Ich habe noch ein paar Versuche angestellt, und ich bin immer
mehr der Überzeugung, dass die Interrupt-on-Change (IOC)
Routinen synchron gekoppelt sind mit den Timer2-Overflows.

Mit sicherheit nicht, es sei denn, dass es in den Errata
vermerkt ist.

Inzwischen habe ich herausbekommen, dass der IOC auch dann ausgeführt wird, wenn ich an meinem Port G4 keine externen Flanken anlege. Und dass dort dann eine Häufigkeit mit dem als PWM definierten Ausgang stattfindet, konnte ich auch leicht nachweisen. Wohlgemerkt springt er bei jeder PWM-Flanke zwar in die Routine, allerdings wird sie nur bei erfüllter Bedingung auch durchlaufen, und diese setzt eben die Pegelumschaltung voraus. Jetzt ist mir auch klar geworden, warum ich überhaupt diese Klemmung durch das Flag benötigte – ohne dieses geht gar nichts, weil nämlich nicht nur einmal pro Portflanke die Routine abgearbeitet würde, sondern bei jeder PWM-Flanke.
Die Sachlage ist also anders: Der IOC detektiert den PWM-Ausgang. Versuche, diesen durch Beschreiben des IOC-Enable-Registers auszublenden, sind mir nicht gelungen.

Wenn ich nämlich diesen Timer2-Wert auslese und auf dem
Display darstelle, so ist dieser zunächst Null, aber wenn ich
eine IOC-Flanke anlege, kommt z. B. immer der Wert 40. Er
vergrößert sich, wenn ich vor der get-timer-Anweisung noch
weitere Anweisungen einfüge. Das lässt fast nur den Schluss

dass mit Deiner Interruptbehandlung etwas nicht stimmt. Bist
Du sicher, dass Du beide Interruptflags beider Interrupts
korrekt setzt und löschst, oder liefert Dir dein Compiler dazu
die Funktionalität, dann bitte dort nachschauen.

Ich habe jetzt nur noch einen einzigen Interrupt, nämlich den IOC, und dieser macht das oben Beschriebene. Der Compiler löscht schon die Interruptflags beim Verlassen der Routine, sonst hätte ich ja schon andere Probleme gehabt.

Diese IOCs sind wahrscheinlich doch eher nur etwas für
Wakeup-Prozeduren.

Ja, denn wie im Datenblatt zu lesen ist, können bei
Aktivitäten auf den Ports auch mal Interrupts verloren gehen.
Wenn man das beachtet, sollten sie aber auch für alles andere
gut sein.

Ja, dachte ich auch, aber anscheinend muss man eine Möglichkeit finden, nur einen einzigen Portanschluss für den IOC zu aktivieren und alle anderen zu unterbinden. Und das ist mir jedoch noch nicht gelungen.

Ich werde den jetzigen S/W-Stand mal morgen hier reinschreiben, weil ich ihn gerade nicht hier habe.

Ich denke aber, für meine Anwendung komme ich auch so klar, denn die zu messenede Periodendauer ist wesntlich länger als die PWM-Periodendauer. Trotzdem wäre es natürlich interessehalber schön gewesen, die Lösung zu finden.

Gruß

Dieter

Bestätigung durch Listfile und Datenblatt
Hallo Achim,

nach dem Studium des Listfiles und des Datenblattes des 12F683 habe ich herausgefunden, dass die Anweisung enable_interrupts(int_ext) keineswegs für ALLE Pins einen IOC freischaltet, sondern nur für einen, und zwar den GP2. Und das ist ja gerade gleichzeitig der PWM-Ausgang! Meine Annahme war also richtig.

Dieser CCS-Compiler schweigt sich leider aus, was die einzelnen Interrupts genau machen, und ebenso das Chip-spezifische h-File. Da kann man nur probieren und immer wieder probieren. Laut Datenblatt muss ja das individuelle IOC für jeden I/O-Port machbar sein, möglicherweise muss ich aber hierzu einen ASM-Code einfügen.

Das Programm arbeitet dennoch auch mit dieser Einschränkung, denn die Zählung der PWM-Flanken während einer Periode meines externen Rechtecksignals funktioniert soweit ja. Das Ganze dient übrigens der Regelung eines DirectDrive-Plattenspielermotors.

Gruß

Dieter

Hallo Dieter,

  1. Dein Problem liegt schon beim IOC-Register.
    Dies musst du so konfigurieren, dass nur PIN_A4 einen Interrupt auslöst.

  2. Sobald aber mehr als 1 GPIO den Interrupt auslöst (Erweiterungen) benötigst du das Flag. Allerdings geht das etwas eleganter. Sinn ist einen Flankenwechsel an PIN_A4 zu erkennen:

    #int_ext
    void ext_isr()
    {
    BIT temp = input(PIN_A4);
    if ( INT_FLAG ^ temp )
    {
    if ( temp )
    {
    COUNTER_LO = get_timer2();
    COUNTER_HI = 0;
    }
    INT_FLAG = temp;
    }
    }

Ich weiss jetzt nicht welchen Datentyp „input(PIN_A4)“ liefert. Den Typ „BIT“ musst du anpassen.

Dein Fehler ist übrigens noch der, dass du davon ausgehen musst, dass PIN_A4 jederzeit seinen Zustand ändern kann !
In deiner Funktion bist du davon ausgegangen, dass PIN_A4 seinen Zustand zwischen zwei Abfragen nicht ändern kann.

MfG Peter(TOO)

Anmerkung
Hallo,

Dieser CCS-Compiler schweigt sich leider aus, was die…

Du solltest Dir angewöhnen, immer die Original-Datenblätter zu verwenden. Sonst lernst Du, wie das Spielchen ‚Stille Post‘ geht.
Gruß
loderunner

Hallo,

Dieser CCS-Compiler schweigt sich leider aus, was die…

Du solltest Dir angewöhnen, immer die Original-Datenblätter zu
verwenden. Sonst lernst Du, wie das Spielchen ‚Stille Post‘
geht.

also ich verwende das Original-Datenblatt von Microchip. Darüberhinaus arbeite ich halt mit einem C-Compiler. Oder ist das nicht in Ordnung?

Gruß

Dieter

Hallo Peter,

danke für die Antwort. Der Trick mit der Flagumschaltung per EXOR ist gut, werde ich einbauen.

  1. Dein Problem liegt schon beim IOC-Register.
    Dies musst du so konfigurieren, dass nur PIN_A4 einen
    Interrupt auslöst.

Ja, das habe ich erkannt. Aber ich denke, einen individuellen Pin auf Änderung kann ich nur korrekt konfigurieren, wenn das INTCON und das IOC-Register korrekt beschrieben sind. Im Listfile des Compilers habe ich gelesen, dass dieser bei enable_interrupts(int_ext) im INTCON-Register das INTE-Bit setzt, was bedeutet, dass nur GP2 (PWM) auf Änderungen reagiert. Und im Moment habe ich noch keine andere Interrupt-Möglichkeit gefunden, die das Bit GPIE setzt. Danach muss dann nur noch das richtige Bit im IOC-Register gesetzt sein, damit nur auf Änderungen am GP4 reagiert wird. Aber so weit bin ich noch nicht.

  1. Sobald aber mehr als 1 GPIO den Interrupt auslöst
    (Erweiterungen) benötigst du das Flag. Allerdings geht das
    etwas eleganter. Sinn ist einen Flankenwechsel an PIN_A4 zu
    erkennen:

#int_ext
void ext_isr()
{
BIT temp = input(PIN_A4);
if ( INT_FLAG ^ temp )
{
if ( temp )
{
COUNTER_LO = get_timer2();
COUNTER_HI = 0;
}
INT_FLAG = temp;
}
}

Dein Fehler ist übrigens noch der, dass du davon ausgehen
musst, dass PIN_A4 jederzeit seinen Zustand ändern kann

Ja, das habe ich mal in Kauf genommen, da am Pin ein Rechtecksignal mit wechselnder Frequenz ansteht, und diese ist immer viel kleiner als die PWM-Frequenz. Somit hätte ich als Auflösung die Periodendauer der PWM.

Gruß

Dieter

Hallo,

also ich verwende das Original-Datenblatt von Microchip.
Darüberhinaus arbeite ich halt mit einem C-Compiler. Oder ist
das nicht in Ordnung?

doch, ganz genauso ist das okay.
Ich bin nur darauf aufmerksam geworden, als Du in den Infos des Compilers nach Informationen über den Interrupt gesucht hast. Das führt oftmals nicht zum Ziel. Was ja hier auch der Fall war: Du musstest in den Listings nachschauen.
Das wäre übrigens der nächste Tip: die Ausgabelistings eines Compilers sind immer eine Fundgrube, wenn mal ein Programm nicht läuft.
Gruß
loderunner

Hallo Dieter,

Dein Fehler ist übrigens noch der, dass du davon ausgehen
musst, dass PIN_A4 jederzeit seinen Zustand ändern kann

Ja, das habe ich mal in Kauf genommen, da am Pin ein
Rechtecksignal mit wechselnder Frequenz ansteht, und diese ist
immer viel kleiner als die PWM-Frequenz. Somit hätte ich als
Auflösung die Periodendauer der PWM.

Ist aber ein Designfehler.

Da du nicht weisst WANN das Signal wechselt !
Auch wenn du die Frequenz mit einer höheren abtastest, kann das Signal genau zwischen zwei Zugriffen auf PIN_A4 wechseln.
Dann erzeugt deine Routine falsche Ergebnisse.

Dazu kommt noch, dass es vom Compiler abhängt, welcher Code erzeugt wird. Wenn er gute Optimierungen hat, kann es sein, dass der Compiler nur einen Zugriff erzeugt.

Das kann dann eine lustige Sucherei ergeben, je nachdem welchen Compiler man benutzt und welche Optionen gesetzt sind oder nicht.
Je nachdem ändert sich das auch, wenn man nur eine zusätzliche Zeile Code einfügt, weil dann für die Optimierung ein Register fehlt.

Grundsätzlich darfst du nie Annehmen dass ein IO-Pin sich nicht ändert. In deinem Fall können also beide IFs bei einem Durchgang ausgeführt werden.

MfG Peter(TOO)

Hallo Peter,

Da du nicht weisst WANN das Signal wechselt !
Auch wenn du die Frequenz mit einer höheren abtastest, kann
das Signal genau zwischen zwei Zugriffen auf PIN_A4 wechseln.
Dann erzeugt deine Routine falsche Ergebnisse.

Diese Interrupt-Kollision habe ich dadurch behoben, dass ich die zweite Int-Routine mit dem Timer2 rausgeschmissen habe und die Counter_Hi-Inkrementierung am Anfang des INT_EXT-Interrupts mache, vor den Bedingungen.

Bei 50 Hz am Pin erhalte ich einen Zählerstand von 156, das genügt mir erst mal. Zur Auflösungssteigerung könnte ich immer noch mehr als eine Periode vermessen.

Natürlich wäre dennoch eine direkte Interruptroutine für GP4 interessant, und Timer2 könnte man dann zusätzlich abfragen, dann hätte man gleich 16 Bit Auflösung. Aber wahrscheinlich muss ich das in Assembler schreiben, wenigstens diesen Teil.

Gruß

Dieter

Level-Getriggert und Flanken getriggert
Hallo Dieter,

ich kenne jetzt Deinen Compiler nicht, aber bei GP2 ist die Besonderheit, dass dieser als einziger als Flanken-Interrupt verwendet werden kann. Das ist unabhängig vom IOC.

Auch wenn Dein Compiler dir mit nicht ANSI-Routinen vieles abnimmt, in der Regel musst Du Datenblatt von Compiler und Controller zusammen studieren, um überhaupt zu verstehen, was gemeint ist.

Es ist ansonsten für den Compilerhersteller kaum möglich, die Funktionalität eines so komplexen microcrontrollers einerseits auszureizen, andererseits handhabbar zu haben.

Assembler ist in der Regel bis auf wenige ausnahmen meist nicht notwendig

Gruß
achim