Forum: Mikrocontroller und Digitale Elektronik PIC 18 in und aus Sleep modus mit UART Interrupt


von Schläfer (Gast)


Lesenswert?

Ich habe einen PIC18LF25K50. Spannungsversorgung kommt von Batterie.
Nach Initialisierung des Mikrocontrollers, UART Schnittstelle, LCD und 
gesamter Peripherie würde ich um die Batterie zu schonen den PIC gerne 
in den Sleep Modus schicken.

Der Trigger, um den PIC aus diesem wieder herauszuholen solle wenn 
möglich die UART Receive Leitung sein.

Wie schickt man den PIC in den Sleep Modus? Was macht das Macro...
1
#define Sleep()  {_asm sleep _endasm}

Wie sieht das Programmkonstrukt in etwa aus?
1
int main ()
2
{
3
// Initialisierung PIC, UART etc.
4
while(1)
5
{
6
// setze PIC in Schlafmodus
7
// Überprüfe ob Daten von extern über UART "reinkommen"
8
// Ja dann mache etwas
9
// Sonst bleibe im Sleep Modus
10
}
11
}

Wie definiert man den UART als Trigger für das Erwachen aus dem Sleep 
Modus?

Ich weiss das ist alles etwas wage formuliert.

Ein einfaches Programmbeispiel für den obigen Fall würde mir sehr 
helfen.

Danke

von Stefan (Gast)


Lesenswert?

Sollte mit dem INTCON Register
gehen. Schau mal ins Datenblatt.
Vielleicht Wake up on Port Change machen.

von Ludwig S. (big_l)


Lesenswert?

ich erinnere mich nur dunkel aber du musst auf jeden fall darauf achten 
dass das GIE(Global Interrupt Enable)bit gesetzt ist, das PIE(Peripheral 
Interrupt Enable)Bit und das Interruptbit für die Uart wenn ein byte 
empfangen wurde (vergessen wies hieß).

Wenn du das GIE zB nicht gesetzt hast sind alle interrupts ausgeschaltet 
obwohl die im anderen alle an geschaltet sind.

Und ich glaube dannn musst du noch aufpassen das der nicht in der 
Interrupt routine hängen bleibt weil du das Interruptbit für die UART 
per software glaub ich löschen musst indem du den Register liest.

von WehOhWeh (Gast)


Lesenswert?

Vertrackt an der Sache ist, dass beim Sleep kein Clock da ist, aber die 
UART einen Clock für die Baudrate braucht. Von daher ist das Aufwachen 
bei UART-Tätigkeit nicht so selbstverständlich wie man denken sollte.

Aber dein PIC kann das günstigerweise. Das Feature nennt sich "Auto wake 
up on break". Das müsstest du einschalten. Siehe auch 17.4.3 im 
Datenblatt.

Laut Datenblatt sollte dein µC dann bei jedem UART Receive automatisch 
aufwachen.

Du musst aber beachten, dass du ein oder mehrere Byte verlierst, weil 
der Clock des PIC erst einmal starten muss, das dauert manchmal etwas. 
Speziell wenn ein Quarz dran ist auch mal etwas länger.

Sende also ein "wakeup"-Paket vor Start der Kommunikation.

von Schläfer (Gast)


Lesenswert?

