在分析位域操作之前首先知道如何对一个变量进行【位操作】。从计算机存储方面来讲,位操作的优势在于:有时候一些数值无需占用一整个或多个字节,可能只占某几个bit,使用位操作会比较节省内存资源。
在C语言中,对一个变量进行位操作时,最常见的方法是:通过移位、按位与、按位或等操作来对变量进行某个bit的操作,例如:
value |= (1<<23);
但是,如果一个32bit的变量反复通过移位、按位与、按位或操作,后期维护的时候,代码看起来难以分析、维护,比如如下代码:
value |= ((1 << xxx) & ~(1 << xxx)) | ((1 << xxx) & ~(1 << 9)) | ((1 << 23) | (xxx)) & (xxx)xxx
具体某个bit的值是几,会变得非常难分析,后期维护很浪费时间。因此,位域操作诞生了!
【位域操作】,是通过联合体、结构体等将变量的每个bit定义出来,如下:
value {
bit0:1;
bit1:1;
bit2:1;
bit3:1;
...
}
这样可以给一个变量某个bit直接赋值,如下:
value.bit0 = 1;
value.bit1 = 0;
value.bit4 = 1;
value.bit7 = 1;
...
后期维护时,代码看起来也非常直观,非常方便。
备注:对于以上【位域操作产生的原因分析】,说的我自己差点都信了!这纯属瞎扯淡!这仅仅只是自己对【位域操作产生原因】的意淫与个人见解,并不能保证这是本质原因。
但是很明显:位域操作确实有这个很明显的上述优点。
下面就根据实例分析一下C语言如何进行位域操作。
示例题目:一个32bit长度的寄存器,使用位域的方式定义一个寄存器变量,并对各个bit进行赋值操作。
#include
#include
#include
/* 寄存器变量类型 */
typedef union {
uint32_t all; //all为32位,整形
struct {
uint32_t bit0:1;
uint32_t bit1:1;
uint32_t bit2:1;
uint32_t bit3:1;
uint32_t bit4:1;
uint32_t bit5:1;
uint32_t bit6:1;
uint32_t bit7:1;
uint32_t bit8:1;
uint32_t bit9:1;
uint32_t bit10:1;
uint32_t bit11:1;
uint32_t bit12:1;
uint32_t bit13:1;
uint32_t bit14:1;
uint32_t bit15:1;
uint32_t bit16:1;
uint32_t bit17:1;
uint32_t bit18:1;
uint32_t bit19:1;
uint32_t bit20:1;
uint32_t bit21:1;
uint32_t bit22:1;
uint32_t bit23:1;
uint32_t bit24:1;
uint32_t bit25:1;
uint32_t bit26:1;
uint32_t bit27:1;
uint32_t bit28:1;
uint32_t bit29:1;
uint32_t bit30:1;
uint32_t bit31:1;
} bit;
} REG32_VALUE;
int main(int argc, char *argv[])
{
REG32_VALUE reg;
/* 1. 按位赋值 */
printf("---------------------\r\n");
reg.all = 0;
printf("reg value = %d\r\n", reg.all);
reg.bit.bit7 = 1;
reg.bit.bit6 = 0;
reg.bit.bit5 = 1;
reg.bit.bit4 = 0;
reg.bit.bit3 = 1;
reg.bit.bit2 = 0;
reg.bit.bit1 = 1;
reg.bit.bit0 = 0;
printf("reg value = %d\r\n", reg.all);
/* 2. 移位赋值 */
printf("---------------------\r\n");
reg.all = 0;
reg.all = reg.all | (1 << 3);
printf("reg value = %d\r\n", reg.all);
/* 3. 直接赋值 */
printf("---------------------\r\n");
reg.all = 0x55;
printf("reg value = %d\r\n", reg.all);
return 0;
}
lsy@ubuntu18:~/practice/language/c/bit$ gcc bit.c
lsy@ubuntu18:~/practice/language/c/bit$ ./a.out
---------------------
reg value = 0
reg value = 170
---------------------
reg value = 8
---------------------
reg value = 85
上述代码结构体位于操作中,每个bit都占用1位,但是在实际应用中,每个成员可能并不是都只占用1个bit,也可以占2bit、3bit等,则实际业务中应该根据需求灵活定义,如下示例代码。
示例题目:一个char类型的变量,使用位域的方式定义变量,并对变量各bit进行赋值操作。
#include
#include
/*
* led变量各个字节含义
* +------+-------------+--------------+--------------+--------------+--------------+--------------+
* | 位 | bit7 - bit6 | bit5 - bit4 | bit3 | bit2 | bit1 | bit0 |
* +------+-------------+--------------+--------------+--------------+--------------+--------------+
* | 含义 | ledNum | ledKind | reserved | led2Stat | led1Stat | led0Stat |
* +------+-------------+--------------+--------------+--------------+--------------+--------------+
*/
typedef union {
char all;
struct {
char led0Stat:1; /* led0状态 */
char led1Stat:1; /* led1状态 */
char led2Stat:1; /* led2状态 */
char :1; /* 该bit不能使用,没有名字的位不能使用,作用仅仅是为了占位 */
char ledKind:2; /* led种类 */
char ledNum:2; /* 当前led个数 */
} bit;
} Led;
int main(int argc, char *argv[])
{
Led led;
printf("sizeof(led) = %ld\r\n", sizeof(led));
printf("----------------------\r\n");
led.all = 0;
printf("led.all = 0x%02x\r\n", led.all);
led.bit.ledNum = 3; /* 0b11 */
led.bit.ledKind = 2; /* 0b10 */
led.bit.led2Stat = 0;
led.bit.led1Stat = 1;
led.bit.led0Stat = 0;
printf("led.all = 0x%02hhx\r\n", led.all); /* hhx表示打印的char类型对应的十六进制 */
return 0;
}
备注:
printf中打印十六进制各类长度描述符如下表格
具体见另一篇博客:【c语言各类型变量按照十六进制printf打印——对应描述符】
变量类型 | 十六进制打印,printf中的描述符 | 示例 |
---|---|---|
int | %x | printf("%x", val); |
char | %hhx | printf("%hhx", val); |
lsy@ubuntu18:~/practice/language/c/bit$ gcc bit1.c
lsy@ubuntu18:~/practice/language/c/bit$ ./a.out
sizeof(led) = 1
----------------------
led.all = 0x00
led.all = 0xe2
以TI的AM437x芯片SPI复用寄存器为例,首先,经过分析后有以下场景:
代码如下:
/* 封装:表示32bit寄存器值的联合体 */
typedef union {
uint32_t all; //value
struct {
uint32_t bit0:1;
uint32_t bit1:1;
uint32_t bit2:1;
uint32_t bit3:1;
uint32_t bit4:1;
uint32_t bit5:1;
uint32_t bit6:1;
uint32_t bit7:1;
uint32_t bit8:1;
uint32_t bit9:1;
uint32_t bit10:1;
uint32_t bit11:1;
uint32_t bit12:1;
uint32_t bit13:1;
uint32_t bit14:1;
uint32_t bit15:1;
uint32_t bit16:1;
uint32_t bit17:1;
uint32_t bit18:1;
uint32_t bit19:1;
uint32_t bit20:1;
uint32_t bit21:1;
uint32_t bit22:1;
uint32_t bit23:1;
uint32_t bit24:1;
uint32_t bit25:1;
uint32_t bit26:1;
uint32_t bit27:1;
uint32_t bit28:1;
uint32_t bit29:1;
uint32_t bit30:1;
uint32_t bit31:1;
} bit;
} reg32_value;
/*
* 寄存器属性结构体:
* vaddr : 寄存器地址
* value : 寄存器值,类型为寄存器值联合体
*/
typedef struct {
uintptr_t vaddr;
reg32_value value;
} am437x_reg;
#include "reg.h"
/*
* spi1复用功能寄存器,4个寄存器(SPI的4个复用引脚)
* 类型均为:am437x_reg统一类型
*/
typedef struct {
am437x_reg sts;
am437x_reg conf_mcasp0_aclkx;
am437x_reg conf_mcasp0_fsx;
am437x_reg conf_mcasp0_axr0;
} am437x_pin_mux;
void spi_pin_mux_init(am437x_pin_mux *spi_pin)
{
/* 省略非关键代码 */
/* 寄存器赋值示例 */
spi_pin->conf_mcasp0_aclkx.value.bit.bit24 = 1;
spi_pin->conf_mcasp0_aclkx.value.bit.bit18 = 0;
spi_pin->conf_mcasp0_aclkx.value.bit.bit2 = 0;
spi_pin->conf_mcasp0_aclkx.value.bit.bit1 = 1;
spi_pin->conf_mcasp0_fsx.value.bit.bit18 = 0;
spi_pin->conf_mcasp0_fsx.value.bit.bit17 = 1;
spi_pin->conf_mcasp0_fsx.value.bit.bit1 = 1;
spi_pin->conf_mcasp0_axr0.value.bit.bit17 = 1;
spi_pin->conf_mcasp0_axr0.value.bit.bit16 = 1;
spi_pin->conf_mcasp0_axr0.value.bit.bit3 = 0;
/* 省略非关键代码 */
}