Bài 48.324 phút đọc
Trường bit cho thanh ghi
Sau bài này bạn sẽ làm được
- Viết union kết hợp trường bit với giá trị thô
- Nêu năm chi tiết mà chuẩn C để cho trình biên dịch tự quyết
- Giải thích vì sao trường bit có thể sinh ra truy cập một byte
- Chọn được giữa trường bit và mặt nạ thủ công
Trường bit cho phép viết TIM->bits.enable = 1 thay vì TIM->CR1 |= 1u. Cú pháp đẹp hơn hẳn, nhưng chuẩn C để cho trình biên dịch tự quyết năm chi tiết, và với thanh ghi phần cứng thì năm chi tiết đó là quá nhiều.
#Cú pháp gọn hơn hẳn
truong-bit.h
#include <stdint.h>
typedef union {
uint32_t raw; /* truy cập cả thanh ghi */
struct {
uint32_t enable : 1; /* bit 0 */
uint32_t mode : 3; /* bit [3:1] */
uint32_t reserved0 : 4; /* bit [7:4] */
uint32_t prescaler : 8; /* bit [15:8] */
uint32_t : 16; /* trường vô danh, phần còn lại */
} bits;
} CtrlReg;
static volatile CtrlReg *TIM_CR = (volatile CtrlReg *)0x40000000UL;
/* Dùng: */
TIM_CR->bits.enable = 1u;
TIM_CR->bits.mode = 3u;
TIM_CR->bits.prescaler = 72u;
uint32_t ca_thanh_ghi = TIM_CR->raw;Mặt nạ thủ công, dài dòng
/* Bản mặt nạ thủ công, cùng việc */
uint32_t r = *TIM_CR_RAW;
r &= ~(0x7u << 1);
r |= (3u << 1);
r &= ~(0xFFu << 8);
r |= (72u << 8);
r |= 1u;
*TIM_CR_RAW = r;Trường bit, gọn và rõ
/* Bản trường bit, cùng việc */
TIM_CR->bits.mode = 3u;
TIM_CR->bits.prescaler = 72u;
TIM_CR->bits.enable = 1u;#Năm điều chuẩn C không bảo đảm
| Chi tiết | Chuẩn C nói gì | Hậu quả với thanh ghi |
|---|---|---|
| Thứ tự cấp phát bit | Do cài đặt quyết định | Trường đầu tiên có thể là bit thấp nhất hoặc bit cao nhất |
| Trường có được vắt qua ranh giới đơn vị lưu trữ không | Do cài đặt quyết định | Một trường 6 bit ở vị trí 30 có thể bị đẩy sang từ kế |
| Kiểu nào được phép làm trường bit | Chỉ int, unsigned int và _Bool là bắt buộc | uint8_t và uint32_t là mở rộng, dù mọi trình biên dịch đều hỗ trợ |
| Trường bit kiểu int có dấu hay không | Do cài đặt quyết định | Một trường 1 bit kiểu int có thể chứa 0 và âm 1 |
| Độ rộng truy cập sinh ra | Chuẩn không nói gì | Trình biên dịch có thể sinh ra lệnh đọc ghi một byte |
#Truy cập sai độ rộng
Đây là vấn đề nghiêm trọng nhất, và nó không nằm trong danh sách các điều chuẩn C bỏ ngỏ, mà nằm ở chỗ chuẩn C hoàn toàn không nói gì.
do-rong.c
typedef union {
volatile uint32_t raw;
struct {
uint32_t a : 8;
uint32_t b : 8;
uint32_t c : 8;
uint32_t d : 8;
} bits;
} Reg;
static volatile Reg *R = (volatile Reg *)0x40000000UL;
void dat_b(void)
{
R->bits.b = 0x5Au;
}terminal
arm-none-eabi-gcc -O2 -S do-rong.c -o - | sed -n '/dat_b:/,/bx/p'
dat_b:
ldr r3, .L2
movs r2, #90
strb r2, [r3, #1] @ ghi MOT BYTE, khong phai 32 bit
bx lrCách buộc truy cập đúng độ rộng
an-toan.c
/* Cách 1: đọc cả thanh ghi vào biến TẠM không volatile,
sửa trường bit trên biến tạm, rồi ghi cả thanh ghi. */
void dat_b_an_toan(void)
{
Reg tam;
tam.raw = R->raw; /* một lệnh đọc 32 bit */
tam.bits.b = 0x5Au; /* sửa trên RAM, không phải thanh ghi */
R->raw = tam.raw; /* một lệnh ghi 32 bit */
}
/* Cách 2: bỏ hẳn trường bit, dùng mặt nạ như Bài 48.1. */
void dat_b_mat_na(void)
{
uint32_t r = R->raw;
r &= ~(0xFFu << 8);
r |= (0x5Au << 8);
R->raw = r;
}terminal
arm-none-eabi-gcc -O2 -S an-toan.c -o - | sed -n '/dat_b_an_toan:/,/bx/p'
dat_b_an_toan:
ldr r3, .L4
ldr r2, [r3] @ doc 32 bit
bic r2, r2, #65280
orr r2, r2, #23040
str r2, [r3] @ ghi 32 bit
bx lr#Khi nào dùng được
| Tình huống | Trường bit | Mặt nạ thủ công |
|---|---|---|
| Thanh ghi phần cứng | Rủi ro cao | Nên dùng |
| Cấu trúc dữ liệu nội bộ tiết kiệm RAM | Rất tốt | Dài dòng không cần thiết |
| Giao thức mạng hoặc định dạng tệp | Không dùng được, bố cục không xác định | Nên dùng |
| Cờ trạng thái trong một struct của bạn | Rất tốt | Không cần |
| Mã phải chạy trên nhiều kiến trúc | Không dùng được | Nên dùng |
dung-tot.c
/* Trường bit rất hợp cho cấu trúc dữ liệu NỘI BỘ của bạn,
nơi bố cục không cần khớp với thứ gì bên ngoài. */
typedef struct {
uint16_t nhiet_do; /* nhân 10, tức 253 là 25.3 độ */
unsigned int co_loi : 1;
unsigned int dang_do : 1;
unsigned int che_do : 2;
unsigned int so_lan_thu : 3;
unsigned int : 1; /* đệm cho tròn byte */
} TrangThaiCamBien;
/* sizeof là 4 byte thay vì 8 nếu dùng năm biến uint8_t riêng.
Trên chip 4 KB RAM với một trăm cảm biến, đó là 400 byte tiết kiệm. */terminal
./so-kich-thuoc
dung truong bit : sizeof = 4 dung uint8_t rieng: sizeof = 8 voi 100 phan tu: 400 byte so voi 800 byte
#Cách thay thế an toàn
thay-the.h
#include <stdint.h>
/* Giữ được sự tiện lợi của trường bit mà không có rủi ro:
một struct THƯỜNG không volatile để tính toán,
cộng hai hàm chuyển đổi qua lại với giá trị thô. */
typedef struct {
uint8_t enable;
uint8_t mode;
uint8_t prescaler;
} TimCauHinh;
static inline uint32_t tim_dong_goi(const TimCauHinh *c)
{
return ((uint32_t)(c->enable & 0x01u) << 0)
| ((uint32_t)(c->mode & 0x07u) << 1)
| ((uint32_t)(c->prescaler & 0xFFu) << 8);
}
static inline TimCauHinh tim_thao_goi(uint32_t raw)
{
TimCauHinh c;
c.enable = (uint8_t)((raw >> 0) & 0x01u);
c.mode = (uint8_t)((raw >> 1) & 0x07u);
c.prescaler = (uint8_t)((raw >> 8) & 0xFFu);
return c;
}
/* Dùng: */
TimCauHinh c = tim_thao_goi(TIM3->CR1);
c.prescaler = 72u;
c.enable = 1u;
TIM3->CR1 = tim_dong_goi(&c); /* MỘT lệnh ghi 32 bit */terminal
arm-none-eabi-gcc -O2 -S thay-the.c -o - | sed -n '/cau_hinh:/,/bx/p'
cau_hinh:
ldr r3, .L2
ldr r2, [r3] @ mot lenh doc 32 bit
bic r2, r2, #65280
orr r2, r2, #18432
orr r2, r2, #1
str r2, [r3] @ mot lenh ghi 32 bit
bx lr# Cùng số lệnh với bản mặt nạ thủ công
arm-none-eabi-size mat-na.o thay-the.o
text data bss dec hex filename
28 0 0 28 1c mat-na.o
28 0 0 28 1c thay-the.oTự làm thử
- Viết union trường bit cho một thanh ghi, gán một trường, rồi xem mã hợp ngữ có sinh ra
strbkhông. - Khai báo trường bit một bit kiểu
int, gán 1 rồi so với 1, và giải thích kết quả. - Viết struct có trường 30 bit rồi trường 4 bit, in
sizeofvà giải thích con số nhận được. - Cài mẫu đọc vào biến tạm rồi ghi cả thanh ghi, và xác nhận mã sinh ra chỉ có
ldrvàstr. - Viết cặp hàm đóng gói và tháo gói cho một thanh ghi, so kích thước mã với bản mặt nạ thủ công.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Trường bit cho cú pháp gọn và tự lo cả xóa lẫn đặt, nhưng bố cục do trình biên dịch quyết định chứ không phải chuẩn C.
- Năm chi tiết bỏ ngỏ: thứ tự bit, vắt qua ranh giới, kiểu cho phép, dấu của trường, và độ rộng truy cập sinh ra.
- Trình biên dịch có thể sinh ra lệnh ghi một byte vào thanh ghi chỉ chấp nhận truy cập 32 bit, và
volatilekhông ngăn được. - Trường bit rất hợp cho struct nội bộ tiết kiệm RAM, và hoàn toàn không hợp cho thanh ghi phần cứng, giao thức mạng hay định dạng tệp.
- Cách an toàn giữ được cú pháp đẹp là dùng struct thường cộng hai hàm đóng gói và tháo gói, sinh ra đúng cùng số lệnh với mặt nạ thủ công.