Instrumentez votre analyseur logique avec libsigrok

Magazine
Marque
Hackable
Numéro
43
Mois de parution
juillet 2022
Spécialité(s)


Résumé

L'analyseur logique fait partie des outils indispensables lorsqu'il s'agit de mettre au point des projets impliquant des communications série ou parallèle. Comme nous l'avons vu dans de précédents articles ([1] et [2]), accompagnés des bons logiciels, ils permettent très simplement d'espionner des bus SPI, i2c, série, LCD, etc. Mais il est possible d'aller bien plus loin et de développer sa propre solution pour traiter des signaux bien plus exotiques...


Body

Le matériel concerné ici sera la myriade de clones des premiers analyseurs logiques développés et vendus par Saleae. Aujourd'hui, leurs produits (Logic, Logic Pro, etc.) sont bien différents, mais la première génération développée utilisait les puces Cypress EZ-USB FX2LP, faisant fonctionner un firwmare stocké en RAM et fourni par l'application du constructeur. Le FX2 est un microcontrôleur bien particulier, capable de transmettre directement l'état des GPIO en USB avec un nombre minimal de composants complémentaires. Ce mode de fonctionnement a permis à certains constructeurs peu scrupuleux de développer des produits similaires, parfaitement compatibles avec l'application originale, à un prix défiant toute concurrence, allant même jusqu'à réutiliser la marque déposée (là, il ne s'agit plus de clones, mais de contrefaçons). Aujourd'hui, le constructeur a fait évoluer ses produits et il n'est plus possible, pour les « cloneurs », de faire fonctionner leur produit avec l'applicatif de Saleae.

En parallèle, un projet open source appelé Sigrok a vu le jour, proposant non seulement une application alternative à la solution propriétaire de Saleae, mais également un firmware entièrement réécrit permettant ainsi d'utiliser les clones de façon parfaitement légale (et morale). Aujourd'hui, on trouve toujours ces clones sur les sites habituels (eBay, Amazon, etc.), pour une dizaine d'euros (voire moins pour un devkit CY7C68013A) et ils sont toujours parfaitement utilisables, même si légèrement dépassés pour les applications hautes fréquences (24 MHz maximum).

Sigrok, accompagné de PulseView, une application graphique Qt de visualisation et de décodage de protocoles, permet donc, avec un budget réduit, de disposer d'un outil capable d'analyser toutes sortes de signaux digitaux. Mais la modularité de Sigrok pourra également être exploitée via la libsigrok, fournissant un accès abstrait à l'ensemble des matériels supportés, ainsi que quelques fonctions utilitaires. La libsigrok repose massivement sur la GLib [3] et propose, en plus de l'utilisation native en C, des bindings pour C++, Python, Java et Ruby.

« Pourquoi utiliser la libsigrok alors que PulseView nous fournit tout le nécessaire ? », me demanderez-vous. Tout simplement pour pouvoir traiter des signaux non standardisés et non normalisés, et surtout ne pas s'en tenir au simple décodage et à l'affichage des données capturées. Avec libsigrok, vous pouvez intégrer les fonctionnalités d'analyse logique dans vos propres programmes, sans avoir à gérer la partie bas niveau du pilotage du FX2. L'ensemble de la configuration du périphérique est prise en charge par la libsigrok, du nombre d'échantillons à collecter à la gestion des triggers, en passant par le choix de la fréquence d'échantillonnage.

On pourrait voir le fait d'utiliser la libsigrok comme une tentative d'écriture d'un clone de PulseView, mais bien que ceci soit parfaitement possible, il s'agit plutôt de créer une suite complète du traitement de l'information, à commencer par la détection des signaux, leur interprétation, mais également leur utilisation. À titre d'exemple, et même si cela ne s'est pas avéré être l'idée la plus éclairée aux vues des capacités du FX2, je me suis penché sur la libsigrok dans le but de lire et de décoder un signal vidéo. Il ne s'agit que d'un PoC (Proof of Concept ou preuve de concept), mais l'idée était de lire les signaux RGBi d'un Commodore 128 (mode 80 colonnes), avec pour objectif de décoder l'information, la stocker dans un buffer et finalement produire une image RGB 24-bits. Notez que la sortie RGBi en question n'est pas analogique (contrairement au VGA), mais, comme c'est le cas pour CGA, utilise uniquement des signaux pouvant être à l'état bas ou haut (VSync, HSync, rouge, vert, bleu et intensité).

