Applications of AVR single chip controllers AT90S, ATtiny, ATmega and ATxmega DCF77 synchronized alarm clock with LCDDate and time |

For counting down a second at 1 MHz clock frequency a counter is needed that can countdown from 300,000 to zero. For this we need 20 bits, a single byte or a 16-bit counter is insufficient for that. Such a counter in assembler can be constructed as down-counter or as up-counter. A down-counter has first to set a delay loop constant, then to down-count (LSB first, if carry: MSB next, if carry HSB last. Hint: use SUB or SUBI to down-count instead of DEC because DEC does not influence the carry flag when the register is decreased from zero to 0xFF.

That would produce a source code like this:

```
ldi R16,BYTE1(cDelay) ; Set start values in R18:R17:R16
ldi R17,BYTE2(cDelay)
ldi R18,BYTE3(cDelay)
DelayLoop:
subi R16,1 ; Down-count LSB
brcc DelayCheck ; No carry: check zero
subi R17,1 ; Down-count MSB
brcc DelayCheck ; No carry: check zero
subi R18,1 ; Down-count HSB
DelayCheck:
tst R16 ; Check LSB
brne DelayLoop
tst R17 ; Check MSB
brne DelayLoop
tst R18 ; Check HSB
brne DelayLoop
DelayEnd:
; One second is over
```

When up-counting the source code is similar:
```
clr R16 ; Start from zero
clr R17
clr R18
DelayLoop:
subi R16,-1 ; up-count LSB, carry flag reversed!
brcs DelayCheck
subi R17,-1 ; Up-count MSB, carry flag reversed!
brcs DelayCheck
subi R18,-1 ; Up-count HSB, carry flag reversed!
DelayCheck:
cpi R16,BYTE1(cDelay)
brne DelayLoop
cpi R17,BYTE2(cDelay)
brne DelayLoop
cpi R18,BYTE3(cDelay)
brne DelayLoop
DelayEnd:
; One second over
```

Now, which number cDelay should be chosen for a second? For that
we need to know how many clock cycles the whole loopings require.
As each BRCC/BRCS and BREQ/BRNE needs two clock cycles in case
it jumps and one clock cycle in case it does not jump, a very
sophisticated formula would be needed to calculate the complete
number of cycles from a constant cDelay, and vice versa.
The only way out of this is to construct the different branches in a way that they consume the same number of execution cycles. Each line executed is analyzed for its clock cycles and, with the help of NOP instructions and jumps, delayed so that each branch needs the same clock cycles. In case of the down-counting the subtraction phase so would look like this:

```
DelayLoop: ; 0 clock cycles
subi R16,1 ; + 1 = 1 clock cycle
brcc Delay1 ; + 1 = 2 for not jumping, + 2 = 3 for jumping
subi R17,1 ; + 1 = 3 clock cycles
brcc Delay2 ; + 1 = 4 for not jumping, +2 = 5 for jumping
subi R18,1 ; +1 = 5 clock cycles
brcc Delay3 ; +1 = 6 for not jumping, + 2 = 7 for jumping
rjmp DelayCheck ; + 2 = 8 clock cycles
Delay1: ; 3 clock cycles
nop ; + 1 = 4 clock cycles
nop ; + 1 = 5 clock cycles
Delay2: ; 5 clock cycles
nop ; + 1 = 6 clock cycles
nop ; + 1 = 7 clock cycles
Delay3: ; 7 clock cycles
nop ; + 1 = 8 clock cycles
DelayCheck: ; 8 clock cycles
; (Now check if all zero)
```

Now the complete execution between DelayLoop: and DelayCheck:
needs 8 clock cycles, whereever all those branches run along.
A predictable execution time for this. Now the same for
the DelayCheck: that checks if all registers are zero:
```
DelayCheck: ; 8 clock cycles
tst R16 ; + 1 = 9 clock cycles
brne DelayCont1 ; + 1 = 10 for not jumping, + 2 = 11 for jumping
tst R17 ; + 1 = 11 clock cycles
brne DelayCont2 ; + 1 = 12 for not jumping, + 2 = 12 for jumping
tst R18 ; + 1 = 13 for not jumping, + 2 = 14 for jumping
breq DelayEnd ; + 1 = 14 for not jumping, + 2 = 15 for jumping
rjmp DelayLoop ; + 2 = 16
DelayCont1: ; 11 clock cycles
nop ; + 1 = 12 clock cycles
DelayCont2: ; 12 clock cycles
nop ; + 1 = 13 clock cycles
nop ; + 1 = 14 clock cycles
rjmp DelayLoop ; + 2 = 16
DelayEnd: ; 15 clock cycles
nop ; + 1 = 16 clock cycles
; One second is over
```

So the whole number of clock cycles N needed isof which the 3 is for the initial phase of setting the registers, and cDelay for one second can be calculated simply like this:

The result for N = 1,000,000 is 62,499 (rounded down), which corresponds to 3 + 62,499 * 16 = 999,987 cycles. If you need to be more exact (see accuracy discussion below), you can add 13 NOP instructions at the end.

Note that in our example R18 is zero, because 62,499 fits into two registers and we can skip that, but without removing the code (because that would change code execution times and with a shorter loop would go beyond the 16 bit limit).

With those instruments we can construct whatever type of counting loops (three, four, five, etc. bytes) to fit to any time (minutes, hours, days and years) to arrive at an exact timing.

If you need basics of AVR timers, please consult this web-page: there is anything about timers in assembler, about crystals, etc.

Unfortunately timers and their prescalers work in binary mode, so they like the two's more than the ten's. If our clock rate is at 1 MHz (which is more the ten's domain) a timer which divides by 64 yields 15,625. The next higher binary divider, 128, already has a frequency which is not an integer value but has a fraction following. Not to speak about divider rates of 256, 2,048 or 16,384 or even higher (for which all timers have a prescaler).

This here are the divider rates when using possible prescaler values in 8- and 16-bit timers. We see that the 8-bit timer has rather low divider rates even with a prescaler of 1,024. To come down to one second our xtal would have to be either of the 32,768 Hz type or hand-crafted to our selected frequency. And: the controller would act but would act like a lame duck at those low frequencies.

More promising is a 16-bit timer with a prescaler of 64. Xtals with a frequency of 4.194,304 MHz are sold on each street corner or electronics store and cost around 25 Cents. With that the seconds timer is already complete, just do the following:

- attach the Xtal and two ceramic capacitors of 18 pF
to GND to an AVR that has an internal Xtal oscillator,

- change the fuses of the AVR to use an external xtal with medium or high frequency,
- copy the interrupt vector table of the controller with
all
*RETI*except for the Reset vector (*RJMP Start*) and the overflow interrupt for TC1 (*RJMP TC1OvfIsr*), - write an overflow interrupt service routine "TC1OvfIsr"
for the timer with the two instructions
*SET*(set the T flag in the status register) and*RETI*(Return from Interrupt), - in the main program "Start:" init the stack pointer,
- then set the 16-bit timer to normal operation and the prescaler to 64,
- enable TC1 overflow interrupts (TOIE1) and the interrupts
generally with
*SEI*, and - ask in a loop if the seconds flag T is set, if yes clear
it with
*CLT*and do what you'll want to do in each second.

- Those who find a 4.1 MHz clock too fast and want to save battery current, or
- who do not find such an Xtal on their street corner,
- who need the 16-bit timer for other more valuable purposes, e. g. to play a melody with it when the clock reaches 07:30 in the morning,

In any case the selected xtal frequency divided by the prescaler and by the timer used has to be a pure integer value. If the result of the division is not one but an integer value: use an 8-bit (smaller or equal 256) or a 16-bit register (256 to 65,536) to divide the resulting frequency down to one.

With the above named xtals an 8-bit timer has the following register divider values.

These are the crystals that can be used with an 8-bit timer, the different prescalers and a register divider. Red marked register dividers are 16-bit, green marked ones are 8 bit. Only at 3.072 MHz a 16-bit divider is necessary.

In Assembler dividing within the overflow interrupt service routine goes like this:

```
; Calculation constants
.equ clock = 2097152 ; Xtal frequency
.equ prescaler = 1024
.equ timertop = 256
.equ second_divider = clock / timertop / prescaler ; = 8
;
; Registers
.def rSreg = R15 ; Save status
.def rSeconddivider = R17 ; Counter down
.def rFlag = R18 ; Flag register
.equ bSek = 0 ; Second flag in flag register
;
Tc0OvflwInt:
in rSreg,SREG ; Save SREG
dec rSeconddivider ; Divide by 8
brne TC0OvflwIntRet ; Not yet zero
ldi rSecondendivider,second_divider ; Restart divider
sbr rFlag,1<<bSek ; Set seconds flag: one second is over
Tc0OvflwIntRet:
out SREG,rSreg ; Restore SREG
reti
```

Many nice clock frequencies such as 1 or 2 MHz do not work with those methods. In those cases the timer has to be made more ten-friendly by clearing it when he reaches a count of 100 or 250. This can be done in the so-called CTC mode (Clear Timer on Compare). If the counter reaches the value that is stored in its Compare Port Register COMPA, the next count will restart the timer at zero (in 16-bit timers the Input Capture Port Register ICR can also be used for the comparison purpose). The one extra clock cycle means that the compare value has to be one smaller than the desired division rate. If you need the same timer also for other purposes, e.g. as a pulse width generator, the CTC mode setting also alters its resolution.

With 1 MHz clock the prescaler can be set to 64 (which means 15,625 kHz on the timer input), can set the compare value to 124 (dividing with CTC by 125) and dividing with a register divider by 125 to yield one second. Many other frequencies can be counted down like that to an exact second.

Provided that we have

- the counter running,
- its interrupts enabled (TOIE0/TOIE1 with an overflow int resp. OCIE0A/OCIE1A with a compare match A int),
- the main interrupt flag I in SREG is set,
- the interrupt vector is properly set and branches to the service routine, and
- the stack is initiated and works properly.

Top of page | Seconds | Formats | Date and time |

Instead of dividing the day into two halves (AM and PM) we use here the European notation for hours (0 to 23).

Writing the numbers as '0' to '9' means that the ASCII respresentation is meant. Even assemblers understand that:

```
ldi R16,'0' ; Load ASCII 0
ldi R17,'7' ; Load ASCII 7
ldi R18,':' ; Load : character as separator
```

If we want to write eight characters to SRAM we reserve space
in SRAM for it:
```
.dseg
TimeAscii:
.bytes 8
```

and write the following code:
```
.cseg
ldi ZH,HIGH(TimeAscii) ; Point to SRAM address
ldi ZL,LOW(TimeAscii)
ldi R16,'0' ; Load ASCII 0
st Z+,R16 ; Store in SRAM and increase address
ldi R16,'1'
st Z+,R16
ldi R16,':'
st Z+,R16
ldi R16,'2'
st Z+,R16
ldi R16,'3'
st Z+,R16
ldi R16,':'
st Z+,R16
ldi R16,'4'
st Z+,R16
ldi R16,'5'
st Z,R16
```

A different formulation for that would be:
```
ldi YH,High(TimeAscii) ; MSB of the SRAM address to YH
ldi YL,Low(TimeAscii) ; LSB to YL
ldi R16,'0' ; ASCII-Null in R16
st Y,R16 ; To Hour-Tens
inc R16
std Y+1,R16 ; To Hour-Ones
inc R16
std Y+3,R16 ; To Minutes-Tens
inc R16
std Y+4,R16 ; To Minute-Ones
inc R16
std Y+6,R16 ; To Second-Tens
inc R16
std Y+7,R16 ; To Second-Ones
ldi R16,':' ; Separator to R16
std Y+2,R16 ; To the first separator location
std Y+5,R16 ; To the second separator location
```

STD (and when rading: LDD) does not change Y but temporarily
adds the displacement behind + and writes the byte in R16
to this location. This works with Y and Z, but not with X.
To increase the second-ones by one second. If this leads to the ASCII character behind '9', it has to restart with '0' and has to increase the second-tens. With the constant address in Y that goes like that:

```
ldi YH,High(TimeAscii) ; MSB of the SRAM address to YH
ldi YL,Low(Zeit) ; LSB to YL
ldd R16,Y+7 ; Read second-ones to R16
inc R16 ; Increase second-ones by one
std Y+7,R16 ; Write increased second-ones
cpi R16,'9'+1 ; Compare with ASCII code for next char behind nine
brcs Done ; If carry set no overflow to next higher digit
ldi rmp,'0' ; Restart second-ones
std Y+7,R16 ; Write ASCII-zero to second-ones
ldd R16,Y+6 ; Read second-tens
inc R16 ; Increase second-tens
std Y+6,R16 ; and write back to second-tens
cpi R16,'6' ; Second-tens at six?
brcs Done ; No, ready
; ... Minutes and hours similarly
Fertig:
; ... Done with the clock increase
```

When increasing the hour, those two criteria come into play:
- if the hour-ones are larger than '9', and
- if the hour-ones are four AND the hour-tens are '2'.

The comparison if the ones have exceeded the nine is now done with the instruction

When displaying the BCD codes on the LCD one simply has to add 48 to the BCD. Because there is no

- We write the 48 to another register (e.g.
*LDI R17,48*) and add this register to the BCD in R16:*ADD R16,R17*. - Set the bits bits 4 and 5 in the BCD with either
*ORI R16,0x30*or with*SBR R16,0x30*or with*SBR R16,(1<<4)|(1<<5)*. - Subtract -48 from the BCD with
*SUBI R16,-'0'*. Subtracting a negative number is the same as adding the positive number.

When displaying on the LCD do not forget to insert the separator on the correct location, otherwise the time would look a bit strange.

If we want to increase such a second, minute or hour we can use

The more simple solution for that uses a special hardware feature that each CPU, including the AVR CPUs, has: the half-overflow flag H in the status register. This flag bit signals if during adding an overflow from the lower to the upper nibble occurred. To use this feature we add 7 to the lower nibble: if the lower nibble was nine before adding, a half overflow would occur setting the H flag. The upper nibble would be increased and would alread be fine. In case that the lower nibble was not nine H is clear and we would have to subtract 6 from the result. The conditional branching instructions BRHC and BRHS can be used.

The source code would be:

```
; Increase lower nibble of R16
ldi R17,7 ; Add 7 to packed BCD
add R16,R17 ; in R16
brhs DoNotSubtract6
ldi R17,6 ; Subtract 6 from packed
sub R16,R17 ; from R16
DoNotSubtract6:
; Done
```

To save one register (R17) we can use Even though it is a little bit more complicated to increase the lower nibble it is much simpler to check if seconds or minutes exceed 59 or the hours exceed 23: just compare the packed BCD with the next higher limit.

```
cpi R16,0x60 ; Compare seconds with 60
brne done
clr R16 ; Restart seconds
; ...
cpi R17,0x60 ; Compare minutes with 60
brne done
clr R17 ; Restart minutes
; ...
cpi R18,0x24 ; Compare hours with 24
brne done
clr R18 ; Restart hours
; ...
```

Instead of two registers to be compared now we have only
one to be compared if the hours reached 24. And instead
of two registers to be cleared there is only one. A clear
advantage of packed BCD over ASCII and BCD.
The whole packed BCD second increase as assembler source code:

```
ldi ZH,High(sTimePbcd) ; Z points to hours in packed BCD format
ldi ZL,Low(sTimePbcd)
ldd R16,Z+2 ; Read the seconds
subi R16,-7 ; Add seven
brhc ChkSec ; H clear, tens increased, check seconds for 60
subi R16,6 ; H set, subtract six
ChkSec:
std Z+2,R16 ; Write seconds
cpi R16,0x60 ; 60 seconds?
brcs Done ; No, completed
; One minute is over
clr R16 ; Restart seconds
std Z+2,R16 ; Write seconds to SRAM
ldd R16,Z+1 ; Read minutes
subi R16,-7 ; Add seven
brhc ChkMin ; H clear, tens increased, check minutes for 60
subi R16,6 ; H set, subtract six
ChkMin:
std Z+1,R16 ; Write minutes to SRAM
cpi R16,0x60 ; 60 minutes reached?
brcs Done ; No, completed
; One hour is over
clr R16 ; Restart minutes
std Z+1,R16 ; And write to SRAM
ld R16,Z ; Read hours
subi R16,-7 ; Add seven
st Z,R16 ; And write to SRAM
brhc ChkHour ; H clear, tens increased, check hours for 24
subi R16,6 ; H set, subtract six
st Z,R16 ; and write to SRAM
ChkHour:
cpi R16,0x24 ; 24 hours over?
brcs Done ; Smaller than 24
clr R16 ; Restart hours
st Z,R16 ; Write to SRAM
; Increase date here
Done: ; Increase done
```

This is it. Those who do not believe that it works can simulate
the source code by setting the SRAM location sTimePbcd to some
desired values, such as 0x23:0x59:0x59 and run through the code.
To display the time formatted as packed BCDs on an LCD the upper nibble has to converted to an ASCII character, then the lower nibble. In case of the hours that goes like that:

```
ld R16,Z ; Z points to hour in SRAM, read hours
swap R16 ; Exchange nibbles: make upper to lower nibble
and R16,0x0F ; Isolate lower nibble
subi R16,-'0' ; Add ASCII zero
rcall LcdChar ; R16 to LCD
ld R16,Z ; Read hours again
andi R16,0x0F ; Isolate lower nibble
subi R16,-'0' ; Add ASCII zero
rcall LcdChar ; R16 to LCD
```

The further display of the separator, the minutes, another separator
and the seconds is all the same. If you use the instruction
That is all and your time processing is complete in packed BCD.

```
.def rHour = R4 ; Hours register
.def rMin = R5 ; Minutes register
.def rSec = R6 ; Seconds register
IncSec:
ldi R16,60 ; Detect end of seconds and minutes
inc rSec
cp rSec,R16 ; Seconds smaller than minute end?
brcs Done ; No
clr rSec ; Restart seconds
inc rMin ; Increase minutes
cp rMin,R16 ; Minutes smaller than hour end?
brcs Done ; No
clr rMin ; Restart minuts
inc rHour ; Increase hours
ldi R16,24 ; Hours per day
cp rHour,R16 ; Hours smaller than day end?
brcs Done ; No
clr rHour ; Restart hours
Done:
; Time increase done
```

With those 14 simple instructions for a 24 hour clock it is
a simple thing to program a clock. No reason at all to include
a powerful datetime library and blow up those 14 simple
instructions by at least 100-fold.
The display of the binary encoded time information now requires a binary-to-decimal-to-ASCII conversion. The following routine converts the binary in R16 to two ASCII digits and displays those on the LCD.

```
Bin2Dec2:
clr R0 ; Count tens in R0
Bin2Dec2a:
inc R0 ; Increase counter
subi R16,10 ; Subtract 10
brcc Bin2Dec2a ; No carry, repeat counting and subtract
subi R16,-10-48 ; Undo last subtraction (add 10) and
; convert to ASCII (add 48)
push R16 ; Is needed later on, pushed to stack
ldi R16,'0'-1 ; Counter minus one plus ASCII zero
add R16,R0 ; Add counter value
rcall LcdChar ; R16 as character to LCD
pop R16 ; Restore second digit from stack
rjmp LcdChar ; and write to LCD
```

The routine LcdChar, usually in the LCD include code, displays
the character in R16 on the current position on the LCD and
advances the cursor to the next position on the LCD. Due to the
use of For testing the routine you can point Z to the SRAM location SRAM_START and instead of the include routine LcdChar: you use

The complete display of the time then goes like this:

```
Display:
mov R16,rStd ; Hours to R16
rcall Bin2Dec2 ; Call conversion and display routine
ldi R16,':' ; Separator
rcall LcdChar ; to LCD
mov R16,rMin ; Minutes to R16
rcall Bin2Dec2 ; Call conversion/display
ldi R16,':' ; Another separator
rcall LcdChar ; to LCD
mov R16,rSec ; Seconds to R16
rjmp Bin2Dec2 ; Jump to conversion/display
```

With these 20 instructions, of which 10 are for the display
of binaries on the LCD, the whole stuff is not too sophisticated.
Top of page | Seconds | Formats | Date and time |

- Pope Gregor the XIII has, in the year 1582, issued the bulletin
"
*Inter gravissimas*", which decided that - in contrary to seconds, minutes, hours and years days and years - do not start with zero but with a one, - the fact that the speed of the earth in rounding the sun is not a simple number but has some fractional parts (365.242188 days), that make it necessary to define some extra time to the 365 days. The Egytians rounded this up a bit (to 365.25 days) and worked with that when they defined their tax paying algorithm. Pope Gregor wanted to be more exact, but did not e. g. define an extra "Earth rumble day" with a special duration of 5 hours, 48 minutes, 45 seconds, 43 milliseconds, 199 microseconds and a few nanoseconds. Instead of this simple solution pope Gregor went into a much more sophisticated algorithm that makes the life of calendar designers and assembler programmers more complicated,
- he decided that the monthes have not an equal number of days, e.g. ten monthes from 0 to 9, with 36 days each (day 0 to 35), but defined 12 monthes instead, even though 365 / 12 = 30.416666 (periodic) and started with 1 instead,
- the decision to choose 12 as the month's basis leaded to further
complicated consequences:
- the number of days per month was not a constant but varied,
- this number of days did not follow simple rules, e.g. such as 30/31 alternating with the December length varying, but the alternating rule was reversed between July and August and February's length was largely varied,
- the February's length was defined as being 28, but
- not every four years if the year can be divided by four without a fractional part, then it is 29 days long,
- but not every 100 years, when the year can be divided by 100 without a fractional part, then it is 28 days long,
- but not every 400 years, when the year can be divided by 400 without a fractional part, then it is 29 days long,

- he further decided that one week has seven weekdays and not 10, an ancient "magic" number rather than a decimal's society preferred selection, but
- their numbering remained undefined, still causing confusion: some start with the Monday, others with the Sunday.

- The format convention Days:Months:Years reverses priority.
- The format convention Months/Days/Years simply confuses any priority rule.
- The format convention Years:Monthes:Days would be similar to the time format, but is rarely used (only by computer-kids.

Now, how many days has the current month?

Month | Days | N | Corr. | N corr. | dom=31? |
---|---|---|---|---|---|

January | 31 | 0b0001 | yes | ||

February | 28/29 | 0b0010 | no, spec. | ||

March | 31 | 0b0011 | yes | ||

April | 30 | 0b0100 | no | ||

May | 31 | 0b0101 | yes | ||

June | 30 | 0b0110 | no | ||

Juli | 31 | 0b0111 | yes | ||

August | 31 | 0b1000 | -1= | 0b0111 | yes |

September | 30 | 0b1001 | -1= | 0b1000 | no |

October | 31 | 0b1010 | -1= | 0b1001 | yes |

November | 30 | 0b1011 | -1= | 0b1010 | no |

December | 31 | 0b1100 | -1= | 0b0111 | yes |

The February is special and needs its own algorithm to determine if it has 28 days (no leap year) or 29 days (leap year). So we'll have to read the year, have to AND it with 0x03. If that yields zero, dom is 29, otherwise it is 28.

In the other months it starts with the rule, that uneven months (1, 3, 5 or 7) have 31 days, even monthes (4 or 6) 30 days. As you see for the other months, this rule changes from August on. We correct the months by subtracting 1 from the month. Now the rule continues and for even months (8, 10 or 12, corrected to 7, 9 or 11) the number of days is 31, for uneven (9 or 11, corrected to 8 or 10) it is 30 days.

If the month is larger than seven, we'll have to subtract one to correct those to the rule that uneven months (now corrected) have 31 days.

This all makes programmers confused. To determine the number of days of a month we need this algorithm. Looks complicated but isn't that complicated in assembler:

```
;
; Subroutine calculates the number of days of a month
; Month in rMonth, Year in rYear, result in rDom
;
DaysOfMonth:
ldi rDom,31 ; 31 days
cpi rMonth,2 ; February?
brcs DaysOfMonthRet ; January
brne DaysOfMonth1 ; Not February
; February
ldi rDom,28 ; No leap year
mov R16,rYear ; Leap year?
andi R16,0x03 ; Last two bits of year?
brne DaysOfMonthRet ; Last two bits not 00
ldi rDom,29 ; Leap year
rjmp DaysOfMonthRet
DaysOfMonth1:
mov R16,rMonth ; Copy month
cpi R16,8
brcs DaysOfMonth2 ; March to July
dec R16 ; Monthes larger or equal August, reverse
DaysOfMonth2:
ldi rDom,31 ; Months with 31 days
andi rmp,0x01 ; Uneven?
brne DaysOfMonthRet
ldi rDom,30
DaysOfMonthRet:
ret ; Done
```

Simulation of the DOM routine with the monthes 1 to 12 for a leap
year (first line) and a non-leap year (second line) demonstrates
that the days for the monthes (+01 to +0C), as written to the SRAM,
are correct.
Simulation was, of course, performed with avr_sim.

With these tools we can start to design a complete time and date flow diagram for a date/time clock. Rectangles are calculations, rhombuses are display operations and squares rotated by 45 degrees are decisions or conditional branches.

In the algorithm only those display elements are updated that are changed during an increase of that second. As a change of the year requires an update to all displayed elements on the LCD the update algorithm runs from bottom to the top while the time and date algorithm runs from top to bottom.

The first decision is that the date and time on the LCD is controlled by a different routine during a date and time input. Note that in that case the increase in time does not work but waits for a completion of the input procedure.

Here the complete increase time/date routine in assembler:

```
;
; Increase time in seconds
;
IncSec:
ldi ZH,High(DateTimeBuffer)
ldi ZL,Low(DateTimeBuffer)
ldd R16,Z+6 ; Seconds
inc R16
std Z+6,R16
cpi R16,60
brcs DisplaySec
clr R16
std Z+6,R16
ldd R16,Z+5 ; Minutes
inc R16
std Z+5,R16
cpi R16,60
brcs DisplayMin
clr R16
std Z+5,R16
ldd R16,Z+4 ; Hours
inc R16
std Z+4,R16
cpi R16,24
brcs DisplayHours
clr R16
std Z+4,R16
ld R16,Z ; Weekdays
inc R16
st Z,R16
cpi R16,7
brcs IncDay
clr R16
st Z,R16
IncDay:
rcall DaysOfMonth ; Get days of that month in R16
inc R16
mov rData,R16
ldd R16,Z+1
inc R16
std Z+1,R16
cp R16,R0
brcs DisplayWeekdays
ldi R16,1
std Z+1,R16
ldd R16,Z+2 ; Monthes
inc R16
std Z+2,R16
cpi R16,13
brcs DisplayMonthes
ldi R16,1
std Z+2,R16
ldd R16,Z+3 ; Years
inc R16
cpi R16,100
std Z+3,R16
brcs DisplayYears
clr R16
std Z+3,R16
DisplayYears:
; ...
DisplayMonthes:
; ...
DisplayWeekdays:
; ...
DisplayHours:
; ...
DisplayMinutes:
; ...
DisplaySeconds:
; ...
ret
```

To simulate the work of IncSec: here a few tests. First, the year change on the 31nd of December, 2017, is shown. The line from address

- 0x0070 shows the original data for the Sunday at that date, for 23:23:59, in binary format,
- 0x0080 shows the date/time increased by one second, as binary,
- 0x0090 displays the original date/time in ASCII, and
- 0x00B0 on shows the increased date/time in ASCII.

Simulation was again, of course, performed with avr_sim.

This simulates the seconds increase for February 28, 2019, which was not a leap year. Also correct.

And this is the seconds increase on February 28, 2020, which is a leap year. The routine obviously works correct.

I wish you success in the selfmaking of date/time routines in assembler.

Top of page | Seconds | Formats | Date and time |

©2018-2023 by http://www.avr-asm-tutorial.net