Binary-Coded Decimal (BCD) is a numeric encoding in which each decimal digit (0-9) is stored as its own 4-bit binary value, rather than encoding the full number in binary. For example, the decimal value 59 is stored as 0101 1001 in packed BCD, not as the straight binary encoding 0x3B (which equals 89 in decimal).
In practice
BCD appears most often in RTCs (real-time clock ICs). Devices such as the DS1307, DS3231, and PCF8523 store hours, minutes, seconds, and date fields in BCD registers. Firmware must convert these fields to binary integers before doing arithmetic, and convert back before writing them. A common bug is reading a BCD register and treating it as a plain binary integer, which produces wrong values because the encoded decimal digits are misread as a base-2 number (e.g., reading 0x12 BCD as the integer 18 instead of the intended decimal 12).
BCD is also common in seven-segment display drivers and legacy numeric entry interfaces, where hardware or software maps each digit to a display segment directly. Some microcontrollers, including many 8051 variants and certain PIC devices (support varies by family), include a DAA (Decimal Adjust Accumulator) or similar instruction that corrects the result of a binary addition to be valid BCD, making BCD arithmetic feasible without a software lookup table.
Two common packing formats are used in embedded systems. Packed BCD stores two decimal digits per byte (one per nibble), which is the format used by most RTC chips. Unpacked BCD stores one decimal digit per byte, wasting the upper nibble, and is sometimes seen in display pipelines or BCD-to-ASCII conversions where the direct nibble-to-character mapping (add 0x30) is convenient.
Arithmetic on BCD values requires care. Standard binary addition does not automatically produce valid BCD results; carry between nibbles must be handled explicitly either with a DAA instruction or in software. On Cortex-M cores, which have no DAA instruction, BCD arithmetic is typically done in software, usually by unpacking digits, adding, and re-packing with manual carry propagation, though software libraries can encapsulate this handling.
Frequently asked
Why do so many RTC chips use BCD instead of plain binary?
RTCs predate much standardization and were designed so that simple display hardware could read individual digit nibbles directly without any conversion. The convention has persisted because the register maps of popular devices like the DS1307 became de-facto standards that later chips maintained for software compatibility.
How do I convert a BCD byte to a decimal integer in C?
For packed BCD: `uint8_t bcd_to_dec(uint8_t bcd) { return (bcd >> 4) * 10 + (bcd & 0x0F); }`. The reverse is: `uint8_t dec_to_bcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); }`. These are the standard patterns used when reading or writing
RTC registers.
What values are invalid in a BCD nibble, and why does it matter?
Nibble values 0xA through 0xF (10-15) are undefined in BCD. If firmware writes an out-of-range value to an
RTC register, the chip's behavior is undefined and may corrupt the time-keeping state. This can happen when dec_to_bcd() receives a value outside 0-99, so input validation matters.
ARM Cortex-M has no DAA instruction. How is BCD arithmetic handled?
On Cortex-M, BCD arithmetic is done entirely in software. The typical approach is to unpack each nibble into a separate byte, perform addition with manual carry detection between digits, and re-pack the result. For simple use cases like incrementing an
RTC value by one second, converting to binary, incrementing, and converting back is often cleaner and fast enough.
Is BCD the same as hexadecimal?
No. Both use 4-bit groups, but hexadecimal uses all 16 values (0x0-0xF) to represent numbers in base 16, while BCD only uses values 0x0-0x9 to represent a single base-10 digit. The blog post 'Embedded Toolbox: Programmer's Calculator' is a useful reference for keeping these number representations straight when working at the bit level.
Differentiators vs similar concepts
BCD is sometimes confused with hexadecimal because both pack numeric values into 4-bit nibbles. Hexadecimal is a base-16 representation that uses all 16 possible nibble values and is a compact way to express binary data. BCD is a base-10 representation that restricts each nibble to values 0-9 and is used to preserve individual decimal digits. A value like 0x59 in hexadecimal means 89 in decimal; as a packed BCD byte it means 59 in decimal. The two encodings are numerically equal only for values 0-9.