La fréquence d'échantillonnage de 24 MHz n'est pas suffisante pour obtenir un résultat parfait, mais cela fonctionne et constitue tout de même un bel exercice. Le point notable ici est le fait qu'il s'agit d'un traitement qu'il n'est pas possible d'effectuer avec un applicatif comme PulseView, qui se contente de décoder des protocoles, mais ne fait finalement rien des données (si ce n'est les présenter à l'utilisateur). La libsigrok nous donne l'opportunité de compléter n'importe quelle acquisition avec un traitement immédiat. Il pourra s'agir de désassembler à la volée des instructions sur un bus, recomposer une image, capturer un firmware, etc.

Bien entendu, PulseView repose lui-même sur la libsigrok et si votre distribution GNU/Linux propose cet applicatif, vous aurez donc également accès à cette bibliothèque, et très probablement au paquet de développement correspondant (libsigrok-dev dans le cas de Debian et ses dérivés comme Ubuntu, Raspbian, Raspberry Pi OS, etc.).

sig clone-s

Contrairement à ce qui est honteusement marqué sur le périphérique, ceci n'est absolument pas un produit Saleae, mais un clone bas de gamme reprenant la même construction et les mêmes composants, au point de pouvoir fonctionner avec l'applicatif original de l'époque. Voyez-vous toute la perversité de la chose ? Saleae a investi le temps et les ressources R et D (hard et soft) et les cloneurs se contentent de vendre des contrefaçons bas de gamme en se reposant sur l'ingénierie logicielle achevée par d'autres.

1. Premier code juste pour goûter

Dans sa plus simple expression, un code utilisant la libsigrok se résume à peu de choses, mais comme avec toutes les bibliothèques, ceci ne servira que de point de départ, tout en permettant de vérifier que l'ensemble fonctionne. Notre code source débutera donc avec quelque chose comme :

#include <stdlib.h>
#include <libsigrok/libsigrok.h>
 
int main(int argc, char**argv)
{
  struct sr_context *ctx;
 
  if(sr_init(&ctx) != SR_OK) {
    fprintf(stderr, "Error initializing libsigrok!\n");
    exit(EXIT_FAILURE);
  }
 
  // Faire des trucs
 
  sr_exit(ctx);
 
  return(EXIT_SUCCESS);
}

Rien de bien complexe ici, nous retrouvons le mécanisme assez classique d'initialisation reposant sur l'utilisation d'une structure (ctx) regroupant les informations propres à la bibliothèque sous la forme d'un « contexte ». Notez cependant que sr_context n'est pas un type (typedef), mais une structure, ce qui est sensiblement peu commun et qu'on retrouve dans toute la libsigrok. Attention donc aux réflexes et habitudes, car le compilateur ne manquera pas de vous rappeler qu'il y a des struct qui manquent...

Pour accompagner ce code, nous rédigerons un Makefile assez simple :

TARGET := sigtest
WARN    := -Wall
DEBUG   := -g
CFLAGS := -O2 $(DEBUG) ${WARN} `pkg-config --cflags libsigrok`
LDFLAGS := `pkg-config --libs libsigrok`
CC      := gcc
 
C_SRCS    = $(wildcard *.c)
OBJ_FILES = $(C_SRCS:.c=.o)
 
all: ${TARGET}
 
%.o: %.c
        ${CC} ${WARN} -c ${CFLAGS} $< -o $@
 
${TARGET}: ${OBJ_FILES}
        ${CC} ${WARN} -o $@ $(OBJ_FILES) ${LDFLAGS}
 
clean:
        rm -rf *.o ${TARGET}

Comme dit précédemment, la libsigrok utilise intensivement la GLib et celle-ci sera automatiquement mentionnée dans les sorties de pkg-config, utilisées en argument du compilateur et de l'éditeur de liens.

sig c128-s

L'analyseur logique en situation, connecté à la sortie DB9 fournissant un signal vidéo RGBi pour le mode 80 colonnes du Commodore 128.

2. Trouver le périphérique

Le projet Sigrok ne concerne pas uniquement les périphériques purement digitaux basés sur le microcontrôleur Cypress EZ-USB FX2LP. En réalité, c'est toute une gamme de produits qui sont supportés [4], et ce, via différentes interfaces (USB, HID, Bluetooth, série, etc.). Bien entendu, certains matériels se ressemblent et sont pris en charge de façon identique, avec un pilote unique, comme c'est le cas pour les clones Saleae sur base FX2. L'approche à adopter dans votre code consiste donc à tout d'abord choisir un pilote, puis sur cette base, détecter et utiliser le périphérique découvert.

