如何将(最多 4 个)uint_8t 和 int8_t 组合成一个 uint32_t?

how to pack mix of (up to 4) uint_8t and int8_t into one uint32_t?

如何编写一组宏来将有符号和无符号字节值(最多 4 个)混合打包到一个 32 位变量


动机:

我有(第三方)库,它提供 uint32_t 参数的功能,实际上包含 1-4 个字节,每个字节都可以签名或未签名,具体取决于其他一些情况。在内部它被分解为 4 bytes,然后传输到其他地方,字节被读取并解释为 signedunsigned 并使用。

我有很多代码会调用这个函数,但如果我能写一些简单的 macro/macros 将正确数量的值打包并在一行。

函数原型为:

void get_data(int addr,uint32_t cmd,uint8_t cmdlen,uint8_t *data,uint8_t max_len)

我想要这样的快捷方式:

#define GET_DATA1(cmd1,var,len) get_data(this->i2c,PACK1(cmd1),(uint8_t *)&(var),len)
#define GET_DATA2(cmd1,cmd2,var,len) get_data(this->i2c,PACK2(cmd1,cmd2),(uint8_t *)&(var),len)
#define GET_DATA3(cmd1,cmd2,cmd3,var,len) get_data(this->i2c,PACK3(cmd1,cmd2,cmd3),(uint8_t *)&(var),len)
#define GET_DATA4(cmd1,cmd2,cmd3,cmd4,var,len) get_data(this->i2c,PACK4(cmd1,cmd2,cmd3,cmd4),(uint8_t *)&(var),len)

调用如下:

uint8_t status
uint8_t item=130;
char name[5];
int8_t speed = -20;

GET_DATA1(READ_STATUS,status,1); // reads 1 byte status
GET_DATA2(READ_ITEM_NAME,item,name,5); // read 5 chars long name of item 130
GET_DATA3(SET_SPEED_LIMIT,30,status,1); // set maximum speed to 30 (globally) and read 1 byte status
GET_DATA4(SET_ITEM_SPEED,item,speed,status,1); // set speed of unit 130 to -20 (reversal) and read 1 byte status
// and so on for different combination of values and commands and such

PACKx 宏现在是这样的:

#define PACK1(cmd1) (cmd1),1
#define PACK2(cmd1,cmd2) ((cmd1<<8)|cmd2),2
#define PACK3(cmd1,cmd2,cmd3) ((cmd1<<16)|(cmd2<<8)|cmd3),3
#define PACK4(cmd1,cmd2,cmd3,cmd4) ((cmd1<<24)|(cmd2<<16)|(cmd3<<8)|cmd4),4

但它并不如我所愿。

1) 它只需要最后 2 个参数(可能是因为某些地方转换为 16 位变量)

2) 它对带符号的变量不起作用(可能使它们在某个地方变长)

我收到很多这样的警告:

PACK.ino:5:55: warning: left shift count >= width of type
#define PACK4(cmd1,cmd2,cmd3,cmd4) ((cmd1<<24)|(cmd2<<16)|(cmd3<<8)|cmd4)

示例代码:

#define PACK1(cmd1) (cmd1)
#define PACK2(cmd1,cmd2) ((cmd1<<8)|cmd2)
#define PACK3(cmd1,cmd2,cmd3) ((cmd1<<16)|(cmd2<<8)|cmd3)
#define PACK4(cmd1,cmd2,cmd3,cmd4) ((cmd1<<24)|(cmd2<<16)|(cmd3<<8)|cmd4)

#define ww(a) Serial.write(a);
#define pp(a,b) Serial.print(a,b);

void hh(uint32_t x){
    uint8_t v;
    ww("     ( 0x");
    for (int i=3;i>=0;i--) { v= (x>>(8*i))& 0xFF;if(v<16) ww("0");pp(v,HEX);};
    ww(" )");
}
#define TEST1(cmd1)         ww("\r\n"); ww(" 0x");pp(cmd1,HEX); ww(" = 0x");pp(PACK1(cmd1),HEX);    cmd=PACK1(cmd1);    ww(" => 0x");pp(cmd,HEX);   hh(cmd);
#define TEST2(cmd1,cmd2)        ww("\r\n"); ww(" 0x");pp(cmd1,HEX);ww(" 0x");pp(cmd2,HEX);  ww(" = 0x");pp(PACK2(cmd1,cmd2),HEX);   cmd=PACK2(cmd1,cmd2);   ww(" => 0x");pp(cmd,HEX);   hh(cmd);
#define TEST3(cmd1,cmd2,cmd3)       ww("\r\n"); ww(" 0x");pp(cmd1,HEX);ww(" 0x");pp(cmd2,HEX);ww(" 0x");pp(cmd3,HEX);   ww(" = 0x");pp(PACK3(cmd1,cmd2,cmd3),HEX);  cmd=PACK3(cmd1,cmd2,cmd3);  ww(" => 0x");pp(cmd,HEX);   hh(cmd);
#define TEST4(cmd1,cmd2,cmd3,cmd4)  ww("\r\n"); ww(" 0x");pp(cmd1,HEX);ww(" 0x");pp(cmd2,HEX);ww(" 0x");pp(cmd3,HEX); ww(" 0x");pp(cmd4,HEX);   ww(" = 0x");pp(PACK4(cmd1,cmd2,cmd3,cmd4),HEX); cmd=PACK4(cmd1,cmd2,cmd3,cmd4); ww(" => 0x");pp(cmd,HEX);   hh(cmd);

