将字节的位转换为单个位并返回

Converting bits of a byte to single bits and back

我有下面的结构

typedef struct fpButtons {

  /**     BYTE 0         **/        
  uint8_t       button_1:1;       
  uint8_t       button_2:1;
  uint8_t       button_3:1;
  uint8_t       button_4:1;
  uint8_t       button_5:1;
  uint8_t       button_6:1;
  uint8_t       button_7:1;
  uint8_t       button_8:1;

  /** And BYTE 2 which I didn't paste to save SO's space lol **/
  // button_9:1 to button_16:1
} FP_BUTTONS;

这个函数给出一个无符号整数,它的位应该构建上面的结构

void FP_UpdateData(FP_BUTTONS *data, uint8_t b1, uint8_t b2) 
{
  data->button_1 = (b1 & ( 1 << 1 )) >> 1;
  data->button_2 = (b1 & ( 1 << 2 )) >> 2;
  data->button_3 = (b1 & ( 1 << 3 )) >> 3;
  data->button_4 = (b1 & ( 1 << 4 )) >> 4;
  data->button_5 = (b1 & ( 1 << 5 )) >> 5;
  data->button_6 = (b1 & ( 1 << 6 )) >> 6;
  data->button_7 = (b1 & ( 1 << 7 )) >> 7;
  data->button_8 = (b1 & ( 1 << 8 )) >> 8;

  //Do the same thing for button 9 to 16

}

现在存储后,我必须将它发送到其他地方:

void APP_Send(){

  uint8_t packet[2];

  packet[0] = *((uint8_t*) &FP_BUTTONS_Data);
  packet[1] = *(((uint8_t*) &FP_BUTTONS_Data)+1);    

  //Send stuff away.....
}

尽管付出了所有努力,但上面的 none 代码似乎有效。我在嵌入式处理器上做这件事,它真的很难调试。我想知道一些 C-Guru 家伙是否可以告诉我这些代码有什么问题?

位是从 0 开始索引的,但是您将移位编码为好像位是从 1 开始索引的。您必须使用 07 来轮班 uint8_t

此外,可以删除右移。

试试这个:

void FP_UpdateData(FP_BUTTONS *data, uint8_t b1, uint8_t b2) 
{
  data->button_1 = (b1 & ( 1 << 0 )) != 0;
  data->button_2 = (b1 & ( 1 << 1 )) != 0;
  data->button_3 = (b1 & ( 1 << 2 )) != 0;
  data->button_4 = (b1 & ( 1 << 3 )) != 0;
  data->button_5 = (b1 & ( 1 << 4 )) != 0;
  data->button_6 = (b1 & ( 1 << 5 )) != 0;
  data->button_7 = (b1 & ( 1 << 6 )) != 0;
  data->button_8 = (b1 & ( 1 << 7 )) != 0;

  //Do the same thing for button 9 to 16
}

void APP_Send()
{
  uint8_t packet[2];

  uint8_t *data = (uint8_t*) &FP_BUTTONS_Data;
  packet[0] = data[0];
  packet[1] = data[1];

  //Send stuff away.....
}

话虽如此,如果您将结构数据包装在 unions:

中,那么所有这些手动移位都是不必要的
typedef struct fpButtons {

  /**     BYTE 0         **/        
  union {
    struct {
      uint8_t       button_1:1;       
      uint8_t       button_2:1;
      uint8_t       button_3:1;
      uint8_t       button_4:1;
      uint8_t       button_5:1;
      uint8_t       button_6:1;
      uint8_t       button_7:1;
      uint8_t       button_8:1;
    };
    uint8_t         rawbuttons_1;
  };

  /**     BYTE 1         **/        
  union {
    struct {
      uint8_t       button_9:1;       
      uint8_t       button_10:1;
      uint8_t       button_11:1;
      uint8_t       button_12:1;
      uint8_t       button_13:1;
      uint8_t       button_14:1;
      uint8_t       button_15:1;
      uint8_t       button_16:1;
    };
    uint8_t         rawbuttons_2;
  };

} FP_BUTTONS;