Commençons donc par la sélection du pilote :

  struct sr_dev_driver **driverlist;
  struct sr_dev_driver *driver = NULL;
 
  driverlist = sr_driver_list(ctx);
  for(int i=0; driverlist[i]; i++) {
    if(strcmp(driverlist[i]->name, "fx2lafw") == 0) {
      driver=driverlist[i];
      break;
    }
  }
 
  if(!driver) {
    fprintf(stderr, "Unable to select fx2lafw driver\n");
    exit(EXIT_FAILURE);
  }
 
  // Initialisation pilote
  sr_driver_init(ctx, driver);

La liste des pilotes présents est obtenue via sr_driver_list() nous retournant une liste de sr_dev_driver se terminant par NULL. Nous parcourons la liste à la recherche d'un pilote dont le nom est "fx2lafw", puis utilisons le pointeur obtenu avec sr_driver_init() pour procéder à l'initialisation. Dès lors, nous pouvons scanner les périphériques présents, et ici, utiliser le premier qui sera détecté :

  GSList *devlist;
  struct sr_dev_inst *sdi = NULL;
 
  devlist = sr_driver_scan(driver, NULL);
 
  if(devlist) {
    if((sdi = devlist->data) == NULL) {
      fprintf(stderr, "Unable to get fx2lafw device data!\n");
      exit(EXIT_FAILURE);
    }
  } else {
    fprintf(stderr, "No fx2lafw device found!\n");
    exit(EXIT_FAILURE);
  }
 
  g_slist_free(devlist);
  printf("Using: %s %s %s %s %s\n",
      sr_dev_inst_vendor_get(sdi), sr_dev_inst_model_get(sdi),
      sr_dev_inst_version_get(sdi), sr_dev_inst_sernum_get(sdi),
      sr_dev_inst_connid_get(sdi));

sr_driver_scan() nous retournera la liste des périphériques sous la forme d'une GSList, un type fourni par la GLib permettant de créer des listes chaînées. Notez qu'il est à la charge du développeur de libérer la mémoire avec g_slist_free() et que ce genre de différences entre des opérations qui par ailleurs sont très similaires (lister les pilotes vs lister les périphériques) sont légion dans l'API de la libsigrok. La documentation de l'API [5] spécifie heureusement ce genre de choses, mais la vérification systématique avec Valgrind (option --leak-check=yes) sera indispensable pour éviter les fuites de mémoire.

À ce stade, nous avons dans sdi un descripteur de périphérique que nous pouvons utiliser :

  if(sr_dev_open(sdi) != SR_OK) {
    fprintf(stderr, "Error opening fx2lafw device!\n");
    exit(EXIT_FAILURE);
  }
 
  // Faire des trucs
 
  sr_dev_close(sdi);
  sr_exit(ctx);
 
  return(EXIT_SUCCESS);
}

3. Configuration et GVariant

Nous avons accès au périphérique et pouvons donc désormais l'utiliser. Il nous faut cependant tout d'abord ajuster sa configuration à nos besoins et en particulier, la fréquence d'échantillonnage ainsi que la quantité d'échantillons à capturer. La libsigrok étant en mesure de prendre en charge nombre de périphériques (et pas seulement des analyseurs logiques), la configuration peut sembler relativement « touffue ». En effet, obtenir ou définir une valeur de configuration passe par l'utilisation des fonctions sr_config_get() et sr_config_set(), prenant en argument, entre autres choses, une clé de configuration issue de l'énumération sr_configkey déclarée dans libsigrok.h. Notez que ces fonctions doivent être utilisées après sr_dev_open().

L'énumération est assez importante puisqu'elle couvre l'ensemble des périphériques supportés. Fort heureusement, nous ne développons pas un outil générique, mais un programme entièrement dédié à une unique tâche et à un unique matériel. Les deux clés de configuration qui nous intéressent sont SR_CONF_SAMPLERATE pour la fréquence d'échantillonnage, et SR_CONF_LIMIT_SAMPLES pour la quantité de données à collecter. Notez que dans le code qui va suivre, nous ne vérifierons pas les valeurs de retour des fonctions afin de garder l'exemple (et l'article) à une taille raisonnable. Il faudra cependant toujours vous assurer que les fonctions de la libsigrok retournent effectivement SR_OK.

sig logic-s

Voici un véritable Saleae Logic basé sur un microcontrôleur Cypress EZ-USB FX2LP. Un œil bien exercé remarquera peut-être certaines différences très subtiles entre l'original et les clones... (sarcasmes).

