Skip to content

Latest commit

 

History

History
162 lines (119 loc) · 6.36 KB

2023-03-04-bits.md

File metadata and controls

162 lines (119 loc) · 6.36 KB
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: #17