Problema endian en STM32

11

Estoy usando arm gcc (CooCox) para programar un descubrimiento STM32F4, y he estado luchando con un problema endian

Estoy muestreando con un ADC de 24 bits a través de SPI. Como están llegando tres bytes, MSB primero tuve la idea de cargarlos en una unión para hacerlos (¡esperaba, de todos modos!) Un poco más fáciles de usar.

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

Cargo los datos usando lecturas spi en analogin0.spibytes [0] - [2], con [0] como MSB, luego los escupo a través de USART a megabaudios, 8 bits a la vez. No hay problemas.

Los problemas comenzaron cuando intenté pasar los datos a un DAC de 12 bits. Este SPI DAC quiere palabras de 16 bits, que consisten en un prefijo de 4 bits que comienza en el MSB, seguido de 12 bits de datos.

Los intentos iniciales consistieron en convertir los dos complementos que el ADC me dio para compensar el binario, al xoringar analogin0.spihalfwords [0] con 0x8000, desplazar el resultado a los 12 bits inferiores y luego agregar el prefijo en forma aritmética.

Increíblemente frustrante, hasta que me doy cuenta de que analogin0.spibytes [0] = 0xFF y analogin0.spibytes [1] = 0xB5, analogin0.halfwords [0] era igual a 0xB5FF y no 0xFFB5 !!!!!

Después de notar esto, dejé de usar operaciones aritméticas y la media palabra, y me quedé con la lógica bit a bit y los 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)

... y esto funcionó bien. Cuando miro la temperatura después de la primera línea de código, es 0xFFB5 y no 0xB5FF, así que todo está bien

Entonces, para preguntas ...

  • Cortex es nuevo para mí. No recuerdo el intercambio de bytes de PIC en int16, a pesar de que ambas plataformas son poco endian. ¿Es esto correcto?

  • ¿Hay alguna forma más elegante de manejar esto? Sería genial si pudiera poner el ARM7 en modo big-endian. Veo muchas referencias de que Cortex M4 es bi-endian, pero todas las fuentes parecen detenerse antes de decirme cómo . Más específicamente, ¿cómo coloco el STM32f407 en modo big-endian , aún mejor si se puede hacer en gcc. ¿Es SOLO una cuestión de establecer el bit apropiado en el registro AIRCR? ¿Hay alguna ramificación, como tener que configurar el compilador para que coincida, o problemas matemáticos más tarde con bibliotecas inconsistentes?

Scott Seidman
fuente
2
"Dado que entran tres bytes, MSB primero" - eso es big-endian, mientras que su CPU es little-endian, entonces ahí es donde comienza su problema. Buscaría funciones de compilación de macros / biblioteca estándar para hacer un intercambio de bytes de 16/32 bits, generalmente se implementan de la manera más eficiente para la plataforma de CPU en particular. Por supuesto, el uso de desplazamiento / ANDing / ORing en bits también está bien.
Laszlo Valko
Supongo que podría rellenar analogin0.spibytes en cualquier orden que desee, pero eso también parece un poco tramposo, ya que tendría que recordar el orden para desatascarlo para pasarlo a través de usart. Creo que el formato de 3 bytes hace que las cosas sean un poco no estándar. Si esto fuera c ++, podría considerar una clase.
Scott Seidman
3
CMSIS tiene __REV()y __REV16()para revertir bytes.
Turbo J
3
No es una trampa en absoluto: cuando está haciendo E / S a este nivel, debe ser consciente y tratar con la relación entre el orden de bytes externo y el orden de bytes interno. Mi preferencia es convertir representaciones externas a / desde representaciones "nativas" (internas) en el nivel más bajo en la jerarquía de software que tenga sentido, y dejar que todo el software de nivel superior se ocupe solo del formato nativo.
Dave Tweed
A pesar de que el núcleo diseñado por ARM Corp. puede operar con cualquier endianness, la implementación de STM del núcleo ARM en STM32F407 es solo little endian. Consulte el Manual de referencia RM0090 página 64. AIRCR.ENDIANNESS es un bit de solo lectura.
Laszlo Valko

Respuestas:

6

Los sistemas integrados siempre tendrán el problema big-endian / little-endian. Mi enfoque personal ha sido siempre codificar la memoria interna con el endianiness nativo y hacer cualquier intercambio correcto cuando los datos entran o salen.

Cargo los datos usando lecturas de spi en analogin0.spibytes [0] - [2], con [0] como MSB

Al cargar [0] como MSB, está codificando el valor como big-endian.

analogin0.spibytes [0] = 0xFF y y analogin0.spibytes [1] = 0xB5, analogin0.halfwords [0] fue igual a 0xB5FF

Esto indica que el procesador es little-endian.

Si, en cambio, carga el primer valor en [2] y vuelve a trabajar en [0], entonces ha codificado el número entrante como little-endian, esencialmente haciendo el intercambio a medida que ingresa el número. Una vez que esté trabajando con la representación nativa, puede volver a su enfoque original de usar operaciones aritméticas. Solo asegúrese de volverlo a big-endian cuando transmita el valor.

DrRobotNinja
fuente
5

Con respecto a la recompensa "Realmente quiero saber sobre el modo big endian srm32f4", no hay modo big endian en este chip. STM32F4 hace todo el acceso a la memoria en little endian.

El manual del usuario http://www.st.com/web/en/resource/technical/document/programming_manual/DM00046982.pdf menciona esto en la página 25. Pero hay más. En la página 93 puede ver que hay instrucciones de intercambio endian. REV y REVB para bit inverso y inverso. REV cambiará la endianess para 32 bits y REV16 lo hará para datos de 16 bits.

C. Towne Springer
fuente
3

Aquí hay un fragmento de código para una corteza M4, compilado con 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

Desde C, la llamada puede ser:

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

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

No sé cómo hacerlo más rápido que esto :-)

Olivier Monnom
fuente
puede usar una macro o una función en línea para acelerar este código
pro
¿Qué pasa con los datos de 24 bits?
pengemizt
1

Para CooCox STM32F429 esto está bien:

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);
Argebir Laboratuvar
fuente