sr_config_set() prend en argument le périphérique concerné (sdi ici), un éventuel groupe de canaux ou NULL, la clé de configuration et une valeur à attribuer. Oublions les groupes de canaux qui ne sont pas pertinents pour un périphérique FX2 (il n'y a qu'un groupe avec les 8 canaux disponibles) et concentrons-nous sur la valeur à passer. Il s'agit ici d'un argument à fournir sous la forme d'un pointeur vers un GVariant, un type fourni par GLib (struct GVariant) constitué d'une valeur et d'une information concernant le type de cette valeur. De plus, la GLib s'occupe de l'allocation et de la libération de la mémoire associée aux GVariant via un mécanisme de comptage de référence.

Pour utiliser sr_config_set(), nous devrons donc convertir un entier (typiquement un uint64_t) en GVariant. Mais la libsigrok offre une fonction utilitaire supplémentaire, prenant en argument une chaîne de caractères représentant une valeur « naturelle » et produisant un uint64_t. Cette fonction, sr_parse_sizestring(), reconnaît les expressions comme "4 M" (4000000), "25khz" (25000) ou encore "2g" (2000000000) et placera la valeur entière correspondant dans la variable dont le pointeur est passé en argument.

Configurer la fréquence d'échantillonnage peut donc être fait ainsi :

  uint64_t mysrate;
  GVariant *newsr;
 
  // Chaîne en entier
  sr_parse_sizestring("2 MHz", &mysrate);
 
  // Entier en GVariant
  newsr = g_variant_new_uint64(mysrate);
 
  // Configuration
  sr_config_set(sdi, NULL, SR_CONF_SAMPLERATE, newsr);

Et le nombre d'échantillons de manière similaire :

  uint64_t myslimit = 100000;
  GVariant *newsl;
  newsl = g_variant_new_uint64(myslimit);
  sr_config_set(sdi, NULL, SR_CONF_LIMIT_SAMPLES, newsl);

Pour appliquer effectivement cette configuration, il ne faudra pas oublier d'appeler :

sr_config_commit(sdi);

Encore une fois, il est important de vérifier les valeurs de retour de chaque fonction. Le firmware pour le microcontrôleur FX2, par exemple, n'est pas en mesure d'utiliser une fréquence arbitrairement choisie. Les fréquences utilisables sont 25 kHz, 50 kHz, 100 kHz, 200 kHz, 250 kHz, 500 kHz, 1 MHz, 2 MHz, 3 MHz, 4 MHz, 6 MHz, 8 MHz, 12 MHz, 16 MHz et 24 MHz. Toute autre valeur provoquera une erreur qu'il faudra prendre en compte, en particulier si la fréquence est obtenue de l'utilisateur via une option en ligne de commandes, par exemple.

Notez également qu'une fonction inverse de sr_parse_sizestring() existe, c'est sr_si_string_u64() permettant d'obtenir une valeur « naturelle » à partir d'un uint64_t. Un autre élément important, en particulier si votre code prend du volume, est le fait que sr_parse_sizestring() « consomme » (sink) le GVariant en décomptant une référence et donc en demandant implicitement à la GLib de libérer ici la mémoire. Après l'appel à sr_parse_sizestring(), newsr et newsl ne sont donc plus utilisables, ce qui peut être relativement perturbant, car pour d'autres fonctions, la libération est laissée à la discrétion du développeur.

sig probes-s

Fournies avec le clone, ces sondes pour analyseur logique correspondent parfaitement au niveau de qualité du produit lui-même : à n'utiliser QUE si vous n'avez absolument aucun autre choix. Il est très important de ne jamais négliger cet aspect, et ce, au point que le wiki de Sigrok y consacre une page entière [10].

4. Session et fonction callback

Nous sommes presque prêts à capturer nos premiers signaux, mais ceci ne peut être fait qu'en exécutant une session. Une session est une phase d'acquisition déclenchée par un appel à sr_session_start() prenant en argument un pointeur de pointeur vers une session (struct sr_session**) préalablement configurée. La notion de session véhicule à la fois le ou les périphériques à utiliser, la ou les fonctions callback de traitement des données et le ou les triggers déclenchant l'acquisition (cf. plus loin).

Pour procéder à une acquisition, nous devons donc créer une session et y ajouter le périphérique concerné :

  struct sr_session *ses;
  
  // Création session
  sr_session_new(ctx, &ses);
  
  // Ajout matériel
  sr_session_dev_add(ses, sdi);

La libsigrok est très souple à ce niveau. Rien ne nous empêche, en effet, d'utiliser plusieurs périphériques dans une seule session pour, par exemple, utiliser plusieurs clones Saleae Logic pour capturer des signaux sur un bus de 16, 24 ou 32 bits. Il est même possible de combiner des périphériques très différents et ainsi collecter des données digitales avec un matériel et analogiques avec un autre (comme un multimètre).

