Forum: Mikrocontroller und Digitale Elektronik PIC 16f688 : Timer 1 ungenau


von Uwe P. (quappe)


Lesenswert?

Hallo Zusammen,

aktuell bastel ich an einer Meßschaltung um die Kapazität eines
LIPO-Akku zu ermitteln.Der Ablauf ist folgender :
1. Akku auf 4,15 V laden
2. bei Erreichen von 4,15V noch Zeit x warten
3. Umschalten auf Entladung bis 3,75 V erreicht sind dabei die Zeit 
zählen
4. Laden bis 3,9 V zur Lagerung des Akku

Das Programm an sich funktioniert. Leider habe ich Problem mit dem 
Timer1. Der Timer läuft  nicht mit 2HZ. Die Interrupt-Service-Routine 
ist bewusst klein gehalten. Mit den Presets s. Proggy verliere ich in 2 
Minuten 3 Sekunden.

Trotz stundenlanger Suche kann ich keinen Fehler finden.

Der Code ist in CC5X geschrieben.

Quellcode
1
// RA0 = Analog Input      RC0 = LCD RS-Pin
2
// RA1 = Output Relais Laden    RC1 = LCD E-Pin   R/W = GND
3
// RA2 = Output Relais Entladen    RC2 = LCD D4
4
// RA3 = Input  Taster      RC3 = LCD D5
5
// RA4 = Output n.n      RC4 = LCD D6
6
// RA5 = Output LED      RC5 = LCD D7
7
8
9
#include "16F688.H"
10
#pragma config |= 0b11.0011.1101.0100      // Konfigurations-Wort
11
12
uns16  AD_DEZ, ZAHL;
13
uns16  EIN, ZEH, HUN, TSD;
14
uns16   MERKER1, MERKER2, SEK;
15
bit   LADEN      @ PORTA.1;
16
bit   ENTLADEN  @ PORTA.2;
17
bit    START    @ PORTA.3;
18
char   WAS;    // N=Nichts ; L=Laden ; E=Entladen
19
uns8  LAUF;    // Anzahl Durchlauf
20
float24  mAh;    // Kapazität in mAh
21
22
23
24
#include "int16CXX.H"
25
#pragma origin 4
26
interrupt ISR( void)
27
{
28
 int_save_registers          // W, WAS (and PCLATH)
29
 GIE = 0;            // Interrupts global aus
30
 TMR1ON=0;    
31
 if (TMR1IF)
32
  {
33
  SEK++;
34
  TMR1IF   = 0;          // Timer1 Interruptflag Löschen;
35
   TMR1H  = 11;               // preset for timer1 MSB register
36
   TMR1L  = 71;               // preset for timer1 LSB register
37
   }
38
 TMR1ON=1;
39
 GIE    = 1;          // Interrupts global an
40
 int_restore_registers       // W, WAS (and PCLATH)
41
}
42
43
#define LCD_RS    0b0000.0001             //RS-Pin ist Bit0, LCD Register Select (RC0)
44
#define LCD_CS    0b0000.0010             //E-Pin ist Bit1, LCD Chip Select (RC1)
45
46
#pragma char PORTQ @ PORTC               //PORT auswählen, DB7-Bit5, DB6-Bit4, DB5-Bit3, DB4-Bit2
47
48
#include "delay4.c"
49
#include "math16.h"
50
#include "lcd.c"
51
#include "MATH24F.H"
52
53
54
void ADC(void)
55
{
56
  uns16   ADWERT;          // 16 Bit Variable
57
  uns16   ADSUM;
58
  int    i;
59
  uns16 AD_64;
60
  uns16 AD_128;
61
  ADSUM=0;
62
  for(i=1;i<65;i++)          // Mittelwert über 64 Messungen 
63
  {
64
   GO=1;               // Wandlung wird gestartet
65
   while (GO);             // Hier wird gewartet bis die Wandlung beendet ist
66
     ADWERT = ADRESH;        // Wert aus Registern zusammenbasteln
67
  ADWERT = ADWERT << 8;      // Ergebnis steht im adwert 5V / 10 bit = 5/1024 = 4,8mV Auflösung
68
  ADWERT = ADWERT | ADRESL;
69
    ADSUM  = ADSUM + ADWERT;
70
    Delay1ms(1);   
71
  } 
72
    ADSUM  = ADSUM >> 6;      // 16-Bit Variable = Mittelwert obere 10 Bit
73
  AD_DEZ = ADSUM + ADSUM ;
74
  AD_DEZ = AD_DEZ + AD_DEZ;
75
    AD_DEZ = AD_DEZ + ADSUM;     // AD_DEZ = (ADWERT * 5) / 1,024
76
    AD_64  = AD_DEZ;
77
    AD_128 = AD_DEZ;
78
    AD_64  = AD_64 >> 6;
79
    AD_128 = AD_128 >> 7;
80
  AD_DEZ = AD_DEZ - AD_64 ;
81
  AD_DEZ = AD_DEZ - AD_128 ;
82
83
    ZAHL = AD_DEZ;
84
    TSD=(ZAHL % 10);        // Modulo rechnen, dann den ASCII-Code von '0' addieren
85
    ZAHL /= 10;
86
    HUN=(ZAHL % 10);        // Modulo rechnen, dann den ASCII-Code von '0' addieren
87
    ZAHL /= 10;
88
    ZEH=(ZAHL % 10);        // Modulo rechnen, dann den ASCII-Code von '0' addieren
89
    ZAHL /= 10;
90
    EIN=ZAHL;            // Modulo rechnen, dann den ASCII-Code von '0' addieren
91
}
92
93
94
95
void main(void)                     // Hauptprogramm
96
{
97
98
 INTCON     = 0b.0000.0000;      // erst einmal Interupts aus 
99
 CMCON0     = 0b.0000.0111;      // Comparators Off
100
 ANSEL      = 0b.0000.0001;      // RA0 = Input / Rest Output
101
 TRISA      = 0b.0000.0001;      // RA0 = Input / Rest Output
102
 TRISC      = 0b.0000.0000;      // PORT C alles Ausgänge
103
 PORTC      = 0;          // alle Ausgänge auf 0   
104
 ADCON1     = 0b.0000.0010;      // A/D Conversion Clock 000 = FOSC/32
105
106
 MERKER1=0;
107
 MERKER2=0;
108
109
 //lcdport=0;
110
 //Delay1ms(1000);                    // 1s warten
111
 LCDInit();                         // LCD Initialisieren
112
 Delay1ms(1000);
113
 LCDCls();                // Bildschirm löschen
114
 Delay1ms(1000);
115
116
117
 T1CON   = 0b0011.0000;        // Teiler = 1:8 ; Fosc/4
118
 TMR1ON = 0;            // Timer 1 stoppen  
119
 TMR1H  = 11;               // preset for timer1 MSB register
120
 TMR1L  = 71;               // preset for timer1 LSB register
121
 TMR1IF = 0;            // Timer1 Interruptflag Löschen;
122
 TMR1IE = 1;            // Timer1 Interrupt erlauben
123
 T0IE   = 0;            // Timer0 Interrupt verbieten
124
 PEIE   = 1;            // Pereferie Interrupt erlauben
125
 GIE   = 1;               // Interrups global einschalten
126
 WAS  ='N';
127
 mAh  = 0;
128
 SEK  = 0;
129
 LAUF  = 0;
130
 while (1)
131
132
 {
133
134
  ADCON0 = 0b.1000.0001;        // Channel 000  ADC global einschalten 
135
  Delay1ms(1);
136
  ADC();  
137
                        
138
  LCDPos(2,2);                       // Position der ersten Zeichen 2 Zeile, Position 0
139
  LCDWriteChar((char)EIN+0x30);             // String Anzeigen
140
  LCDPos(2,3);
141
  LCDString(".");
142
  LCDPos(2,4); 
143
  LCDWriteChar((char)ZEH+0x30);
144
  LCDPos(2,5);
145
  LCDWriteChar((char)HUN+0x30);
146
  LCDPos(2,6);
147
  LCDWriteChar((char)TSD+0x30);
148
  LCDPos(2,7);
149
  LCDString("V");
150
  if (SEK != MERKER1)        // blinkende LED ... mal sehen ob das Prog noch läuft
151
   {
152
     if (PORTA.5)
153
        PORTA.5=0;
154
      else 
155
       PORTA.5=1;
156
    MERKER1=SEK;
157
  }  
158
 if (START)
159
  {
160
   SEK=0;
161
   mAh=0;
162
   WAS='L';
163
   LAUF=0;
164
  }
165
166
// Laden erfolgt mit einer Konstantspannungsquelle 4,15 V mit Strombegrenzung 0,5 A.
167
// Entladen erfolgt mit Konstanstspromsenke 0,5 A bis 3,75 V.
168
// Nach dem Drücken des Starttasters so lange laden, bis die Akkuspannung >= 4,15V ist. Dann weiter 1h laden. 
169
// Entladen (Konstantstromsenke 0,5 A ) und die Zeit messen. Fällt die Spannung unter 3,75V Entladen stoppen.
170
// Gemessene Zeit in SEK * 0,5 A / 3600 (60 Min * 60 Sek) = Kapazität in mAh
171
// Zum Lagern sollen LIPO-Akku nur 3,9 V haben. Somit wird nach dem Messen der Akku nur auf 3,9V geladen. 
172
173
174
switch (WAS) 
175
{
176
 case 'N':
177
  LADEN=0;
178
  Delay1ms(500);   // 0,5 Sek. warten
179
  ENTLADEN=0;
180
  if (LAUF == 1 && SEK > 0 )
181
     {
182
       mAh=(SEK*5)/72;        // = Sekunden (doppelt da 2Hz) * 500 (mAh) / 360 S (*2)  
183
     LCDPos(1,0);
184
     ZahlString(mAh);
185
       LCDPos(1,5);
186
       LCDString("mAh");
187
     }
188
    else
189
   {
190
       LCDPos(1,0);
191
       LCDString("Start");
192
     }
193
 break;
194
195
 case 'L':               // Solange warten bis die Spannung >= 4,15 V ist dann 1 Stunde warten anschl. trennen und WAS 
196
  ENTLADEN=0;            // Relais für die Konstantstronsenke ausschalten
197
  Delay1ms(500);           // 0,5 Sek. warten
198
  LADEN=1;              // Relais für die Ladung einschalten 
199
  LCDPos(1,0);
200
  LCDString("L:     ");
201
  if (AD_DEZ >= 3900 && LAUF == 1)  //  im zweiten Ladezyklus und Ladeschlußspannung erreicht 
202
    {
203
   LADEN=0;            // Stoppe laden
204
   Delay1ms(500);           // 0,5 Sek. warten
205
   WAS='N';
206
     mAh=(SEK*5)/72;        // = Sekunden (doppelt da 2Hz) * 500 (mAh) / 3600 S (*2)  
207
     LCDPos(1,0);
208
     LCDString("F");
209
   LCDPos(1,2);
210
   ZahlString(mAh);
211
    }
212
213
  if (AD_DEZ >= 4150 && LAUF == 0)  // im ersten Ladezyklus und Ladeschlußspannung erreicht 
214
   {                // Spannung größer gleich 4,15 V Start Wartezeit 1h
215
    if (TMR1ON)            // Timer läuft bereits 
216
    {
217
    if (SEK >= 720)      // Ruhezeit abgelaufen Starte Entladung
218
      {
219
        LAUF=1;
220
      WAS='E';
221
      TMR1ON=0;        // Stoppe Timer
222
      }
223
        else
224
          {
225
       LCDPos(1,2);
226
           ZahlString(SEK/2); 
227
          }
228
    }
229
   else              // Timer läuft noch nicht -> Starten
230
    {
231
    SEK=0;
232
    TMR1IF=0;          // Interruptflag löschen
233
    TMR1ON=1;          // Starte Timer bis 3600 Sek = 1h
234
     }
235
  }
236
 break;
237
238
 case 'E':
239
  LADEN=0;
240
  Delay1ms(500);             // 0,5 Sek. warten  
241
  if (AD_DEZ >= 3750 )          // > 3,75V
242
     {  
243
    if (!TMR1ON)            // Timer gestopt ?
244
      {
245
        SEK=0;            // Sekundenzähler löschen
246
        TMR1IF=0;            // Interruptflag löschen
247
        TMR1ON=1;            // Timer starten
248
      }
249
     ENTLADEN=1;
250
     }  
251
   else
252
   {
253
     TMR1ON=0;            // Timer stoppen
254
     ENTLADEN=0;            // Stoppe Entladung
255
     Delay1ms(500);           // 0,5 Sek. warten
256
     LAUF=1;              // 1. Zyklus beendet
257
     WAS='L';              // Laden wieder einschalten
258
   }
259
   mAh=(SEK*5)/72;
260
   LCDPos(1,0);
261
   LCDString("E:");
262
   LCDPos(1,2);
263
   ZahlString(mAh);
264
 break;
265
}
266
267
268
}
269
270
271
}

