找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4478

积分

0

好友

580

主题
发表于 昨天 01:36 | 查看: 12| 回复: 0

位域(Bit Field)与位操作正是“以位为单位”描述数据结构的利器,但 C 语言 原生位域具有实现相关性(implementation-defined)与未定义行为风险。

适用场景

在 MCU/SoC、RTOS 与裸机项目中,我们往往需要精细控制硬件寄存器、通信协议字段与存储布局,目标包括:

  • 降低 RAM/Flash 占用,缩短数据帧长度
  • 以最少的访存实现寄存器原子修改
  • 确保跨平台/跨编译器的数据表示一致
  • 提升可读性、可维护性与可测试性

位、掩码与位域

  • 概念
    • 位(bit):二进制最小存储单元;位位置从最低位 bit0 递增
    • 掩码(mask):用 1 指示目标位,用 0 屏蔽无关位
    • 位域(bit field):一段连续的位区间,用作一个数值字段
  • 三大基本操作(以无符号整型 y 与掩码 mask 为例)
#define BIT(n)                  ( (uint32_t)1u << (n) )
#define BIT_SET(y, mask)        ( (y) |=  (mask) )
#define BIT_CLEAR(y, mask)      ( (y) &= ~(mask) )
#define BIT_FLIP(y, mask)       ( (y) ^=  (mask) )
  • 位域的“取值/赋值”通式
    • 取值:value = (y >> start) & mask
    • 赋值:y = (y & ~(mask << start)) | ((x & mask) << start)

寄存器映射的安全访问

目标:对硬件寄存器的某个位域进行“读-改-写”,且不破坏其他位。

示例:假设有 32 位寄存器 CTRL,其字段如下:

  • bit[0] EN:使能位
  • bit[3:1] MODE:工作模式(3 位)
  • bit[7:4] DIV:分频系数(4 位)

推荐使用“手工位操作 + 明确掩码”的方式,避免 C 位域的不确定布局。

#include <stdint.h>

#define REG32(addr)             (*(volatile uint32_t *)(addr))
#define CTRL_BASE               (0x40000000u)
#define CTRL                    REG32(CTRL_BASE)

// 字段位置与掩码(面向移植独立)
#define CTRL_EN_POS             (0u)
#define CTRL_EN_MASK            (0x1u)

#define CTRL_MODE_POS           (1u)
#define CTRL_MODE_MASK          (0x7u)   // 3 bits

#define CTRL_DIV_POS            (4u)
#define CTRL_DIV_MASK           (0xFu)   // 4 bits

// 通用位域操作(可复用宏,见后文宏库)
#define BF_PREP(x, pos, mask)   ( ((uint32_t)(x) & (mask)) << (pos) )
#define BF_GET(y, pos, mask)    ( ((uint32_t)(y) >> (pos)) & (mask) )
#define BF_CLR(y, pos, mask)    ( (uint32_t)(y) & ~((mask) << (pos)) )
#define BF_SET(y, x, pos, mask) ( BF_CLR((y), (pos), (mask)) | BF_PREP((x), (pos), (mask)) )

static inline void ctrl_enable(uint8_t en)
{
uint32_t r = CTRL;
    r = BF_SET(r, en ? 1u : 0u, CTRL_EN_POS, CTRL_EN_MASK);
    CTRL = r;
}

static inline void ctrl_set_mode(uint32_t mode)
{
uint32_t r = CTRL;
    r = BF_SET(r, mode, CTRL_MODE_POS, CTRL_MODE_MASK);
    CTRL = r;
}

static inline uint32_t ctrl_get_div(void)
{
    return BF_GET(CTRL, CTRL_DIV_POS, CTRL_DIV_MASK);
}

要点:

  • 所有位置与掩码集中定义,便于查阅与自动生成(SVD/CSV/DSL 转换)。
  • 读-改-写前先读取寄存器镜像到局部变量,减少总线读次数。
  • BF_SET 先清后置,保证只修改目标位域。

协议/存储打包的跨端一致性

常见的做法,是把多个小字段压缩到紧凑的字节流中,再进行传输或持久化。

推荐做法:以“明确字节序 + 手工位操作”的方式构建/解析报文。

示例:将 10-bit tile、2-bit prio、4-bit pal 打包为 16 位字段(与 GBA OAM attr2 类似的布局)。

#include <stdint.h>

