【问题标题】:Converting bits of a byte to single bits and back将字节的位转换为单个位并返回
【发布时间】:2015-05-30 01:48:24
【问题描述】:

我有下面的结构

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.....
}

尽管付出了所有努力,但上面的代码似乎都不起作用。我在嵌入式处理器上这样做,它真的很难调试。不知道有没有 C-Guru 大佬能告诉我这段代码有什么问题?

【问题讨论】:

  • 你的第一班不应该是1&lt;&lt;0吗? (不是解决方案,因为它仍然应该做某事 - 错误,但仍然。)
  • 当涉及到可移植性,甚至在具有字节序的单一平台中时,位域显然是一个主要问题。可能值得避免使用它们,而只需要自己执行按位逻辑。就是说,如果它们工作正常并且您对此没意见,我真的建议在这里与fpButtonsuint8_t 建立一个工会。然后您不必费心提取/打包位。否则,当您无论如何都要做所有这些按位提取时,使用位字段似乎特别没有意义。
  • @Ike 我真的不能使用联合解决方案,因为项目的其他部分需要它。
  • @SaeidYazdani 怎么样?对于依赖它的其他部分,您仍然可以将原始的 fpButton 字段从联合中取出。
  • @Ike 哎呀你是对的!

标签: c bit-manipulation bitwise-operators stm32


【解决方案1】:

位是 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.....
}

【讨论】:

  • 只是一个简单的问题,因为替代方案更好:你的问题是否比在顶部函数中使用 data-&gt;button_1 = (b1 &gt;&gt; 0) &amp;1; (等等)更有意义,因为它会像 test+@987654329 这样交易@反对shr+and?看到这一点,我一定用过这样的结构数百次了——但我想换班更贵。
  • 分配给rawbuttons_1 将同时设置所有8 位,不需要位操作,只需一个MOV 分配。分配给button_1 将只设置那一位而不是其他 7 位,因此使用了位操作并且会更昂贵。
  • 是的,这是更好的选择。我指的是你的构造data-&gt;button_1 = (b1 &amp; ( 1 &lt;&lt; 0 )) != 0;,乍一看似乎比我的更复杂,使用单个数据移位。 (顺便说一句,我可能已经回答了我自己的问题。)
  • 您的原始代码使用 2 次移位来获得布尔结果,而我的示例使用 1 次移位和 1 次比较。与轮班相比,比较的工作量更少。一个像样的编译器应该能够优化出&lt;&lt; 0,因为它基本上是一个空操作。
【解决方案2】:

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

假设您的所有位字段都是 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;

【讨论】:

    【解决方案3】:

    代码有一些缺陷:

    • 您编写 C,但对 APP_Send() 使用 C++ 标记和 C++ 签名(对于空参数列表,C 需要 void)。
    • 位域有一些问题,主要是因为它们的顺序没有定义(字段可能是自上而下或自下而上分配的 - 都已经看到了)。
    • (b1 &amp; ( 1 &lt;&lt; 1 )) &gt;&gt; 1 将计算为 int。对于有符号类型,右移是实现定义的。尽管它可能有效,但它肯定不是“防御性编程”;-)
    • 位从 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 上很好地优化这一点。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-19
      • 2012-07-05
      相关资源
      最近更新 更多