Danke schon einmal.
Ich fange mal mit dem Interrupt an. Habe noch nie damit gearbeitet, 
deswegen lasst bitte ein wenig Nachsicht walten.
1
#pragma code isr = 0x08 // store the below code at address 0x08
2
#pragma interrupt isr   // let the compiler know that the function isr() is an interrupt handler
3
4
void isr(void)
5
{
6
    if(PIR1bits.RCIF == 1) // if the USART receive interrupt flag has been set
7
    {
8
     
9
    }
10
11
    PIR1bits.RCIF = 0;  // make sure the RX flag is clear
12
}
13
14
#pragma code // return to the default code section
15
16
17
void main(void) {
18
    
19
    Init_UART();
20
21
    // interrupts / USART interrupts configuration
22
    RCONbits.IPEN   = 0; // disable interrupt priority
23
    INTCONbits.GIE  = 1; // enable interrupts
24
    INTCONbits.PEIE = 1; // enable peripheral interrupts.
25
    PIE1bits.RCIE   = 1; // enable USART receive interrupt
26
    PIE1bits.TXIE   = 0; // disable USART TX interrupt

Über das INTCON Register aktiviere ich die Interrupts. In der Interrupt 
Routine selbst muss ich das auslösende Interrupt - Flag am Ende wieder 
löschen, damit Interrupts wieder möglich sind. Dies mache ich über
1
PIR1bits.RCIF = 0;  // make sure the RX flag is clear

Ist dies das richtige Bit?
Das deaktivieren von Interrupts am Anfang der ISR über PIE1bits.RCIE  = 
0; kann ich mir sparen? Dies geschieht automatisch bei Springen in die 
ISR, damit keine weiteren Interrupts ausgelöst werden können?


Eine weitere Frage: Woher was der PIC was den Interrupt Triggerns soll 
(nämlich genau wenn UART Daten anliegen)??

Beim PIC24 kenn ich das in etwa über:
1
void __attribute__((interrupt, no_auto_psv)) _U1RXInterrupt(void)

Damit ist mir klar, dass der Interrupt über U1RX getriggert wird. Woher 
weiss der PIC das in diesem Fall?

Mir ist wichtig, dass ich das einmal sauber runterprogrammiert bekommn 
und auch nachvollziehen kann. Deswegen bin ich über jeglichen Input 
dankbar. Sleep Modus kümmere ich mich dann danach.

Danke

von Schläfer (Gast)


Lesenswert?

Aus irgendwelchen Gründen bleibe ich immer in der IST hängen. Woran 
könnte das liegen, speziell beim dem angegebenen Code?

von Schläfer (Gast)


Lesenswert?

bzw. ich bleibe nur NICHT hängen, wenn ich in der ISR

[c]

if(PIR1bits.RCIF == 1)
{}
[c]

abfrage.

Wie kann das sein? Hier checke ich doch nur die TX Leitung und schicke 
das empfangene Character wieder zurück?

Was ich in der ISR ausführe dürfte doch eigentlich keinen Einfluß haben?

von Chris B. (dekatz)


Lesenswert?

Das hier:
>
>     PIR1bits.RCIF = 0;  // make sure the RX flag is clear
>
bewirkt gar nichts.
Das RCIF kann nicht manuell gelöscht werden sondern nur indem man den 
RX-Puffer ausliest.
Zitat aus dem DB : .....The RCIF interrupt flag bit is read-only, it 
cannot be set or cleared by software.

von PICler (Gast)


Lesenswert?

Schläfer schrieb:
> bzw. ich bleibe nur NICHT hängen, wenn ich in der ISR
>
> [c]
>
> if(PIR1bits.RCIF == 1)
> {}
> [c]

Das RC-Interrupt Flag (RCIF) wird zurückgesetzt, wenn es gelesen wird 
(siehe S.117 im Datenblatt). Wenn du in der ISR das RCIF nicht liest, 
bleibt es gesetzt und nach verlassen der ISR wird sofort wieder 
hineingesprungen.

Also: In der ISR als erstes das RCIF auslesen (was du mit der IF-Abfrage 
machst), um es zu löschen.

Generell ist es gut, am Anfang jeder ISR das jeweilige IR-Flag zu 
prüfen. Dadurch kann geprüft werden, ob die ISR auch wirklich durch 
einen Interrupt aufgerufen wurde und nicht etwa durch einen Fehler.

von PICler (Gast)


Lesenswert?

Oppps,

natürlich muss das RC-Register gelesen werden, um das lag zu löschen...

von Schläfer (Gast)


Lesenswert?

OK, danke sehr.

Ich habe jetzt einmal folgendes probiert:
1
void isr(void)
2
{
3
4
    if(PIR1bits.RCIF == 1) // if the USART receive interrupt flag has been set
5
    {
6
7
       
8
    }
9
10
11
}

Dies führt ebenfalls zum Daueraufenthalt in der Schleife, da das RCIF 
Bit nicht wieder gelöscht wird.

Jedoch...
1
void isr(void)
2
{
3
    unsigned char c;
4
5
    if(PIR1bits.RCIF == 1) // if the USART receive interrupt flag has been set
6
    {
7
    c = RCREG; // read Dummy
8
       
9
    }

funktioniert, da ich durch das Lesen des RX Buffers automatisch (und nur 
dann!!!) das RCIF bit wieder gelöscht wird?

Soweit richtig verstanden?
Schönen Dank für die schnellen Hinweise.

von Ludwig S. (big_l)


Lesenswert?

Ja soweit ist das richtig...
ich will ja nicht klugscheißen aber

Ludwig S. schrieb:
> Und ich glaube dannn musst du noch aufpassen das der nicht in der
> Interrupt routine hängen bleibt weil du das Interruptbit für die UART
> per software glaub ich löschen musst indem du den Register liest.

ich hatte das bereits geschrieben... wenn auch zugegebener maßen etwas 
eilig aber es stand die ganze zeit da^^

von Schläfer (Gast)


Lesenswert?

Ludwig S. schrieb:
> Ja soweit ist das richtig...
> ich will ja nicht klugscheißen aber
>
> Ludwig S. schrieb:
>> Und ich glaube dannn musst du noch aufpassen das der nicht in der
>> Interrupt routine hängen bleibt weil du das Interruptbit für die UART
>> per software glaub ich löschen musst indem du den Register liest.
>
> ich hatte das bereits geschrieben... wenn auch zugegebener maßen etwas
> eilig aber es stand die ganze zeit da^^

Ja ok, Du hast natürlich Recht.
Per Software löschen habe ich irgendwie damit in Verbindung gebracht das 
Bit manuell zu löschen (Dies habe ich mit PIR1bits.RCIF = 0; probiert).
Das es durch das Lesen des Registers gelöscht wird hast Du zwar 
geschrieben, war mir leider nicht ganz klar bzw. habe ich nicht 
verstanden.
Danke auf jeden Fall bis zu diesem Punkt!

von Schläfer (Gast)


Lesenswert?

Nochmal eine weitere Frage bzgl. Interrupts:

- Darf man in Interruptroutinen weitere Funktionen aufrufen (kompilier 
bar ist es)?
- Darf man es, es wird jedoch nicht empfohlen?
- wie sieht es mit delays in ISR aus?

von Peter D. (peda)


Lesenswert?

Schläfer schrieb:
> - Darf man in Interruptroutinen weitere Funktionen aufrufen (kompilier
> bar ist es)?
> - Darf man es, es wird jedoch nicht empfohlen?
> - wie sieht es mit delays in ISR aus?

Man darf alles.
Man muß nur berücksichtigen, daß dabei die Ausführung des Main und aller 
anderen Interrupts nicht höherer Priorität angehalten wird.
Gegen ein Delay von max 10..100µs spricht fast nichts.

von Schläfer (Gast)


Lesenswert?

Ich würde jetzt gerne noch den kompletten string, der über UART 
geschickt wird in der ISR in einem Buffer abspeichern.
Die geschickten Strigs werden in der Regel mit einem carriage Return 
abgeschlossen.

Hier ist mein bisheriger Code
1
unsigned char rx_char;
2
unsigned int i = 0;
3
4
5
char buffer_rx[100];
6
7
8
9
// start ISR code
10
#pragma code isr = 0x08 // store the below code at address 0x08
11
#pragma interrupt isr   // let the compiler know that the function isr() is an interrupt handler
12
13
void isr(void)
14
{
15
    Nop();
16
    Nop();
17
    Nop();
18
    if(PIR1bits.RCIF == 1) // if the USART receive interrupt flag has been set
19
    {
20
        while(rx_char != '/r')
21
        {
22
            rx_char = getcUSART1();
23
            buffer_rx[i] = rx_char;
24
            if(rx_char != '/n')
25
            i++;
26
        }
27
    }
28
    Nop();
29
    Nop();
30
    Nop();
31
32
}
33
34
#pragma code // return to the default code section
35
// end ISR code

Leider wird der zweite Buchstabe immer mehrmals geschrieben. Ich springe 
also nicht nur im Array weiter wenn einer neuer Buchstabe anliegt, 
sondern mit jedem Step. Wenn das carriage Return '/r' kommt soll aus der 
ISR herausgesprungen werden.

Fällt der Fehler direkt ins Auge?

von Schläfer (Gast)


Lesenswert?

Noch vergessen:
1
unsigned char getcUSART1 (void)
2
{
3
    char  c;
4
    if (RCSTAbits.OERR)  // in case of overrun error
5
    {                    // we should never see an overrun error, but if we do,
6
        RCSTAbits.CREN = 0;  // reset the port
7
        c = RCREG;
8
        RCSTAbits.CREN = 1;  // and keep going.
9
    }
10
    else
11
    {
12
        c = RCREG;
13
    }
14
// not necessary.  EUSART auto clears the flag when RCREG is cleared
15
//  PIR1bits.RCIF = 0;    // clear Flag
16
17
    return c;
18
}

von Schläfer (Gast)


Lesenswert?

Eigentlich will ja nur über ein empfangenes UART Signal ein Interrupt 
auslösen (das klappt ja bis dato) und in dieser Routine den kompletten 
gesendeten UART String (nicht nur 1 Byte) in einem char Array 
abspeichern. Die Länge des Strings soll dabei variable sein (nämlich so 
lang wie der gesendete UART String).

Hätte nicht gedacht dass dies so kompliziert ist :)

von PICler (Gast)


Lesenswert?

Schläfer schrieb:
> Eigentlich will ja nur über ein empfangenes UART Signal ein Interrupt
> auslösen (das klappt ja bis dato) und in dieser Routine den kompletten
> gesendeten UART String (nicht nur 1 Byte) in einem char Array
> abspeichern. Die Länge des Strings soll dabei variable sein (nämlich so
> lang wie der gesendete UART String).

Regel: Mach' so wenig wie möglich in einer ISR.

In Hauptprogramm
  Puffer für String reservieren
  Zeiger auf das erste Zeichen setzen

In der ISR
  Empfagenes Zeichen in den Puffer schreiben
  Puffer voll? -> Flag setzen
  Ende-Kenung gekommen? -> Flag setzen
  Zeiger auf nächsten Pufferplatz setzen
  ISR verlassen


Im Hauptprogramm die Flags auswerten ung ggfls. den String verarbeiten 
und wieder Sleep aufrufen.


Keinesfalls in der ISR auf den ganzen String warten. Was machst du sonst 
z. b. wenn die Ende-Kennung nicht kommt?

von Schläfer (Gast)


Lesenswert?

PICler schrieb:
> Schläfer schrieb:
>> Eigentlich will ja nur über ein empfangenes UART Signal ein Interrupt
>> auslösen (das klappt ja bis dato) und in dieser Routine den kompletten
>> gesendeten UART String (nicht nur 1 Byte) in einem char Array
>> abspeichern. Die Länge des Strings soll dabei variable sein (nämlich so
>> lang wie der gesendete UART String).
>
> Regel: Mach' so wenig wie möglich in einer ISR.
>
> In Hauptprogramm
>   Puffer für String reservieren
>   Zeiger auf das erste Zeichen setzen
>
> In der ISR
>   Empfagenes Zeichen in den Puffer schreiben
>   Puffer voll? -> Flag setzen
>   Ende-Kenung gekommen? -> Flag setzen
>   Zeiger auf nächsten Pufferplatz setzen
>   ISR verlassen
>
> Im Hauptprogramm die Flags auswerten ung ggfls. den String verarbeiten
> und wieder Sleep aufrufen.
>
> Keinesfalls in der ISR auf den ganzen String warten. Was machst du sonst
> z. b. wenn die Ende-Kennung nicht kommt?

Super!!!

Genau sowas habe ich gesucht und hat mir extrem weitergeholfen.
Danke dafür.

von Schläfer (Gast)


Lesenswert?

Das einlesen des UART Strings über die ISR Routine funktioniert jetzt 
tadellos. Danke dafür an Alle.

Jetzt möchte ich ja nach erfolgreich übertragenen String den MCU wieder 
in den Sleep Modus versetzten. Das funktioniert in etwa wie folgt:
1
...
2
void main(void)
3
{
4
while(1)
5
{
6
     if(flag == 1)   // Wenn Endekennung oder Buffer voll
7
     {
8
         // verarbeite UART string irgendwie
9
         clean_buffer();  // Lösche Buffer wieder
10
         i = 0;           // zeige wieder auf erstes Element im Buffer
11
         flag = 0;        // Lösche Flag, da Wort erfolgreich erkannt
12
         Sleep();         // Schicke PIC18 über Makro wieder in Sleep Modus
13
     }
14
}
15
}

So weit so gut. Wie funktioniert das jetzt genau mit dem Erwachen aus 
dem Sleep Modus. Ich würde ja gerne NICHT nach Irgendeiner Zeit den PIC 
wieder erwachen, sondern immer genau dann wenn neue UART Daten anliegen. 
Laut dem Reference Manual ("Section 28 Watchdog Timer and Sleep Mode") 
soll dies auch möglich sein.

Wenn ich einen UART RX Interruptroutine implementiert habe über
1
RCONbits.IPEN   = 0; // disable interrupt priority
2
INTCONbits.GIE  = 1; // enable interrupts
3
INTCONbits.PEIE = 1; // enable peripheral interrupts.
4
PIE1bits.RCIE   = 1; // enable USART receive interrupt
5
PIE1bits.TXIE   = 0; // disable USART TX interrupt

Erwacht der PIC dann automatisch genau dann wenn die ISR Routine 
ausgelöst wird oder muss ich das Erwachen "händisch" in der ISR Routine 
angeben??

Was genau hat es mit dem Watchdog auf sich? Ich habe gelesen, dass 
dieser gelöscht bzw. disablet sein muss?

In den Config Bits sieht das bei mir bis dato wie folgt aus:
1
#pragma config WDTEN    = SWON      // Watchdog Timer controlled by SWDTEN
2
#pragma config WDTPS    = 32768     // WDT postscalar

So wie ich es jetzt habe (siehe oben) scheint der PIC in den Sleep Modus 
zu springen, aber danach nie wieder hinaus.

Die von WehohWeh angesprochene Problematik mit der disableten Clock (und 
der damit fehlenden Baudrate) während des Sleep Modus scheint mir auch 
noch nicht ganz klar und muss vermutlich beachtet werden.

Hat da jemand ein paar Codezeilen für mich?
Danke

von Schläfer (Gast)


Lesenswert?

Ich zitiere mal aus dem von WehohWeh genannten Abschnitt Auto Wake Up 
Break" -> 17.4.3 im datasheet PIC1825K50:

"The Auto-Wake- up Feature is enabled by Setting the WUE bit of the 
BAUDCONx Register. Once set, the normal receive sequence on RX/DT is 
disabled, and the EUSART remains in an idle state, Monitoring for a 
wake-up Event Independent of the CPU mode."


Ich habe es also wie folgt (ohne Erfolg versucht):
1
...
2
void main(void)
3
{
4
while(1)
5
{
6
     if(flag == 1)   // Wenn Endekennung oder Buffer voll
7
     {
8
         // verarbeite UART string irgendwie
9
         clean_buffer();  // Lösche Buffer wieder
10
         i = 0;           // zeige wieder auf erstes Element im Buffer
11
         flag = 0;        // Lösche Flag, da Wort erfolgreich erkannt
12
         BAUDCONbits.WUE = 1;
13
         Sleep();         // Schicke PIC18 über Makro wieder in Sleep Modus
14
     }
15
}
16
}

Ich vermute mal dies kann noch nicht alles sein!

von Schläfer (Gast)


Lesenswert?

Jemand eine Idee?

von Schläfer (Gast)


Lesenswert?

Ich habe versucht mich exakt an die "Anleitung" im Datenblatt zum Auto 
Wake Up des PICs zu halten (S.276).
1
void main(void)
2
{
3
while(1)
4
{
5
...
6
BAUDCON1bits.WUE = 1;     // Setze dieses Bit bevor PIC in SLEEP Modus to enter Auto Wake Up
7
putcUSART(0x00);          // Sende Break Sequenz, dieses Byte wird verworfen
8
Sleep();                  // versetzte PIC in Schlafmodus 
9
}
10
}

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.