Mon application nécessite que je stocke la valeur dans un compteur 16 bits mais en raison de problèmes de carte PCB, elle nécessite que les 8 bits inférieurs du compteur soient inversés (01001110 à 01110010). Le code est en cours d'écriture en C (GCC) et le registre du compteur est de type "int" (16 bits). Mon application utilise un MCU Atmel ATtiny 8 bits. Je comprends que si je déclare que le registre de compteur est de type "int", le compilateur allouera 2 emplacements de RAM. Dois-je simplement extraire l'octet inférieur avec un masque, puis réorganiser les bits et les recoller avec quelque chose comme;
counter = counter & 0x00 clear lower byte value counter = counter + (register with the reversed 8 bits) // Then, Replace lower byte value with new value
Cela devrait-il fonctionner? Merci
4 Réponses :
Vous avez une faute de frappe:
__flash unsigned char rotated_bytes[] = { 0x00, 0x80, 0x40, 0xC0, 0x20, ... }; new_byte = rotated_byte[orig_byte];
devrait être
new_byte = 0; if (orig_byte & 0x80) new_byte |= 0x01; if (orig_byte & 0x40) new_byte |= 0x02; ...
ou
counter &= 0xFF00;
pour effacer l'octet inférieur. Vous pouvez inverser les bits en tournant un peu à la fois vers une autre variable. Si le timing est critique, vous devez le faire dans l'assembly car C n'a pas d'opérateur de rotation et la fonction doit être simulée, par exemple
counter = counter & 0xFF00;
etc. est probablement l'un des moyens les plus rapides de C. ou si vous pouvez épargner 256 octets de mémoire flash, utilisez simplement une table, par exemple
counter = counter & 0x00 clear lower byte value
(remplacez __flash par le mot-clé étendu de votre compilateur pour signifier «mémoire programme») p >
Les valeurs de votre table de recherche sont incorrectes et l'opération requise n'est en aucun cas une "rotation". Vous avez également mis l'accent sur les performances lorsqu'aucune exigence n'était mentionnée dans la question - c'est-à-dire une optimisation prématurée.
Vous avez raison de dire que j'ai eu une idée de la rédaction du tableau. Je commençais à écrire 0b10000000, 0b01000000, 0b11000000, etc. et d'une manière ou d'une autre, j'ai dit: "Faisons-le en décimales et faisons le désordre. Oups. ATTiny a 2K à 16K de mémoire flash, donc je suis sûr que les optimisations finiront par devenir importantes , même si ce n'est pas immédiatement. En outre, il est bon de considérer le compromis entre l'espace et le code comme un "sac des astuces" dans la programmation embarquée. Et oui, je voulais écrire "décaler" les bits.
Peut-être, mais les contraintes de mémoire suggèrent que l'optimisation de espace plutôt que de temps est plus probable et ni votre boucle déroulée ni votre recherche ne sont efficaces dans l'espace de code. Vous avez peut-être corrigé la recherche - je l'ai corrigée pour vous.
Un bon compromis serait d'avoir une recherche de 16 octets de valeurs de quartet (4 bits).
L'approche naïve avec huit if
s est la meilleure. Atmel est livré avec de jolies instructions de manipulation. Mon gcc traduit if (orig_byte & 0x40) new_byte | = 0x02;
en seulement deux instructions d'assembleur! (SBRC: sauter l'instruction suivante si le bit du registre est effacé, suivi de l'instruction OR)
@ user5329483: "best" est subjectif et est une question d'opinion et d'exigences et de contraintes locales, et ne peut être revendiqué que si vous avez évalué toutes les autres options par rapport à ces exigences. C'est probablement dans l'ensemble la meilleure des deux options présentées dans cette réponse pour cet objectif spécifique.
> si (x & 0x01) y | = 0x80; > si (x & 0x02) y | = 0x40; > si (x & 0x04) y | = 0x20; > si (x & 0x08) y | = 0x10; > si (x & 0x10) y | = 0x08; > si (x & 0x20) y | = 0x04; > si (x & 0x40) y | = 0x02; > si (x & 0x80) y | = 0x01; est juste 8x 4 octets, ou 32 octets, et au plus 16 cycles. C'est donc assez difficile à battre.
Votre description verbale du processus est correcte, mais votre pseudo-code est inexact et incomplet.
Vous devez copier le LSB du compteur
avant d'effacer il; sinon vous aurez perdu les bits dont vous avez besoin pour inverser. Vous devez effacer correctement le LSB, et vous pouvez inverser les bits LSB directement dans le compteur LSB comme suit:
| | Instruction Count | | |Algorithm | No Opt | -O3 | -Os | + Data (bytes)| |----------|:------:|:---:|:---:|:-------------:| | Loop | 38 | 88 | 23 | 0 | | LookUp16 | 59 | 38 | 37 | 16 | | LookUp8 | 137 | 65 | 62 | 8 |
Vous devriez également utiliser les types stdint.h uint16_t
et uint8_t
pour cette opération plutôt que de compter sur int
ayant une taille particulière - cela rendra le code plus portable et testable sur un système où int
n'est pas 16 bits. Et en général, vous devriez utiliser des types non signés lorsque vous effectuez des opérations par bit.
Une méthode un peu plus rapide, même si elle nécessite peut-être un peu plus d'espace ROM, est d'utiliser une table de recherche. Une table de recherche de 256 octets est plutôt lourde à générer et sur un ATtiny plutôt prohibitif en termes d'utilisation de la mémoire. Au contraire, cela peut être fait presque aussi efficacement en utilisant une recherche de 16 octets comme suit:
static const uint8_t lookup[] = { 0x80, 0xC4, 0xA2, 0xE6, 0x91, 0xD5, 0xB3, 0xF7 } ; uint8_t msnib = ( lsb & 0x01 ) ? lookup[(lsb & 0xf) >> 1] >> 4 : lookup[(lsb & 0xf) >> 1] & 0xf ; uint8_t lsnib = ( lsb & 0x10 ) ? lookup[(lsb & 0xf0) >> 5] >> 4 : lookup[(lsb & 0xf0) >> 5] & 0xf ; counter |= (lsnib | msnib << 4) ;
Vous pouvez même emballer la table de recherche et utiliser seulement 8 octets (0x80, 0xC4, etc.) :
// Copy counter LSB uint8_t lsb = (uint8_t)(counter & 0xFFu) ; // Clear counter LSB counter &= 0xff00u ; static const uint8_t lookup[] = { 0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE, 0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF } ; counter |= lookup[lsb & 0xf] << 4 | lookup[lsb >> 4] ;
Mais la réduction de la taille de la table de consultation ne sera probablement pas justifiée par l'augmentation de la taille du code pour la manipulation de bits supplémentaire qui en résulte - et c'est juste un peu " trop intelligent "- il a fallu du temps pour bien faire les choses!
La première méthode a l'avantage de pouvoir être appliquée à un nombre arbitraire de bits. Les deux solutions de table de consultation peuvent être étendues à n'importe quelle taille de mot qui est un multiple de 4 bits sans changer la taille de la table de consultation, donc bien évolutive.
Analyse comparative strong >
J'ai testé chaque implémentation sur https://godbolt.org/ défini sur AVR GCC 4.6.4 en utilisant trois paramètres d'optimisation différents. Le nombre d'instructions exclut le code d'entrée / sortie de fonction ajouté pour le rendre compilable et représente uniquement les instructions générées à partir du code source dans cette réponse.
// Copy counter LSB uint8_t lsb = (uint8_t)(counter & 0xFFu) ; // Clear counter LSB counter &= 0xff00u ; // Reverse LSB bits and mask into counter LSB for( uint8_t mask = 0x80u; mask != 0; lsb >>= 1, mask >>= 1 ) { counter |= ((lsb & 0x01u) != 0) ? mask : 0 ; }
Le test en dit peu sur le temps d'exécution, mais si la taille du code est critique, l'algorithme de boucle avec optimisation de l'espace ( -Os
) est probablement le meilleur choix.
La table de recherche est sans aucun doute plus rapide quel que soit le niveau d'optimisation, et la table de recherche de 16 octets avec l'une ou l'autre des optimisations peut être un équilibre raisonnable. Pour -O3
, il est globalement plus petit et plus rapide que la boucle déroulée de 88 instructions. Il a également l'avantage distinct que la taille du code est beaucoup moins variable avec des paramètres d'optimisation qui peuvent minimiser les surprises lors du basculement entre les versions de débogage et de publication.
La recherche de 8 octets a peut-être peu de mérite sinon d'être assez intéressant.
Je conviens qu'une LUT n'est probablement pas la bonne approche. C'est rarement une optimisation polyvalente appropriée sur un grand microcontrôleur, c'est donc presque certainement une mauvaise idée sur un MCU gravement contraint en mémoire. Vérifiez que votre compilateur optimisera la boucle d'inversion de bits. Cela pourrait, mais aucune garantie. Sinon, vous feriez peut-être mieux d'écrire vous-même le code de manipulation de bits en utilisant une sorte d'algorithme SWAR.
@CodyGray a dit: "Je suis d'accord qu'une LUT n'est probablement pas la bonne approche." , mais je suis presque sûr que je n'ai pas dit cela, donc c'est une opinion plutôt qu'un accord. Je ne serais pas aussi catégorique. Il sera sans aucun doute plus rapide que l'algorithme "walking-one", et si le compilateur optimisait la boucle en la déroulant, la solution de table de consultation pourrait même être plus petite. Chacun a ses avantages possibles, et la solution doit prendre en compte les contraintes d'espace / temps et les exigences de l'application. La recherche de 256 éléments suggérée par Richard est certainement prohibitive.
@CodyGray: J'ai ajouté quelques métriques de taille de code qui suggèrent que la LUT n'est pas sans mérite.
Le moyen le plus simple d'inverser les bits d'un octet consiste à utiliser des unions et des champs de bits comme ci-dessous.
> struct ST_BYTE { unsigned char b0 : 1; unsigned char b1 : 1; unsigned char b2 : 1; unsigned char b3 : 1; unsigned char b4 : 1; unsigned char b5 : 1; unsigned char b6 : 1; unsigned char b7 : 1; } ; union U_BYTE { struct ST_BYTE byteflag; unsigned char charflag; }; unsigned char Reverse_Bits_in_Byte(U_BYTE local_byte) { U_BYTE Byte_Var2; Byte_Var2.byteflag.b0 =local_byte.byteflag.b7; Byte_Var2.byteflag.b1 =local_byte.byteflag.b6; Byte_Var2.byteflag.b2 =local_byte.byteflag.b5; Byte_Var2.byteflag.b3 =local_byte.byteflag.b4; Byte_Var2.byteflag.b4 =local_byte.byteflag.b3; Byte_Var2.byteflag.b5 =local_byte.byteflag.b2; Byte_Var2.byteflag.b6 =local_byte.byteflag.b1; Byte_Var2.byteflag.b7 =local_byte.byteflag.b0; return (Byte_Var2.charflag); } void main() { int i; for(i=0;i<8;i++) { Byte_Var1.charflag = pow(2,i); printf("\nBefore Reverse %02X\n", Byte_Var1.charflag); Byte_Var1.charflag = Reverse_Bits_in_Byte(Byte_Var1); printf("\nAfter Reverse %02X\n", Byte_Var1.charflag); } }
Notez que même si c'est facile, ce n'est pas une méthode recommandée pour elle-même reasons .
C'est le choix de programmeur de l'adopter ou non.
Le problème majeur avec l'utilisation du champ de bits est que l'ordre des bits est défini par l'implémentation. Heureusement dans ce cas particulier, comme l'OP demande d'inverser les bits, peu importe que l'ordre des bits soit gros ou petit boutiste :-)
Même l'accès au bit est également défini par l'implémentation. Certains peuvent accéder juste un bit et certains peuvent accéder à un octet pour modifier un bit. J'ai proposé cette logique pour les codes de base simples. L'auteur doit prendre un appel.
Voici mon approche:
;reversed_lo sits in R22 ;hi sits in R21 ;lo goes to R18 000007DF 60.fd SBRC R22,0 Skip if bit in register cleared 000007E0 02.c0 RJMP PC+0x0003 Relative jump 000007E1 20.e0 LDI R18,0x00 Load immediate 000007E2 01.c0 RJMP PC+0x0002 Relative jump 000007E3 20.e8 LDI R18,0x80 Load immediate 000007E4 61.fd SBRC R22,1 Skip if bit in register cleared 000007E5 20.64 ORI R18,0x40 Logical OR with immediate 000007E6 62.fd SBRC R22,2 Skip if bit in register cleared 000007E7 20.62 ORI R18,0x20 Logical OR with immediate 000007E8 63.fd SBRC R22,3 Skip if bit in register cleared 000007E9 20.61 ORI R18,0x10 Logical OR with immediate 000007EA 64.fd SBRC R22,4 Skip if bit in register cleared 000007EB 28.60 ORI R18,0x08 Logical OR with immediate 000007EC 65.fd SBRC R22,5 Skip if bit in register cleared 000007ED 24.60 ORI R18,0x04 Logical OR with immediate 000007EE 66.fd SBRC R22,6 Skip if bit in register cleared 000007EF 22.60 ORI R18,0x02 Logical OR with immediate 000007F0 66.23 TST R22 Test for Zero or Minus 000007F1 0c.f4 BRGE PC+0x02 Branch if greater or equal, signed 000007F2 21.60 ORI R18,0x01 Logical OR with immediate 000007F3 30.e0 LDI R19,0x00 Load immediate 000007F4 a9.01 MOVW R20,R18 Copy register pair 000007F5 58.2b OR R21,R24 Logical OR 000007F6 ca.01 MOVW R24,R20 Copy register pair 000007F7 08.95 RET Subroutine return
Mon compilateur produit 25 instructions au coût de 50 octets pour cette fonction:
uint16_t Flipper(uint8_t hi, uint8_t reversed_lo) { uint8_t lo=0; if (reversed_lo & 0x01) lo |= 0x80; if (reversed_lo & 0x02) lo |= 0x40; if (reversed_lo & 0x04) lo |= 0x20; if (reversed_lo & 0x08) lo |= 0x10; if (reversed_lo & 0x10) lo |= 0x08; if (reversed_lo & 0x20) lo |= 0x04; if (reversed_lo & 0x40) lo |= 0x02; if (reversed_lo & 0x80) lo |= 0x01; return (hi<<8) | lo; }
Voilà comment je le ferais. Si votre processeur a une opération de bits inversés, c'est probablement le moyen le plus rapide de le faire, sinon, l'opération inverse n'est pas trop mauvaise. Il existe de nombreux exemples sur la façon de faire cela.
Pour info, votre instruction et au niveau du bit effacera tous les bits. Cela devrait être quelque chose comme
counter & = 0xFF00
s'il s'agit d'une valeur 16 bits.et avec 0xFF00. plus fonctionne mais étant tellement lié à un OU se sent mieux. en plus n'est pas faux ...
Ne vous fiez pas à une taille particulière de
int
- ou utilisez un type signé, utilisez unuint16_t
.Ne postez pas de réponses «merci» (ni même de commentaires) sur SO est une question et non un forum de discussion. Vous montrez votre appréciation sur SO en acceptant et / ou en votant pour les réponses.