#define ATTR2_TILE_POS  (0u)
#define ATTR2_TILE_MASK (0x3FFu)    // 10 bits
#define ATTR2_PRIO_POS  (10u)
#define ATTR2_PRIO_MASK (0x3u)      // 2 bits
#define ATTR2_PAL_POS   (12u)
#define ATTR2_PAL_MASK  (0xFu)      // 4 bits

static inline uint16_t pack_attr2(uint32_t tile, uint32_t prio, uint32_t pal)
{
    uint32_t y = 0u;
    y = BF_SET(y, tile, ATTR2_TILE_POS, ATTR2_TILE_MASK);
    y = BF_SET(y, prio, ATTR2_PRIO_POS, ATTR2_PRIO_MASK);
    y = BF_SET(y, pal,  ATTR2_PAL_POS,  ATTR2_PAL_MASK);
    return (uint16_t)y;
}

static inline void unpack_attr2(uint16_t v, uint32_t *tile, uint32_t *prio, uint32_t *pal)
{
    if (tile) *tile = BF_GET(v, ATTR2_TILE_POS, ATTR2_TILE_MASK);
    if (prio) *prio = BF_GET(v, ATTR2_PRIO_POS, ATTR2_PRIO_MASK);
    if (pal)  *pal  = BF_GET(v, ATTR2_PAL_POS,  ATTR2_PAL_MASK);
}

如需跨字节序传输:统一采用 网络序(大端),在入/出站时用 htons/ntohs 或自定义字节翻转。

通用位操作/位域宏库

#ifndef BITFIELD_UTILS_H
#define BITFIELD_UTILS_H

#include <stdint.h>

// 单比特与多比特掩码
#define BIT_U32(n)                  ( (uint32_t)1u << (uint32_t)(n) )

// 生成 len 位的低位掩码;对 len==32 做安全处理
#define U32_MASK_LEN(len)           ( ((uint32_t)(len) >= 32u) ? 0xFFFFFFFFu : ((uint32_t)1u << (uint32_t)(len)) - 1u )

// 基础位操作
#define BIT_SET_U32(y, mask)        ( (y) |=  (uint32_t)(mask) )
#define BIT_CLEAR_U32(y, mask)      ( (y) &= ~(uint32_t)(mask) )
#define BIT_FLIP_U32(y, mask)       ( (y) ^=  (uint32_t)(mask) )

// 位域预处理/清除/获取/设置(pos: 起始位;len: 位宽)
#define BF_MASK_U32(pos, len)       ( U32_MASK_LEN(len) << (uint32_t)(pos) )
#define BF_PREP_U32(x, pos, len)    ( ((uint32_t)(x) & U32_MASK_LEN(len)) << (uint32_t)(pos) )
#define BF_GET_U32(y, pos, len)     ( ((uint32_t)(y) >> (uint32_t)(pos)) & U32_MASK_LEN(len) )
#define BF_CLR_U32(y, pos, len)     ( (uint32_t)(y) & ~BF_MASK_U32((pos), (len)) )
#define BF_SET_U32(y, x, pos, len)  ( BF_CLR_U32((y), (pos), (len)) | BF_PREP_U32((x), (pos), (len)) )

// 若已手工定义了掩码,用下列宏避免重复计算掩码
#define BF_PREP_MASK_U32(x, pos, mask)   ( ((uint32_t)(x) & (uint32_t)(mask)) << (uint32_t)(pos) )
#define BF_GET_MASK_U32(y, pos, mask)    ( ((uint32_t)(y) >> (uint32_t)(pos)) & (uint32_t)(mask) )
#define BF_CLR_MASK_U32(y, pos, mask)    ( (uint32_t)(y) & ~((uint32_t)(mask) << (uint32_t)(pos)) )
#define BF_SET_MASK_U32(y, x, pos, mask) ( BF_CLR_MASK_U32((y), (pos), (mask)) | BF_PREP_MASK_U32((x), (pos), (mask)) )

#endif // BITFIELD_UTILS_H

这些宏已经可以直接复制进 BSP 或驱动层,配合寄存器映射和协议打包使用。云栈社区上也有不少嵌入式开发者分享过类似的位域封装实践,遇到移植性问题时不妨对照一下。




上一篇:嵌入式C语言实现继承:结构体嵌套、向上转型与 container_of 实战
下一篇:嵌入式Linux构建系统怎么选?Buildroot与Yocto对比
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-3 20:52 , Processed in 1.125704 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表