Trích xuất Nibble từ Register 8-bit trong C
Khi lần đầu tiên tôi thấy đề bài trên EWskills "trích xuất một nibble từ register 8-bit", tôi nghĩ rằng điều này sẽ rất đơn giản. Sau cùng, một nibble chỉ là 4 bit, đúng không? Nhưng trong quá trình làm, tôi đã phát hiện ra rằng tôi có nhiều lỗ hổng trong hiểu biết của mình — không chỉ về thao tác bit mà còn về cách mà số hệ thập phân, số hệ thập lục, mặt nạ (mask) và cú pháp C thực sự hoạt động. Trong bài viết này, tôi sẽ đưa bạn qua những nhầm lẫn của mình, cách tôi đã điều chỉnh suy nghĩ và những gì tôi đã học về phát triển firmware trong C.
Vấn đề
Chúng ta có:
- Một giá trị register 8-bit (0–255).
- Một vị trí:
0có nghĩa là trích xuất nibble thấp (bit 0–3).1có nghĩa là trích xuất nibble cao (bit 4–7).
Chúng ta cần trả về giá trị nibble dưới dạng số thập phân (0–15).
Ví dụ
- Đầu vào:
reg = 0xAB, pos = 0Kết quả:11 - Đầu vào:
reg = 0xAB, pos = 1Kết quả:10
Lúc đầu, điều này khiến tôi cảm thấy bối rối. Hãy cùng khám phá tại sao.
Nhầm Lẫn Ban Đầu
1. Số Thập Phân, Thập Lục và Nhị Phân
Lấy 0xAB làm ví dụ:
- Ở dạng nhị phân:
1010 1011 - Ở dạng thập phân:
171
Khi chúng ta chia nó thành nibbles:
- Nibble trên:
1010→ hex0xA→ thập phân 10 - Nibble dưới:
1011→ hex0xB→ thập phân 11
Đề bài nói rằng đầu ra phải là 11. Lúc đầu, tôi nghĩ rằng đây là số nhị phân 11 (mà là thập phân 3). Đó là sai lầm của tôi! Đầu ra mong đợi là số thập phân, mặc dù các giá trị register được viết bằng hệ thập lục. Khi tôi hiểu điều đó, mọi thứ trở nên rõ ràng hơn.
2. Viết Mặt Nạ
Tôi cũng gặp khó khăn với mặt nạ. Đề bài đã nói:
Đối với một trường 3-bit → mặt nạ = 0x07
Tôi nghĩ “chờ đã, không phải 0x07 chỉ là thập phân 7 sao? Nó không nên là cái gì khác sao?”
Thực tế là: vâng, 0x07 là thập phân 7. Nhưng ở dạng nhị phân, nó là 0000 0111. Đó chính xác là những gì chúng ta muốn cho một mặt nạ 3-bit — 3 bit cuối cùng được đặt thành 1.
Điều này đã dẫn tôi đến một quy tắc chung:
- Mặt nạ N-bit = (1 << N) – 1
Ví dụ:
- 3 bit →
(1 << 3) – 1 = 7 = 0x07→0000 0111 - 4 bit →
(1 << 4) – 1 = 15 = 0x0F→0000 1111 - 8 bit →
(1 << 8) – 1 = 255 = 0xFF→1111 1111
Vì vậy, để trích xuất nibble (4 bit), mặt nạ của chúng ta nên là 0x0F.
3. Lỗi Trong Mã C Đầu Tiên Của Tôi
Dưới đây là lần thử đầu tiên của tôi:
c
unsigned char extractNibble(unsigned char reg, int pos) {
if (pos = 0) { // Ôi không!
unsigned char lower = (reg >> 0) & 0x07;
return lower;
} else {
unsigned char higher = (reg >> 6) & 0x07;
return higher;
}
}
Những sai sót tôi đã mắc phải:
if (pos = 0)→ Đây là phép gán, không phải phép so sánh. Nó luôn đánh giá là sai. Nó nên làif (pos == 0).- Mặt nạ của tôi là
0x07(3 bit), nhưng một nibble là 4 bit, vì vậy nó nên là0x0F. - Việc dịch chuyển nibble trên của tôi là
>> 6, nhưng nó nên là>> 4.
Mã Đã Được Sửa Chữa
Dưới đây là phiên bản đã sửa:
c
#include <stdio.h>
unsigned char extractNibble(unsigned char reg, int pos) {
if (pos == 0) {
// Nibble dưới
return reg & 0x0F;
} else {
// Nibble trên
return (reg >> 4) & 0x0F;
}
}
int main() {
unsigned char reg;
int pos;
scanf("%hhu %d", ®, &pos);
printf("%d", extractNibble(reg, pos));
return 0;
}
Giờ đây, nó hoạt động cho tất cả các trường hợp:
0xAB, pos=0→ 110xAB, pos=1→ 100xFF, pos=0→ 15
Làm Cho Nó Thêm Phong Cách "Firmware"
Trong các hệ thống nhúng, chúng ta thường sử dụng các macro để làm rõ:
c
#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);
}
Điều này loại bỏ câu lệnh if, và rõ ràng thể hiện mối quan hệ giữa vị trí, dịch chuyển và mặt nạ.
Những Bài Học Chính Của Tôi
- Trích xuất nibble: Một nibble là 4 bit, và chúng ta luôn sử dụng mặt nạ
0x0Fsau khi dịch chuyển. - Thập phân so với Thập lục: Các giá trị register có thể được viết bằng hệ thập lục, nhưng đầu ra thường là số thập phân. Đừng nhầm lẫn chúng!
- Công thức mặt nạ:
(1 << N) – 1là cách phổ quát để có được mặt nạ N-bit. - Cú pháp C:
=so với==— một sai sót nhỏ có thể phá vỡ logic. - Phong cách firmware: Các macro giúp thao tác với register dễ đọc và nhất quán hơn.
Kết Luận
Những gì dường như là một bài toán "trích xuất một nibble" đơn giản thực sự đã dạy tôi rất nhiều về thao tác bit, hệ thống số và những cạm bẫy trong lập trình C. Đây là những kiến thức thiết yếu trong phát triển firmware — hầu hết mọi register phần cứng yêu cầu bạn phải mặt nạ, dịch chuyển và trích xuất các trường một cách an toàn.
Nếu bạn là người mới trong lập trình C nhúng, tôi khuyên bạn nên luyện tập với những nhiệm vụ nhỏ như thế này. Chúng có thể trông tầm thường, nhưng chúng chuẩn bị cho bạn cho firmware thực tế, nơi sự khác biệt giữa 0x07 và 0x0F có thể có nghĩa là UART hoặc ADC của bạn hoàn toàn không hoạt động.