void FP_UpdateData(FP_BUTTONS *data, uint8_t b1, uint8_t b2) 
{
  data->rawbuttons_1 = b1;
  data->rawbuttons_2 = b2;
}

void APP_Send()
{
  uint8_t packet[2];

  packet[0] = FP_BUTTONS_Data.rawbuttons_1;
  packet[1] = FP_BUTTONS_Data.rawbuttons_2;

  //Send stuff away.....
}

如果您实际上是将两个八位字节保存到自定义位压缩结构中,只是为了将这两个八位字节拉回以将它们发送到其他地方,您可能会考虑改变您的方法。

假设您所有的位字段都是 1 位宽(如您的示例中的情况),您可以使用位移来访问值(编译器无论如何都会为您执行),如下所示:

/* Button shift values */
typedef enum
{
    BUTTON_1 = 0,
    BUTTON_2 = 1,
    ...
    BUTTON_16 = 15,
} button_t;

void FP_UpdateData(uint16_t *buttons, uint8_t b1, uint8_t b2) 
{
    /* You could also just return the value rather than passing a pointer
     * around like this. Even better, skip this function call and do the
     * math. */
    *buttons = (uint16_t)b1;
    *buttons |= (uint16_t)(b2 << 8);
}

bool get_button_state(uint16_t *buttons, button_t button)
{
    return *buttons & (1 << button);
}

void APP_Send()
{
    uint8_t packet[2];

    packet[0] = (uint8_t)(buttons & 0xff);
    packet[1] = (uint8_t)((buttons >> 8) & 0xff);
}

如果您使用的是 GCC,则可以执行以下操作来制作压缩结构:

typedef struct __atrribute__((packed)) {

  /**     BYTE 0         **/        
  unsigned       button_1:1;       
  unsigned       button_2:1;
  unsigned       button_3:1;
  unsigned       button_4:1;
  unsigned       button_5:1;
  unsigned       button_6:1;
  unsigned       button_7:1;

  /** And BYTE 2 which I didn't paste to save SO's space lol **/
  // button_9:1 to button_16:1
} FP_BUTTONS;

代码有一些缺陷:

  • 您编写了 C,但使用 C++ 标记和 C++ 签名 APP_Send()(对于空参数列表,C 需要 void)。
  • 位域有一些问题,主要是因为它们的顺序没有定义(字段可能是自上而下或自下而上分配的——都已经看到了)。
  • (b1 & ( 1 << 1 )) >> 1 将计算为 int。对于有符号类型,右移是实现定义的。虽然它可能有效,但肯定不是 "defensive programming";-)
  • 位从 0 开始,而不是 1。您的错误称为 "of by one" 并且非常常见(它甚至可能是最常见的编程错误之一)。
  • 此代码可能非常昂贵,因为编译器可能无法将其优化为基本指令。

因此,在嵌入式系统上,通常只使用 uint8_t 作为位图并避免使用位域结构。这是因为不能保证这些会匹配外围寄存器的布局,例如移动等非常烦人,很可能会破坏你的时间(例如中断处理程序)。

此外,使用掩码可以非常轻松地一次操作多个位。

enum {
    BIT0 = (1U<<0),
    BIT1 = (1U<<1),
    ...
};   
uint8_t buttons;

... in a function:
buttons |= BIT1 | BIT2;     // set the corresponding bits
buttons &= ~(BIT5 | BIT7);  // clear those bits

if ( buttons & BIT4 )
    ...

GPIOB->OUTR = BIT4 | BIT6;  // set the output bits on STM32F port B

这就是嵌入式编程中实际完成的方式。请注意,STM32F 有 16 位 GPIO 端口,而不是 8 位。

哦,要获得一个位作为 0/1 值:

int single_bit = !!(buttons & BIT3)

这将创建一个带有第一个否定的布尔结果,标准为 0 或 1。第二次否定将再次使该积极逻辑。然而,这很少使用,上面 if ... 中的一个在大多数情况下就足够了。 然而,一个好的编译器(如 gcc)应该在 Cortex-M 上很好地优化它。

后者你可以简单地分配给你的每个位域。如果幸运的话,编译器知道该模式并创建一个简单的赋值(或者至少只是一个带有两条指令的位域传输)。