Pilote de moteur pas à pas pour votre ordinateur Linux

Gazette Linux n°122 — Janvier 2006

François Poulain

Adaptation française

Joëlle Cornavin

Relecture de la version française

Article paru dans le n°122 de la Gazette Linux de janvier 2006.

Article publié sous Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.


Table des matières

1. Introduction
2. Bases des moteurs pas à pas
3. Idées de matériel
4. Comment interfaçons-nous le matériel avec l'ordinateur Linux ?
5. Qu'est-ce qu'un module ?
6. Compilation des modules du noyau
7. Makefile (noyau 2.4.24)
8. Anatomie des pilotes de périphériques
9. Initialisation du pilote
10. Comment cela fonctionne-t-il ?
11. Opérations sur fichier
12. Conclusion

Cet article est destiné aux utilisateurs Linux qui souhaitent se servir de leur ordinateur Linux pour du travail concret. Je ferai également partager quelques expérimentations intéressantes que j'ai faites avec ma machine AMD.

1. Introduction

Apprendre de nouvelles choses est amusant mais peut être un peu frustrant. Ainsi, vous voulez écrire un pilote de périphérique. Le nom lui-même fait haute technologie ! Vous avez quelques compétences dans le langage de programmation C et souhaitez les étendre. De plus, vous avez écrit quelques programmes normaux à exécuter en tant que processus dans l'espace utilisateur, et maintenant vous voulez entrer dans l'espace noyau — où l'action proprement dite a lieu. Pourquoi des pilotes de périphériques Linux ? La réponse est :

  • Pour le plaisir ;

  • Pour le profit (Linux a le vent en poupe en ce moment, spécialement Linux embarqué) ;

  • Parce que vous pouvez !

Bien qu'il soit possible d'apprendre le codage des pilotes de périphériques en lisant quelques livres et documents PDF écrits par les maîtres, c'est une approche compliquée et exigeante en termes de temps. Nous choisirons l'approche facile et rapide, qui est la suivante :

  • Trouver du code préécrit, opérationnel ;

  • Comprendre comment fonctionne ce code ;

  • Le modifier pour qu'il corresponde à nos besoins.

Faisons un démarrage facile avec quelques fondamentaux.

2. Bases des moteurs pas à pas

Les moteurs pas à pas sont des moteurs à courant continu (DC, Direct Current) spéciaux, classiquement utilisés dans des applications comme le pilotage de zooms d'appareils photo et l'entraînement de film, télécopieurs, imprimantes, copieurs, dispositifs d'alimentation en papier et trieurs, lecteurs de disques et en robotique.

Un moteur pas à pas à courant continu transforme les impulsions de courant en rotation de moteur. Un moteur unipolaire (à une seule tension) typique contient quatre enroulements bobinés. Appliquer une tension à ces enroulements force le moteur à avancer d'un pas. En fonctionnement normal, deux bobines à enroulements sont activées en même temps, faisant se déplacer le moteur d'un pas horaire. Si la séquence est appliquée dans le sens inverse, le moteur tournera dans le sens anti-horaire. La vitesse de rotation est contrôlée par la fréquence des impulsions.

Un pas typique complet de rotation est 1.8 degré soit 200 pas par rotation (360 degrés). En changeant la période entre deux pas successifs, on peut réguler la vitesse du moteur et contrôler l'angle de rotation en comptant le nombre de pas.

Motif des bits en mode à pas entier
 VertBleuOrangeRougeValeur hexa de sortie
Pas 01010A
Pas 110019
Pas 201015
Pas 301106

3. Idées de matériel

Voici le schéma de câblage du moteur :