Pour traiter la masse de données résultante de cette capture, un mécanisme de callback est utilisé. Nous devons donc créer une fonction qui sera appelée à plusieurs reprises, à chaque mise à disposition d'un lot de données. Pour l'exemple, notre fonction sera relativement simple :

void mycb(const struct sr_dev_inst *sdi,
    const struct sr_datafeed_packet *packet,
    void *cb_data)
{
  const struct sr_datafeed_logic *logic;
 
  switch(packet->type) {
    case SR_DF_HEADER:
      printf("SR_DF_HEADER\n"); break;
    case SR_DF_LOGIC:
      logic = packet->payload;
      printf("SR_DF_LOGIC: %lu / %u\n",
          logic->length, logic->unitsize);
      break;
    case SR_DF_END:
      printf("SR_DF_END\n"); break;
  }
}

Plusieurs types de paquets sont susceptibles d'être envoyés (voir enum sr_packettype dans libsigrok.h), en fonction de la nature des données qu'ils contiennent (le payload). Avec notre analyseur logique à base de FX2, nous n'avons besoin de traiter que trois types de paquets :

  • SR_DF_HEADER indiquant que le payload est un entête de capture de type struct sr_datafeed_header ;
  • SR_DF_END marquant la fin de la capture (pas de payload) ;
  • SR_DF_LOGIC signifiant que nous avons affaire à des données d'acquisition sous la forme d'une struct sr_datafeed_logic.

Cette dernière structure prend la forme suivante :

struct sr_datafeed_logic {
    uint64_t length;
    uint16_t unitsize;
    void *data;
};

Nous retrouvons ici la taille totale des données du paquet (length), celle d'un élément unitaire en octet (unitsize) et un pointeur vers les données elles-mêmes (*data). Dans le cas de notre analyseur, un unique octet est suffisant pour encoder l'état des huit entrées et unitsize sera égale à 1. length en revanche sera dépendant de la fréquence d'échantillonnage configurée. Plus celle-ci est importante, plus la taille d'un paquet augmente et donc moins les appels à la fonction de callback seront fréquents. Traiter les données reviendra à simplement parcourir le contenu pointé par data lors de chaque appel du callback, et ce, en fonction de la taille de unitsize (ici, octet par octet, où chaque bit correspond à l'état d'une entrée du périphérique).

Cette fonction pourra être ajoutée dans notre session en utilisant :

  // Ajout callback
  sr_session_datafeed_callback_add(ses, mycb, NULL);

Et il ne restera plus, ensuite, qu'à débuter la session avec :

  // Démarrer session
  sr_session_start(ses);
 
  // Bloquer en attendant la fin
  sr_session_run(ses);
  
  // Détruire la session
  sr_session_destroy(ses);

sr_session_run() est une simple fonction utilitaire déclenchant une mainloop GLib pour rendre le code bloquant. Il est également possible de travailler de façon non bloquante et de définir un autre callback avec sr_session_stopped_callback_set(), permettant de déclencher une action en fin de session. Cette alternative permet d'intégrer plus facilement la capture à un code reposant déjà sur une boucle, comme une application graphique, par exemple.

Nous disposons à présent d'un code pleinement fonctionnel, configuré pour procéder à l'acquisition de 100000 échantillons à une fréquence de 2 MHz. L'exécution de cet exemple après compilation nous affichera :

Using: Saleae Logic (null) usb/2-2.2.3
SR_DF_HEADER
SR_DF_LOGIC: 20480 / 1
SR_DF_LOGIC: 20480 / 1
SR_DF_LOGIC: 20480 / 1
SR_DF_LOGIC: 20480 / 1
SR_DF_LOGIC: 18080 / 1
SR_DF_END

Nous avons obtenu cinq paquets de données totalisant 100000 octets dont le traitement pourra se faire à votre convenance. Dans le cadre de mes expérimentations autour du signal RGBi de la machine Commodore, il s'agissait de composer un buffer RGB dépendant de l'état des lignes de la sortie, puis lors de l'arrivée du paquet SR_DF_END, d'enregistrer le tout dans un fichier pour analyse et conversion dans un format graphique. L'objectif étant, à la prochaine itération sur le code, de rafraîchir régulièrement ce buffer et d'avoir en parallèle un thread chargé d'utiliser son contenu pour afficher l'image (sans doute avec SDL) en temps réel.

sig capture-s

Après plusieurs heures de tâtonnements éprouvants faute de documentation pratique détaillée, obtenir une image de la sortie vidéo est une récompense durement méritée (dopamine !).

