crsf负载数据每个通道是11位,需要从中摘取出各通道的数据,在以往的做法中通常使用位运算实现。如
ch1 = (buf[0] | buf[1] << 8) & 0x07FF;
ch2 = (buf[1] >> 3 | buf[2] << 5) & 0x07FF;
ch3 = (buf[2] >> 6 | buf[3] << 2 | buf[4] << 10) & 0x07FF;
ch4 = (buf[4] >> 1 | buf[5] << 7) & 0x07FF;
不太优雅。
在该仓库的CRSF解析中发现了使用位域强转的方法,很不错。
#define PACKED __attribute__((packed))
typedef struct crsf_header_s
{
uint8_t device_addr; // from crsf_addr_e
uint8_t frame_size; // counts size after this byte, so it must be the payload size + 2 (type and crc)
uint8_t type; // from crsf_frame_type_e
uint8_t data[CRSF_MAX_PAYLOAD_LEN + 1];
} PACKED crsf_header_t;
typedef struct crsf_channels_s
{
unsigned int ch0 : 11;
unsigned int ch1 : 11;
unsigned int ch2 : 11;
unsigned int ch3 : 11;
unsigned int ch4 : 11;
unsigned int ch5 : 11;
unsigned int ch6 : 11;
unsigned int ch7 : 11;
unsigned int ch8 : 11;
unsigned int ch9 : 11;
unsigned int ch10 : 11;
unsigned int ch11 : 11;
unsigned int ch12 : 11;
unsigned int ch13 : 11;
unsigned int ch14 : 11;
unsigned int ch15 : 11;
} PACKED crsf_channels_t;
crsf_channels_t* ch = (crsf_channels_t*)&(p->data);
_channels[0] = ch->ch0;
_channels[1] = ch->ch1;
_channels[2] = ch->ch2;
_channels[3] = ch->ch3;
_channels[4] = ch->ch4;
_channels[5] = ch->ch5;
_channels[6] = ch->ch6;
_channels[7] = ch->ch7;
_channels[8] = ch->ch8;
_channels[9] = ch->ch9;
_channels[10] = ch->ch10;
_channels[11] = ch->ch11;
_channels[12] = ch->ch12;
_channels[13] = ch->ch13;
_channels[14] = ch->ch14;
_channels[15] = ch->ch15;
但是我是在Visual Studio中开发的,查找了挺久,发现__attribute__((packed))的语法替换的#pragma pack(1)并不能取消struct中位域的对齐,无奈这个优雅的方法用不上了。
于是想自己实现多个通道的解析,但是不想写成手动位运算的方式,故自动实现。
思路就是把所有的数据排起来,找到对应通道所在的位置,然后用0x7FF(11位全是1)的mask把数据取下来,再移位得到通道数据。以下代码
void CrsfSerial::packetChannelsPacked(const crsf_header_t *p)
{
for (int i = 0; i < 16; ++i)
{
uint16_t start = (i * 11) / 8 ;
uint8_t startBit = (i * 11) % 8;
uint32_t mask = 0x7ff << startBit;
uint32_t data = *((uint32_t*) (p->data + start));
_channels[i] = (data & mask) >> startBit;
}
...
}
其中p->data + start取出对应通道数据最低位所在缓存数组的地址,然后把该数组地址强转成(uint32_t*)类型数组的地址,然后取出元素得到了包含通道数据的uint32数据,然后再将mask移位,取与,再移回来即可。
也可以用联合来做数据转换
typedef union
{
uint8_t datain[4];
uint32_t dataout;
}intExchange;
void CrsfSerial::packetChannelsPacked(const crsf_header_t *p)
{
intExchange aaa{};
for (int i = 0; i < 16; ++i)
{
uint16_t start = (i * 11) / 8 ;
uint8_t startBit = (i * 11) % 8;
uint32_t mask = 0x7ff << startBit;
memcpy(aaa.datain, p->data + start, 4);
uint32_t data = aaa.dataout;
_channels[i] = (data & mask) >> startBit;
}
...
}
至此得到了所有通道的数据,可以开始快乐的做映射了~