uint32_t cmd;

uint8_t u1,u2,u3,u4;
int8_t i1,i2,i3,i4;


void setup()
{
    Serial.begin(115200);           // start serial for output
    u1=0x12;
    u2=0x34;
    u3=0x56;
    u4=0x78;

    i1=1;
    i2=-1;
    i3=8;
    i4=-8;

    TEST1(u1)
    TEST2(u1,u2)
    TEST3(u1,u2,u3)
    TEST4(u1,u2,u3,u4)
    ww("\r\n=========================================================");
    TEST1(i1)
    TEST2(i1,i2)
    TEST3(i1,i2,i3)
    TEST4(i1,i2,i3,i4)
    ww("\r\n=========================================================");
    TEST4(u1,u2,u3,u4)
    TEST4(u1,u2,u3,u4)
    TEST4(u1,u2,u3,u4)
    TEST4(u1,u2,u3,u4)
    ww("\r\n=========================================================");
    TEST4(i1,u2,u3,u4)
    TEST4(u1,i2,u3,u4)
    TEST4(u1,u2,i3,u4)
    TEST4(u1,u2,u3,i4)
    ww("\r\n=========================================================");
    TEST4(i2,u2,u3,u4)
    TEST4(u1,i2,u3,u4)
    TEST4(u1,u2,i2,u4)
    TEST4(u1,u2,u3,i2)

}
void loop()
{
}

结果(差):

 0x12 = 0x12 => 0x12     ( 0x00000012 )
 0x12 0x34 = 0x1234 => 0x1234    ( 0x00001234 )
 0x12 0x34 0x56 = 0x3456 => 0x3456       ( 0x00003456 )
 0x12 0x34 0x56 0x78 = 0x5678 => 0x5678  ( 0x00005678 )
=========================================================
 0x1 = 0x1 => 0x1        ( 0x00000001 )
 0x1 0xFFFFFFFF = 0xFFFFFFFF => 0xFFFFFFFF       ( 0xFFFFFFFF )
 0x1 0xFFFFFFFF 0x8 = 0xFFFFFF08 => 0xFFFFFF08   ( 0xFFFFFF08 )
 0x1 0xFFFFFFFF 0x8 0xFFFFFFF8 = 0xFFFFFFF8 => 0xFFFFFFF8        ( 0xFFFFFFF8 )
=========================================================
 0x12 0x34 0x56 0x78 = 0x5678 => 0x5678  ( 0x00005678 )
 0x12 0x34 0x56 0x78 = 0x5678 => 0x5678  ( 0x00005678 )
 0x12 0x34 0x56 0x78 = 0x5678 => 0x5678  ( 0x00005678 )
 0x12 0x34 0x56 0x78 = 0x5678 => 0x5678  ( 0x00005678 )
=========================================================
 0x1 0x34 0x56 0x78 = 0x5678 => 0x5678   ( 0x00005678 )
 0x12 0xFFFFFFFF 0x56 0x78 = 0x5678 => 0x5678    ( 0x00005678 )
 0x12 0x34 0x8 0x78 = 0x878 => 0x878     ( 0x00000878 )
 0x12 0x34 0x56 0xFFFFFFF8 = 0xFFFFFFF8 => 0xFFFFFFF8    ( 0xFFFFFFF8 )
=========================================================
 0xFFFFFFFF 0x34 0x56 0x78 = 0x5678 => 0x5678    ( 0x00005678 )
 0x12 0xFFFFFFFF 0x56 0x78 = 0x5678 => 0x5678    ( 0x00005678 )
 0x12 0x34 0xFFFFFFFF 0x78 = 0xFFFFFF78 => 0xFFFFFF78    ( 0xFFFFFF78 )
 0x12 0x34 0x56 0xFFFFFFFF = 0xFFFFFFFF => 0xFFFFFFFF    ( 0xFFFFFFFF )

