Quel est le problème avec cette conversion en code C pour AVR?

8

J'ai défini deux variables:

uint8_t a[2];
uint16_t b;

Ensuite, je veux utiliser acomme variable de type uint16_t, par exemple

b = (uint16_t)a;

Mais c'est faux! Mes programmes ne fonctionnent pas correctement avec un tel code. Tout est OK lorsque je remplace bà uint8_t b[2]et de l' exploitation de l' utilisation par éléments.

Pourquoi?


la source
5
Pourquoi ne jetez-vous pas quelques valeurs dans votre exemple et dites-nous ce que vous attendez de «correct» afin que nous puissions réellement vous aider sans spéculer sur votre intention sémantique.
vicatcu
1
Ce serait un bien meilleur ajustement pour Stack Overflow.
sharptooth

Réponses:

16

aest un pointeur vers un tableau d'octets. Si vous le transformez en uint16_t et l'affectez à b, alors bcontiendra l'adresse de la base du tableau (où il est stocké) dans SRAM. Si vous souhaitez traiter les deux octets du tableau acomme un entier, utilisez une union comme suggéré par user14284, mais sachez que l'union représentera le tableau d'octets dans l'ordre des octets de la mémoire de l'architecture (dans AVR, ce serait peu -endian, ce qui signifie que l'octet 0 est l'octet le moins significatif). La façon d'écrire cela dans le code est:

union{
  uint8_t a[2];
  uint16_t b;
} x;

x.b[0] = 0x35;
x.b[1] = 0x4A;

// by virtue of the above two assignments
x.a == 0x4A35 // is true

Une autre façon de le faire sans utiliser l'union est de atranstyper en un pointeur uint16_t puis de le déréférencer comme ceci:

uint8_t a[2] = {0x35, 0x4A};
uint16_t b = *((uint16_t *) a);
b == 0x4A35; // because AVR is little endian

Si vous utilisez le tampon pour stocker de grandes données endiennes (par exemple, l'ordre des octets du réseau), vous devrez alors permuter les octets pour utiliser l'une de ces techniques. Un moyen de le faire sans aucune branche ni variable temporaire est:

uint8_t a[2] = {0x35, 0x4A};
a[0] ^= a[1];
a[1] ^= a[0];
a[0] ^= a[1];

a[0] == 0x4A; // true
a[1] == 0x35; // true

Soit dit en passant, ce n'est pas un AVR ou même un problème intégré uniquement. Niveau d'application code réseau sur des PC appels généralement appelés fonctions htonl, htons(hôte réseau, et 16 32- bits variantes) et ntohl, ntohs(réseau hôte, et les variantes 32- 16 bits) dont la mise en oeuvre sont l' architecture cible dépend de savoir si elles permutez ou non les octets (en supposant que les octets tels qu'ils sont transmis «sur le câble» sont toujours en gros bout quand ils font partie de mots à plusieurs octets).

vicatcu
la source
C'est une excellente réponse. La clé étant ' a' seule est un pointeur.
Jon L
2
"Une autre façon de le faire sans utiliser l'union est de transtyper un pointeur uint16_t puis de le déréférencer" - En fait, ce type de transtypage enfreint souvent les règles strictes d'alias. Vous ne devriez pas le faire sauf si vous compilez avec -fno-strict-aliasing.
Jim Paris
3

Si votre intention est de concaténer les deux variables 8 bits en une variable 16 bits, utilisez a union. Si vous souhaitez aconvertir un seul membre de en b, spécifiez l'élément du tableau que vous souhaitez utiliser.

Joe Hass
la source
2

Sur votre code, vous ne transformez que le pointeur sur le tableau.

Vous devez convertir la valeur indiquée par a.

b = (uint16_t)*a;

Je n'ai jamais utilisé AVR mais si vous travaillez avec une architecture 16 bits, vous devez vous assurer qu'un mot est aligné. Ne pas le faire peut entraîner une exception.

Bruno Ferreira
la source
1
... ce n'est absolument pas son intention ... il veut que b soit lié aux deux éléments de a (cela ignorerait totalement a [1]), il n'y a pas non plus d'exceptions ou de restrictions sur ce que vous pouvez lancer dans avr-gcc
vicatcu
0

Chaque membre d' un est un nombre de 8 bits. Il ne peut rien contenir de plus gros. Le convertir en 16 bits ne fait rien pour a . Il extrait simplement la valeur que pourrait contenir a et la convertit en 16 bits afin qu'elle corresponde au format de b lorsque la valeur y est stockée.

Vous n'avez même pas fait référence à un membre d' un . Vous devez utiliser un [0] ou un [1] (et non un [2]!). Si vous utilisez un seul, vous obtenez simplement l'adresse de celui-ci. (Bonne prise, Bruno).

Déclarer un comme un tableau de deux nombres à 8 bits n'en fait pas non plus un nombre à 16 bits. Vous pouvez faire certaines choses par programmation pour stocker et récupérer des valeurs 16 bits en utilisant des séquences de 8 bits, mais pas comme vous le pensiez.

gbarry
la source
0

Si vous souhaitez convertir les octets aen une valeur de 16 bits et que la représentation est en petit-boutien (les 8 bits inférieurs de la valeur viennent dans le premier octet), faites

uint16_t b = a[0] | (a[1] << 8);

Pour une représentation big-endienne,

uint16_t b = (a[0] << 8) | a[1];

Évitez d'utiliser des pointeurs ou des unions pour ce faire, car cela entraîne des problèmes de portabilité.

starblue
la source