分类: 嵌入式
2016-08-23 21:37:08
微信硬件平台提供的demo中传输数据格式如下。每次数据传输时,都将有效数据打包,然后再添加上固定包头包尾后发送。
官网提供的demo是实现点灯发送消息什么的,所以为了区分这些消息以及一些其他附加功能又在 有效数据(也就是上面的protoalbuf打包的变长包体) 里面定义了一个包头
typedef struct
{
uint8_t m_magicCode[2];
uint16_t m_version;
uint16_t m_totalLength;
uint16_t m_cmdid;
uint16_t m_seq;
uint16_t m_errorCode;
}BlueDemoHead;
比如通过 m_cmdid来区分 点灯还是亮灯什么的。 这个头只是为了点灯这个demo做的而已,我们使用的时候完全可以不用这个头,只有固定包头才是必须的
PS:blueDemoHead 刚连接时的Auth,和init过程是没有这个包头的。
在使用微信demo二次开发时,为了可扩展我们保留了 这个 为了点灯demo而做的内部包头。于是有效数据为 blueDemoHead+跟随数据 这部分打包然后再加上固定包头。最终发给微信。同样从微信接收到的数据也是这样的类型。
问题也就是出现在这里,微信demo内部的对 微信push过来的数据的解包代码是直接将 uint8_t类型 指针强转成BlueDemoHead 类型指针,这样就可以通过结构体的形式来方便的访问数据。
mpbledemo2.c文件中的解包代码:只看解push包的解包部分
//data为收到的微信发过来的所有数据
int mpbledemo2_data_consume_func(uint8_t *data, uint32_t len){
…………….
switch(ntohs(fix_head->nCmdId))
{
case ECI_resp_auth:
……….……….
Break;
case ECI_resp_init:
……………………
break;
case ECI_push_recvData:
RecvDataPush *recvDatPush;
//这里从固定头后面开始解包。
recvDatPush = epb_unpack_recv_data_push(data+fix_head_len, len-fix_head_len);
…………………….
//解包后recvDatPush->data.data就是指向有效数据了即(blueDemoHead+负载 //数据)
//就是这里的强制转换存在问题!!!!
BlueDemoHead *bledemohead = (BlueDemoHead*)recvDatPush->data.data;
……………………
//原因在于解包函数实际上是将recvDatPush->data.data指向有效数据部分
// 也就是指向了mpbledemo2_data_consume_func的参数data 后的某个偏移
//从这个偏移开始到recvDatPush->data.len就是有效数据(blueDemoHead+负载 //数据)
//与是下面的代码就可能存在问题了,因为recvDatPush->data.data指向的偏
//移的物理地址可能是非 半字对齐,于是bledemohead也是非半字对齐而
//m_cmdid成员在结构体中的偏移为6,所以该成员的地址也可能是非半字对
//齐,于是如果你使用的BLE芯片MCU数据访问需要地址对齐,那么下面的
//访问就出问题,而51822 ble芯片是M0的处理器要求对 半字的访问需地址
//半字对齐,于是执行下面这句代码就可能出现硬件错误异常。
if(ntohs(bledemohead->m_cmdid ) == openLightPush){
综上: 可能发生错误的根本原因在于
该demo跑在需要数据访问为地址对齐的MCU上时,这时候如果bledemohead被赋值后如果不是半字对齐,那么导致通过指针访问其偏移为6字节的m_cmdid成员时变出现 硬件错误异常。
之所以说可能发生错误是因为recvDatPush->data.data 最终指向的地址可能是半字对齐可能不是,如果是就不会发生错误,如果不是那么执行那一句指针访问成员就会发生错误了。
我们在使用过程中,之前都是一直正常运行,后来传输一次较多数据时 150多字节,发生了死机现象,最终才定位到这个 非对齐访问的问题。
上面的解释有点抽象:我抓了一下 数据处理过程方便大家理解:
下面是正常时的情况, data存放了微信发过来的所有数据,存放地址有下图所示为 0x20004388, 前面说过解包函数就是将recvDatPush->data.data 指向有效数据的起始位置,有下图所示,除去固定包头fe 01 00 53 75 31 0 00 以及protoalbuf打包函数打包时加在前面的0a 00 12 44 ,则真正有效的数据从偏移12开始 就是从地址0x20004388+12 = 0x20004394开始,也就是下图打印的。改地址是半字对齐的,所以将其转换成 结构体指针 bledemohead是也是半字对齐,有因为bledemohead 中成员m_cmdid偏移为6所以其地址也是半字对齐,于是bledemohead->m_cmdid 访问就不会出现问题
下面是一个异常情况:
数据的存放地址还是0x20004388,但是这次有效数据的偏移是8字节固定包头+5字节protoalbuf打包时额外添加的,于是偏移为13字节
0x20004388+13 = 0x20004395,该地址非半字对齐,于是在访问bledemohead的成员m_cmdid时就进行了一次非对齐访问,于是出现硬件错误异常。
有上面分析可知,问题的出现是因为偏移的问题,测试发现数据量比较小时 打包时在有效数据前面额外添加的数据只有4字节,而固定包头有8字节,于是有效数据的偏移就是12字节。
Data的地址满足半字对齐,于是偏移12字节的位置的地址也满足半字对齐,所以就不会出现问题。
而数据量较多时从上图可以发现除了 8字节固定包头 protoalbuf打包函数额外添加的数据有5字节,这就最终导致有效数据的偏移为13 也就导致了 地址非半字对齐。
目前并未测试 当数据量更多时,protoalbuf打包时添加的数据是否又会变多比如说为6个,那么这时候 又可以”正常”运行了。不过这样终究是存在bug隐患。
解决的办法有很多,比如不使用demo中的这个bledemohead,自己定义一个更加单简单的格式,比如在数据前面加2个字节来区分每次数据表示的不同意思,并且解析的时候通过数据一字节一字节访问。 recvDatPush->data.data[0] == aa&&recvDatPush->data.data[1]==0xBB表示一种数据,而recvDatPush->data.data[0]==0xAA&&recvDatPush->data.data[1]==0xCC时表示另一种数据。。 或者依旧使用这个bledemohead头,但是解包后的数据访问不要通过转换成结构体指针然后访问成员的方式去访问,而是直接通过解包后的uint8_t型指针一个一个字节去访问,比如recvDatPush->data.data[0]是否等于什么,recvDatPush->data.data[1]是否等于什么,recvDatPush->data.data[2]是否等于什么 …….. 这种方式来区分各种不同的数据