11

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??

Scott Seidman
  • 29,274
  • 4
  • 44
  • 109
  • 2
    _"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. – Laszlo Valko Jan 11 '14 at 00:04
  • 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. – Scott Seidman Jan 11 '14 at 00:14
  • 3
    [CMSIS](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHCAEJD.html) has `__REV()` and `__REV16()` for reversing bytes. – Turbo J Jan 11 '14 at 01:12
  • 3
    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. – Dave Tweed Jan 11 '14 at 01:16
  • 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. – Laszlo Valko Jan 13 '14 at 00:30
  • yes definitely not a cheat, just the real world we live in I typically create byte packing/unpacking defines RD14/WR24 #define WR(x, y) {((unsigned char *)&x)[0] = y>>16;((unsigned char *)&x)[1] = y>>16;((unsigned char *)&x)[2] = y;} #define RD(x) ((((unsigned char *)&x)[0]<<16)|(((unsigned char *)&x)[1]<<8)|(((unsigned char *)&x)[2])) – Taniwha Jan 13 '14 at 08:49

4 Answers4

7

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.

DrRobotNinja
  • 358
  • 1
  • 6
6

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.

C. Towne Springer
  • 2,141
  • 11
  • 14
4

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 :-)

papyDoctor
  • 101
  • 3
1

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);
PeterJ
  • 17,131
  • 37
  • 56
  • 91