5. Triggers

Notre code de démonstration fait le travail en capturant les données, mais il le fait dès le lancement du programme, ce qui n'est pas nécessairement une solution adaptée. En effet, l'objectif d'une capture n'est généralement pas d'obtenir un lot de bits à un moment aléatoire, mais une séquence entière à partir d'un événement précis. Cet événement est souvent un signal récurrent marquant le début d'une transmission, comme un signal /CS asservissant un composant (flash, RAM, ROM, périphérique, etc.). Dans le cas de mon outil de capture vidéo, le rafraîchissement de l'écran est lié au signal VSync (50 ou 60 Hz selon le modèle) ramenant le faisceau d'électrons du tube cathodique à sa position initiale en haut à gauche de l'écran. La capture d'une image complète débute donc par un changement d'état sur la ligne où est connecté ce signal, qu'il est possible de détecter en configurant un déclencheur ou trigger en anglais.

Cette fonctionnalité indispensable à tout analyseur logique est, bien entendu, supportée par le périphérique FX2 et par la libsigrok. Il est cependant décomposé en plusieurs éléments, un trigger pouvant comporter plusieurs étapes ou stages, eux-mêmes comportant un ou plusieurs matchs correspondants à un état ou changement d'état survenant sur une entrée, constituant un événement.

Ces événements, listés dans l'énumération sr_trigger_matches sont, pour les entrées digitales :

  • SR_TRIGGER_ZERO : déclenche lorsque l'entrée est à l'état bas ;
  • SR_TRIGGER_ONE : idem à l'état haut ;
  • SR_TRIGGER_RISING : déclenche sur le front montant (bas à haut) ;
  • SR_TRIGGER_FALLING : sur le front descendant (haut à bas) ;
  • SR_TRIGGER_EDGE : déclenche sur un changement d'état quelconque.

Et pour les entrées analogiques :

  • SR_TRIGGER_RISING : augmentation de la valeur mesurée ;
  • SR_TRIGGER_FALLING : réduction de la valeur ;
  • SR_TRIGGER_OVER : dépassement d'une valeur définie ;
  • SR_TRIGGER_UNDER : passage sous une valeur définie.

Dans un cas d'usage courant comme celui qui nous occupe dans notre démonstration ou dans mon petit projet de capture vidéo, les matchs seront le plus souvent SR_TRIGGER_RISING ou SR_TRIGGER_FALLING, pour capturer et agir lors d'un changement d'état, comme un signal /CS. Nous allons donc considérer cet exemple, avec l'entrée 0 du clone Saleae hypothétiquement reliée à un signal passant de l'état bas (masse) à l'état haut (Vcc) lors d'une « transaction ». Pour ce faire, nous commençons par décrire un canal (channel) correspondant à l'entrée en question :

struct sr_channel chan_vsync = {
    .sdi = sdi,
    .index = 0,
    .type = SR_CHANNEL_LOGIC,
    .enabled = true,
    .name = "vsync"
};

sdi est notre descripteur de périphérique, index est le numéro de l'entrée concernée, type est le type d'entrée (ici, SR_CHANNEL_LOGIC pour une entrée digitale), enable détermine l'état actif ou non et name est une simple chaîne descriptive. Notez qu'il n'est pas forcément nécessaire de procéder de la sorte. Il est également possible de récupérer directement une liste (GSList) de canaux fournie par le périphérique avec sr_dev_inst_channels_get() et d'utiliser les struct sr_channel y figurant dans les fonctions que nous allons voir dans un instant. L'approche optimale n'est pas détaillée dans la documentation de l'API et la libsigrok ne fournit pas d'exemples d’utilisation (mais uniquement des fichiers de tests des fonctionnalités [6]).

Nous créons ensuite un nouveau trigger :

  // Création trigger
  struct sr_trigger* mytrig;
  mytrig = sr_trigger_new("trig on hsync");

Puis ajoutons un stage à ce dernier :

  // Ajout stage dans trigger
  struct sr_trigger_stage* mytrigstage;
  if((mytrigstage = sr_trigger_stage_add(mytrig)) == NULL) {
      fprintf(stderr, "Invalid trigger!\n");
      exit(EXIT_FAILURE);
  }

Et ajoutons le match dans le stage :

  // Ajout match dans stage
  sr_trigger_match_add(mytrigstage,
              &chan_vsync,
              SR_TRIGGER_RISING,
              0.0f);