Hat jemand eine Idee oder einen Tip für mich ??

Vielen Dank + viele Grüße

Quappe

von Chris B. (dekatz)


Lesenswert?

Hardware(die sauberste Lösung):

Wenn du den internen Oszillator verwendest, dann einen externe Quarz 
anschliessen und die <config> entsprechend ändern.

oder

Software (gilt immer nur für den grade verwendeten Controller):

Wenn keine Pins mehr für einen externen Quarz frei sind, denn den 
internen  Oszillator kalibrieren - das zuständige Register dafür ist 
OSCTUNE, das "wie" kann man bei www.sprut.de nachlesen (zum. das 
Prinzip, kann man aber auf jeden Controller umschreiben).

Oder die genau Abweichung über eine längeren Zeitraum erfassen und die 
Korrektur rechnerisch in's Programm einbauen.

von Matthias (Gast)


Lesenswert?

Chris B. schrieb:
> Wenn keine Pins mehr für einen externen Quarz frei sind, denn den
> internen  Oszillator kalibrieren - das zuständige Register dafür ist
> OSCTUNE, das "wie" kann man bei www.sprut.de nachlesen (zum. das
> Prinzip, kann man aber auf jeden Controller umschreiben).

Dann solltest du allerdings eine Temperaturregelung vorsehen, damit die 
Frequenz auch halbwegs stabil bleibt.

