Sida 1 av 4

TMR0 på PIC16F628A går för långsamt

Postat: 19 januari 2008, 23:39:23
av chrille112
Jag har googlat, läst massor av trådar och läst datablad, men har inte lyckats lära mig hur jag ska få timerinterupts att fungera på min pic.

Kör PIC 16F628A med extern kristall på 20Mhz ansluten på RA6/OSC2/CLKOUT, RA7/OSC1/CLKIN

Jag kör ingen prescaler.

Koden nedan fungerar, men den växlar till sekunder för långsamt - så jag har uppenbarligen tänkt fel någon stans.

Så här har jag tänkt när jag beräknat tiden:

Kod: Markera allt

               Processorhastighet = 20 mhz
               20/4 = 5 mhz (instruction clock är 5 mhz)
               5 000 000 instruktioner per sekund
               5 000 instruktioner per ms
               5 instruktioner per us
               
               Om man startar tmr0 på 1 så räknar den 255 steg, och det tar 51 us
Jag har försökt att implementera zero-subtraction-tjofräs-timer (namn?), men det kanske är här jag har missuppfattat något?

Kod: Markera allt

long TT;
void interrupt() {
     if(TT<=0)
     {
             timeSECOND++;
             TT+=1000000;
     }
     else
     {
        TT-=51; // se kommentar nedan
     }

  TMR0   = 1;
  INTCON = 0b00100000;           // Set T0IE, clear T0IF
}

void main() {
  TRISB = 0;                // PORTB is output
  timeSECOND=10;

  TT=1000000;

  OPTION_REG = 0b10000000;
  TMR0  = 1;
  INTCON = 0b10100000;           // Enable TMRO interrupt

  while(1)
  {
    calcTime();
  }
}


Postat: 20 januari 2008, 01:57:48
av JustNeed
Det skulle hjälpa om du skrev hur mycket långsammare det var.

1. TMR0 har en 1:2 prescaler om man sätter OPTION till 0b1000 0000.
Jag har inte prövat, men för att få 1:1 borde man sätta OPTION.3 till 1 för att assigna prescalern till WDT istället.
Om sekunderna tar ungefär dubbla tiden är det förmodligen det som är felet.

2. Om man ska sätta TMR0 till ett uträknat värde ska man göra det så tidigt som möjligt i interruptrutinen(eller kompensera genom att sätta ett högre värde). Som det är nu hinner den gå några extra cykler vilket leder till att det tar längre tid än om du inte skulle satt den alls.
Om det skiljer några tiondelar så är detta förmodligen felet.

3. I kompileraren jag använder är long 16 bitar vilket inte räcker till att räkna ner från en miljon. Om det är felet borde du fått en varning på det.

FÖ ser jag inte hur calcTime() ser ut

Postat: 20 januari 2008, 03:40:11
av bearing
Det är 256 steg. Lägg till 6 på timern varje gång du är i interruptet (nollställ inte) så får du 250 steg - en siffra som delas jämnt i 5 miljoner, dvs det kan enkelt göras en räknare får 5M cykler. Om du använder prescaler kommer det nog inte bli helt perfekt eftersom adderingen nollställer prescalern (tror jag i alla fall... inte säker).

Postat: 20 januari 2008, 10:23:23
av chrille112
>>1. TMR0 har en 1:2 prescaler om man sätter OPTION till 0b1000 0000.
Verkar som att detta var felet! Satte OPTION till 0b10001000 istället (PSA=1), och nu verkar det som att den går som den ska! :)

>>2. Om man ska sätta TMR0 till ett uträknat värde ska man göra det så tidigt som möjligt i interruptrutinen
Jag har fllyttat if-satsen till mainloopen, så nu kör jag bara TT+=50 i interupt

>>3. I kompileraren jag använder är long 16 bitar vilket inte räcker till att räkna ner från en miljon. Om det är felet borde du fått en varning på det.
Jag har inte fått någon varning, så detta verkar inte vara ett problem hos mig (MikroC)

>>FÖ ser jag inte hur calcTime() ser ut
Detta är en rätt oviktig funktion i sammanhanget, den lägger bara på en minut när det gått 60 sek osv

>>Det är 256 steg. Lägg till 6 på timern varje gång du är i interruptet (nollställ inte) så får du 250 steg - en siffra som delas jämnt i 5 miljoner,
Bra idé! Kör på 250 steg nu istället

Tack för er hjälp! Nu är det bara att låta den stå på ett dygn och se hur exakt den är... :wink:


