Forum: Mikrocontroller und Digitale Elektronik PIC18F26K22 Timer0 1sec Interrupt für Uhrzeit


von Markus B. (pspracers)


Lesenswert?

Hallo Leute!

Ich versuche eine Zeitschaltuhr zu bauen, dazu sollte unter anderem die 
aktuelle Uhrzeit auf einem LC-Display angezeigt werden. Die Uhrzeit muss 
nicht exakt sein, sie wird auch von hand eingestellt. Nun habe ich mit 
der Formel
T = ((1/Fosc)*4)*PRESCALER*(2^16-x)
1 = ((1/16000000)*4)*64*(2^16-x)
für x=3036 ausgerechnet.
Mit diesem Wert habe ich aber auf 20 Sekunden bereits eine Verzögerung 
von ca. einer Sekunde. Mit dieser Formel komme ich also nicht weiter :/ 
Ich habe auch anderen Prescaler mit dieser Formel versucht, ohne Erfolg.
Testweise habe ich für x = 8000 versucht, hierbei habe ich schon einen 
genaueren Takt bekommen. Nach ca 5 Minuten habe ich aber auch schon 
einen Unterschied feststellen können. Nach 17 Minuten sind es ca. 2 
Sekunden Unterschied.
Messen kann ich den Zeitunterschied nur mit meiner Handy-Stoppuhr nach 
Gefühl.

Ich verwende einen PIC18F26k22 und dessen interner 16MHz Oszillator mit 
PLL (4x).

Hier ist meine Software, etwas redzuiert
1
/*
2
 * File:   main.c
3
 * Author: Markus
4
 *
5
 * Created on February 7, 2017, 11:54 PM
6
*/
7
//LIBRARIES
8
#include <xc.h>
9
#include <pic18f26k22.h>
10
#include <plib.h>
11
#include <string.h>
12
#include <stdio.h>
13
#include <delays.h>
14
#include "lcd.h"
15
16
//DEFINITIONS
17
#define TMR0Reload  3036
18
typedef unsigned char Boolean;
19
#define TRUE    1
20
#define true    1
21
#define FALSE   0
22
#define false   0
23
24
//CONTROLLER INITIALISATION
25
#pragma config FOSC = INTIO67
26
#pragma config PLLCFG = ON
27
#pragma config WDTEN = OFF
28
#pragma config LVP = OFF
29
#pragma config MCLRE = INTMCLR
30
31
//STRUCTURES
32
typedef struct
33
{
34
    char sec;
35
    char min;
36
    char hr;
37
}
38
Time;
39
40
//GLOBAL VARIABLES
41
Time systemTime;
42
Boolean timeChangedFlag;
43
44
45
void interrupt ISR(void)
46
{
47
    if (INTCONbits.TMR0IF == 1 && INTCONbits.TMR0IE == 1)
48
    {
49
        TMR0IF=0;
50
        
51
        systemTime.sec++;
52
        if(systemTime.sec == 60)
53
        {
54
            systemTime.sec = 0;
55
            systemTime.min++;
56
            if(systemTime.min == 60)
57
            {
58
                systemTime.min = 0;
59
                systemTime.hr++;
60
                if(systemTime.hr == 24)
61
                    systemTime.hr = 0;
62
            }
63
        }
64
        
65
        timeChangedFlag = true;
66
        
67
        WriteTimer0(TMR0Reload);
68
    }
69
}
70
71
void InitCoreFunctions()
72
{
73
    //SYSTEMTAKT
74
    OSCCON = 0x72;
75
    OSCCON2 = 0x88;
76
    OSCTUNE= 0xCF;
77
    
78
    //Global interrupt disabled during startup
79
    INTCONbits.GIE = 0;
80
    
81
    //Peripherial interrupt enable
82
    INTCONbits.PEIE = 1;
83
    
84
    //Disable Interrupt priorities
85
    RCONbits.IPEN = 0;
86
    
87
    //OUTPUTS
88
    LATA = 0x00;
89
    LATB = 0x00;
90
    LATC = 0x00;
91
    
92
    //ANALOG/DIGITAL IO
93
    ANSELA = ANSELB = ANSELC = 0x00;
94
    
95
    //Disable all PORTB Pull-Ups
96
    INTCON2bits.RBPU = 1;
97
    
98
    //IO-DIRECTION
99
    TRISC = 0xFF; //RELAIS
100
    TRISB = 0xFF; //0..1BUTTON 2..4 HC-SR501 6..7 ICSP
101
    
102
    //LCD
103
    /*  0..Enable 1
104
     *  1..Enable 2
105
     *  2..Register-Select
106
     *  3..Read/Write
107
     *  4..Data 4
108
     *  5..Data 5
109
     *  6..Data 6
110
     *  7..Data 7
111
     */
112
    TRISA = 0x00;
113
    
114
    //Set system time default
115
    systemTime.sec = 0;
116
    systemTime.min = 0;
117
    systemTime.hr = 0;
118
    
119
    //TMR0ON,16Bit, INTCLK,PRESCALER 1_64
120
    T0CON = 0b10000101;
121
    WriteTimer0(TMR0Reload);
122
    INTCONbits.TMR0IF = 0;
123
    INTCONbits.TMR0IE = 1;
124
    
125
    
126
    //LCD Backlight
127
    TRISBbits.TRISB4 = 0;
128
    LATBbits.LATB4 = 1;
129
    
130
    LCD_Init();
131
    
132
    //Global interrupt enabled
133
    INTCONbits.GIE = 1;
134
}
135
136
void main(void) 
137
{
138
    char str[20];
139
    
140
    InitCoreFunctions();
141
    
142
    timeChangedFlag = false;
143
    
144
    sprintf(str,"Uhrzeit: %02d:%02d:%02d ", systemTime.hr, systemTime.min, systemTime.sec);
145
    LCD_string_line(1,str);
146
    LCD_string_line(2,"Dieser Text steht in der 2. Zeile");
147
    LCD_string_line(3,"Dieser Text steht in der 3. Zeile");
148
    LCD_string_line(4,"Dieser Text steht in der 4. Zeile");
149
    while(1)
150
    {
151
        //print new time
152
        if(timeChangedFlag)
153
        {
154
            timeChangedFlag = false;
155
            sprintf(str,"Uhrzeit: %02d:%02d:%02d ", systemTime.hr, systemTime.min, systemTime.sec);
156
            LCD_string_line(1,str);
157
        }
158
        
159
    }
160
}

