将字节的位转换为单个位并返回
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 开始索引的。您必须使用 0
到 7
来轮班 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
s:
中,那么所有这些手动移位都是不必要的
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 上很好地优化它。
后者你可以简单地分配给你的每个位域。如果幸运的话,编译器知道该模式并创建一个简单的赋值(或者至少只是一个带有两条指令的位域传输)。
我有下面的结构
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 开始索引的。您必须使用 0
到 7
来轮班 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
s:
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 上很好地优化它。
后者你可以简单地分配给你的每个位域。如果幸运的话,编译器知道该模式并创建一个简单的赋值(或者至少只是一个带有两条指令的位域传输)。