2
votes

Comment une personne inverse-t-elle l'ordre des 8 bits inférieurs d'une valeur «int» et laisse les 8 bits supérieurs inchangés?

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


5 commentaires

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 un uint16_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.


4 Réponses :


1
votes

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 >


7 commentaires

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.



6
votes

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.


3 commentaires

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.



0
votes

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.


2 commentaires

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.



1
votes

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;
}


0 commentaires