问题(再次):

1) 它只需要最后 2 个参数(可能是因为某些地方转换为 16 位变量)

2) 它对带符号的变量不起作用(可能使它们在某个地方变长)

我不确定库和你的用例是什么,但我可以在你的 PACKx 宏中看到以下错误:

#define PACK3(cmd1,cmd2,cmd3) ((cmd1<<16)|(cmd2<<8)|cmd3) // ! (cmd2 << 8) == 0

由于cmd2类型限制为8位,左移8位(或更多)会导致原来的8bit类型溢出,所以结果大概是0...

...更准确地说,结果是 "undefined"(在评论中归功于@GavinPortwood)...更准确地说,我们将不得不进入整数提升细节并想知道为什么您的系统将 uint8_t 提升为 16 位类型而不是 32 位类型...

...但无论哪种方式,手动将类型转换为所需的位数以及将结果截断为所需的位数是一个很好的做法。

cmd3也是如此。

您可能还应该考虑对带符号的变量进行位移在系统之间是不一致的,因为某些系统会保护带符号的位不被移位 - 因此 (-4 >> 1) == -2 而不是丢失带符号的位,这可能会给你126(如果我的数学是正确的)。

我最后的建议是,我会尽可能避免使用一长串宏。

我认为包装库可能是更好的选择,它会使维护更容易。

话虽如此,这是我未经测试的宏解决方案:

struct GET_DATA_args {
  uint8_t cmd;
  uint8_t arg1;
  uint8_t arg2;
  uint8_t arg3;
  uint8_t argc;
  uint8_t *dest;
  uint8_t limit;
  int addr;
};

static inline void GET_DATA(struct GET_DATA_args args) {
  uint32_t cmd = args.cmd | ((uint32_t)args.arg1 << 8) |
                 ((uint32_t)args.arg2 << 16) | ((uint32_t)args.arg3 << 24);
  if (!args.limit) /* use 1 as a default value. */
    args.limit = 1;
  get_data(args.addr, cmd, args.argc + 1, args.dest, args.limit);
}

#define GET_DATA(...)                                                          \
  GET_DATA((struct GET_DATA_args){.addr = this->i2c, __VA_ARGS__})

你可以这样使用它:

GET_DATA(.argc = 2, .cmd = SET_SPEED_LIMIT,
         .arg1 = 30, .arg2 = status,
         .dest = &status /*, .limit = 1 - by default */);

所以问题出在错误的类型上(正如所指出的),可以通过将所有参数显式转换为 8 位无符号(以保留值而不扩展符号)然后转换为足够长的时间来解决类型。

这个对我有用(示例代码在没有警告的情况下编译并产生正确的结果)(正如 Lundin 指出的,转换的组织方式应该有点不同,所以我修复了 SHIFT_IT。仍然产生相同的结果好结果):

#define SHIFT_IT(cmd,bits) (((uint32_t)(uint8_t)cmd)<<bits)

#define PACK1(cmd1) (SHIFT_IT(cmd1,0))
#define PACK2(cmd1,cmd2) (SHIFT_IT(cmd1,8)|SHIFT_IT(cmd2,0))
#define PACK3(cmd1,cmd2,cmd3) (SHIFT_IT(cmd1,16)|SHIFT_IT(cmd2,8)|SHIFT_IT(cmd3,0))
#define PACK4(cmd1,cmd2,cmd3,cmd4) (SHIFT_IT(cmd1,24)|SHIFT_IT(cmd2,16)|SHIFT_IT(cmd3,8)|SHIFT_IT(cmd4,0))

结果(好):

 0x12 = 0x12 => 0x12     ( 0x00000012 )
 0x12 0x34 = 0x1234 => 0x1234    ( 0x00001234 )
 0x12 0x34 0x56 = 0x123456 => 0x123456   ( 0x00123456 )
 0x12 0x34 0x56 0x78 = 0x12345678 => 0x12345678  ( 0x12345678 )
=========================================================
 0x1 = 0x1 => 0x1        ( 0x00000001 )
 0x1 0xFFFFFFFF = 0x1FF => 0x1FF         ( 0x000001FF )
 0x1 0xFFFFFFFF 0x8 = 0x1FF08 => 0x1FF08         ( 0x0001FF08 )
 0x1 0xFFFFFFFF 0x8 0xFFFFFFF8 = 0x1FF08F8 => 0x1FF08F8  ( 0x01FF08F8 )
=========================================================
 0x12 0x34 0x56 0x78 = 0x12345678 => 0x12345678  ( 0x12345678 )
 0x12 0x34 0x56 0x78 = 0x12345678 => 0x12345678  ( 0x12345678 )
 0x12 0x34 0x56 0x78 = 0x12345678 => 0x12345678  ( 0x12345678 )
 0x12 0x34 0x56 0x78 = 0x12345678 => 0x12345678  ( 0x12345678 )
