STM32上的字节序问题


11

我正在使用arm gcc(CooCox)对STM32F4discovery进行编程,而我一直在努力解决字节序问题

我正在通过SPI使用24位ADC进行采样。由于要传入三个字节,因此MSB首先具有将它们加载到联合中的想法(无论如何,我都希望如此!),以便于使用。

typedef union
{
int32_t spilong;
uint8_t spibytes [4];
uint16_t spihalfwords [2];} spidata;
spidata analogin0;

我使用spi读取将数据加载到Analogin0.spibytes [0]-[2]中,其中[0]作为MSB,然后通过USART以8比特的高波特率将它们吐出。没问题。

当我尝试将数据传递到12位DAC时,问题就开始了。此SPI DAC需要16位字,其中包括从MSB开始的4位前缀,然后是12位数据。

最初的尝试是将ADC给我的二进制补码转换为二进制偏移量,方法是对0x8000的Analogin0.spihalfwords [0]进行异或运算,将结果移至最低12位,然后在算术上加上前缀。

令人难以置信的令人沮丧,直到我注意到对于analogin0.spibytes [0] = 0xFF和analogin0.spibytes [1] = 0xB5,analogin0.halfwords [0]等于0xB5FF而不是0xFFB5!

注意到这一点后,我停止使用算术运算和半字,并停留在按位逻辑和字节上

uint16_t temp=0;
.
.
.


// work on top 16 bits
temp= (uint16_t)(analogin0.spibytes[0])<<8|(uint16_t)(analogin0.spibytes[1]);
temp=temp^0x8000; // convert twos complement to offset binary
temp=(temp>>4) | 0x3000; // shift and prepend with bits to send top 12 bits to DAC A


SPI_I2S_SendData(SPI3,temp); //send to DACa (16 bit SPI words)

...而且效果很好。当我在第一行代码后查看temp时,其值为0xFFB5,而不是0xB5FF,所以一切都很好

所以,对于问题...

  • 皮质对我来说是新的。我不记得PIC在int16中曾经进行过字节交换,即使这两个平台都是低位字节序。这个对吗?

  • 有没有更优雅的方式来解决这个问题?如果我能将ARM7置于大端模式,那将是很好的。我看到许多关于Cortex M4的参考资料都是二端序的,但是所有来源似乎都没有真正告诉我方法。更具体地说,我如何将STM32f407置于大端模式,如果可以在gcc中完成的话,甚至更好。这仅仅是在AIRCR寄存器中设置适当的位的问题吗?是否有任何影响,例如必须将编译器设置为匹配的,或者以后使用不一致的库对数学进行修正?


2
“因为进来了三个字节,所以MSB在先” -这是大字节序,而CPU是小字节序,因此这就是问题的出处。我会寻找编译器宏/标准库函数来进行16/32位字节交换,通常它们是针对特定CPU平台以最有效的方式实现的。当然,使用按位移位/ AND / ORing也可以。
Laszlo Valko 2014年

我想我可以按我想要的任何顺序填充Analogin0.spibytes,但这似乎也有点作弊,因为我必须记住解压缩该命令以使其通过usart传递的命令。我认为3字节格式会使事情有点不规范。如果这是c ++,我可能会考虑一个类。
Scott Seidman 2014年

3
CMSIS具有__REV()__REV16()用于反转字节。
Turbo J

3
这根本不是骗子-当您在此级别上执行I / O时,您必须了解并处理外部字节顺序和内部字节顺序之间的关系。我自己的偏好是在有意义的软件层次结构中的最低级别将外部表示与“本机”(内部)表示转换,并让所有更高级别的软件仅处理本机格式。
Dave Tweed 2014年

即使ARM公司设计的内核能够以任意一种字节序运行,但STM在STM32F407中对ARM内核的实现仅是低端字节序。请参见参考手册RM0090第64页。AIRCR.ENDIANNESS是只读位。
Laszlo Valko 2014年

Answers:


6

嵌入式系统将始终存在big-endian / little-endian问题。我个人的方法是始终以本机字节序对内部存储器进行编码,并在数据进入或离开时立即进行任何交换。

我使用spi读取将数据加载到Analogin0.spibytes [0]-[2],其中[0]作为MSB

通过将[0]加载为MSB,您可以将该值编码为big-endian。

Analogin0.spibytes [0] = 0xFF和Analogin0.spibytes [1] = 0xB5,analogin0.halfwords [0]等于0xB5FF

这表明处理器是低位优先的。

如果相反,您将第一个值加载到[2]中并返回到[0],则您已将传入的数字编码为little-endian,实际上是随着数字的输入进行交换。使用本机表示形式后,您可以返回使用算术运算的原始方法。传输值时,只需确保将其翻转回big-endian。


5

关于赏金“真的想知道srm32f4大字节序模式”,该芯片上没有大字节序模式。STM32F4以小字节序进行所有内存访问。

用户手册http://www.st.com/web/zh/resource/technical/document/programming_manual/DM00046982.pdf在第25页上提到了这一点。但是,还有更多内容。在第93页上,您可以看到有字节序交换说明。REV和REVB用于反向和反向位。REV将更改32位的字节序,而REV16将更改16位数据。


3

这是用gcc编译的Cortex M4的代码片段

/*
 * asmLib.s
 *
 *  Created on: 13 mai 2016
 */
    .syntax unified
    .cpu cortex-m4
    .thumb
    .align
    .global big2little32
    .global big2little16
    .thumb
    .thumb_func
 big2little32:
    rev r0, r0
    bx  lr
 big2little16:
    rev16   r0, r0
    bx  lr

从C调用可以是:

 extern uint32_t big2little32(uint32_t x);
 extern uint16_t big2little16(uint16_t x);

 myVar32bit = big2little32( myVar32bit );
 myVar16bit = big2little16( myVar16bit );

不知道怎么做比这快:-)


您可以使用宏或内联函数来使此代码更快
Pro于2008年

那24位数据呢?
pengemizt

1

对于CooCox STM32F429,这是可以的:

typedef union {
  uint8_t  c[4];
  uint16_t   i[2];
  uint32_t  l[1];
}adc;

adc     adcx[8];

...

// first channel ...
    adcx[0].c[3] = 0;
    adcx[0].c[2] = UB_SPI1_SendByte(0x00);
    adcx[0].c[1] = UB_SPI1_SendByte(0x00);
    adcx[0].c[0] = UB_SPI1_SendByte(0x00);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.