When I first saw the problem statement on EWskills “extract a nibble from an 8-bit register”, I thought this would be easy. After all, a nibble is just 4 bits, right? But along the way, I discovered I had several gaps in my understanding — not just about bit manipulation, but also about how hexadecimal numbers, masks, and C syntax really work. In this post, I’ll walk you through my confusion, how I corrected my thinking, and what I learned about firmware development in C.
The Problem
We are given:
- An 8-bit register value (0–255).
-
A position:
-
0
means extract the lower nibble (bits 0–3). -
1
means extract the upper nibble (bits 4–7).
-
We need to return the nibble value as a decimal number (0–15).
Example
- Input:
reg = 0xAB, pos = 0
Output:11
- Input:
reg = 0xAB, pos = 1
Output:10
At first, this confused me. Let’s unpack why.
My Initial Confusion
1. Decimal vs Hexadecimal vs Binary
Take 0xAB
.
- In binary:
1010 1011
- In decimal:
171
When we split this into nibbles:
- Upper nibble:
1010
→ hex0xA
→ decimal 10 - Lower nibble:
1011
→ hex0xB
→ decimal 11
The problem statement said the output should be 11
. At first, I thought this was binary 11
(which is decimal 3). That was my mistake! The expected output is in decimal, even though the register values are written in hexadecimal. Once I understood that, things became much clearer.
2. Writing the Mask
I also struggled with the mask. The statement said:
For a 3-bit field → mask = 0x07
I thought “wait, isn’t 0x07
just decimal 7
? Shouldn’t it be something else?”
The reality is: yes, 0x07
is decimal 7. But in binary, it is 0000 0111
. That’s exactly what we want for a 3-bit mask — the last 3 bits set to 1
.
This led me to a general rule:
- N-bit mask = (1 << N) – 1
Examples:
- 3 bits →
(1 << 3) – 1 = 7 = 0x07
→0000 0111
- 4 bits →
(1 << 4) – 1 = 15 = 0x0F
→0000 1111
- 8 bits →
(1 << 8) – 1 = 255 = 0xFF
→1111 1111
So for nibble extraction (4 bits), our mask should be 0x0F
.
3. A Bug in My First C Code
Here’s my first attempt:
unsigned char extractNibble(unsigned char reg, int pos) {
if (pos = 0) { // Oops!
unsigned char lower = (reg >> 0) & 0x07;
return lower;
} else {
unsigned char higher = (reg >> 6) & 0x07;
return higher;
}
}
Mistakes I made:
-
if (pos = 0)
→ This was an assignment, not a comparison. It always evaluates to false. It should have beenif (pos == 0)
. - My mask was
0x07
(3 bits), but a nibble is 4 bits, so it should have been0x0F
. - My upper nibble shift was
>> 6
, but it should have been>> 4
.
The Corrected Code
Here’s the fixed version:
#include <stdio.h>
unsigned char extractNibble(unsigned char reg, int pos) {
if (pos == 0) {
// Lower nibble
return reg & 0x0F;
} else {
// Upper nibble
return (reg >> 4) & 0x0F;
}
}
int main() {
unsigned char reg;
int pos;
scanf("%hhu %d", ®, &pos);
printf("%d", extractNibble(reg, pos));
return 0;
}
This now works for all cases:
-
0xAB, pos=0
→ 11 -
0xAB, pos=1
→ 10 -
0xFF, pos=0
→ 15
Making It More "Firmware Style"
In embedded systems, we often use macros for clarity:
#define NIBBLE_MASK 0x0F
#define NIBBLE_BITS 4
#define EXTRACT_NIBBLE(reg, pos) (((reg) >> ((pos) * NIBBLE_BITS)) & NIBBLE_MASK)
unsigned char extractNibble(unsigned char reg, int pos) {
return EXTRACT_NIBBLE(reg, pos);
}
This eliminates the if
statement, and clearly shows the relationship between position, shift, and mask.
My Key Learnings
- Nibble extraction: A nibble is 4 bits, and we always mask with
0x0F
after shifting. - Decimal vs Hex: Register values may be written in hex, but outputs are usually in decimal. Don’t confuse them!
- Mask formula:
(1 << N) – 1
is the universal way to get an N-bit mask. - C syntax trap:
=
vs==
— a small slip that breaks logic. - Firmware style: Macros make register manipulation more readable and consistent.
Conclusion
What seemed like a simple “extract a nibble” problem actually taught me a lot about bit manipulation, number systems, and C programming pitfalls. These are the bread and butter of firmware development — almost every hardware register requires you to mask, shift, and extract fields safely.
If you’re new to embedded C, I’d recommend practicing small tasks like this. They may look trivial, but they prepare you for real-world firmware, where the difference between 0x07
and 0x0F
can mean your UART or ADC doesn’t work at all.
Top comments (0)