** EDIT: Jag ser redan nu att den verkar gå fel cirka 10 sekunder per minut :(
Kollade i asm-koden för interupt. Motsvarar all denna kod TT+=50 ?

Kod: Markera allt

;WakeupLight.c,73 :: 		TT-=50; // se kommentar nedan
$0015	$3032			MOVLW	50
$0016	$00F0			MOVWF	STACK_0
$0017	$01F1			CLRF	STACK_0+1
$0018	$01F2			CLRF	STACK_0+2
$0019	$01F3			CLRF	STACK_0+3
$001A	$0870			MOVF	STACK_0, 0
$001B	$02A0			SUBWF	_TT, 1
$001C	$0871			MOVF	STACK_0+1, 0
$001D	$1C03			BTFSS	STATUS, C
$001E	$0F71			INCFSZ	STACK_0+1, 0
$001F	$02A1			SUBWF	_TT+1, 1
$0020	$0872			MOVF	STACK_0+2, 0
$0021	$1C03			BTFSS	STATUS, C
$0022	$0F72			INCFSZ	STACK_0+2, 0
$0023	$02A2			SUBWF	_TT+2, 1
$0024	$0873			MOVF	STACK_0+3, 0
$0025	$1C03			BTFSS	STATUS, C
$0026	$0F73			INCFSZ	STACK_0+3, 0
$0027	$02A3			SUBWF	_TT+3, 1
Isåfall måste jag ju kompensera för den i timern. 19 instruktioner, 3.8 µS??
Borde bli 76000 µS per sekund = 76 ms
4,56 sekunder extra per minut? Eller har jag räknat fel här?

Postat: 20 januari 2008, 11:12:50
av Marta
Det finns bara ett sätt att få exakt timing och det gäller i synnerhet för högnivåspråk: Kör timern free-running och ladda aldrig om den.

Det udda delningstal som sedan behövs i programvaran är mycket enkelt att hantera. Finns i andra trådar, vill inte tjata om det nu igen.

Behöver Dddu inte den höga upplösning som Din snabba interrupt ger och TMR1 är ledig så använd den istället. Det slukar bara CPU att ha en onödigt snabb interrupt, speciellt i högnivå med dess massiva overhead.

Postat: 20 januari 2008, 11:27:37
av JustNeed
Marta: Om man bara ska ha processorn till att räkna tid och har PS 1:1 borde det väl gå att få exakt timing om man räknar lite, eller?

Sätter du TMR0 = X i slutet av interruptet måste du kompensera för det, ja (+interrupthoppet samt registerundansparning o.dyl). Jag mins inte riktigt, men det är möjligt att TMR0 dessutom står still någon instruktionscykel efter att den har blivit satt (borde stå i databladet isf).

Postat: 20 januari 2008, 11:28:32
av Icecap
Jag håller med marta och tycker att det är fel att börja trimma på detta vis. Har du inte Timer 1 tillgänglig? Med den kan du ställa hårdvaran så att du får lämplig tid mellan varje interrupt.

Om du är låst till Timer 0 kan du ju köra så att du har en variabel som är stor nog. 5MHz delad med 256 är 51,2µs. Om du då adderar 512 till den stora variablen och sedan kollar om den är >= 10.000.000.

Är den det drar du av 10.000.000 och klickar upp sekunderna ett steg osv.

Börjar den från noll kommer första sekund att ha en rest på 128 och räknar man om lite på det hela kan du lägga till 4 till variabeln och kolla mot (och subtrahera) 312500, det ger samma resultat.

På det vis kommer sekunderna att vara lite ojämna (±0,5 s) men långtidsstabiliteten är korrekt.

Kollar man mot 31250 får man en upplösning på 1/10 sek och plötsligt kan det passas in i en unsigned int med ett jitter på ±0,05 sek och kollar man mot 3125 blir upplösningen 1/100 sek och jitter på ±0,005 sek.

Postat: 20 januari 2008, 13:50:43
av chrille112
Tanken med detta projektet är en klocka - mest för att lära sig grunderna i PIC. Så att metoden är krävande gör egentligen inte så mycket, men självklart är det bra att lära sig att göra rätt från början.

>>Det finns bara ett sätt att få exakt timing och det gäller i synnerhet för högnivåspråk: Kör timern free-running och ladda aldrig om den.
Vet inte riktigt vilka trådar du menar, kan du länka?

>>Har du inte Timer 1 tillgänglig?
Jo, men jag har en LCD ansluten på pin RB6/T1OSO/T1CKI/PGC och RB7/T1OSI/PGD, så därför känndes det bökigt att flytta kristallen dit. Men det låter som att det vore en god idé att byta till den istället? På vilket sätt är den att föredra? Vad jag förstår så är skillnaden att den är på 16 bit, och därför kan man använda en större prescaler?

>>10.000.000 och klickar upp sekunderna ett steg osv.
Detta låter som en bra idé, men varför skulle detta bli mer exakt än 1.000.000 som jag kör?

Postat: 20 januari 2008, 16:57:00
av Icecap
Men.... varför skulle du flytta något jä... kristall???

Du kanske skulle läsa på lite rörande Timer 1.... och kanske kolla extra noga med klock-källor...

Postat: 20 januari 2008, 17:43:45
av bearing
Håller med om att timer1 är ett bättre val. Men om du gör som jag sa, att du aldrig nollställer timern, utan adderar istället så behöver du inte kompensera för något (förutsatt att adderingen sker nära efter omslaget). Du kan använda en unsigned 16-bitars variabel istället för en 32-bitars (som räknar till 20000 och adderas med 1 istället) så blir det mindre kod.

Postat: 20 januari 2008, 18:57:21
av chrille112
>>Men.... varför skulle du flytta något jä... kristall???

Just nu är den ansluten på RA7/OSC1/CLKIN och RA6/OSC2/CLKOUT (enligt det här exemplet http://www.voti.nl/blink/pics/b-628-150.gif)

Den här texten i databladet tolkade jag som att kristallen måste vara ansluten på RB6 för att kunna köra extern, annars används den interna kristallen. Men jag kanske har missuppfattat det totalt? Är det så att den interna kristallen i detta fallet är den externa som hela processorn körs på?

TMR1CS: Timer1 Clock Source Select bit
1 = External clock from pin RB6/T1OSO/T1CKI/PGC (on the rising edge)
0 = Internal clock (FOSC/4)

bearing, menar du så här? Hur vet jag isåfall när jag ska öka på sekunderna?

Kod: Markera allt

void interrupt() {
  TMR1   += 6;
  PIE1.TMR1IE = 0;
}
Ber om ursäkt om jag ställer korkade frågor, men jag har verkligen försökt att förstå databladet :( En dag lovar jag att vara lika tålmodig som ni och hjälpa nybörjare... :wink:

Postat: 20 januari 2008, 20:59:02
av Icecap
Om din systemklocka duger när du kör Timer0 duger den också med Timer1!

Jag har gjort ett antal projekt av den typ och jag har aldrig haft ett sekundärt kristall på den.

Man KAN koppla på ett lågfrekvent lågenergi kristall på den extra oscillator (läs: 32768Hz kristall) som finns men man MÅSTE inte. Systemklockan är systemklockan och den duger fint att köra Timer1 på, det är den som heter "Fosc/4" vilket betyder att det är frekvensen på CLKIN/4.

Du kan även använda Timer2 till att få delat ner den interna 5MHz (@ 20MHz kristall) till ett lämpligt värde.

Postat: 20 januari 2008, 21:12:00
av chrille112
Okej, men systemklockan i detta fallet är alltså den externa kristallen på 20 mhz jag har nu?

Kretsen har en intern klocka på 4 Mhz, men den har jag läst är alldeles för oexakt för att kunna använda i klocka-sammanhang.

Postat: 20 januari 2008, 21:38:16
av Icecap
Om du har satt CONFIG till extern HS kristall är svaret ja: systemklockan är då kristallens frekvens delad med 4, alltså 5MHz. Observera att det skrivs "MHz" då "mhz" som bäst betyder millihertz och 1 mhz (eller mHz som det egentligen ska skrivas) är 1 miljard gånger mindre än 1 MHz.

Inom elektroniken kan det finnas tillfällen där båda värden kan verka rimliga och då är det en jävla skillnad att skriva fel.

Postat: 20 januari 2008, 22:50:37
av sodjan
Om det är själva klockdelen av applikationen som är det viktiga,
så skulle jag fundera på en kristall som ger en bas-klocka (efter
delning via TMR0 eller 1) som går "jämt upp" i 1 sek.

T.ex :

6,5536 MHz (ELFA 74-503-64)
TMR0 free-running, 1:64 prescaler -> 100 Hz interrupt (d.v.s 10 ms upplösning)
TMR0 free-running, 1:256 prescaler -> 25 Hz interrupt (d.v.s 40 ms upplösning)

19,6608 MHz (ELFA 74-507-94)
TMR1 free-running, 16-bit mode -> 75 Hz interrupt.

Sedan är det bara att räkna till 25, 75 eller 100 så får man en sekund jämt...