C'est ici que la magie opère, puisque notre match correspond à un front montant (SR_TRIGGER_RISING) sur le canal chan_vsync. Notez le float en guise de dernier argument, qui ici n'a aucune importance, mais pourra être utilisé avec un périphérique analogique et un déclencheur de type SR_TRIGGER_OVER, par exemple.

Enfin, nous ajoutons le trigger à la session (avant sr_session_start(), bien entendu) :

sr_session_trigger_set(ses, mytrig);

Comme précédemment, chaque opération impliquant des fonctions de la libsigrok sous-entend une vérification de la valeur de retour, qui doit être SR_OK en cas de succès. Remarquez également que la libsigrok ne semble pas intégrer de fonctionnalité de repeat triggering et que si vous souhaitez disposer de ce type de mécanique, vous devrez l'implémenter dans votre code afin de réarmer le déclencheur une fois une capture terminée. Ceci est confirmé par ailleurs dans PulseView, où ce type de fonctionnalités n'existe que dans un fork distinct du projet [7]. De plus, il ne faudra pas oublier de libérer la mémoire après la phase de capture avec sr_trigger_free(mytrig) pour éviter toute fuite.

Une fois tout ceci compilé, l'exécution de notre code restera en attente du signal déclencheur et la capture ne débutera que lorsque celui-ci apparaîtra sur l'entrée désignée. Nous n'avons utilisé ici qu'un seul déclencheur, mais là encore, la libsigrok permet de créer des choses beaucoup plus complexes, combinant plusieurs périphériques de types différents et plusieurs matchs et stages pour cerner avec précision un événement précis à surveiller.

sig captureZOOM-s

On distingue ici clairement les limitations de cette solution. Avec un HSync à 15,75 kHz et une résolution de 640×200 (plus front et back porch), les 24 MHz de fréquence d'échantillonnage ne sont pas suffisants pour obtenir un résultat « propre ». Les échantillons ont plus ou moins la taille d'un pixel, ce qui induit un phénomène de jitter (gigue). Une image de qualité acceptable pourrait certainement être obtenue avec un analyseur plus performant (et plus coûteux), mais cela irait à l'encontre de l'objet même de l'expérience.

Conclusion

La libsigrok est fort pratique, mais comme nous venons de le voir, son utilisation est rendue délicate par la quantité de périphériques supportés. Réunir de façon cohérente un ensemble de fonctionnalités couvrant à la fois les analyseurs logiques, les oscilloscopes connectés, les multimètres et même les balances, hygromètres et thermomètres numériques n'est pas une mince affaire. Si on ajoute à cela l'omniprésence de la GLib dans l'API, des différences importantes de logiques d'implémentation d'une fonctionnalité à l'autre ou encore le fait que la documentation soit relativement peu explicite, force est de constater que la libsigrok n'est pas très facile à prendre en main. Elle offre cependant une couche d'abstraction dont il serait difficile de se passer, et l'expérience aidant, permet de réaliser de petits outils « sur mesure », qu'il serait par ailleurs très difficile de créer en attaquant le périphérique au niveau USB (mais pas impossible, voir [8]).

Bien entendu, les réalisations dépendront très fortement des capacités effectives du matériel mis en œuvre et il faudra garder à l'esprit qu'il n'y a pas de solution miracle. Dans le cadre de mon petit projet de traitement de signal vidéo par exemple, l'approche idéale consisterait à utiliser un FPGA/CPLD pour procéder à un premier traitement, puis à passer les données résultantes à un système embarqué pour affichage en HDMI. C'est précisément ce que fait le RGBtoHDMI de David Banks [9] en couplant un CPLD Xilinx XC9572XL à une Raspberry Pi Zero. Remplacer une telle solution par un analyseur logique « discount » fonctionne, mais montre rapidement ses limites. Il est toutefois très satisfaisant de voir s'afficher une image en lieu et place de simples signaux dans PulseView et on imagine sans peine l'étendue des possibilités, car cela va bien au-delà du simple décodage de bus.

Références

[1] https://connect.ed-diamond.com/contenu-premium/analyser-des-signaux-logiques-avec-des-outils-100-open-source

[2] https://connect.ed-diamond.com/Hackable/hk-005/l-analyseur-logique-espionner-les-bus-en-toute-simplicite

[3] https://gitlab.gnome.org/GNOME/glib

[4] http://sigrok.org/wiki/Supported_hardware

[5] https://sigrok.org/api/libsigrok/0.5.2/index.html

[6] https://github.com/sigrokproject/libsigrok/tree/master/tests

[7] https://github.com/Cenkron/pulseview

[8] https://github.com/hoglet67/6502Decoder

[9] https://github.com/hoglet67/RGBtoHDMI