Woran kann es liegen, dass mein Sekundentakt nicht hinhaut?
Ich hoffe ihr könnt mir helfen :)
Weiters wünsche ich noch jedem Leser frohe Ostern!
LG Markus

: Bearbeitet durch User
von Volker S. (vloki)


Lesenswert?

Markus B. schrieb:
> Woran kann es liegen, dass mein Sekundentakt nicht hinhaut?

Davon abgesehen, dass der interne Oszillator sehr ungenau ist,
wäre es viel einfacher und besser einen Timer zu verwenden, der 
automatisch so weit zählt wie nötig. Ohne dass man diesen "reloaden" 
muss.

Schau mal da: 
http://www.hs-ulm.de/users/vschilli/Mikrocontroller/uCQ/_downloads/uCquick-X.pdf 
(5.1 und 5.2)

<edit>Die Berechnug des Reload-Wertes wäre in 4.4.2, macht aber keinen 
Sinn.

: Bearbeitet durch User
von Toxic (Gast)


Angehängte Dateien:

Lesenswert?


von B. P. (skorpionx)


Lesenswert?

Beitrag "Mit Batterie gepufferte Uhr für Steuerung."

Im ersten Projekt (PIC18...) findest du wie man Zeit Flanken im
Interrupt erzeugt. Im zweitem Projekt arbeitet PIC12... mit dem Quarz 
32768 kHz und Interrupt  1 Sek. , sonst mit internem Oszillator. Das 
„schlafen gehen“ (sleep) muss nicht sein.

von Ottmar K. (wil1)


Lesenswert?

@ Markus B

Ich sehe das so:

Grundsaetzlich soll man fosc nur so hoch wie unbedingt erforderlich 
waehlen. Man erspart sich so Klimmzuege.

Mit fosc/4=16MHz kannst Du mit dem TMR0 im 8Bit-Mode und dem Vorteiler 
256 ohne zusaetzlichen Aufwand, nicht viel ausrichten.

