Problème endian sur STM32

11

J'utilise arm gcc (CooCox) pour programmer une découverte STM32F4, et j'ai lutté avec un problème endian

J'échantillonne avec un ADC 24 bits via SPI. Puisque trois octets arrivent, MSB a d'abord eu l'idée de les charger dans une union pour les rendre (j'espérais quand même!) Un peu plus faciles à utiliser.

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

Je charge les données en utilisant des lectures spi dans analogin0.spibytes [0] - [2], avec [0] comme MSB, puis je les recrache via USART à un mégabaud, 8 bits à la fois. Pas de problème.

Les problèmes ont commencé lorsque j'ai essayé de transmettre les données à un DAC 12 bits. Ce DAC SPI veut des mots de 16 bits, qui consistent en un préfixe de 4 bits commençant au MSB, suivi de 12 bits de données.

Les premières tentatives ont consisté à convertir le complément à deux que l'ADC m'a donné pour compenser le binaire, en xorant analogin0.spihalfwords [0] avec 0x8000, en décalant le résultat sur les 12 derniers bits, puis en ajoutant le préfixe arithmétiquement.

Incroyablement frustrant, jusqu'à ce que je remarque que pour analogin0.spibytes [0] = 0xFF et et analogin0.spibytes [1] = 0xB5, analogin0.halfwords [0] était égal à 0xB5FF et non 0xFFB5 !!!!!

Après avoir remarqué cela, j'ai arrêté d'utiliser les opérations arithmétiques et le demi-mot, et je suis resté fidèle à la logique au niveau du bit et aux octets

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)

... et cela a bien fonctionné. Quand je jette un œil à temp après la première ligne de code, son 0xFFB5, et non 0xB5FF, donc tout va bien

Donc, pour les questions ...

  • Le cortex est nouveau pour moi. Je ne me souviens pas que PIC ait jamais échangé des octets en int16, même si les deux plates-formes sont peu endiennes. Est-ce correct?

  • Existe-t-il une manière plus élégante de gérer cela? Ce serait formidable si je pouvais simplement mettre l'ARM7 en mode big-endian. Je vois de nombreuses références au Cortex M4 étant bi-endian, mais toutes les sources semblent s'arrêter de me dire comment . Plus précisément, comment placer le STM32f407 en mode big-endian , encore mieux s'il peut être fait en gcc. Est-ce juste une question de réglage du bit approprié dans le registre AIRCR? Y a-t-il des ramifications, telles que la nécessité de définir le compilateur pour qu'il corresponde, ou des erreurs mathématiques plus tard avec des bibliothèques incohérentes ??

Scott Seidman
la source
2
"Puisque trois octets arrivent, MSB d'abord" - c'est big-endian, alors que votre CPU est petit-endian, c'est donc là que commence votre problème. Je chercherais des macros de compilateur / des fonctions de bibliothèque standard pour effectuer un échange d'octets 16/32 bits, généralement elles sont implémentées de la manière la plus efficace pour la plate-forme CPU particulière. Bien sûr, utiliser le décalage / ANDing / ORing bit par bit est également correct.
Laszlo Valko
Je suppose que je pourrais farcir analogin0.spibytes dans l'ordre que je veux, mais cela semble aussi être un peu une triche, car je devrais me rappeler l'ordre de le décoller pour le passer via usart. Je pense que le format 3 octets rend les choses un peu non standard. S'il s'agissait de c ++, je pourrais envisager une classe.
Scott Seidman
3
CMSIS a __REV()et __REV16()pour inverser les octets.
Turbo J
3
Ce n'est pas du tout une triche - lorsque vous effectuez des E / S à ce niveau, vous devez être conscient de la relation entre l'ordre des octets externes et l'ordre des octets internes. Ma préférence est de convertir les représentations externes en / à partir des représentations "natives" (internes) au niveau le plus bas de la hiérarchie logicielle qui a du sens, et de laisser tous les logiciels de niveau supérieur gérer uniquement le format natif.
Dave Tweed
Même si le cœur conçu par ARM Corp. est capable de fonctionner avec l'une ou l'autre des endianités, l'implémentation par STM du cœur ARM dans STM32F407 n'est que peu endienne. Voir le manuel de référence RM0090 page 64. AIRCR.ENDIANNESS est un bit en lecture seule.
Laszlo Valko

Réponses:

6

Les systèmes embarqués auront toujours le problème big-endian / little-endian. Mon approche personnelle a été de toujours encoder la mémoire interne avec l'endianité native et de faire les échanges nécessaires lorsque les données entrent ou sortent.

Je charge les données en utilisant des lectures spi dans analogin0.spibytes [0] - [2], avec [0] comme MSB

En chargeant [0] comme MSB, vous encodez la valeur en big-endian.

analogin0.spibytes [0] = 0xFF et et analogin0.spibytes [1] = 0xB5, analogin0.halfwords [0] était égal à 0xB5FF

Cela indique que le processeur est peu endian.

Si au lieu de cela, vous chargez la première valeur dans [2] et revenez à [0], alors vous avez encodé le numéro entrant en petit-boutien, effectuant essentiellement le swap lorsque le nombre entre. Une fois que vous travaillez avec la représentation native, vous pouvez revenir à votre approche originale d'utilisation des opérations arithmétiques. Assurez-vous simplement de le retourner en big-endian lorsque vous transmettez la valeur.

DrRobotNinja
la source
5

Concernant la prime "Je veux vraiment savoir sur le mode big endian srm32f4", il n'y a pas de mode big endian sur cette puce. STM32F4 fait tous les accès mémoire en petit endian.

Le manuel d'utilisation http://www.st.com/web/en/resource/technical/document/programming_manual/DM00046982.pdf le mentionne à la page 25. Mais il y a plus. À la page 93, vous pouvez voir qu'il y a des instructions de permutation endienne. REV et REVB pour inverser et inverser bit. REV changera l'endianess pour 32 bits et REV16 le fera pour les données 16 bits.

C. Towne Springer
la source
3

Voici un extrait de code pour un cortex M4, compilé avec 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

Depuis C, l'appel peut être:

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

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

Je ne sais pas comment faire plus vite que ça :-)

Olivier Monnom
la source
vous pouvez utiliser une macro ou une fonction en ligne pour rendre ce code plus rapide
pro
qu'en est-il des données 24 bits?
pengemizt
1

Pour CooCox STM32F429, c'est 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);
Argebir Laboratuvar
la source