C语言学习笔记-位结构体(位域)

目录

一、C结构体之位域

二、定义

1.声明位结构体类型

2. 定义位结构体变量

三、使用方法

四、位结构和共用体联合使用

五 位结构体的对齐

六 使用实例-根据电池BMS协议解析电池数据


一、C结构体之位域

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。

例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。

为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。

所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

二、定义

1.声明位结构体类型

一般形式具体如下:

struct 位结构体名
{
    数据类型 变量名: 位域长度;
    数据类型 变量名: 位域长度;
};

说明:

数据类型:必须是unsigned和signed可修饰的类型(char,short,int,long)。

变量名:是选择项, 可以不命名, 这样规定是为了排列需要。

位域长度:必须是非负的整 数, 范围是0~15, 表示二进制位的个数, 即表示有多少位。

备注:

一个位域必须存储在同一个字节中,不能跨两个字节,因此位域的长度不能大于一个字节的长度。

位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的,例如:

struct k
{
    int a:1
    int :2 /*无位域名,该2位不能使用*/
    int b:3
    int c:2
}; 

2. 定义位结构体变量

举例说明如下:

struct bit
{
    int a:1;//第0位
    int b:2;//第1,2位
    int c:3;//第3,4,5位
    int d:4;//第6,7,8,9位
    int e:6;//第10,11,12,13,14位
}temp;

注意:

位结构中的成员可以定义为unsigned, 也可定义为signed, 但当成员长度为1时, 会被认为是unsigned类型。因为单个位不可能具有符号。

位结构中的成员不能使用数组和指针, 但位结构变量可以是数组和指针, 如果是指针, 其成员访问方式同结构指针。

位结构总长度(位数), 是各个位成员定义的位数之和, 可以超过两个字节。

位结构成员可以与其它结构成员一起使用。

三、使用方法

位结构体变量的赋值和一般结构体赋值的方法是一样的。

初始化定义时—赋值

struct bit
{
    int a:1;//第0位
    int b:2;//第1,2位
    int c:3;//第3,4,5位
    int d:4;//第6,7,8,9位
    int e:6;//第10,11,12,13,14位
}temp={1,3,6,10,26};

初始化后—赋值

temp.a=1;
temp.b=3;
temp.c=6;
temp.d=10;
temp.e=26;

位结构体的使用说明举例如下:

#include <stdio.h>

struct bit
{
    int a:1;//第0位
    int b:2;//第1,2位
    int c:3;//第3,4,5位
    int d:4;//第6,7,8,9位
    int e:6;//第10,11,12,13,14,15位
}temp={1,3,6,10,26};

int main(int argc, char *argv[])
{
    int i=*((int*)&temp);
    printf("0x%x\n",i);
    return 0;
}

运行后将输出:0x6ab7

解释说明:

变量

15、14、13、12、11、10

9、8、7、6

5、4、3

2、1

0

a(1)

1

b(3)

11

c(6)

110

d(10)

1010

e(26)

011010

结果是0b0110101010110111=0x6ab7

四、位结构和共用体联合使用

具体程序如下:

#include <stdio.h>

typedef union 
{
    int  data;
    struct 
    {
        int a:1;//第0位
        int b:2;//第1,2位
        int c:3;//第3,4,5位
        int d:4;//第6,7,8,9位
        int e:6;//第10,11,12,13,14,15位
    }bit;
}_MQ; 

_MQ mq=
{
   .bit.a = 1,
   .bit.b = 3,
   .bit.c = 6,
   .bit.d = 10,
   .bit.e = 26
};

int main(int argc, char *argv[])
{
    printf("data=0x%x\n",mq.data);
    return 0;
}

运行后将输出:0x6ab7

五 位结构体的对齐

如果结构体中含有位域(bit-field),那么VC中准则是:

  1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;

  2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;

  3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式;

系统会先为结构体成员按照对齐方式分配空间和填塞(padding),然后对变量进行位域操作。

六 使用实例-根据电池BMS协议解析电池数据

1.电池状态信息

由3个字节组成:

