There are several items of interest in the choice of code used.
BCD Clock see below.
How to calculate the timing constants using a different clock frequency
Single button implimentation.
The first is that the clock counts in binary coded decimal (hex), the advantage of this idea, is that less code is used in assembler, and the code is preformatted for things such as ASCII characters, or outputting to BCD devices. In some circumstances the amount of variables required for a program can be cut down using this packed method. Here the saving is used to allow the most significant bit of the hour variable to contain system flags, the am/pm indicator, in other programs this could be a 'sign' indicator for a positive or negative number.
One trick that can be used when counting with BCD numbers and saves code, is helpfully provided by the Pics maths unit via the Status register. You may be used to using the Zero or the Carry flag in routines, everytime you write code to check if a number is equal to another number, the compiler will convert that into a test for zero, by subtracting one number from the other, and testing to see if the result is zero. If it is then the numbers must be equal and the zero flag is set in the status register.
The one under used flag in the status register is the DC flag, this is the Digit Carry flag and is set when ever a binary count goes from the lower nibble over to the high nibble of a byte.
15 = 0000 1111 ;all the ones are in the lower nibble, so the DC flag is not set. 0000 0001+ ;the count increases by one 16 = 0001 0000 ;the DC flag is now set,because the Digits have Carried over to the high nibble.
Now if we are counting in BCD the binary digit and the decimal equivalent is -
7 = 0000 0111 8 = 0000 1000 9 = 0000 1001 10 = 0000 1010 ;oops what we want from a decimal viewing point is for this to be 10 = 0001 0000 11 = 0001 0001
Plainly we humans have a digit carry when we count from 9 to 10, this in hex means the count goes from 9 to 'A', and the hex's digit carry is from 15 to 16. Naturally this is nonsense to us, and so we need to perform a maths conversion to force the binary number to behave in the fashion we want, we do this by adding decimal 6 to the binary number like so -
movf sec,w addlw 6
And then test to see if this causes the DC flag to become set.
sec = 7
It is copied into the W register where 6 is added to it, now the instruction addlw always puts the result back into the W register so
sec = 7 = 0000 0111 + addlw = 6 = 0000 0110 W =13 = 0000 1101
But no digit carry so the DC flag is not set and sec still contains the number 7, now let's see what happens when sec is equal to 9
sec = 9 = 0000 1001 + addlw = 6 = 0000 0110 W = 15 = 0000 1111 ;still no digit carry so all is well.
But what about when sec = 10?
sec =10 = 0000 1010 + addlw = 6 = 0000 0110 W =16 = 0001 0000
This is exactly what we want to see H'10 looks like D'10 and the DC flag will be set in the status register. All we need to do now is to copy the contents of the W register to the sec register and continue counting. The DC register will automatically unset itself when sec clicks around to H'11 as no shift from the lower nibble has taken place. The DC flag won't be set again until sec = 0001 1010 .
The complete code snip is -
movf sec,w addlw 6 btfsc STATUS,DC movfw sec ;only get to this instruction if DC is set.
In the actual code I have used btfss instead, as I leave the counting function, if sec is less than D'10 and only update sec once it has reached D'10.
This could have been written all in basic (and was for the function that checks that the programmed times fall within the correct range), but two things made me decide to use assembler in the clock function. For optimisation reasons XCSB doesn't allow the flags in the STATUS register to be tested in basic, so as that part had to be written in assembler then I may as well write the rest of it, secondly all compilers for the 16 series of Pics have to be coerced to use the W register as I have done, and require extra code in basic to achieve this. So the basic equivalent of the above is;
temp = sec + 0x06 asm_start btfss STATUS,DC goto break_func asm_end sec &= 0xF0 sec += 0x10
Note we have to employ a temporary variable to force the compiler to use the W register, also note the two extra lines of code which equals 6 assembler instructions.
Apart from having to recompile the project, the only time conscious part of this project is of course the RTC, we definitely want this to be as accurate and reliable as possible.
Thankfully this is very easy to do and any crystal or resonator can be used up to the maximum speed of the processor chosen.
In the event.def file at the top you will find these 3 lines;
const COUNTH = 0x0F
const COUNTM = 0x42
const COUNTL = 0x40
These are used to load the RTC software counter with default values. As this particular RTC function uses a Bresenham style 24 bit counting algorithm using the 8 bit timer2, the clock registers needs to count at the same rate as the internal clock frequency.
Hence in this project a 4MHz crystal is used, therefore with the Pic's divide by 4 internal instruction cycle, we end up with an instruction frequency of 1MHz.
Grab your calculators and you will see that the 24 bit hex number H'0F 42 40 just happens to be 1MHz in decimal. To use another crystal frequency all that has to be done is to divide the frequency by 4, hexify it and alter the above code with the new numbers.
COUNT H M L
Crystal is 16MHz /4 instruction frequency = 4MHz = H'32 09 00
Crystal is 3.6864Mz /4 '' '' = 921.6KHz = H'0E 10 00