04/03/2023
202303040800 bit perl
Les bits
J'ai toujours eu un peu de mal à avoir la représentation binaire d'une valeur, qu'elle soit au format décimal ou hexadécimal (là j'ai vraiment beaucoup de mal). Mais jouer avec des microcontrôleurs impose tôt ou tard de se retrouver avec ce genre de chose:
RCC->APB1ENR |= 0x200000;
APB1ENR est une valeur sur 32 bits décomposés en bit (parfois en groupe de bit) et ici on active un de ces bits. Mais lequel ? Pour me simplifier la vie, je me suis donc fendu d'un script (Perl, what else ?) sans prétention qui m'affiche, pour chaque argument, sa valeur binaire:
$ perl bits.pl 0x200000 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 -------------------------------------------------------------------------------------------------------------------------------- 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0x200000 (2097152)
En consultant la documentation, je comprend que le bit 21 du registre APB1ENR correspond (pour une blue pill) à l'activation de I2C1. Parce que certains registres sont sur 16 bits, je peux aussi réduire le nombre de bit à afficher:
$ perl bits.pl -16 0x800 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 ---------------------------------------------------------------- 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0x800 (2048)
ou modifier la largeur de chaque colonne (pas le plus simple à lire, j'en conviens):
$ perl bits.pl -8 -w 1 5 7 9 76543210 -------- 00000101 5 (5) 00000111 7 (7) 00001001 9 (9)
Je peux aussi utiliser des valeurs binaires directement à des fins de comparaison:
$ perl bits.pl -8 0b00000111 0b10101010 5 0xa 7 6 5 4 3 2 1 0 -------------------------------- 0 0 0 0 0 1 1 1 0b00000111 (7) 1 0 1 0 1 0 1 0 0b10101010 (170) 0 0 0 0 0 1 0 1 5 (5) 0 0 0 0 1 0 1 0 0xa (10)
Les opérateurs et fonctions Perl
Avec l'opérateur .. je peux facilement obtenir la liste des nombres de 0 à 15:
$ perl -le 'print 0 .. 15' 0123456789101112131415
Mais pas ceux de 15 à 0:
$ perl -le 'print 15 .. 0' <rien, nada, que dalle>
Pour afficher mes nombres dans l'ordre décroissant, il me suffit d'utiliser reverse:
$ perl -le 'print reverse 0 .. 15' 1514131211109876543210
Pour afficher une valeur sur 3 caractères j'utilise %3s. Pour formater size bits sur une largeur de width il me suffit d'utiliser l'opérateur x:
$fmt = "%${width}s" x $size-- . '%s'; // on n'oublie pas le '%s' pour le retour à la ligne
Parce que j'utilise reverse, je dois commencer par le retour chariot:
printf $fmt, reverse "\n", 0 .. $size;
Pour connaitre la valeur d'un bit à la position $pos d'une valeur $value, je dois déplacer la valeur 1 de $pos positions:
1 << $pos
faire un et avec la valeur:
$value & (1 << $pos)
et redécaler ce résultat de pos positions (les parenthèses sont nécessaires du fait de la priorité des opérateurs):
($value & (1 << $pos)) >> $pos
Pour obtenir la valeur des $size premiers bits de $register:
map { ($register & (1 << $_)) >> $_ } 0 .. $size;
map va appliquer le block ({ .. }) pour chaque valeur ($_) de la list 0 .. $size
Le script
Comme d'habitude, pour quelques lignes de code, on se trouve avec une gestion des options, de l'aide, de la vérification d'arguments ... et paf, 50 lignes:
$ cat -n bits.pl 1 #!/usr/bin/perl 2 3 use strict; 4 use warnings FATAL => 'all'; 5 use utf8; 6 7 use Getopt::Long; 8 9 sub usage { 10 print <<EOT 11 Usage: $0 [-8|-16|-32] [--width=width] [-h] number ... 12 13 Example: 14 $0 42 0xcafe 0b101 15 $0 -8 -w 1 5 16 EOT 17 ; 18 exit; 19 } 20 21 my ($size, $width, $help) = (32, 4); 22 GetOptions( 23 '8' => sub { $size = 8 }, 24 '16' => sub { $size = 16 }, 25 '32' => sub { $size = 32 }, 26 27 'width=i' => \$width, 28 'help' => \$help, 29 ) or usage(); 30 31 if ($help || !scalar @ARGV) { usage(); } 32 33 my $fmt; 34 sub dump_register { 35 my $register = $_[0] =~ /^0[bx]/i ? oct $_[0] : $_[0]; 36 printf $fmt, reverse " $_[0] ($register)\n", map { ($register & (1 << $_)) >> $_ } 0 .. $size; 37 } 38 39 if ($width < 2 && $size > 8) { $width = 3; } 40 $fmt = "%${width}s" x $size-- . '%s'; 41 42 printf $fmt, reverse "\n", 0 .. $size; 43 printf $fmt, (('-' x $width)) x ($size + 1) , "\n"; 44 45 foreach (@ARGV) { dump_register($_); }
Pour finir
Quand je tombe sur ce genre de chose:
GPIOA->CRL &= ~(0xf << (2 * 4)); GPIOA->CRL |= 0xA << (2 * 4);
Il me suffit de rajouter en fin de script:
my $register = $ARGV[0] =~ /^0[bx]/i ? oct $ARGV[0] : $ARGV[0]; $register &= ~(0xf << (2 * 4)); dump_register($register); $register |= 0xA << (2 * 4); dump_register($register);
et de le lancer avec 0xffffffff (pour que tous les bits soient à 1):
$ perl bits.pl 0xffffffff 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 -------------------------------------------------------------------------------------------------------------------------------- 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0xffffffff (4294967295) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 4294963455 (4294963455) 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 1 1 1 4294966015 (4294966015)
La première instruction initialise à zéro le 3ème groupe de 4 bits, la seconde active les bits 1 (9) et 3 (11) de ce 3ème groupe. Et tout est plus clair !
Commentaires: https://github.com/bsdsx/blog_posts/issues/17