Le circuit se compose de quatre transistors de puissance TIP122 (T1, T2, T3 et T4), quatre résistances de 220 ohms (R1, R2, R3 et R4), quatre résistances de 3.3 kohms (R5, R6, R7 et R8), quatre diodes libres 1N4148 (D1, D2, D3 et D4) et un circuit tampon LM7407 (IC1). Le tampon 7407 utilisé ici est un sextuple tampon haute tension à collecteur ouvert. Les résistances 3.3 kohms sont les résistances de rappel vers le niveau haut (pull-up resistors) pour le tampon à collecteur ouvert. L'entrée pour ce tampon vient du port parallèle. La sortie du tampon a une capacité en courant supérieure à la sortie du port parallèle, qui est nécessaire pour déclencher le transistor ; il isole également le circuit du port parallèle du PC et par conséquent fournit une protection supplémentaire contre des tensions de réaction potentiellement dangereuses susceptibles de se produire en cas de dysfonctionnement du circuit. La diode connectée entre l'alimentation et le collecteur est utilisée en tant que diode libre et également pour protéger le transistor de la force contre-électromotrice (back EMF) de l'inductance moteur. Le moteur employé dans mon expérimentation (et documenté ici) est le STM 901 de Srijan Control Drives.

Pendant un fonctionnement normal, le motif de sortie du PC pilote le tampon et les transistors correspondants sont mis sous tension, ce qui provoque la conduction du courant à travers ces bobines du moteur pas à pas qui sont connectées au transistor alimenté et fait avancer le moteur d'un pas. L'impulsion suivante déclenchera une nouvelle combinaison de transistors, et par conséquent un nouvel ensemble de bobines, ce qui fera avancer le moteur d'un autre pas. Le schéma d'excitation que nous avons employé a déjà été présenté ci-dessus.

4. Comment interfaçons-nous le matériel avec l'ordinateur Linux ?

Pour ce faire, vous pouvez utiliser soit le port parallèle, soit le port série. Nous choisirons le port parallèle comme interface numérique entre le PC et le matériel (pilote de moteur pas à pas). Le port parallèle peut être considéré comme un registre, et les opérations d'E/S (entrée/sortie) réalisées simplement en écrivant la configuration binaire (des nombres comme 0xA, 10, 1010, etc.) dans ce registre. L'adresse de base du port parallèle est 0x378. Le port parallèle du PC est un connecteur femelle en forme de D à 25 broches situées à l'arrière de l'ordinateur. On l'emploie normalement pour connecter un ordinateur à une imprimante, mais beaucoup d'autres types de matériel pour ce port sont disponibles.

Le port parallèle originel de l'IBM PC® comportait un total de 12 sorties numériques et 5 entrées numériques accessibles via 3 ports consécutifs sur 8 bits dans l'espace d'E/S du processeur.

  1. 8 broches de sortie accessibles via le port des DONNÉES ;

  2. 5 broches d'entrée (une inversée) accessibles via le port d'ÉTAT ;

  3. 4 broches de sortie (trois inversées) accessibles via le port de COMMANDE ;

  4. Les 8 broches restantes sont reliées à la terre.

