# How to compare time-of-day numbers stored in Binary Coded Decimal in PIC

I am using a PIC18f which has an internal real-time clock. The the day, month and year values are stored in BCD format. The data sheet says these values are stored in BCD format in Registers.

I want to compare time. E.g. if (time==10.20) // 10 hours twenty minutes

Do I need to do some conversions to other data type like int/char while dealing with BCD in Registers?

-

Binary-coded (BCD) decimal simply simply means each decimal digit is encoded in 4 bits (0-9, or 0000 to 1001), with the remaining six values (10-15, or 1010 to 1111) unused. Usually two BCD digits are placed side by side inside an 8-bit byte, so values of 0-99 can be encoded.

So a minutes value of 59 would be encoded as:

0101 1001     0x05  0x09


0011 1011    0x3B    (3*16 + 11 = 59)


Encoding digits this way makes it easy to display them, since you only have to shift and mask them, no binary to decimal conversion is necessary. E.g. if you have two BCD digits in a byte BCDdigits (which might represent one of the registers in the RTCC module), then

ones = BCDdigits & 0xf;
tens = BCDdigits >> 4;


These examples all assume unsigned variables.

Two multi-digit numbers both in BCD format can be compared directly, as long as they are aligned correctly.

For example, the registers in the PIC18 RTCC are all 8 bit. So you have to either do this:

if ((HOURS == 0x10) && (MINUTES == 0x20))


or this

if ((HOURS << 8) | MINUTES ) == 0x1020)


where HOURS and MINUTES are the hours and minutes registers in the PIC18 RTCC.

If the second number to be compared is not in BCD, then you will need to convert them to a common base, such as minutes. So if you have one byte containing minutes in BCD, and another containing hours in BCD, you could compute:

unsigned short minutes;
minutes = 600*(HOURS >> 4) + 60*(HOURS & 0x0f)
+ 10*(MINUTES >> 4) + (MINUTES & 0x0f);


and then compare that with your other value also converted to minutes.

-
Beware of using left/right shift for maths, some processors are wrong-endian. Also, with embedded, you need to make sure minutes is a big enough variable to store the answer. –  John U Feb 19 at 15:14
@JohnU That's an issue of how the register appears to the processor. You still want the internal BCD representation for, say 1020 to be 0x1020, even if the hardware stores it as 0x0201 or whatever. That part can be abstracted by some clock get / set functions. –  Kaz Feb 20 at 1:48

BCD literals are the same as hexadecimal, except that you never use the digits A-F.

For example, to compare hours and minutes, you'd simply write

if (time == 0x1020) { /* 10:20 in BCD */
...
}


Use comments liberally to indicate which constants are BCD, or write a macro that makes it clear.

-

BCD clocks are something of an annoyance to work with; I have no idea why they are so popular.

If one simply wishes to compare two times or dates, one may simply compare the numerical parts of the components in order from least significant to most significant; year wraparound from 99 to 00 may have been an issue 15 years ago, but not anymore. It's important to beware of the fact that many BCD chips use the upper bits of "tens" fields for various purposes, so one may have to mask the read values with 0x3F or 0x7F before doing comparisons. Additionally, one needs to either make certain that the device is set for "24-hour" mode, or else deal with the fact that most if not all devices which use BCD have (and may default to) a "12-hour" mode which numbers the hours 0x12, 0x01..0x09, 0x10, 0x11, 0x52, 0x41..0x0x49, 0x50, 0x51 [some devices may put the AM/PM indicator in bit 7 instead of bit 6]; on such devices, simple numerical comparisons won't work since 12:00am will sort between 11:00am and 1:00pm, and 12:00pm will sort after 11:00pm.

My recommendation when using any sort of RTC chip is to convert the time from it into a linear number of seconds. Convert each byte to a decimal value by doing something like:

unsigned char bcd_to_dec(unsigned char bcd)
{
return bcd - 3*(bcd >> 4);
}


For dates after March 1, 2000, Subtract 3 from the month. If that is negative or greater than 12, add 12 to the month and subtract one from the year. That will shift the calendar so March 1 is the first day of month 0 of each year, so February 29 will be the last day of a year.

Figure the March 1 day number for the year (relative to March 1, 2000) by computing (year*365)+(year/4u). Add a day number for the current month using a 12-entry table. Then add (current day - 1). That will give the number of days since March 1, 2000.

Multiply that by 24, add the hours, multiply that by 60, add the minutes, multiply that by 60, and add the seconds.

All this is a somewhat annoying bit of work, but it will make it possible to perform calculations with relative times much more easily than was possible with BCD. One may minimize the amount of code space required if one defines a global value of type unsigned long, and a method:

void mul(unsigned char factor) { ulong_acc *= factor; }


Doing that will reduce each multiplication step to be nothing more than a movlw followed by a function call.

-