在 Keil 中从 uint8_t 数组获取 uint16_t 值

Getting uint16_t value from uint8_t array in Keil

我尝试在不使用“<<”运算符的情况下从 UART 数据包中获取变量。

uint8_t buffer[8] = {0x11,0x22,0x33,0x44};
uint16_t val = *((uint16_t *)buffer);

如果我在 keil 中尝试上面的代码,它可以正常工作。当我在结构编译器中为数组尝试它时,它不会给出错误,但它会在运行时进入硬故障处理程序。

typedef struct
{
  uint8_t address;
  uint8_t opID; 
  uint8_t dataLen;
  uint8_t data[250];
  uint8_t crc[2];
}MODBUS;

MODBUS receivedData;
uint16_t val = *((uint16_t *)receivedData.data);

我也在在线 C 编译器中尝试了这个(结构中的数组)。它工作没有任何问题。我应该怎么做才能在keil中使用相同的东西?

使用 memcpy 通常是处理此类转换的正确方法,因此:

uint16_t target;
uint8_t source[]={1,2,3,4};
memcpy(&target, source, sizeof(target));

要将较短整数数组更改为较长整数数组,您必须相应地修改它:

uint16_t target[2]={0};
uint8_t source[4]={1,2,3,4};
memcpy(target, source, sizeof(target));

记得相应地调整尺寸。

*((uint16_t *)buffer); 有两个未定义的行为错误:

  • buffer 可能未对齐,转换为更大的指针类型可能导致访问未对齐或硬件 trap/exception.
  • 该代码违反了指针取消引用类型系统,即所谓的“严格的别名违规”。 What is the strict aliasing rule? 这在嵌入式系统编译器中通常不是问题,但无论如何它仍然是未定义的行为,您不应该编写那样的代码。

此外,您还有一个潜在问题,即 UART 协议的网络字节序可能与 CPU 字节序不匹配。 What is CPU endianness? 大多数 UART 协议使用 Big Endian。

要解决这个问题,您可以使用另一个答案中提到的 memcpy。或者更有效地,包装器 union:

typedef union
{
  uint8_t  u8  [8];
  uint16_t u16 [4];
} uart_buf_t;

这解决了错位和严格别名的问题,但它不会解决潜在的字节顺序不匹配问题。要解决这个问题,您需要改用位移位。

注意:所讨论的目标是 Cortex®-M0+ 的变体,它支持未对齐的访问。因此,以下假设无关紧要。

硬故障最可能的原因是: 您正在使用不支持未对齐访问的内核。 例如,皮质 M0 变体。

建议:

  1. 一步一步调试,查看变量的内存位置,特别是:

    的内存位置

    receivedData.data

  2. 尝试通过字节对齐访问该位置,检查是否会发生硬故障。

    uint8_t val = *((uint8_t *)receivedData.data);

如果我猜对了,像下面这样修改结构体定义,看看是否能解决问题。

typedef struct {
  uint8_t address;
  uint8_t opID; 
  uint8_t dataLen;
  uint8_t dummy_byte;
  
  uint8_t data[250];
  uint8_t crc[2];
}MODBUS;