Pour lire un octet (8 bits) entrant dans un port, appelez inb(port) ; pour produire un octet, appelez outb(valeur, port) (notez l'ordre des paramètres). Les données en sortie sont fournies par un circuit intégré TTL (Transistor-Transistor Logic, logique transistor-transistor) totem-pôle 74LS374, qui peut procurer (+) 2,6 mA et absorber (-) 24 mA. La meilleure solution pour empêcher des dommages sur le port est d'utiliser des optocoupleurs  ainsi, le port est complètement isolé électriquement des périphériques matériels extérieurs.

5. Qu'est-ce qu'un module ?

Les modules sont des morceaux de code que l'on peut charger dans, et décharger depuis, un noyau en cours d'exécution, sur demande. Ils étendent la fonctionnalité du noyau sans qu'il soit nécessaire de redémarrer le système. Les pilotes de périphériques sont une classe de modules qui permet au noyau de commander le matériel connecté au système. Dans cet article, j'ai écrit un pilote de périphérique simple pour commander un pilote de moteur pas à pas. Il est maintenant temps d'ouvrir une session sur votre terminal et de commencer à coder votre tout premier module.

Démarrons de façon inhabituelle ! Vous avez écrit un certain nombre de programmes « Bonjour, monde ». Cette fois, les choses sont un peu différentes — « Pas de portes, pas de fenêtres, c'est ouvert ». Vous verrez ces mots écrits dans votre terminal quand vous insérerez votre premier module.


/* monModule.c - le module de noyau le plus simple. */
/* Pour compiler ce module, saisissez 'gcc -o monModule'

#include <linux/module.h> /* Nécessaire pour tous les modules */
#include <linux/kernel.h> /* Nécessaire pour KERN_ALERT       */

int init_module(void)
{
	printk("<1>Pas de portes, pas de fenêtres, c'est ouvert\n");
	return 0;	/* Un retour différent de zéro signifie que init_module a échoué ; */
			/* Le module ne peut être chargé				   */
}

void cleanup_module(void)
{
	printk("Au revoir\n");
}

La fonction de « démarrage » dans un module de noyau est init_module() qui est appelée quand le module est inséré (via la commande insmod) dans le noyau et la fonction de « fin » (nettoyage) cleanup_module() est appelé juste avant que le module ne soit déchargé (via la commande rmmod).

6. Compilation des modules du noyau

Les modules du noyau doivent être compilés avec certaines options de GCC pour pouvoir fonctionner. De plus, il faut également les compiler avec certains certains symboles définis. C'est parce que les fichiers d'en-tête du noyau doivent se comporter différemment, suivant que nous compilons un module du noyau ou un exécutable.

  1. -C : un module de noyau n'est pas un exécutable indépendant, mais un fichier objet qui sera lié au noyau au moment de l'exécution à l'aide de insmod.

  2. -O2 : comme le noyau fait un usage intensif des fonctions intégrées (inline), les modules doivent être compilés avec le drapeau d'optimisation positionné. Sans optimisation, certains des appels de macros en assembleur échoueront. Ceci entraînera l'échec du chargement du module car insmod ne trouvera pas ces fonctions dans le noyau.

  3. -D_KERNEL_ : définir ce symbole indique aux fichiers d'en-tête que le code sera exécuté en mode noyau, non en tant que processus utilisateur.

  4. -W -Wall : active tous les avertissements.

7. Makefile (noyau 2.4.24)

À titre d'exemple, jetons un coup d'œil aux options que nous allons employer lors de la compilation du module stepper étudié un peu plus loin dans cet article :

TARGET := stepper
WARN   := -W -Wall
INCLUDE:= /usr/src/linux-2.4/include
#INCLUDE:= -isystem /usr/src/`uname -r`/include
CFLAGS := -O2 -DMODULE -D__KERNEL__ ${WARN} -I${INCLUDE}
all : stepper.o
#${TARGET}.o : ${TARGET}.c
clean:
	rm -rf *.o

Vous pouvez en apprendre plus sur l'utilitaire make en saisissant man make.

8. Anatomie des pilotes de périphériques

Il y a beaucoup de documentation sur le Web, dont des PDF et des livres électroniques (ebooks) sur les pilotes de périphériques. De plus, vous pouvez télécharger quelques guides pratiques sur le site web du Linux Documentation Project. Pour l'instant, contentez-vous de lire ces aspects attentivement ; plus tard, vous pourrez passer à des références plus détaillées.

  • Un pilote de périphérique a deux côtés. Un côté communique avec le reste du noyau et le matériel, alors que l'autre dialogue avec l'utilisateur.

  • Pour communiquer avec le noyau, le pilote s'inscrit à des sous-systèmes pour répondre à des événements. Un tel événement pourrait être l'ouverture d'un fichier, l'écriture de quelques données utiles, le branchement à chaud d'une clé de mémoire USB, etc.

  • Du fait que Linux est de type UNIX et que sous UNIX « tout est fichier », les utilisateurs communiquent avec les pilotes de périphériques via des fichiers de périphériques, qui sont comme une représentation numérique d'un périphérique matériel.

  • Un moteur pas à pas est un périphérique caractère, et donc l'utilisateur communique avec ce dernier via un fichier de périphérique caractère. L'autre type courant de fichier de périphérique est le mode bloc. Dans cet article, nous ne nous intéresserons qu'aux fichiers de périphériques caractère.

  • L'utilisateur commande le moteur pas à pas au travers du fichier de périphérique /dev/stepper.

  • Quand l'utilisateur ouvre /dev/stepper, le noyau appelle la routine d'ouverture du pilote de moteur pas à pas.

  • Quand l'utilisateur ferme /dev/stepper, le noyau appelle la routine de libération du pilote de moteur pas à pas.

  • Quand l'utilisateur lit ou écrit depuis, ou dans /dev/stepper — je pense que vous avez saisi l'idée…

Examinons maintenant notre code.


#include <linux/module.h>
#include <asm/uaccess.h>
#include <asm-i386/io.h>
#include <linux/fs.h>

#define LPT_BASE 0x378
#define DEVICE_NAME "stepper"

static int Major,i,j,k;
static int Device_Open = 0;

static int pattern[2][8][8] = {
	{{0xA,0x9,0x5,0x6,0xA,0x9,0x5,0x6},{0xA,0x8,0x9,0x1,0x5,0x4,0x6,0x2}},
        {{0x6,0x5,0x9,0xA,0x6,0x5,0x9,0xA},{0x2,0x6,0x4,0x5,0x1,0x9,0x8,0xA}}
};

int step(void)
{
	if(k<8) {
//                      printk("%d\n",pattern[i][j][k]);
			outb(pattern[i][j][k],LPT_BASE);
			k++;
	}
        else  {
		k=0;
		outb(pattern[i][j][k],LPT_BASE);
//		printk("%d\n",pattern[i][j][k]); /*#####*/
                k++; /*#####*/
	}
        return 0;
}

static int stepper_open(struct inode *inode,struct file *filp)
{
	if(Device_Open) return -EBUSY;
	printk("Ouverture en mode Lecture/Écriture ...\n");
	Device_Open++;
	return 0;
}
	
static int stepper_release(struct inode *inode,struct file *filp)
{
	printk("Fermeture ...\n");
	Device_Open --;
	return 0;
}
static int stepper_write(struct file *file, const char *buffer, size_t len, loff_t *offset)
{
	char data;
        char cmd;
	get_user(data,buffer);
	switch (cmd=data) {
		case 'H': 
			printk("Reportez-vous au fichier README\n");
			break;
		case 'h':
			printk("Initialisation en mode demi-pas\n");
			j=0; 
			break;
		case 'f':
                        printk("Initialisation en mode pas entier\n");
			j=1;
			break;
		case 'F':
			i=0; 
			step();
			break;
		case 'R':
			i=1;
			step();
       	                break;
//		default:
//			printk("Reportez-vous au fichier README, les commandes sont H, h, F, f, et R\n");
//			break;
	}
	return 1;
}

static struct file_operations fops={
        open:stepper_open,
	write:stepper_write,
        release:stepper_release,
};

int init_module(void)
{
	Major = register_chrdev(0, DEVICE_NAME, &fops);
	if (Major < 0) {
		printk("<1>L'enregistrement du périphérique caractère avec %d a échoué\n",Major);
		return Major;
	}
	printk("<1>Enregistrement réussi, a obtenu le nombre majeur= %d\n",Major);
	return 0;
}

void cleanup_module(void)
{
	printk("<1>Non enregistré\n");
	unregister_chrdev(Major,DEVICE_NAME);
}

Suivez ce lien pour télécharger le fichier.

9. Initialisation du pilote

  1. La fonction init_module() est appelée lors de l'initialisation du pilote.

  2. La fonction cleanup_module() est appelée lorsque le pilote est supprimé du système.

  3. La fonction init() inscrira les crochets qui permettront d'appeler le code du pilote quand l'événenement approprié se produira.

  4. Il y a divers liens qui peuvent être inscrits : opérations sur fichiers, opérations sur bus PCI, opérations sur bus USB, opérations sur réseau. Ici, il s'agit d'une opération sur fichier.

  5. Le pilote enregistre un périphérique caractère lié à un nombre majeur et un utilisateur peut créer des points d'accès correspondant à ce nombre majeur. La commande suivante le fera pour vous :

    
    mknod /dev/stepper c 254 0
    
    

10. Comment cela fonctionne-t-il ?

Un programme de l'espace utilisateur peut écrire des commandes dans le fichier du périphérique pour faire tourner le moteur pas à pas selon un angle souhaité à une vitesse souhaitée. La vitesse de rotation dépend du délai précisé dans le programme utilisateur.

Les commandes intégrées permettant de commander le moteur pas à pas sont indiquées ci-dessous.

  • H : aide

  • h : mode demi-pas

  • f : mode pas entier

  • F : rotation d'un pas horaire

  • R : rotation d'un pas anti-horaire

11. Opérations sur fichier

Le pilote se sert des opérations sur le fichier de périphérique suivantes :

  1. Ouverture pour allouer des ressources ;

  2. Fermeture pour libérer les ressources ;

  3. Écriture du motif requis sur le port parallèle ;

  4. Il n'y a pas de lecture dans notre programme, mais si vous le souhaitez, vous pouvez lire le motif actuel sur le port parallèle.

Si vous écrivez F une seule fois dans /dev/stepper, le moteur tournera selon son angle de pas minimal. Si vous continuez à écrire F dans /dev/stepper, le moteur tournera en permanence. L'appel système write le fera pour vous.

#include "header.h"

main ()
{
        char t,buf[6] = {'h','f','F','R','H','q'};
        int fd,rt,i,j;
        size_t count;
        printf("Sélectionnez le mode \n(1) [Demi-pas horaire]\n(2) [Demi-pas anti-horaire]\n(3) [Pas entier horaire]\n(4) [Pas entier anti-horaire] :
");
        t=getchar();
        if(t=='1') {i=0; j=2;}
        else if(t=='2') {i=0; j=3;}
        else if(t=='3') {i=1; j=2;}
        else {i=1; j=3;}
        fd=open("stepper",O_WRONLY);
        rt=write(fd,&buf[i],count);
        for(i=0;i<1000;i++) {
                rt=write(fd,&buf[j],count);
                usleep (100000);
        }
        close(fd);
}

De plus, si vous connaissez bien l'écriture de scripts shell, vous pouvez faire de même en écrivant un script shell simple. À présent, vous pouvez commencer à dialoguer avec le périphérique par le biais du fichier /dev/stepper. Ce sera vraiment intéressant si vous communiquez dans le « langage » Linux — je veux parler d'un script shell. Il suffit d'utiliser des commandes echo simples comme indiqué ci-dessous :


echo H > /dev/stepper

12. Conclusion

J'espère vous avoir donné quelques notions de base sur le codage des pilotes de périphériques et peut être un « petit pas vers la robotique ». Voici un schéma détaillé d'un bras de robot commandé par moteur pas à pas. N'hésitez pas à l'essayer. On peut connecter trois moteurs pas à pas simultanément au port parallèle du PC et obtenir une mobilité par pas, ce qui permet à chacun de commencer à réfléchir à un système de mobilité innovant complexe, avec programmation multiprocessus (multi-threaded) sous Linux. Vous pouvez également ajouter des fonctions C à notre module pour améliorer sa fonctionnalité… Les possibilités sont infinies !

Je suis un fervent de Linux résidant en Inde. J'aime la liberté et la puissance qu'offre Linux. Je dois remercier mon mentor, M. Pramode C. E. pour m'avoir initié au monde merveilleux de Linux.

J'ai terminé mon B-Tech (équivalent de la maitrise) en Electrical and Electronics Engineering au Govt. Engineering College de Thrissur (Kerala, Inde) (de 2001 à 2005). En ce moment, je travaille chez inDSP Audio Technologies Pvt. Ltd à Trivandrum (Inde) en tant que Embedded Systems Engineer (ingénieur systèmes embarqués).

Je passe mon temps libre à lire des livres sur Linux et étendre mes compétences dans ce domaine. Mes autres centres d'intérêt comprennent les pilotes de périphériques, les systèmes embarqués, la robotique et la commande de processus.

Adaptation française de la Gazette Linux

L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.

Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://wiki.traduc.org/Gazette_Linux.

Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.