事故现场
最近在项目中需要处理两个平台之间的通讯,开发环境如下:
- 上位机环境:i.MX8QM (ARM64 Linux)
- 上位机编译器:aarch-linux-gcc
- 单片机环境:STM32F407VE
- 单片机编译器:IAR 8.22.3
- 通讯方式:i.MX8QM作为主机,STM32F407VE作为从机,通过SPI通讯。
两者在通讯时使用同一份通讯包结构体头文件,其中核心接头体如下
#define SPI_MSG_BUFF_LEN 132
enum SPI_CMD_INDEX {
SPI_ERROR = 0,
SPI_CONFIG_DATA,
SPI_LOG,
SPI_STOP,
SPI_ACK
};
struct SpiMessage {
enum SPI_CMD_INDEX Cmd;
uint8_t Len;
uint16_t Num;
uint32_t HeadCrc;
uint8_t Data[SPI_MSG_BUFF_LEN];
};
这里我人为的对数据做了对齐操作,按我的理解,我认为enum的数量没有超过255,一个字节的空间足够存放数据,所以长度应该是1字节,在IAR编译通过,并用逻辑分析仪测试,没有问题。输出的报文头的字节码为:<0x02, 0x23, 0x02, 0x00>。
从机完成后再在linux上实现驱动,但是出现了两个现象:
- 发送一个定长报文的数据比预期的结构体长3个字节,
- 报文头的字节码变成了<0x02, 0x00, 0x00, 0x00>,
我意识到enum长度在不同编译器的下应该是不同的(事实上这个主要和编译器的编译参数有关)。
解决方法
在struct中将enum SPI_CMD_INDEX
替换为uint8_t
,强制结构体中数据的字长。修改后代码如下:
struct SpiMessage {
uint8_t Cmd;
uint8_t Len;
uint16_t Num;
uint32_t HeadCrc;
uint8_t Data[SPI_MSG_BUFF_LEN];
后面赋值的时候由于进行了变量类型转换,编译器会报警,可以将enum
替换为#define
:
#define SPI_ERROR 0
#define SPI_CONFIG_DATA 1
#define SPI_LOG 2
#define SPI_STOP 3
#define SPI_ACK 4
扩展
由于使用enum
根据编译器参数的不同会导致编译出来的代码对结构体的理解不同,所以在编写与其他模块交互或者通讯类的代码时需要注意不能使用enum
,这点可以参考linux ioctl编写时会用一个__IOC()
的宏来定义交互的指令。
而enum
在模块内部调用的时候就显得非常有用,在函数传参、switch
等关键词中可能大大降低代码错误的概率,并且在C++中对非enum
定义的量是无法给enum
定义的量赋值的,例如以下操作时非法的:
#define SPI_MSG_BUFF_LEN 132
enum SPI_CMD_INDEX {
SPI_ERROR = 0,
SPI_CONFIG_DATA,
SPI_LOG,
SPI_STOP,
SPI_ACK
};
struct SpiMessage {
enum SPI_CMD_INDEX Cmd;
uint8_t Len;
uint16_t Num;
uint32_t HeadCrc;
uint8_t Data[SPI_MSG_BUFF_LEN];
};
struct SpiMessage tmpMsg;
void main() {
tmpMsg.Cmd = 1; // 代码非法,1不是enum SPI_CMD_INDEX定义的变量
tmpMsg.Cmd = SPI_CONFIG_DATA; // 合法代码
}