Sida 1 av 2

Hur får jag en timer att räkna i sekunder?

Postat: 11 januari 2008, 13:45:45
av Urahara
Hej hej!!
Jag håller på att programmera en klocka men har fastnat :( Jag arbetar med en PIC16F886 och programmerar i C++ och MikroC och jag har tänkt att få en timer att räkna på något sätt, helst i sekunder men millisekunder och liknande går också bra. Anledningen är som sagt att jag vill programmera en klocka och vill då att timern är själva räknaren som resten av programmet är beroende av.
Tanken är att timern ska uppdatera en variabel varje sekund. Jag vet inte heller om timern ger ut en signal för att uppdatera variabeln och att jag måste skriva något speciellt kommando för att utnyttja signalen eller om allt görs automatiskt. Jag skulle vara väldigt glad om någon kunde hjälpa mig med detta.

Tack på förhand!!!

Postat: 11 januari 2008, 14:08:10
av Niklas-k
Jag kan ju hjälpa dig med en bra länk : http://www.mikroe.com/forum/viewtopic.p ... hlight=rtc

Förvisso i Basic men går att översätta. Jag har gjort liknade men i Mikropascal.

Postat: 11 januari 2008, 14:19:32
av Urahara
OK tackar så mkt... jag ska kolla på det

Postat: 11 januari 2008, 15:50:24
av Marta
Det enklaste sättet är att låta timer1 köra fritt och ge en interrupt varje gång den "vänder". Det kräver lite mera av programmet, men det är inget svårt alls. Fördelen är att timingen är helt exakt och dessutom kalibrerar Du klockan i mjukvara så att den går exakt rätt.

Kod: Markera allt

interruptrutin
  dekrementera heltal
  om noll
    addera kalibreringskonstant till deltal
    addera med carry kalibreringskonstant till heltal
    räkna upp klockan en sekund
  /om noll
/interruptrutin

Postat: 12 januari 2008, 00:14:49
av $tiff
Lite smålustigt nog är att den bästa lösningen är en extern självkompenserande kristalloscillator som ger dig pulser på 1 Hz (eller ofta 32,768 kHz). Detta gör du till ett avbrott via valfri interruptpinne. Detta är det lättaste sättet att få en noggrannhet neråt fåtal ppm.
Exempel på sådan krets är DS32k

Postat: 12 januari 2008, 11:04:40
av Rick81
Om du kan tolerara ett interupt var 256 us, så kan du har en int som adderas med 256 varje period och sedan när det är en period kvar sätts tmr0 till det resterande värdet.

Jag skulle ha gjort något sådant här med kristall 4 MHz. (observera att jag skriver bara strukturen du måste själv modifiera den för att den ska fungera. Värden måste nog modiferas också)

Kod: Markera allt

int timeUs;
int timeMs;
int timeSec;
void interupt()
{
   if(T0IF)
   {
      timeUs = timeUs + 256;   // varje period 256 us
      if(timeUs > (1000 - 256))
      {
         // kompensera för att varje interupt period är 256 us
         // sista loopen dras det bort.
         TMR0 = 1000-256*3 - compensation;   // compensation = kompensation för tiden det tar att exekvera interrupt rutinen
         if(timeUs >= 1000)
         {
            timeUs = 0;
            timeMs++;
            if(timeMs >= 1000)
            {
               timeMs = 0;
               timeSec++;
            }
      }
      T0IF = 0;
   }
}
Observera dock att en kristall inte är helt exakt och om du måste ha exakthet är $tiffs alternativ bättre.

Postat: 12 januari 2008, 15:37:00
av bengt-re
En separat RTC som ligger på I2C är annars trevlig - lite dyrare, men väldigt trevligt i alla applikationer där man behöver klocka till en uC.

ST har en RTC ( ST M41T00S) med "allt" godis. Kostar iofs omkring 30 pix i småserier, men väldigt trevlig krets. Det finns billigare, men denna vet jag iaf att det är lätt att få att snurra. Bra datablad också som vanligt ifrån ST.

http://www.farnell.com/datasheets/84543.pdf

Postat: 13 januari 2008, 00:01:00
av chrille112
I Rick81:s svar så kan man väl hoppa över mS-delen och köra på uS hela vägen? Det finns väl ingen anledning att räkna mS?

Kod: Markera allt

         if(timeUs >= 1000000)
         {
               timeSec++;
         }
Kan man få fram på något sätt hur lång tid interupt-funktionen tar, så att man kan ställa in kompensationen korrekt?

Postat: 13 januari 2008, 00:43:02
av TomasL
Det finns två väldigt enkla alternativ, antingen kan du köra tmr0 eller vilken det nu är via en extern oscuillator, typ 32768 hz, eller också så kör du den på vanligt sätt, laddar den med ett frö så du får ett INT när du vill ha det, typ varje ms eller så.

skulle personligen göra int rutinen kort typ:

Kod: Markera allt

void interrupt (){

bGIEH=0; //stäng int
bGIEL=0; //stäng int
tmr0=seed; //Återladda tmr0
ms=ms+1; //öka ms räknaren
bGIEH=1; //starta int
bGIEL=1; //starta int
}
Ungefär så, du får anpassa det till din proc och dialekt.

uppdateringar av övriga variabler typ HMS osv sköter du i main()

Detta förutsätter att du sätter prescaler och seed så tmr0 tippar över till 0 varje ms och att tmr0INT är påslaget.

Postat: 13 januari 2008, 01:10:03
av oJsan
Kikade lite snabbt i databladet. I kapitel 6.4, som handlar om Timer1, kan man läsa detta:

Kod: Markera allt

A low-power 32.768kHz crystal oscillator is built-in
between pins T1OSI (input) and T1OSO (amplifier
output). The oscillator is enabled by setting the
T1OSCEN control bit of the T1CON register. The
oscillator will continue to run during Sleep.
The Timer1 oscillator is identical to the LP oscillator.
The user must provide a software time delay to ensure
proper oscillator start-up.
Kan det vara användbart?

Själv skulle jag ha gjort som ThomasL beskriver (tack, då slapp jag skriva det ;) )
Jag får väl stå för exemplet då istället:
Med 4MHz kristall får du 1MHz intern klocka på PIC. Använd prescaler för att dela med 8. Då kommer 16-bit-timern öka med 1 steg var 8:e us.
För att få 1s behöver vi ett avbrott efter 1000000/8=125000 steg, vilket är mer än räknaren klarar av (max 2^16).
Om vi istället nöjer oss med ett avbrott var 500:e ms så behöver vi ett avbrott efter 500000/8=62500 steg.
Det 'frö' som Thomas skriver om ska då vara 2^16-62500 = 3036
Byt den externa kristallen till 2MHz och du får avbrott varje sekund! =)

Postat: 13 januari 2008, 12:41:12
av Rick81
chrille112: Jepp det går att skippa ms räknaren. Bara man tänker på kompenseringen.

Kompensering tas lättast fram genom att simulera koden i MPLAB. Nollställ stopwatchen med brytpunkt i början på interrupt och kör fram tills TMR0 sätts till värde. Tänk på att denna tid förloras vid varje interupt, dvs varje 256 us.

TomasL lösning gör att man slipper ha en us räknare, men man får tänka på att man måste kompensera för tiden i interupten på denna också. (Om man vill ha exakt).

Postat: 13 januari 2008, 12:50:12
av TomasL
Det borde bli 6 instuktionscykler (beroende på hur bra kompilatorn är) i interrupten, tiden beror ju på klockan.
Dvs 6 us vid 4 MHz, vilket måste dras av från "seeden"
Sedan kostar interrupten några cyckler, returen kostar ingenting, då räknaren startar om igen direkt.
Det hela beror ju naturligtvis hur exakt man vill ha det.

Postat: 13 januari 2008, 15:38:29
av Marta
Starta om räknaren med att addera in värdet, om den nu inte skall få lov att köra free-run. Då kompenseras det för de cykler som tickat iväg från att den "vänt" och genererat interrupt tills att den skrivs om.

Vad är det förresten för fel på att använda breesenham-räknare i mjukvara? Det blir ändå aldrig helt exakt med en kristall och man behöver kunna kalibrera. Då är det en fördel att kunna göra detta i mjukvara med känd steglängd istället för att vrida trimkonding.

Dessutom kan man använda godtycklig kristallfrekvens och även optimera kringkomponenterna för stabilitet utan att tänka på frekvensens absolutvärde.

Postat: 13 januari 2008, 17:26:43
av sodjan
Enklast om man ska köra med en free-running timer är att välja en
kristall frekvens så att man ine behöver ladda om räknaren alls.
Det finns flera frekvenser som är jämna potenser av 2 och alltså
går att dela ner jämt till 1 sek. Kolla t.ex tabellen på :
http://en.wikipedia.org/wiki/Crystal_oscillator
Där finns flera frekvenser som delas jämt ner till lämpliga jämna
delar av en sekund.

Notera att det viktiga är att man får ett antal timer interrupt/sekund
som går jämt upp, om det sedan är 1, 10, 32, 58 eller något annat
antal spelar ingen roll, det håller man ordning på i ISR'en...

Postat: 13 januari 2008, 19:22:50
av Marta
Den lilla haken är bara den att frekvenen som står stämplad på kristallen är
ju ett börvärde, ärvärdet brukar aldrig vara exakt nog för att användas som
tidbas i en klocka. Frekvensen blir udda om man inte kalibrerar i hårdvara.

Eftersom det är svårt och kräver en räknare med jättefin tidbas så är det
för en amatör bättre att kalibrera i mjukvara. Då går det att räkna fram den
nya delaren utifrån vetskap om den gamla och det fel den producerar över en
längre tidsperiod. Därigenom kan man få exakt kalibrering utan den fina
räknaren med rubidiumnormal, det räcker med klockan i TV och en
"gatetime" på en dag, vecka eller månad eller kanske ännu längre allt efter
som noggrannheten närmar sig det rätta värdet.

Det är så enkelt att implementera en breesenham-delare i mjukvara att jag
ser ingen anledning att låta bli. Tekniken är helt enkelt för dåligt känd, det
är antagligen orsaken till att inte fler använder den. Fördelen är enorm att
kunnaställa en delare på t.ex. 8.586925814 istället för bara heltal.

Med 4 bytes, heltal plus tre deltal, så kan man ställa delaren i steg på
1/16777216-delar och det räcker för alla normal kristaller. Det enda som
behöver göras är 4 stycken 1-bytes add with carry. Svårare är det inte.

Här är assemblerkod, inklusive variabeldefinitioner.

Kod: Markera allt


BCNTER	*			BREESENHAM COUNTER FOR SECONDS TICK
.0CNT	.BS 1			WHOLE NUMBERS
.1CNT	.BS 1			FIRST FRACTION, 1/256
.2CNT	.BS 1			SECOND FRACTION, 1/65536
.3CNT	.BS 1			THIRD FRACTION, 1/16.777.216

.0LOAD	.EQ EESAVE.B0LOAD	WHOLE NUMBERS RELOAD
.1LOAD	.EQ EESAVE.B1LOAD	FIRST FRACTION RELOAD
.2LOAD	.EQ EESAVE.B2LOAD	SECOND FRACTION RELOAD
.3LOAD	.EQ EESAVE.B3LOAD	THIRD FRACTION RELOAD

	*			NOMINAL DIVIDER IS 9.536 743 164
.0DFALT .EQ 9			WHOLE NUMBERS DEFAULT
.1DFALT .EQ 137 		FIRST FRACTION DEFAULT
.2DFALT .EQ 104 		SECOND FRACTION DEFAULT
.3DFALT .EQ 0			THIRD FRACTION DEFAULT


DOTIME	*			DO BREESENHAM DOUNTER
	DSZ BCNTER.0CNT 	COUNT DOWN WHOLE NUMBER
	RTS			NO COUNTOUT-DONE

	*			RELOAD BY ADDING IN FRACTIONAL COUNT VALUE

	LOD A,BCNTER.3LOAD	GET 3:RD FRACTION
	ADD BCNTER.3CNT,A	ADD IT IN

	LOD A,BCNTER.2LOAD	GET 2:ND FRACTION
	SFC CY			WAS THERE A CARRY?
	ISZ A=BCNTER.2LOAD	YES-GET VALUE INCREMENTED BY ONE
	ADD BCNTER.2CNT,A	ADD IN, BUT ONLY IF NO NEW CARRY-OVER

	LOD A,BCNTER.1LOAD	GET 1:ST FRACTION
	SFC CY			WAS THERE A CARRY?
	ISZ A=BCNTER.1LOAD	YES-GET VALUE INCREMENTED BY ONE
	ADD BCNTER.1CNT,A	ADD IN, BUT ONLY IF NO NEW CARRY-OVER

	LOD A,BCNTER.0LOAD	GET WHOLE NUMBER
	SFC CY			WAS THERE A CARRY?
	ISZ A=BCNTER.0LOAD	YES-GET VALUE INCREMENTED BY ONE
	ADD BCNTER.0CNT,A	ADD IN, BUT ONLY IF NO NEW CARRY-OVER

	*			A SECOND HAS ELAPSED, COUNT UP TIMEKEEPING