Mein Vorschlag (wenn es schon fosc/4=16Mhz sein muss):

fosc:  16.000.000Hz
PLL:  fosc * 4 =64.000.000 Hz
working Cycles/s = Fosc*PLL/4 =16.000.000
Ein TMR0-Overflow-Interrupt kann nur sinnvoll verwendet werden, wenn der 
TMR0 in den 16Bit-Modus versetzt wird (siehe oben).

TMR0 16Bit-Mode: Set T0CON,T08BIT (Bit 6) = 1

counts 0 - 65535 (65536 clocks) (TMR0H:TMR0L)

Sinnvoll waere z.B der Overflow-Interrupt alle 5ms. Bei jedem 
TMR0-Overflow wird sodann ein Zaehler incrementiert.
Bei Zaehlerstand = 200 ist 1s verstrichen.

Nebenbei kann der Interrupt auch noch fuer 1/10s oder auch zum 
Entprellen von Tasten nuetzlich sein.

Bei der Verwendung des Vorteilers :2 muss der Interrupt bei
Zaehlerueberlauf 80.000 / 2 = 40.000 erfolgen.

Dazu muss der Zaehler nach jedem Ueberlauf auf Preset =
65.536 - 40.000 = 25.536 gesetzt werden (TMR0H:TMR0L).

Lies doch einfach mal das Datenblatt Abschnitt
"11.0 TIMER0 MODULE"
FIGURE 11-2: TIMER0 BLOCK DIAGRAM (16-BIT MODE)

mfG Ottmar



mfG Ottmar

von Noch einer (Gast)


Lesenswert?

Laut Mplabx Simulator haut dein Programm so hin. Nur 1/100000 
Abweichung.

(Das Stopwatch Window des Simulators zeigt die abgelaufene Zeit zwischen 
den Breakpoints an).

Breakpoint auf "TMR0IF=0;"
Stopwatch cycle count = 4000048 (1,000012 s)

Breakpoint auf "systemTime.sec = 0;"
Stopwatch cycle count = 240002887 (60,000722 s)

Unmittelbar vorm "WriteTimer0" ist TMR0L immer noch 0 -- haut auch hin.

Würde ich mir erst mal den internen Oszillator genauer anschauen.

von Norbert S. (pianoforte)


Lesenswert?

Den internen Oszillator kannst du ruhig verwenden.
Bei den PIC18F.. sind die ab Werk her schon auf 1% genau geeicht.
Genaueres steht im Datenblatt !
Das reicht schon für eine sehr genaue Uhr.
Meine Uhren laufen auch mit dem internen Oszillator und haben gerade mal 
ein paar Sekunden Abweichung im Monat, wenn nicht sogar weniger.


Die Abweichung kannst du auch über längere Zeit ermitteln und dann per 
Software kompensieren !
Ist nicht schwer...

von Markus B. (pspracers)


Lesenswert?

Vielen Dank für die vielen Antworten, ich seh mir das morgen alles an! 
:)
Ihr seid spitze!

von Felix A. (davinciclaude)


Lesenswert?

Was auch noch einen Einfluss hat, ist die Temperaturstabilität des 
Oszis.
Finger auf das Gehäuse legen sorgt meist schon für einen ziemlich 
krassen Drift.
Wenn du aber eh Netzspannung an deiner Schaltung hast 
(Zeitschaltuhr...?), könntest du auch die Netzfrequenz mitzählen. Hier 
hat mal einer behauptet, die sei langfristig auf 1 ppm genau.
Und sonst Korrekturtaster und basta :)

von Volker S. (vloki)


Lesenswert?

Norbert S. schrieb:
> Bei den PIC18F.. sind die ab Werk her schon auf 1% genau geeicht.
> Genaueres steht im Datenblatt !
> Das reicht schon für eine sehr genaue Uhr.

Geeicht?

1% ist für eine Uhr lächerlich. In 100s (also 1min40s) bis zu 1 Sekunde 
Abweichung. In einer Stunde dann 36 Sekunden. Am Tag 14,4 Minuten...
(Hoffentlich habe ich mich nicht verrechnet ;-)


Norbert S. schrieb:
> Meine Uhren laufen auch mit dem internen Oszillator und haben gerade mal
> ein paar Sekunden Abweichung im Monat, wenn nicht sogar weniger.
Die Temperatur ist wohl recht stabil?


