11
\$\begingroup\$

I'm using arm gcc (CooCox) to program an STM32F4discovery, and I've been wrestling w/ an endian problem

I'm sampling with a 24 bit ADC via SPI. Since three bytes are coming in, MSB first I had the idea of loading them into a union to make them (I hoped, anyway!) a little easier to use.

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

I load the data using spi reads into analogin0.spibytes[0]-[2], with [0] as the MSB, then I spit them out via USART at a megabaud, 8 bits at a time. No problems.

The problems started when I tried to pass the data in to a 12 bit DAC. This SPI DAC wants 16 bit words, which consist of a 4 bit prefix starting at the MSB, followed by 12 bits of data.

Initial attempts were to convert the twos complement the ADC gave me to offset binary, by xor-ing analogin0.spihalfwords[0] with 0x8000, shifting the result to the bottom 12 bits, and then adding the prefix on arithmetically.

Incredibly frustrating, until I notice that for analogin0.spibytes[0]=0xFF and and analogin0.spibytes[1]=0xB5, analogin0.halfwords[0] was equal to 0xB5FF and not 0xFFB5!!!!!

After noticing this, I stopped using arithmetic operations and the halfword, and stuck to bitwise logic and the bytes

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)

... and this worked fine. When I peek at temp after the first line of code, its 0xFFB5, and not 0xB5FF, so all is good

So, for questions ...

  • Cortex is new to me. I can't recall PIC ever byte swapping in int16's, even though both platforms are little endian. Is this correct?

  • Is there a more elegant way to handle this? It would be great if I could just put the ARM7 into big-endian mode. I'm seeing many references to Cortex M4 being bi-endian, but all the sources seem to stop short of actually telling me how. More specifically, how do I place the STM32f407 into big-endian mode, even better if it can be done in gcc. Is this JUST a matter of setting the appropriate bit in the AIRCR register? Are there any ramifications, such as having to set the compiler to match, or math screwups later with inconsistent libraries??

\$\endgroup\$
6
  • 2
    \$\begingroup\$ "Since three bytes are coming in, MSB first" - that's big-endian, whereas you CPU is little-endian, so that's where your problem starts. I'd look for compiler macros/standard library functions to do 16/32-bit byte swapping, usually they are implemented in the most efficient way for the particular CPU platform. Of course, using bit-wise shifting/ANDing/ORing is also okay. \$\endgroup\$ Jan 11, 2014 at 0:04
  • \$\begingroup\$ I suppose I could stuff analogin0.spibytes in any order I want to, but that seems like a bit of a cheat as well, as I'd have to remember the order to unstuff it to pass it via usart. I think the 3-byte format makes things a bit non-standard. If this were c++, I might consider a class. \$\endgroup\$ Jan 11, 2014 at 0:14
  • 3
    \$\begingroup\$ CMSIS has __REV() and __REV16() for reversing bytes. \$\endgroup\$
    – Turbo J
    Jan 11, 2014 at 1:12
  • 3
    \$\begingroup\$ It isn't a cheat at all -- when you're doing I/O at this level, you must be aware of and deal with the relationship between the external byte order and the internal byte order. My own preference is to convert external representations to/from "native" (internal) representations at the lowest level in the software hierarchy that makes sense, and let all higher-level software deal with just the native format. \$\endgroup\$
    – Dave Tweed
    Jan 11, 2014 at 1:16
  • \$\begingroup\$ Even though the core designed by ARM Corp. is able to operate with either endianness, STM's implementation of the ARM core in STM32F407 is little-endian only. See the Reference Manual RM0090 page 64. AIRCR.ENDIANNESS is a read-only bit. \$\endgroup\$ Jan 13, 2014 at 0:30

4 Answers 4

7
+50
\$\begingroup\$

Embedded systems will always have the big-endian/little-endian issue. My personal approach has been to always encode internal memory with the native endianiness and make any swaps right when data enters or leaves.

I load the data using spi reads into analogin0.spibytes[0]-[2], with [0] as the MSB

By loading [0] as the MSB, you're encoding the value as big-endian.

analogin0.spibytes[0]=0xFF and and analogin0.spibytes[1]=0xB5, analogin0.halfwords[0] was equal to 0xB5FF

This indicates that the processor is little-endian.

If instead, you load the first value into [2] and work back to [0], then you've encoded the incoming number as little-endian, essentially making the swap as the number enters. Once you're working with the native representation, you can return to your original approach of using arithmetic operations. Just make sure to flip it back to big-endian when you transmit the value.

\$\endgroup\$
6
\$\begingroup\$

Regarding bounty "Really want to know about srm32f4 big endian mode", there is no big endian mode on this chip. STM32F4 does all memory access in little endian.

The user manual http://www.st.com/web/en/resource/technical/document/programming_manual/DM00046982.pdf mentions this on page 25. But there is more. On page 93 you can see there are endian swapping instructions. REV and REVB for reverse and reverse bit. REV will change endianess for 32 bits and REV16 will do it for 16 bit data.

\$\endgroup\$
4
\$\begingroup\$

Here is a code snippet for a cortex M4, compiled with gcc

/*
 * 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

From C, the call can be:

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

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

Don't know how to do faster than this :-)

\$\endgroup\$
2
  • \$\begingroup\$ you can use a macro or an inline function to make this code quicker \$\endgroup\$
    – pro
    Nov 8, 2017 at 8:55
  • \$\begingroup\$ what about 24 bit data? \$\endgroup\$
    – pengemizt
    Nov 27, 2019 at 23:47
1
\$\begingroup\$

For CooCox STM32F429 this is OK:

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);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.