将一个字节的位转换为单个位和后面的位

时间:2015-05-30 01:48:24

标签: c bit-manipulation bitwise-operators stm32

我有下面的结构

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家伙是否可以告诉我这些代码有什么问题?

3 个答案:

答案 0 :(得分:4)

比特是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.....
}

话虽如此,如果你将结构数据包装在union中,那么所有这些手动移动都是不必要的:

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 :(得分: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;

答案 2 :(得分:2)

代码有一些缺陷:

  • 您编写C,但使用C ++标记和APP_Send()的C ++签名(对于空参数列表,C需要void)。
  • Bitfields存在一些问题,主要是因为它们的排序没有定义(字段可能是自上而下或自下而上分配的 - 都已经看过了。)
  • (b1 & ( 1 << 1 )) >> 1将计算为int。对于签名类型,右移是实现定义的。虽然它可能有效,但肯定不是&#34;防御性编程&#34;; - )
  • 比特从0开始,而不是1.你的错误被称为&#34;由一个&#34;并且很常见(它甚至可能是最常见的编程错误之一)。
  • 此代码可能非常昂贵,因为编译器可能无法将其优化为基本指令。

因此,在嵌入式系统上,通常只使用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上很好地优化它。

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