[10] https://sigrok.org/wiki/Probe_comparison



Article rédigé par

Par le(s) même(s) auteur(s)

Gestion des périphériques USB sous [Free|Net|Open]BSD

Magazine
Marque
GNU/Linux Magazine
Numéro
268
Mois de parution
mars 2024
Spécialité(s)
Résumé

Lorsqu'on fait connaissance avec l'un des systèmes héritiers du BSD d'origine, à savoir FreeBSD, NetBSD ou encore OpenBSD, tout en ayant une certaine expérience de GNU/Linux, il est relativement facile de retrouver ses petits. Certes, depuis quelques années, le système d'init et la gestion des services de GNU/Linux se sont drastiquement écartés des principes propres à la philosophie Unix, jusqu'alors respectés par GNU/Linux. Dans l'ensemble, la transition est relativement aisée, mais il y a cependant un point sur lequel les différences sont telles que quelques explications s'avèrent nécessaires : la gestion des périphériques « hotplug » (USB) et de leurs permissions.

Continuons notre exploration des Java Cards : jckit 3.0.4, NFC et code PIN

Magazine
Marque
Hackable
Numéro
53
Mois de parution
mars 2024
Spécialité(s)
Résumé

Dans un précédent article, nous avons découvert le monde des smartcards et des Java Cards en particulier, en prenant en main un modèle, certes toujours utilisable, mais relativement ancien (NXP J2A081). Il est temps aujourd'hui de nous mettre à jour, de goûter à davantage de modernité en nous penchant sur une version plus récente des spécifications et aussi d'explorer les aspects « sans contact » de cette formidable technologie. Depuis la dernière fois, une certaine expérience a été acquise et nous approfondirons également ce qu'il est possible de faire, autant avec des smartcards « anciennes » qu'avec des déclinaisons plus actuelles...

Hydrabus : un outil pour tous les bus

Magazine
Marque
Hackable
Numéro
53
Mois de parution
mars 2024
Spécialité(s)
Résumé

Vous connaissez la routine : un convertisseur USB/série, un analyseur logique, un petit bout de code sur un Arduino ou un ESP8266 pour vérifier un capteur i2c, une sonde JTAG, un programmeur de flash CH341A ou TL866A, un adaptateur CAN/USB, un programmeur SWD... Et forcément, le matériel qu'il vous faut n'est pas sous la main. Hydrabus est un projet open hardware et open source, cousin du Bus Pirate, qui règle ce problème. Un seul outil pour plein d'usages, un vrai couteau suisse des bus et interfaces en tous genres.

Les derniers articles Premiums

Les derniers articles Premium

Quarkus : applications Java pour conteneurs

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Initié par Red Hat, il y a quelques années le projet Quarkus a pris son envol et en est désormais à sa troisième version majeure. Il propose un cadre d’exécution pour une application de Java radicalement différente, où son exécution ultra optimisée en fait un parfait candidat pour le déploiement sur des conteneurs tels que ceux de Docker ou Podman. Quarkus va même encore plus loin, en permettant de transformer l’application Java en un exécutable natif ! Voici une rapide introduction, par la pratique, à cet incroyable framework, qui nous offrira l’opportunité d’illustrer également sa facilité de prise en main.

De la scytale au bit quantique : l’avenir de la cryptographie

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Imaginez un monde où nos données seraient aussi insaisissables que le célèbre chat de Schrödinger : à la fois sécurisées et non sécurisées jusqu'à ce qu'un cryptographe quantique décide d’y jeter un œil. Cet article nous emmène dans les méandres de la cryptographie quantique, où la physique quantique n'est pas seulement une affaire de laboratoires, mais la clé d'un futur numérique très sécurisé. Entre principes quantiques mystérieux, défis techniques, et applications pratiques, nous allons découvrir comment cette technologie s'apprête à encoder nos données dans une dimension où même les meilleurs cryptographes n’y pourraient rien faire.

Les nouvelles menaces liées à l’intelligence artificielle

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Sommes-nous proches de la singularité technologique ? Peu probable. Même si l’intelligence artificielle a fait un bond ces dernières années (elle est étudiée depuis des dizaines d’années), nous sommes loin d’en perdre le contrôle. Et pourtant, une partie de l’utilisation de l’intelligence artificielle échappe aux analystes. Eh oui ! Comme tout système, elle est utilisée par des acteurs malveillants essayant d’en tirer profit pécuniairement. Cet article met en exergue quelques-unes des applications de l’intelligence artificielle par des acteurs malveillants et décrit succinctement comment parer à leurs attaques.

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 64 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous