I want to make a LED blink at a frequency of 0.5 Hz. So, I use an Atmega328p, clocked at 16 MHz, and the Timer1 in CTC mode, which triggers an interrupt every milisecond. Yet, my program, in Atmel Assembly, looks like this:
.include "m328pdef.inc"
.def WR1 = R16 ; the working registers
.def WR2 = R17
.def WR3 = R18
.def T1 = r13 ; A 24 bits value used to hold the
.def T2 = r14 ; number of milliseconds
.def T3 = r15 ; In T1 is the LSB, and in T3 the MSB
.cseg
.org $0000
RJMP Start
.org OC1Aaddr ; The address of the Timer1A compare match interrupt
JMP Timer1comp
NOP
.org INT_VECTORS_SIZE
;---------------;
; Settings ;
;---------------;
Start:
; Initialize the Stack Pointer
LDI WR1, HIGH(RAMEND)
OUT SPH, WR1
LDI WR1, LOW(RAMEND)
OUT SPH, WR1
; Timer1A setup
CLR T1 ; Reset the ms counter
CLR T2
CLR T3
LDI WR1, (1<<WGM12)|(1<<CS10) ; Timer in CTC mode, TOP = OCR1A
STS TCCR1B, WR1
LDI WR1, HIGH(15999) ; Must wait 16000 clock cycles in order to
LDI WR2, LOW(15999) ; have a frequency of interrupt of 1 kHz.
STS OCR1AH, WR1 ; ISR doesn't interrupt timer, so have
STS OCR1AL, WR2 ; to set OCR1A to 16000 - 1
LDI WR1, 1 << OCIE1A ; Enable Output Compare A match interrupt
STS TIMSK1, WR1
; I/O settings
SER WR1 ; The LED is plugged on any pin of port D
OUT DDRD, WR1
OUT PORTD, WR1
; Enable interrupts
SEI
;---------------;
; Main Loop ;
;---------------;
Loop:
CLI ; Reset the ms counter
CLR T1
CLR T2
CLR T3
SEI
IN WR1, PORTD ; Toggle port D
SER WR2
EOR WR1, WR2
OUT PORTD, WR1
; HERE WAS MY ERROR
;LDI WR1, BYTE1(988) ; 12 clock cycles are passed
;LDI WR2, BYTE2(988) ; We want to wait 1000 ms
;LDI WR3, BYTE3(988)
; CORRECT WAY
LDI WR1, BYTE1(1000) ; We have already spent 12 clock cycles (750 ns)
LDI WR2, BYTE2(1000) ; which is totally negligible against
LDI WR3, BYTE3(1000) ; our delay of 1000 ms
Wait:
CLI
CP T1, WR1 ; Compare the time (ms) since start of loop
CPC T2, WR2 ; with 1000
CPC T3, WR3
SEI
BRLO Wait ; Go to Wait if time < 1000 ms
RJMP Loop
;-----------------;
; Timer1A ISR ;
;-----------------;
Timer1comp:
; Save the current state on the stack
PUSH WR1
IN WR1, SREG
PUSH WR1
INC T1 ; Increment the ms counter
BRNE Timer1compEnd
INC T2
BRNE Timer1compEnd
INC T3
Timer1compEnd:
; Reset the state
POP WR1
OUT SREG, WR1
POP WR1
RETI
The problem is, when I measure the blink frequency of the LED, instead of finding 0.500 Hz, I see 0.506 Hz. I wonder where this error comes from.
Also, I'm really new to assembly programming, so I'll be glad to receive any kind of advise.
EDIT 1
Finally, I found the origin of my problem: in the main Loop, I load WR1:WR2:WR3 with 988 ms, because the main Loop takes 12 clock cycles. However, even if the main Loop takes 12 clock cycles, it only takes 12 / 16000000 = 0.75 µs, which is totally negligible against the 1 s period. We can assume that this Loop takes 0s: then, I wait 12 ms less than I'd expect to, which represent an error of 1.2 % as Michael Karas said. To correct this, I should set WR1:WR2:WR3 to (1000 - 0.00075), so 1000.
EDIT 2
There was a problem with my previous code: in the Wait loop, I tested whether the desired time was higher or equal to the elapsed time. Thus, If the elapsed time is 1000, then the micro controller would wait until the 1001'th millisecond to branch to the main Loop. So, to correct this, I must test whether the elapsed time is smaller or not than the desired time.