第1字节:

  • Bit0:充电标志,置 1 表示检测到充电电流。
  • Bit1:充电过流标志,置 1 表示检测到充电过流。
  • Bit4:放电标志,置 1 表示检测到有效放电电流。
  • Bit5:放电过流标志,置 1 表示发生放电过流。
  • Bit6:放电短路标志,置 1 表示发生放电短路。

第2字节:

  • Bit0:电芯侦测线开路标志,置 1 表示检测到电芯侦测线开路。
  • Bit1:温感侦测线开路标志,置 1 表示检测到温感侦测线开路。
  • Bit4:电芯过压标志,置 1 表示电芯过压。
  • Bit5:电芯欠压标志,置 1 表示电芯欠压。
  • Bit6:总压过高标志,置 1 表示电池组总压过高。
  • Bit7:总压过低标志,置 1 表示电池组总压过低。

第3字节:

保留

2. 电流值

“电流值”为 16 位整型数(int16_t),单位为 0.1A,充电为正、放电为负。

3. 电芯串数

“电芯串数”为 8 位无符号整型数(uint8_t),表示当前 BMS 所管理的电芯串联数量。

4. 电芯电压

“电芯电压”为 16 位无符号整型数的数组,数组的元素个数由“电芯串数”指定。数组元素的所表示的单位为 mV。

5. 循环次数

循环次数” 为 16 位无符号整型数(uint16_t),表示当前电池组己经过的充、放电次数。

6. 剩余电量

“剩余电量”为 16 位无符号整型数(uint16_t),单位为 0.1AH。

7. 总容量

“总容量” 为 16 位无符号整型数(uint16_t),单位为 0.1AH。

8. 开关状态

“开关状态”为 8 位无符号整型数(uint8_t),各 bit 意义如下:

Bit6:充电开关状态, 1 表示开关闭合,允许充电。

Bit7:放电开关状态, 1 表示开关闭合,允许放电。

根据电池应答消息格式定义电池数据结构

#define BATTERY_CELL_NUM 7 //7串电芯

#pragma pack(push)
#pragma pack(1)
typedef struct {
  union {
    struct {
      uint8_t charge_flag:1;                           //充电标志
      uint8_t charge_overcurrent_flag:1;               //充电过流标志
      uint8_t :2;                                
      uint8_t discharge_flag:1;                        //放电标志
      uint8_t discharge_overcurrnet_flag:1;            //放电过流标志
      uint8_t discharge_shortcircuit_flag:1;           //放电短路标志
      uint8_t :1;
    }bit;
    uint8_t byte;
  }status1;
  union {
    struct {
      uint8_t cell_detect_line_open_circuit:1;           //电芯侦测线开路标志
      uint8_t temp_sensor_detect_line_open_circuit:1;    //温感侦测线开路标志
      uint8_t :2;                                        
      uint8_t cell_overvoltage:1;                        //电芯过压标志
      uint8_t cell_undervoltage:1;                       //电芯欠压标志
      uint8_t overvoltage:1;                             //总电压过高标志
      uint8_t undervoltage:1;                            //总电压过低标志
    }bit;
    uint8_t byte;
  }status2;
//  uint8_t status1;
//  uint8_t status2;
  uint8_t status3;
  int16_t current;
  uint8_t cell_num;
  uint16_t cell_voltage[BATTERY_CELL_NUM];
  uint16_t cycles;
  uint16_t remaining_capacity;
  uint16_t total_capacity;
  //uint8_t switch_status;
  union {
    struct {
      uint8_t :6; //bit0-5
      uint8_t charge_switch_status:1; //bit6
      uint8_t discharge_switch_status:1; //bit7
    }bit;
    uint8_t byte;
  }switch_status;
}BatteryRawData_t;
#pragma pack(pop)

分析

通过接收到的电池报文数据,在符合包头、长度、校验等信息后便可以将对应字段的数据直接复制到BatteryRawData_t类型的结构体数据中,通过该结构体便可以直接获取需要的变量信息。这样的处理方式极大的简化了报文的解包处理。

当然这里的应用还有一个前提:超过1个字节的变量传输的时候是按照小端模式(低位字节先传输,高位字节后传输)进行传输的。如果当前协议不符合该格式,是不能够直接应用这种方式的。

  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值