Norbert S. schrieb:
> Die Abweichung kannst du auch über längere Zeit ermitteln und dann per
> Software kompensieren !
> Ist nicht schwer...
Wenn die Abweichung konstant ist...

von Markus B. (pspracers)


Angehängte Dateien:

Lesenswert?

Hallo,

Ich werde jetzt so gut ich kann die möglichen Lösungen durchprobieren :)

Volker S. schrieb:
> Schau mal da:
> http://www.hs-ulm.de/users/vschilli/Mikrocontroller/uCQ/_downloads/uCquick-X.pdf
> (5.1 und 5.2)

Das habe ich nun so probiert, auf 40sek ist mein Programm eine Sekunde 
schneller.
Ich habe folgende Änderungen vorgenommen:
1
//DEFINITIONS
2
#define CCP1COUNT   20000
3
4
//GLOBAL VARIABLES
5
unsigned char count;
6
void interrupt ISR(void)
7
{
8
    if(PIR1bits.CCP1IF == 1 && PIE1bits.CCP1IE == 1)  
9
    {
10
        PIR1bits.CCP1IF = 0;
11
        
12
        count++;
13
        
14
        if(count == 100)
15
        {
16
            count = 0;
17
            systemTime.sec++;
18
            if(systemTime.sec == 60)
19
            {
20
                systemTime.sec = 0;
21
                systemTime.min++;
22
                if(systemTime.min == 60)
23
                {
24
                    systemTime.min = 0;
25
                    systemTime.hr++;
26
                    if(systemTime.hr == 24)
27
                        systemTime.hr = 0;
28
                }
29
            }
30
            timeChangedFlag = true;
31
        }
32
    }
33
}
34
35
void InitCoreFunctions()
36
{
37
    OpenTimer1(TIMER_INT_ON & T1_16BIT_RW & T1_SOURCE_FOSC & T1_PS_1_8, TIMER_GATE_OFF);
38
    OpenECompare1(COM_INT_ON & ECOM_TRIG_SEVNT & ECCP_1_SEL_TMR12,CCP1COUNT);
39
    
40
    count = 0;
41
}
Sowie den TIMER0 teil auskommentiert.
Im Anhang die komplette main.c :)

Ich mache dann weiter mit den weiteren Vorschlägen!
Vielen Dank und noch einen schönen Ostermontag :)

LG Markus

Edit: CCP1COUNT habe ich wie folgt berechnet:
0,01 = (1/16000000)*8*x -> x=20000

: Bearbeitet durch User
von Volker S. (vloki)


Lesenswert?

Markus B. schrieb:
> Das habe ich nun so probiert, auf 40sek ist mein Programm eine Sekunde
> schneller.

Dann ist wohl der interne Oszillator um 2,5% schneller und du musst 
dementsprechend für CC1COUNT 20500 nehmen.

von Toxic (Gast)


Lesenswert?

Volker S. schrieb:
> Markus B. schrieb:
>> Das habe ich nun so probiert, auf 40sek ist mein Programm eine Sekunde
>> schneller.
>
> Dann ist wohl der interne Oszillator um 2,5% schneller und du musst
> dementsprechend für CC1COUNT 20500 nehmen.

Also ich plaediere ja immer noch fuer die Loesung mit einem 
32.768kHz-Oszillator.Hat halt den Vorteil,dass man beliebige PICs 
einsetzen kann ohne gleich wieder kalibieren zu muessen....

von Volker S. (vloki)


Lesenswert?

Toxic schrieb:
> Also ich plaediere ja immer noch fuer die Loesung mit einem
> 32.768kHz-Oszillator.Hat halt den Vorteil,dass man beliebige PICs
> einsetzen kann ohne gleich wieder kalibieren zu muessen....

Ja, das sollte auch stabiler bei Temperaturänderungen sein.

von Markus B. (pspracers)


Lesenswert?

Hallo,

nachdem ich leider den sekundären Oszillator nicht nutzen kann, da ich 
diese Pins (SOSCI, SOSCO) bereits anderwertig in verwendung habe, bleibt 
mir nur noch der Weg über eine Anpassung des CCP1COUNT wertes. Schade, 
hatte mehr Vertrauen in den internen Oszillator gesteckt.
Vielen Dank für die Hilfe! :)
LG Markus