=========================================================
 0x1 0x34 0x56 0x78 = 0x1345678 => 0x1345678     ( 0x01345678 )
 0x12 0xFFFFFFFF 0x56 0x78 = 0x12FF5678 => 0x12FF5678    ( 0x12FF5678 )
 0x12 0x34 0x8 0x78 = 0x12340878 => 0x12340878   ( 0x12340878 )
 0x12 0x34 0x56 0xFFFFFFF8 = 0x123456F8 => 0x123456F8    ( 0x123456F8 )
=========================================================
 0xFFFFFFFF 0x34 0x56 0x78 = 0xFF345678 => 0xFF345678    ( 0xFF345678 )
 0x12 0xFFFFFFFF 0x56 0x78 = 0x12FF5678 => 0x12FF5678    ( 0x12FF5678 )
 0x12 0x34 0xFFFFFFFF 0x78 = 0x1234FF78 => 0x1234FF78    ( 0x1234FF78 )
 0x12 0x34 0x56 0xFFFFFFFF = 0x123456FF => 0x123456FF    ( 0x123456FF )
=========================================================
 0xFFFFFFFF 0x34 0x56 0x78 = 0xFF345678 => 0xFF345678    ( 0xFF345678 )
 0x12 0xFFFFFFFF 0x56 0x78 = 0x12FF5678 => 0x12FF5678    ( 0x12FF5678 )
 0x12 0x34 0xFFFFFFFF 0x78 = 0x1234FF78 => 0x1234FF78    ( 0x1234FF78 )
 0x12 0x34 0x56 0xFFFFFFFF = 0x123456FF => 0x123456FF    ( 0x123456FF )
=========================================================
 0xFFFFFFFF 0x34 0x56 0x78 = 0xFF345678 => 0xFF345678    ( 0xFF345678 )
 0x12 0xFFFFFFFF 0x56 0x78 = 0x12FF5678 => 0x12FF5678    ( 0x12FF5678 )
 0x12 0x34 0xFFFFFFFF 0x78 = 0x1234FF78 => 0x1234FF78    ( 0x1234FF78 )
 0x12 0x34 0x56 0xFFFFFFFF = 0x123456FF => 0x123456FF    ( 0x123456FF )

无论如何,如果我找到更好的方法(也许使用可变宏,只有一个 PACK 和可变数量的命令)我也会在这里添加它。

(BTW: 谢谢大家帮我找路,之前真的深锁)

您可能应该 做的是编写一个函数,使代码类型安全和通用。根据字节顺序(字节顺序)等,此函数可能看起来有点不同。大致如下:

#include <stdint.h>
#include <string.h>

uint32_t pack (size_t size, uint8_t bytes[size])
{
  uint8_t tmp[sizeof(uint32_t)] = {0};
  memcpy(tmp, bytes, size);

  return tmp[0] <<  0 |
         tmp[1] <<  8 |
         tmp[2] << 16 |
         tmp[3] << 24 ; 
}

宏很难阅读、理解和维护,所以我不推荐它们。但如果你坚持使用它们,你 可以 使用可变参数宏做一些邪恶的宏魔法:

(假设 32 位 CPU)

#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>

#define SHIFT(cmd, bits) (((uint32_t)(cmd)) << (bits))

#define PACK1(c1,...)          ( SHIFT(c1, 0) )
#define PACK2(c1,c2,...)       ( SHIFT(c1, 8) | SHIFT(c2, 0) )
#define PACK3(c1,c2,c3,...)    ( SHIFT(c1,16) | SHIFT(c2, 8) | SHIFT(c3,0) )
#define PACK4(c1,c2,c3,c4,...) ( SHIFT(c1,24) | SHIFT(c2,16) | SHIFT(c3,8) | SHIFT(c4,0) )

#define PACK_VARIABLE(c1,c2,c3,c4,N,...) PACK##N (c1, c2, c3, c4, __VA_ARGS__)
#define PACK(...) PACK_VARIABLE(__VA_ARGS__,4,3,2,1,0)

int main (void)
{
  printf("%" PRIX32 "\n", PACK(0xAA));
  printf("%" PRIX32 "\n", PACK(0xAA, 0xBB));
  printf("%" PRIX32 "\n", PACK(0xAA, 0xBB, 0xCC));
  printf("%" PRIX32 "\n", PACK(0xAA, 0xBB, 0xCC, 0xDD)); 
  return 0;  
}

输出:

AA
AABB
AABBCC
AABBCCDD