Understanding Hexadecimal Numbers
Converting numbers between bits, hexadecimal and decimal, and understanding how your microcontroller 'counts'.
If you're simply looking for a convenient way to convert values between binary and hexadecimal, feel free to use our online Hexadecimal to Binary Converter
If you have no idea what a hexadecimal number is, you've probably used them but just don't know it. Have you ever tried to make a web page, and seen things like #FFFFFF (for white) or #FF0000 (for red) in a CSS file or in the HTML? Those are hexadecimal numbers representing a specific colour value (out of a possible 16,777,215 choices!), and are simply a much more concise and efficient way of communicating numeric information. Don't worry if you don't know why #FF0000 is red, but #EFEFEF is really light gray ... we'll explain that below.
While hexadecimal numbers are rarely used by most C#, Java or VB developpers (and they are clearly in the majority), they are used everywhere when working with small embedded systems and microcontrollers, so it's pretty important that you understand what all those funny numbers and letters mean before you can do anything really fun. It's actually not all that complicated once you understand how the math works ... but there aren't a lot of easy to understand explanations out there, so we thought it was worth having a go at explaining it ourselves.
In the beginning was the Bit
Essentially, any microprocessor that you use -- from the humblest 8-bit chip right up to the latest 64-bit multi-core monster -- operates on the same straight-forward principle of 'bits'. Millions and billions of bits, all running around inside your circuits like a tired little army of ants. All a bit knows is whether it's a '1' or a '0', or more usefully whether it's on or off, true or false, enabled or disabled, etc. Bits are pretty boring by themselves, but all modern electronic devices have these simple-minded, hard-working 'bits' at their core, and without them your electric Hello Kitty Manicure Set and the machine that knits together your Power Rangers underwear just wouldn't be the same. And what kind of world would that be?
And the Bit saw that it wasn't good to be alone
While our hard-working little bi-polar bits can be useful on their own, they're social creatures who work better in teams ("many hands makes light work", so they say). Enter the binary numbering system -- the next logical step up the evolutionary ladder. Binary numbers are made by grouping a series of bits together together in a specific order, and by using a specific sequence of 1's and 0's you can make bigger numbers, like say '15' or '65'. Now isn't that fun! How it works is that you stick, say, 8 single-minded little bits together in a row, and mathematically, the number of possible combinations of 1's and 0's (say, 01011010) can be a value between 0 (00000000) and 255 (11111111). If you really want to know all the nitty-gritty details on binary numbers, read up on the Binary numeral system. Basically, though, it goes something like this:
|Binary ||0000 ||0001 ||0010 ||0011 ||0100 ||0101 ||0110 ||0111 ||1000 ||1001 ||1010 |
|Decimal ||0 ||1 ||2 ||3 ||4 ||5 ||6 ||7 ||8 ||9 ||10 |
And the Bits were fruitful and multiplied
While almost everyone has heard of binary numbers, not everyone properly understands the difference between 8-bit, 32-bit and 64-bit (or higher) processor architectures -- except, of course, that bigger is probably better (or at least that's what most of the spam in my mailbox seems to imply). The fundamental difference between these different architectures is directly related to bits and to binary numbering. We call an 8-bit chip '8-bit' because, internally, it uses 'numbers' or collections of bits that are eight 1's or 0's long. That means that we can naturally use values between 0 and 255. Anything larger than this involves more work for the processor as it actually has to get broken down into the individual 8-bit pieces that it knows how to deal with. The advantage offered by 32 and 64-bit processors is that they can naturally use significantly larger numbers without having to do any complicated or cycle-consuming acrobatics, making execution faster amongst other things. You can see the difference between these three common 'bit' lengths below :
| ||Bit Value ||Decimal Equivalent |
|8-bit ||11111111 ||255 |
|32-bit ||11111111111111111111111111111111 ||4,294,967,295 |
|64-bit ||1111111111111111111111111111111111111111111111111111111111111111 ||18,446,744,073,709,551,615 |Sidenote: 32-bit OSes ... and "The Evil Marketing Genius Learns Bit-Speak"
While you're trying to figure out how to pronounce that last number ... have you've ever wondered why your 32-bit OS can only handle up to 4GB memory? It's because 4 GB = 4,294,967,295 bytes, which is the biggest number a 32-bit system can handle. 64-bit OSes don't have this problem and can support huge amounts of memory because they can deal with monstrously large numbers.
While this bit business isn't exactly rocket science, it is fairly important to understand if you're not used to working with these kinds of numbers since you'll see 'bit' notations everywhere, from datasheets to marketing materials. For example, the NXP LPC2148 microcontroller has two built in 10-bit analog to digital converters (ADCs). What that means is that, when converting an analog signal (from, say, a microphone) to a digital signal (like what's stored inside an MP3 or a WAV file), it can convert the value using 10-bits for a possible range of values between 0 and 1023. An 8-bit analog to digital converter would only be able to give you a range of values between 0 and 255, probably meaning a lower quality conversion (though numbers aren't always everything, despite what the Evil Marketing Geniuses would have us believe). Similarly, a 16-bit LCD screen can (in theory!) display 65,535 different colours, which is probably more pleasing than an 8-bit screen which can only display 255 different colours. You get the idea.
And the Bits realized that writing in 1's and 0's is 72-65-61-6C-6C-79-20-73-6C-6F-77
The problem with the binary number system is that it takes a lot of space to write out all those 1's and 0's, making them rather tedious to work with for anything bigger than 1 or 0, and binary numbers are rather prone to human-error since everything looks the same ... especially with huge 32-bit or 64-bit values! One solution that was proposed to fix this was to take all those binary 1's and 0's and convert them into something more usable and concise, and ... dum dum dum ... the hexadecimal numbering system was born! Somewhere some smart person realized that you could replace 4 bits (representing values between 0 and 15) with 1 number or letter between 0-9 and A-F, and communicate the same information using 1/4 the space. Basically, hexadecimal is really just a visual compression engine to make things on the screen look smaller, even though the exact same information is being communicated beneath the surface. The conversion works as follows:
|Bits ||Hex ||Bits ||Hex ||Bits ||Hex ||Bits ||Hex |
|0000 ||0 ||0100 ||4 ||1000 ||8 ||1100 ||C |
|0001 ||1 ||0101 ||5 ||1001 ||9 ||1101 ||D |
|0010 ||2 ||0110 ||6 ||1010 ||A ||1110 ||E |
|0011 ||3 ||0111 ||7 ||1011 ||B ||1111 ||F |Some random binary/hexadecimal numbers and their decimal equivalents
|Decimal Number ||15 ||10 ||5 ||0 ||2 ||4 ||6 ||8 ||10 ||12 |
|Binary ||1111 ||1010 ||0101 ||0000 ||0010 ||0100 ||0110 ||1000 ||1010 ||1100 |
|Hexadecimal ||F ||A ||5 ||0 ||2 ||4 ||6 ||8 ||A ||C |
You can see that it takes a lot less space to convey the same information in hexadecimal, and it's far less error prone to have a string of characters between 0-9 and A-F than it is a line of only 1's and 0's. But what if you want to deal with numbers larger than 15, you ask? You simply add another 4-bit hexadecimal character. For example, if you want to work with an 8-bit value between 0 and 255, rather than 8 1's and 0's you can simple use two hexadecimal characters side by side. For example, C6 is the hexadecimal equivalent of 1100 0110.
For better or for worse, a very large percentage of the 'numbers' that you will see and work with on any microprocessor are most likely to be presented in hexadecimal form, so you better get used to working with them. Essentially, all you really need to understand in most cases are 8-bit and 32-bit hexadecimal values, since those two account for 95% of the values you're likely to see or work with yourself. An 8-bit hexadecimal character is made up of two numbers or letters (ex.: C6). Until the widespread adoption of Unicode in recent years, all text that was handled on your computer was based on 8-bit values, with, for example, the letter 'n' (lower case) being equal to '6E' in hexadecimal notation (or 110 in decimal) using ASCII encoding.
For reference sake, the ASCII equivalent of '72-65-61-6C-6C-79-20-73-6C-6F-77' is 'really slow'.
Recovering the 'Bits' in 32-bit hexadecimal values
If you're working with an 8-bit or a 32-bit processor, and you want to know what a large hexadecimal number really contains, all you need to convert each letter back into it's four bit parts is the conversion table above. An 8-bit processor will use two letter codes (Two 4-bit characters = 8 bits) and a 32-bit processor will use eight letter code (Eight 4-bit characters = 32 bits). As an example, here is how you would convert a 32-bit hexadecimal to binary and back.
|32-bit Binary Value ||0000 ||0000 ||0010 ||0000 ||1000 ||1100 ||0000 ||0000 |
|Hexadecimal Equivalent ||0 ||0 ||2 ||0 ||8 ||C ||0 ||0 |Real World Example: Configuring pins as 'outputs' on a 32-bit microcontroller
That last table is important to wrap your head around, since you'll be doing these kinds of conversions all the time when you really start working with any microprocessor. Since microcontrollers are black and white and, at their lowest level, only understand one thing -- on
-- any settings on the microcontroller are changed by modifying these bits to change from 1 to 0 or the other way around. For example, if we take the LPC2148
microcontroller as an example, we know that this particular microcontroller has 45 basic input and output pins (called General Purpose Input/Outputs
, or GPIOs
) that can act as either 'inputs
' (meaning they bring 1's and 0's into the microprocessor) or as 'outputs
' (meaning they send 1's and 0's out of the microcontroller). This particular chip has a total of 64 pins, but because it uses 32-bit internal logic, it divides all the available pins into two 'pin blocks', each consisting of 32 pins. These are referred to as pin block 0 (pins 0-31) and pin block 1 (pins 32-63). To specify which of these pins should act as outputs in GPIO pin block 0 (the first half of this chips pins), you would need to assign a value to GPIO0_IODIR (sometimes simply named IODIR0 in other LPC2148 header files) , which sets the Input/Output Direction on Bank 0. This value is most commonly passed using 32-bit hexadecimal notation where each bit corresponds to a single pin. Taking the 32-bit value we just converted in the table above, if I were to write:
GPIO0_IODIR |= 0x00208C00;
I would be telling the LPC2148 to set pins 0.10
as outputs. You can see that for yourself by starting at the last bit in the table above and counting the position of the 1's from right to left (keeping in mind that the first pin is actually pin number '0' and not pin '1', since the values go from 0 to 31). We've used the same values in this example and the conversion table above to make it as clear as possible. If it doesn't make sense to you, keep looking at it until you understand it since understanding this is essential to doing anything useful with your microcontroller. It might take a bit of getting used to, but once the conversion logic clicks in your head it becomes mostly
painless to work in hexadecimal. (And if you're wondering what the '0x
' before the numbers means, it's simply a convention in the C programming language to indicate that this is a hexadecimal value. For an 8-bit hex value we would use 0xCD
, for example.
Can you understand now why, in a 24-bit RGB colour scheme, #FF0000 is equal to red, and #EFEFEF is equal to very light gray. 24-bit colour consists of 8-bits of Red, 8-bit of Green, and 8-bits of Blue, meaning #FF0000 in hexadecimal is the same as Red: 255, Green: 0, Blue: 0 in decimal numbering, and #EFEFEF is the same as RED: 239, Green: 239, Blue: 239.
If you're still having a hard time with this, or if you simple want a convenient way to convert values between binary and hexadecimal, feel free to use our online Hexadecimal to Binary Converter