von Volker S. (vloki)


Lesenswert?

Markus B. schrieb:
> Schade, hatte mehr Vertrauen in den internen Oszillator gesteckt.

Selbst 1% Genauigkeit wäre für eine Uhr bei weitem nicht ausreichend.
2,5% widerspricht aber sogar den Spezifikationen hinten im Datenblatt.
(Da steht +-2%)

von Witkatz :. (wit)


Lesenswert?

Markus B. schrieb:
> nachdem ich leider den sekundären Oszillator nicht nutzen kann

Und was ist mit dem primären Oszillator? Sind RA6/RA7 auch schon 
entgültig vergeben, oder kannst du sie für einen externen Quarz frei 
machen.

von Markus B. (pspracers)


Lesenswert?

Ja, da hängt das LCD dran im 4 bitmodus, also da kann ich auch nichts 
mehr rausholen :/

von Felix A. (davinciclaude)


Lesenswert?

Hast du noch einen Timer frei?
Sonst z.B. diesen da:

http://ch.farnell.com/epson/q3103jc010001-sg-3040jc-32-768khz-b/oszillator-spxo-sg-3040jc-32-768khz/dp/1278048RL7

an einen Timer-Clock Eingang hängen und das Periodenregister auf 32767 
setzen, sprich du bekommst jede Sekunde einen Interrupt.

von Markus B. (pspracers)


Lesenswert?

Einzig die Pins RB5 und RB2 sind noch frei, wobei RB2 noch für einen 
Näherungssensor vorgesehen ist, der hätte allerdings den INT2 
draufgeschalten der sich ja auch durchaus eignen würde denke ich :)
nachdem die Platine schon geätzt ist werde ich erstmal die manuelle 
kalibrierung versuchen. Der Epson IC ist aber ein guter Tipp, wenn es zu 
einer neuen Version kommt bei ich den ein!

von Thomas E. (picalic)


Lesenswert?

Markus B. schrieb:
> Der Epson IC ist aber ein guter Tipp, wenn es zu
> einer neuen Version kommt bei ich den ein!

Hmm - wäre es bei einer evtl. neuen Version nicht geschickter, einen 
Quarz am Microcontroller selbst vorzusehen? Notfalls (wenn wirklich alle 
Pins für I/Os verplant sind) halt einen Controller mit ein paar Pins 
mehr...

von Michael D. (Firma: indEAS) (indeas)


Lesenswert?

Noch eine kleine Ergänzung:
Der Timer Pre-Load Wert sollte immer in der ersten Zeile der IRQ Routine 
gesetzt werden. Dann hat der Rest innerhalb der IRQ Routine keinen 
Einfluss mehr auf die Zyklus-Zeit;if-Abfragen brauchen mal mehr mal 
weniger Zeit...

von Volker S. (vloki)


Lesenswert?

Den Timer Preload braucht man nicht, wenn man den Timer so wie schon 
geschehen mit einem CCP Modul kombiniert.
Nur wenn das nicht möglich ist, sollte man IMO keinen Wert laden, 
sondern addieren. Dann ist es relativ egal, zu welchem Zeitpunkt dies 
geschieht.

von Witkatz :. (wit)


Lesenswert?

Markus B. schrieb:
> da ich
> diese Pins (SOSCI, SOSCO) bereits anderwertig in verwendung habe,

Kannst du vielleicht die Pins kurzzeitig für eine Kalibrierroutine frei 
machen? Oder OSCTUNE des PICs bzw. den CCP1COUNT Kalibrierwert mit einem 
Uhrenquarz vor dem Einsetzen in die Schaltung in einem Sockel in einer 
separaten Schaltung kalibrieren?
Vielleicht hilft folgende App Note:
http://ww1.microchip.com/downloads/en/AppNotes/00244a.pdf

Ausserdem sollte
#define CCP1COUNT   19999
definiert werden. Timer wird nämlich beim nächsten Clock Event nach dem 
CCP1 Event genullt. Wenn du CCPR1 auf 20000 definierst, hat die CCP 
Periode 20001 Timer-Takte.

: Bearbeitet durch User
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.