von Erich (Gast)


Lesenswert?

>Mit den Presets s. Proggy verliere ich
>in 2 Minuten 3 Sekunden.

Also 3 auf 120,  2,5 %
Ohne jetzt das Programm durchzuwühlen:
Eine Abweichung in dieser Grösse kann nur ein Programmierfehler sein.
Auch der interner Oszillator deutlich ist genauer.

von Uwe P. (quappe)


Lesenswert?

Vielen Dank für die Antworten.

Ich sehe es so wie Erich. Der Timer kann kaum so ungenau sein ... das 
wäre irre :-).

Im Quelltext kann ich keinen Fehler finden. Setze ich die Preset-Werte 
höher wird der Timer noch langsamer. Genau das Gegenteil müßte der Fall 
sein, da ab diesen Wert hochgezählt wird.

Die Idee mit OSCTUNE werde ich mal versuchen und mich dann melden. Von 
einem externen Quarz würde ich gern absehen wenn es nicht unbedingt sein 
muß ... lieber ein bisschen mehr rechnen :-)

Ich bin ratlos.

viele Grüße

Quappe

von Michael S. (rbs_phoenix)


Lesenswert?

Wenn du die Möglichkeit hast es zu messen, könntest du auch in der 
Config den internen Oscillator auswählen mit CLOCK-Out an RA4. Das läuft 
nicht mit irgendwelcher Software oder so, d.h. dort sollte man 
nachgucken können, wie genau der interne Oscillator ist.

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.