Systèmes informatiques
Fermer ×

Raspberry pi

Installation et configuration

La raspberry pi peut être considérée comme un système linux embarqué ou bien un ordinateur mono carte. Au fur et à mesure des années elle s'est déclinée en plusieurs versions du modèle 1 au modèle 4, sans oublier le modèle zéro.

Il n'y a pas que la framboise utilisée dans cette catégorie, on peut citer la banane ainsi que l'orange. Il existe aussi d'autres cartes similaires comme les cartes rock pi ou encore les cartes Urve pi.

Dans le cas présent, nous allons donner une nouvelle vie à la carte pi2, en installant la dernière version de raspi os en version 32 bits avec l'outil imager. Il est conseillé d'installer cette nouvelle version sur une nouvelle carte microSD de 16Go (8Go peut convenir) afin de ne pas détruire le système existant.
L'installation nécessite un écran HDMI ou tout autre type avec un adaptateur HDMI, un clavier USB, une souris USB, une connexion internet, qui est soit la connexion par câble ou bien par wifi avec une clé USB (la pi2 n'a pas de puce wifi).

La création d'une nouvelle carte SD, ainsi que la configuration de base du système est largement documenté sur le site de raspberry pi.

Après avoir terminé l'installation, on va configurer le système pour pouvoir écrire les programmes de gestion des périphériques. Cela se fait en gérant les préférences depuis l'interface graphique dans le menu préférences->Raspberry pi configuration ou encore en ligne de commande avec sudo raspi-config. Pour pouvoir utiliser notre système, il faudra activer ssh , spi, i2c, après avoir activer ces options, il ne faut pas oublier de les installer (une notification apparaît en haut de l'écran).

Il faut noter que la version 32 bits ne fonctionnera plus à partir du 19 janvier 2038.

Compilation et cross-compilation

Dans les deux cas il est conseillé d'organiser l'espace utilisateur sur la raspberry pi en créant un répertoire bin pour les programmes exécutables et un répertoire sh pour les scripts shell.

Compilation sur la raspberry pi

Si les outils de compilation (geany, make, gcc g++) ne sont pas installés, il faut les installer avec la commande :

sudo apt install geany make gcc g++			
		

Pour créer un programme en C, il faut suivre les exemples donné sur le langage C.

Cross-compilation sur l'ordinateur hôte linux

Sur une machine hôte linux, il faut installer les paquetages du cross-compilateur qui est :

Il est conseillé de se faire un dossier raspberry pour développer les projets avec, par exemple, l'arborescence suivante :

raspberry
+-- projets
|   +-- C
|   |   +-- hello
|   |       +-- hello.c
|   |       +-- Makefile
|   +-- shell
+-- include
+-- lib
		

projets contiendra les répertoires shell et C qui contient un répertoire par application comme le répertoire hello qui contient le fichier Makefile et le fichier source hello.c

include et lib contiendront les fichiers supplémentaires de définition et librairies supplémentaires pour les périphériques (GPIOs, SPI, I2C, ...)

Sur la raspberry, on crée un répertoire bin qui contiendra les programmes transférer, ainsi qu'un répertoire sh qui contiendra les scripts shell.

Exemple, avec le célèbre programme hello

Fichier Makefile

USER=utilisateurpi
IP=adresseipraspberrypi
CC=arm-linux-gnueabihf-gcc
CFLAGS= -Wall
LDFLAGS=
EXEC=hello
SRC= $(wildcard *.c)
OBJS= $(SRC:.c=.o)

all: $(EXEC)

$(EXEC): $(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)
	
%.o: %.c
	$(CC) -c -o $@ $< $(CFLAGS)

upload:
	scp $(EXEC) $(USER)@$(IP):bin

clean: 
	rm *.o $(EXEC)
				

Code source

#include <stdio.h>
#include <stdlib.h>


int main(int argc, char** argv) {
	
	printf("hello world\n");
	return EXIT_SUCCESS;
}
				

Il ne reste plus qu'à compiler et transférer le programme, avec la suite de commandes :

make
make upload
				

Puis se connecter avec la commande ssh pour exécuter le programme sur la cible.

Voir l'exemple de vérification de la taille de la variable time_t et du dépassement de la date

Fichier Makefile

USER=pi
IP=raspiw
CC=arm-linux-gnueabihf-gcc
CFLAGS= -Wall
LDFLAGS=
EXEC=time_t_test
SRC= $(wildcard *.c)
OBJS= $(SRC:.c=.o)

all: $(EXEC)

$(EXEC): $(OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)
	
%.o: %.c
	$(CC) -c -o $@ $< $(CFLAGS)

upload:
	scp $(EXEC) $(USER)@$(IP):bin

clean: 
	rm *.o $(EXEC)
					

Code source

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

int main(int argc, char **argv) {
	
	time_t temps;
	printf("taille temps %d (octets)\n",sizeof(time_t));
	temps = (1UL << 31) - 1  ;
	printf("temps %ld , date maximale : %s\n",temps, ctime(&temps));
	temps = (1UL << 31)  ;
	printf("temps %ld , date dépassée : %s\n",temps,ctime(&temps));
	return EXIT_SUCCESS;
}
					

Résultat affiché

taille time_t 4 (octets)
temps 2147483647 , date maximale : Tue Jan 19 04:14:07 2038

temps -2147483648 , date dépassée : Fri Dec 13 20:55:13 1901
					

On a bien dépassement à partir du 19 janvier 2038 à 4h14mn7s environ.

Gestion des GPIOs

Brochage et GPIOs utilisés

Chaque broche GPIO est identifiée avec un numéro nommé kernelID.

La position des GPIOs est donné sur la page de documentation du matériel de la raspberry pi.

Pour tous les exemples qui suivent, on utilise un port 8 bits qui utilise les GPIOs suivants

bit76543210
kernedID du GPIO26191365222717

Utilisation de l'accès utilisateur au système

Principe

Les GPIOs des systèmes linux embarqués sont accessibles depuis l’espace utilisateur dans le répertoire /sys/class/gpio , système qui est maintenant considéré comme obsolète. Pour les utiliser avec cette méthode il faut respecter les étapes suivantes :

  1. Enregistrer la broche avec son kernelID dans /sys/class/gpio/export
  2. Choisir la direction de la broche GPIO en écrivant "in" ou "out" dans direction
  3. Utiliser les GPIOs en écrivant les valeurs "1" ou "0" sur la broche GPIO correspondante ou bien lire la valeur de la broche GPIO correspondante depuis value
  4. Annuler l’enregistrement de la broche en fin d’utilisation afin de libérer la broche en écrivant le kernelID dans /sys/class/gpio/unexport

Exemple de script shell

Voir l'exemple d'utilisation d'un script shell

Cela se fait avec les commandes echo et cat, ${kernelid} contient le numéro du GPIO :

  1. echo ${kernelid} > /sys/class/gpio/export
  2. en entrée :
    echo in > /sys/class/gpio/gpio${kernelid}/direction
    en sortie :
    echo out > /sys/class/gpio/gpio${kernelid}/direction
  3. en entrée :
    cat /sys/class/gpio/gpio${kernelid}/value
    en sortie :
    echo 0 > /sys/class/gpio/gpio${kernelid}/value
    ou
    echo 1 > /sys/class/gpio/gpio${kernelid}/value
  4. echo ${kernelid} > /sys/class/gpio/unexport

Pour la syntaxe shell, voir le chapitre sur les scripts shell

Exemple avec le programme de test du port 8 bits

La fonction pinexport enregistre l'ensemble des GPIOs de la liste LISTEID qui correspond au tableau cité auparavant. Cela se fait GPIO par GPIO en vérifiant qu'il n'y a pas d'erreur. En cas d'erreur le message est enregistré dans le fichier testio.log.

La fonction piunexport libère chaque GPIO de la liste.

La fonction pindir affecte la même direction à tous les GPIOs de la liste

La fonction pinwrite affecte la même valeur à tous les GPIOs au rythme d'une broche par seconde.

Entre l'export, le choix de la direction ainsi que l'écriture, il est nécessaire d'avoir un délai d'une seconde afin de ne pas avoir d'erreur.

#!/bin/sh

LISTEID="26 19 13 6 5 22 27 17"

pinexport() {
 listepin=$@	
 for pin in $listepin
  do
   echo "export $pin"
   echo $pin 2>> testio.log > /sys/class/gpio/export
   if [ $? != 0 ]
    then
      echo "ERREUR probleme utilisation broche $pin"
   fi
 done	
}

pinunexport() {
 listepin=$@	
 for pin in $listepin
  do
   echo "unexport $pin"
   echo $pin 2>> testio.log > /sys/class/gpio/unexport
   if [ $? != 0 ]
    then
      echo "ERREUR probleme utilisation broche $pin"
   fi
 done	
}

pindir() {
 direction=$1
 shift	
 listepin=$@	
 for pin in $listepin
 do
  gpiopin="gpio${pin}"
  echo "$gpiopin direction $direction"
  echo $direction 2>> testio.log > /sys/class/gpio/${gpiopin}/direction
  if [ $? != 0 ]
    then
      echo "ERREUR probleme direction broche ${gpiopin}"
  fi
done	
}

pinwrite() {
 value=$1
 shift	
 listepin=$@	
 for pin in $listepin
  do
   gpiopin="gpio${pin}"
   echo $value 2>> testio.log  > /sys/class/gpio/${gpiopin}/value
   if [ $? != 0 ]
    then
      echo "ERREUR probleme ecriture broche ${gpiopin}"
   fi   
   sleep 1
 done	
}

pinexport $LISTEID
sleep 1
pindir "out" $LISTEID
sleep 1
echo "envoie 1 sur le PORT"
pinwrite 1 $LISTEID
echo "envoie 0 sur le PORT"
pinwrite 0 $LISTEID
pinunexport $LISTEID
					

Exemple de programme C

On peut modifier le Makefile de hello pour l'adapter à ce projet en changeant le nom du programme exécutable

Voir le codage de gestion d'un GPIO

Pour l'utilisation du C, voir le chapitre sur le langage C.

Dans ce cas on utilise les fonctions de gestion des fichiers : ouverture, lecture, écriture et fermeture. On va créer un fichier qui contient les fonctions de gestion des GPIOs

Fichier de définition gpios.h

#ifndef __GPIOS_H
#define __GPIOS_H


int exportgpio(int );
int unexportgpio(int );
int gpiodirection(int ,char *);
int gpiowrite(int ,int );


#endif
					

La gestion d'un GPIO se fait avec fopen, fprintf, puis fclose. En cas d'erreur, la fonction retourne 0 (faux).

Enregistrer le GPIO consiste à écrire le numéro (kernelid) sur /sys/class/gpio/export.

Libérer le GPIO consiste à écrire le numéro (kernelid) sur /sys/class/gpio/unexport.

Définir la direction consiste à écrire la chaîne "in" ou "out" sur /sys/class/gpio/gpiokernelid/direction, pour créer le nom complet on utilise la fonction sprintf qui permet de construire une chaîne formatée en insérant le numéro du kernelid.

Ecrire sur un GPIO en sortie consiste à écrire la valeur 0 ou 1 sur /sys/class/gpio/value.

Fichier de codage des fonctions gpios.c

#include <stdio.h>
#include "gpios.h"

int exportgpio(int kernelID) {
	FILE *fd;
	fd=fopen("/sys/class/gpio/export","w");	
	if (fd != NULL) {
		fprintf(fd,"%d\n",kernelID);
		fclose(fd);
		return 1;
	}	
	else {
		return 0;
	}
}

int unexportgpio(int kernelID) {
	FILE *fd;
	fd=fopen("/sys/class/gpio/unexport","w");	
	if (fd != NULL) {
		fprintf(fd,"%d\n",kernelID);
		fclose(fd);
		return 1;
	}	
	else {
		return 0;
	}
}

int gpiodirection(int kernelID,char *dir) {
	FILE *fd;
	char nom[50];
	sprintf(nom,"/sys/class/gpio/gpio%d/direction",kernelID);
	fd=fopen(nom,"w");	
	if (fd != NULL) {
		fprintf(fd,"%s\n",dir);
		fclose(fd);
		return 1;
	}	
	else {
		printf("Erreur dir\n");
		return 0;
	}
}

int gpiowrite(int kernelID,int value) {
	FILE *fd;
	char nom[50];
	sprintf(nom,"/sys/class/gpio/gpio%d/value",kernelID);
	fd=fopen(nom,"w");	
	if (fd != NULL) {
		fprintf(fd,"%d\n",value);
		fclose(fd);
		return 1;
	}	
	else {
		return 0;
	}
}
					
Voir le codage de gestion d'un port 8 bits

Puis un fichier qui contient les fonctions du port 8 bits.

Fichier de définition port.h

#ifndef __PORT_H
#define __PORT_H

int portexport(void);
int portunexport(void);
int portdirection(char *);
int portwrite(unsigned char ); 

#endif
					

La liste des kerbelid des GPIOs associés au port 8 bits est déclarée dans un tableau d'entiers.

Chaque fonction de gestion du port fait appel à la fonction de gestion d'un GPIO à l'intérieur d'une boucle qui se termine sur la première erreur rencontrée.

Fichier de codage des fonctions port.c

#include "gpios.h"
#include "port.h"

unsigned port[8] = { 17 , 27 , 22 , 5 , 6 , 13 , 19 , 26};

int portexport() {
	int retour=1;
	for(int i=0;(i<8)&&(retour!=0);i+=1) {
		retour=exportgpio(port[i]);
	}
	return retour;
}
       
int portunexport() {
	int retour=1;
	for(int i=0;(i<8)&&(retour!=0);i+=1) {
		retour=unexportgpio(port[i]);
	}
	return retour;
}

int portdirection(char *dir) {
	int retour=1;
	for(int i=0;(i<8)&&(retour!=0);i+=1) {
		retour=gpiodirection(port[i],dir);
	}
	return retour;
}
       
int portwrite(unsigned char value) {
	int retour=1;
	int bitvalue;
	for(int i=0;(i<8)&&(retour!=0);i+=1) {
		bitvalue= (value >> i) & 1;
		retour=gpiowrite(port[i],bitvalue);
	}
	return retour;
}
					
Voir le codage du programme de test

Programme de test

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "port.h"


int main(int argc, char** argv) {
	
	int err;
	unsigned char valeur=0x55;
	
	if (argc == 2) {
		valeur = strtol(argv[1],NULL,0);
	}
	err=portexport();
	if (!err) {
		fprintf(stderr,"Erreur export\n");
		return EXIT_FAILURE;
	}
	usleep(500000L);
	err=portdirection("out");
	if (!err) {
		fprintf(stderr,"Erreur direction\n");
		return EXIT_FAILURE;
	}
	err=portwrite(valeur);
	if (!err) {
		fprintf(stderr,"Erreur write\n");
		return EXIT_FAILURE;
	}
	err=portunexport();
	if (!err) {
		fprintf(stderr,"Erreur unexport\n");
		return EXIT_FAILURE;
	}		
	return EXIT_SUCCESS;
}
					

Le programme de test permet d'afficher sur le port 8 bits une valeur transmise en décimal ou hexadécimal (préfixée 0x). Cette valeur, transmise sous la forme d'une chaîne de caractères, est convertie en entier avec la fonction strtol.

Après la conversion de cette valeur le programme réalise les étapes de gestion des GPIOs :

  1. enregistrement du port
  2. direction du port définie en sortie
  3. écriture de chaque bit de la valeur entière sur les GPIOs
  4. libération du port

A chaque étape, le programme se termine en cas d'erreur.

Utilisation de l'ancienne librairie WiringPi

Installation sur la raspberry

La librairie WiringPi offre des fonctions de gestion des GPIOs, elle est maintenant considérée comme obsolète, mais est toujours disponible.

Il faut télécharger les codes sources avec, par exemple, la commande :

git clone https://github.com/WiringPi/WiringPi.git

puis suivre les instructions d'installation décrites dans le fichier INSTALL

Cross-compilation

Il faut copier les fichiers nécessaires à la compilation sur l'ordinateur hôte. Sur la raspberry, on prépare l'archive des fichiers nécessaires à la compilation avec les commandes :

mkdir -p wiringpi/include
mkdir wiringpi/lib
cd wiringpi
cp /usr/local/include/wiring* include
cp -P /usr/local/lib/libwiringPi* lib
cp -P /lib/arm-linux-gnueabihf/libcrypt* lib
tar Jcvf wiringdev.tar.xz include lib
		

Sur l'ordinateur hôte, on transfert l'archive créée sur la raspberry avec la commande :

scp utilisateur@adresseip:wiringpi/wiringdev.tar.xz .

Puis on la décompresse dans le dossier raspberry avec la commande :

tar Jxvf wiringdev.tar.xz

Corriger éventuellement les liens symboliques qui pointeraient vers des chemins absolus en les faisant pointer vers le fichier local.

Maintenant on peut modifier le fichier Makefile du projet hello, en changeant le nom du programme exécutable ainsi que les variables CFLAGS et LDFLAGS.

CFLAGS= -Wall -I$(HOME)/raspberry/include
LDFLAGS=-L$(HOME)/raspberry/lib -lwiringPi -lcrypt
		

Utilisation en ligne de commande

Il s'agit d'utiliser la commande gpio, cette commande possède le mode d'exécution root (bit s activé). Cela vient du fait que certains descripteur ne sont pas accessibles par l'utilisateur. Normalement les applications ne doivent pas fonctionner en mode root, c'est le défaut du système WiringPi.

La commande gpio readall affiche le tableau des gpios avec les id des gpios et les id en mode wiring qui sont différents.

Voir le résultat de gpio readall
 +-----+-----+---------+------+---+---Pi 2---+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 | ALT0 | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 | ALT0 | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |  OUT | 0 |  7 || 8  | 1 | ALT0 | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT0 | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |  OUT | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |  OUT | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |  OUT | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI | ALT0 | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO | ALT0 | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK | ALT0 | 0 | 23 || 24 | 1 | OUT  | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | OUT  | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |  OUT | 0 | 31 || 32 | 0 | OUT  | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |  OUT | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |  OUT | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |  OUT | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 2---+---+------+---------+-----+-----+			
					

Le mode ALT0 est le deuxième mode de fonctionnement de la broche. BCM correspond au kernelid GPIO, et wPi à l'id wiringpi.

Pour accéder à un GPIO, il faut effectuer les étapes :

  1. Choisir la direction entrée ou sortie avec la commande
    gpio -g mode kernelID direction
  2. Ecrire sur un GPIO en sortie avec la commande
    gpio -g write kernelID valeur
    ou lire la valeur d'un GPIO en entrée avec la commande
    gpio -g read kernelID

gpio -h permet d'obtenir l'ensemble des possibilités de cette commande

Exemple de script shell

Voir l'exemple d'utilisation d'un script shell
#!/bin/sh

LISTEID="26 19 13 6 5 22 27 17"

pindir() {
	direction=$1
	shift	
	listepin=$@	
	for pin in $listepin
	do
		echo "gpio $pin direction $direction"
		gpio -g mode ${pin} ${direction}
	done	
}

pinwrite() {
	value=$1
	shift	
	listepin=$@
	for pin in $listepin
	 do
		gpio -g write ${pin} $value
		sleep 1
	done
}

pindir "OUT" $LISTEID
echo "envoie 1 sur le PORT"
pinwrite 1 $LISTEID
echo "envoie 0 sur le PORT"
pinwrite 0 $LISTEID
					

Le script shell utilise la commande gpio qui permet de définir la direction et d'écrire et lire les valeurs des GPIOs

L'option -g précise que l'on utilise le kernelid GPIO.

L'option mode spécifie le choix de la direction "IN" ou "OUT"

L'option write est utilisée pour écrire une valeur 1 ou 0 sur la sortie.

Utilisation en C

Wiringpi fournit un ensemble de fonction C pour accéder aux GPIOs.

Exemple de programme C

Voir le codage des fonctions de gestions des gpios

Fichier de définition wgpios.h

#ifndef __WGPIOS_H
#define __WGPIOS_H

int portdirection(unsigned int);
int portwrite(unsigned char);

#endif
					

La fonction portdirection initialise chaque broche GPIO du tableau en entrée ou en sortie

La fonction portwrite extrait chque bit de l'octet pour l'écrire sur le GPIO correspondant.

Fichier de codage des fonctions wgpios.c

#include <wiringPi.h>
#include "wgpios.h"

unsigned port[8] = { 17 , 27 , 22 , 5 , 6 , 13 , 19 , 26};

int portdirection(unsigned int direction) {
	int retour=1;
	for(int i=0;(i<8)&&(retour!=0);i+=1) {
		pinMode(port[i],direction);
	}
	return retour;
}

int portwrite(unsigned char value) {
	int retour=1;
	int bitvalue;
	for(int i=0;(i<8)&&(retour!=0);i+=1) {
		bitvalue= (value >> i) & 1;
		digitalWrite(port[i],bitvalue);
	}
	return retour;
}

					
Voir le codage du programme de test

Programme de test

#include <stdlib.h>
#include <wiringPi.h>
#include "wgpios.h"

int main(int argc, char** argv) {
	
	unsigned char valeur=0x55;
	
	if (argc == 2) {
		valeur = strtol(argv[1],NULL,0);
	}
	wiringPiSetupGpio();
	portdirection(1);
	portwrite(valeur);		
	return EXIT_SUCCESS;
}

					

Gestion des GPIOs avec gpiod

Installation

Le système gpiod, décrit sur le site de C. Blaess, est une nouvelle méthode d'accès aux GPIOs.

Sur la pi2, ce système est représenté par le descripteur /dev/gpiochip0.

L'installation sur la raspberry se fait avec la commande :

sudo apt install gpiod libgpiod-dev

Avec libgpiod-dev qui est utilisé pour la compilation en C de programmes qui utilisent libgpiod.

Cette installation fournit un ensemble de commandes qui sont : gpiodetect gpiofind gpioinfo gpioset gpioset gpiomon.

En C l'utilisation se fait avec deux méthodes différentes :

Pour la première méthode, il faut simplement modifier le nom du programme exécutable dans le fichier Makefile.

Pour la deuxième méthode, il faut copier les fichiers nécessaires à la compilation sur l'ordinateur hôte. Sur la raspberry, on prépare l'archive des fichiers nécessaires à la compilation avec les commandes :

mkdir -p libgpiod/include
mkdir -p libgpiod/lib
cd libgpiod
cp -P /usr/include/gpiod.h* include
cp -P /usr/lib/arm-linux-gnueabihf/libgpio* lib
tar Jcvf libdpiod.tar.xz include lib
		

Sur l'ordinateur hôte, on transfert l'archive créée sur la raspberry, puis on la décompresse dans le dossier raspberry avec la commande :

tar Jxvf libdpiod.tar.xz

Il faut modifier le nom du fichier exécutable du fichier Makefile ainsi que le contenu des variables CFLAGS et LDFLAGS

CFLAGS= -Wall -I$(HOME)/raspberry/include
LDFLAGS=-L$(HOME)/raspberry/lib -lgpiod
		

Utilisation en ligne de commande

La commande gpioinfo fournit le tableau des gpios, cette fois-ci, on ne parle plus d'id mais de numéro de ligne.

Voir le résultat de gpioinfo
gpiochip0 - 54 lines:	
	line   0:     "ID_SDA"       unused   input  active-high 
	line   1:     "ID_SCL"       unused   input  active-high 
	line   2:       "SDA1"       unused   input  active-high 
	line   3:       "SCL1"       unused   input  active-high 
	line   4:  "GPIO_GCLK"       unused  output  active-high 
	line   5:      "GPIO5"       unused   input  active-high 
	line   6:      "GPIO6"       unused  output  active-high 
	line   7:  "SPI_CE1_N"   "spi0 CS1"  output   active-low [used]
	line   8:  "SPI_CE0_N"   "spi0 CS0"  output   active-low [used]
	line   9:   "SPI_MISO"       unused   input  active-high 
	line  10:   "SPI_MOSI"       unused   input  active-high 
	line  11:   "SPI_SCLK"       unused   input  active-high 
	line  12:     "GPIO12"       unused   input  active-high 
	line  13:     "GPIO13"       unused  output  active-high 
	line  14:       "TXD0"       unused   input  active-high 
	line  15:       "RXD0"       unused   input  active-high 
	line  16:     "GPIO16"       unused   input  active-high 
	line  17:     "GPIO17"       unused  output  active-high 
	line  18:     "GPIO18"       unused   input  active-high 
	line  19:     "GPIO19"       unused  output  active-high 
	line  20:     "GPIO20"       unused   input  active-high 
	line  21:     "GPIO21"       unused   input  active-high 
	line  22:     "GPIO22"       unused  output  active-high 
	line  23:     "GPIO23"       unused   input  active-high 
	line  24:     "GPIO24"       unused   input  active-high 
	line  25:     "GPIO25"       unused   input  active-high 
	line  26:     "GPIO26"       unused  output  active-high 
	line  27:     "GPIO27"       unused  output  active-high 
	line  28:       "SDA0"       unused   input  active-high 
	line  29:       "SCL0"       unused  output  active-high 
	line  30:         "NC"       unused   input  active-high 
	line  31:    "LAN_RUN"       unused  output  active-high 
	line  32:  "CAM_GPIO1"       unused  output  active-high 
	line  33:         "NC"       unused   input  active-high 
	line  34:         "NC"       unused   input  active-high 
	line  35:  "PWR_LOW_N"        "PWR"   input  active-high [used]
	line  36:         "NC"       unused   input  active-high 
	line  37:         "NC"       unused   input  active-high 
	line  38:  "USB_LIMIT"       unused  output  active-high 
	line  39:         "NC"       unused   input  active-high 
	line  40:   "PWM0_OUT"       unused   input  active-high 
	line  41:  "CAM_GPIO0" "cam1_regulator" output active-high [used]
	line  42:   "SMPS_SCL"       unused  output  active-high 
	line  43:   "SMPS_SDA"       unused   input  active-high 
	line  44:    "ETH_CLK"       unused   input  active-high 
	line  45:   "PWM1_OUT"       unused   input  active-high 
	line  46: "HDMI_HPD_N"        "hpd"   input   active-low [used]
	line  47: "STATUS_LED"        "ACT"  output  active-high [used]
	line  48:   "SD_CLK_R"       unused   input  active-high 
	line  49:   "SD_CMD_R"       unused   input  active-high 
	line  50: "SD_DATA0_R"       unused   input  active-high 
	line  51: "SD_DATA1_R"       unused   input  active-high 
	line  52: "SD_DATA2_R"       unused   input  active-high 
	line  53: "SD_DATA3_R"       unused   input  active-high 
					

Ecriture avec gpioset et lecture avec gpioget :

Exemple de script shell

Voir l'exemple d'utilisation d'un script shell
LISTEID="26 19 13 6 5 22 27 17"

pinwrite() {
	value=$1
	shift	
	listepin=$@
	for pin in $listepin
	 do
		gpioset gpiochip0 ${pin}=$value
		sleep 1
	done
}

echo "envoie 1 sur le PORT"
pinwrite 1 $LISTEID
echo "envoie 0 sur le PORT"
pinwrite 0 $LISTEID
					

L'écriture sur les GPIOs se fait directement avec la commande gpioset qui effectue l'enregistrement, la direction, l'écriture et la libération. Le premier paramètre est le nom du descripteur (ici gpiochip0) suivi du numéro de ligne avec la valeur. Dans le cas présent le numéro de ligne correspond au numéro de GPIO.

On aurait pu construire une chaîne complète de la ligne de commande afin d'appeler une seule fois la commande gpioset.

Utilisation en C avec le descripteur /dev/gpiochip0

L'accès aux GPIOs, en utilisant le descripteur, se fait avec la fonction ioctl, l'action dépend du mot de commande et de la structure de données utilisée.

Définir la direction

Commande : GPIO_GET_LINEHANDLE_IOCTL et structure gpiohandle_request

Les champs de cette structure permettent de choisir le nombre de GPIOs, définir les lignes GPIOs ainsi que la direction de ces lignes :

Lecture

Commande : GPIOHANDLE_GET_LINE_VALUES_IOCTL et la structure gpiohandle_data

Le champ values qui est un tableau qui contient l'ensemble des valeurs des GPIOs qui valent 0 ou 1

Ecriture

Commande : GPIOHANDLE_SET_LINE_VALUES_IOCTL et la structure gpiohandle_data

Le champ values qui est un tableau doit contenir l'ensemble des valeurs à écrire qui valent 0 ou 1

Exemple de programme C avec le descripteur /dev/gpiochip0

Voir le codage des fonctions de gestions des gpios en utilisant le descripteur

Fichier de définition cgpios.h

#ifndef __CGPIOS_H
#define __CGPIOS_H

#include <linux/gpio.h>

int opengpio(void);
void portwrite(unsigned char );
void closegpio(void); 

#endif
					

Les fonctions opengpio sur le descripteur /dev/gpiochip0 réalise l'ouverture suivie du paramétrage des 8 GPIOs en sortie, en utilisant ioctl et la commande GPIOHANDLE_REQUEST_INPUT après avoir défini la direction ainsi que les numéros des lignes des GPIos utilisés. La structure de données contient, dans le champ fd, l'identifiant du périphérique correspondant aux GPIOs qui sera utilisé par la fonction ioctl pour l'accès aux GPIOs.

La fonction memset permet d'initialiser tous les champs à 0 avant de compléter uniquement les champs utilisés.

La fonction portwrite écrit les bits de l'octet transmis en utilisant ioctl avec la commande GPIOHANDLE_SET_LINE_VALUES_IOCTL sur le descripteur fourni par la commande de définition des directions. La structure de donnée doit être initialisée avant l'appel à ioctl.

Fichier de codage des fonctions cgpios.c

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include "cgpios.h"

int fd;
struct gpiohandle_request port_request;
unsigned port[8] = { 17 , 27 , 22 , 5 , 6 , 13 , 19 , 26};

int opengpio() {
	fd = open("/dev/gpiochip0", O_RDONLY);
	if (fd < 0) {
		return fd;
	}
	memset(&port_request, 0, sizeof(struct gpiohandle_request));
	for(int i=0;i<8;i+=1) {
		port_request.lineoffsets[i] = port[i];
	}
	port_request.flags = GPIOHANDLE_REQUEST_OUTPUT;
	port_request.lines = 8;
	ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &port_request);		
	return fd;
}

void portwrite(unsigned char value) {
	struct gpiohandle_data output_values;
	for(int i=0;i<8;i+=1) {
		int bitvalue= (value >> i) & 1;
		output_values.values[i] = bitvalue;
	}
    ioctl(port_request.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &output_values);
}

void closegpio() {
	close(fd);
}
					
Voir le codage du programme de test

Programme de test

#include <stdio.h>
#include <stdlib.h>
#include "cgpios.h"

int main(int argc, char** argv) {
	
	unsigned char valeur=0x55;
	if (argc == 2) {
		valeur = strtol(argv[1],NULL,0);
	}	
	opengpio();
	portwrite(valeur);
	closegpio();
	return EXIT_SUCCESS;
}
					

Utilisation en C avec les fonctions de la librairie libgpiod

Cette méthode fait appel à l'API libgpiod qui fournit les fonctions d'accès aux GPIOs.

On commence par accéder au circuit avec les fonctions :

Ensuite on réserve les lignes que l'on veut utiliser, on obtient une structure de données, gpiod_line_bulk, qui contient l'ensemble des lignes.

On utilise cette structure de données pour définir la direction des lignes avec la fonction gpiod_line_request_bulk_output pour des lignes en sortie et la fonction gpiod_line_request_bulk_input pour des lignes en entrée

Il ne reste plus qu'à utiliser ces lignes en lecture avec gpiod_line_get_value_bulk ou en écriture avec gpiod_line_set_value_bulk.

Il ne faut pas oublier de libérer les lignes avec la fonction gpiod_line_release_bulk avant de fermer le descripteur.

Exemple de programme C avec la librairie libgpiod

Voir le codage des fonctions de gestions des gpios en utilisant les fonctions de la librairie

Fichier de définition lgpios.h

#ifndef __LGPIOS_H
#define __LGPIOS_H

int opengpio(char *);
void portwrite(unsigned char ); 
void closegpio(void);

#endif
					

Le paramètre de la fonction opengpio correspond au nom du consommateur transmis à la fonction de définition de la direction.

Pour utiliser un port 8 bits, on fait appel aux fonctions *bulk qui permettent de traiter un ensemble de lignes.

Si la fonction opengpio retourne 0, alors tout s'est bien passé.

La fonction portwrite utilise également une fonction *bulk, mais l'ensemble des valeurs utilise un tableau de valeurs. Il faut convertir la valeur entière en tableau de valeurs binaires 0 ou 1.

Enfin il ne faut pas oublier de libérer les lignes avant de fermer le circuit.

Fichier de codage des fonctions lgpios.c

#include <stdio.h>
#include "gpiod.h"
#include "lgpios.h"

struct gpiod_chip *circuit;
struct gpiod_line_bulk lignes;

unsigned port[8] = { 17 , 27 , 22 , 5 , 6 , 13 , 19 , 26};

int opengpio(char *nom) {
	circuit= gpiod_chip_open("/dev/gpiochip0");
	if (circuit == NULL) {
		return -1;
	}
	gpiod_line_bulk_init(&lignes);
	int erreur = gpiod_chip_get_lines(circuit,port,8,&lignes);
	if (erreur != 0) {
		return -2 ;
	}
	erreur = gpiod_line_request_bulk_output(&lignes,nom,0);
	if (erreur != 0) {
		return -3 ;
	}
	return 0;
}

void portwrite(unsigned char value) {
	int tabval[8] ;
	for(int i=0;i<8;i+=1) {
		tabval[i]= (value >> i) & 1;
	}
	gpiod_line_set_value_bulk(&lignes,tabval);

}

void closegpio() {
	gpiod_line_release_bulk(&lignes);
	gpiod_chip_close(circuit);
}
					
Voir le codage du programme de test

Programme de test

#include <stdio.h>
#include <stdlib.h>
#include "lgpios.h"

int main(int argc, char** argv) {

	unsigned char valeur=0x55;

	if (argc == 2) {
		valeur = strtol(argv[1],NULL,0);
	}	
	int res = opengpio(argv[0]);
	if (res != 0) {
		fprintf(stderr,"ERREUR ouverture : %d\n",res);
		return EXIT_FAILURE;
	}
	portwrite(valeur);	
	closegpio();
	return EXIT_SUCCESS;
}
					

Le programme transmets le nom du programme comme nom de consommateur.

Si l'ouverture ne se déroule pas correctement, le programme s'arrête.

Gestion des GPIOs avec pigpiod

Installation

Le système pigpio est une autre méthode d'accès aux GPIOs.

Il y a également deux méthodes d'utilisation de ce système :

Pour la méthode client/serveur, il est nécessaire de démarrer le serveur avec la commande :

sudo systemctl start pigpiod

On peut arrêter le serveur avec la commande :

sudo systemctl stop pigpiod

Dans le cas d'une utilisation permanente, il est possible de démarrer le serveur au démarrage de la raspberry avec la commande :

sudo systemctl enable pigpiod

Service qui peut être désactivé avec la commande :

sudo systemctl disable pigpiod

Pour les deux méthodes d'utilisation de ce système, il faut compléter la liste des fichiers de définitions et librairies sur l'ordinateur hôte

Créer l'archive sur la raspberry :

mkdir -p pigpio/include
mkdir pigpio/lib
cd pigpio
cp /usr/include/pigpio* include
cp -P  /usr/lib/libpigpio* lib
tar Jcvf pigpio.tar.xz include lib
		

Sur l'ordinateur hôte, on transfert l'archive créée sur la raspberry, puis on la décompresse dans le dossier raspberry avec la commande :

tar Jxvf pigpio.tar.xz

Pour utiliser la librairie libgpio, il faut modifier le nom du fichier exécutable du fichier Makefile ainsi que le contenu des variables CFLAGS et LDFLAGS

CFLAGS= -Wall -I$(HOME)/raspberry/include
LDFLAGS=-L$(HOME)/raspberry/lib -lpigpio
		

Pour utiliser le système client/serveur, il faut modifier le nom du fichier exécutable du fichier Makefile ainsi que le contenu des variables CFLAGS et LDFLAGS

CFLAGS= -Wall -I$(HOME)/raspberry/include 
LDFLAGS= -L$(HOME)/raspberry/lib -lpigpiod_if2
		

Utilisation en ligne de commande

Ecriture et lecture avec pigs :

En ligne de commande, il faut utiliser la commande pigs qui utilise la méthode client/serveur.

Exemple de script shell

Voir l'exemple d'utilisation d'un script shell
#!/bin/sh

LISTEID="26 19 13 6 5 22 27 17"

pinwrite() {
	value=$1
	shift	
	listepin=$@	
	for pin in $listepin
	do
		pigs w ${pin} ${value}
		sleep 1
	done
}

echo "envoie 1 sur le PORT"
pinwrite 1 $LISTEID
echo "envoie 0 sur le PORT"
pinwrite 0 $LISTEID
					

L'option w permet d'affecter une valeur 0 ou 1 à un GPIO de numéro kernelID.

Utilisation en C avec libpigpio

Cette méthode utilise l'API pigpio

L'accès au GPIOs se fait avec les étapes :

  1. Initialiser le système de GPIOs avec la fonction gpioInitialise
  2. Définir la direction des GPIOs avec la fonction gpioSetMode
  3. Ecrire une valeur avec la fonction gpioWrite
    ou Lire la valeur avec la fonction gpioRead
  4. Terminer le système de GPIOs avec la fonction gpioTerminate

Le programme réalisé doit être exécuté en root ou en mode utilisateur avec sudo

Exemple de programme C avec libpigpio

Voir le codage des fonctions de gestions des gpios

Fichier de définition pigpios.h

#ifndef __PIGPIOS_H
#define __PIGPIOS_H

int opengpio(void);
int portwrite(unsigned char ); 
void closegpio(void);

#endif
					

La fonction opengpio s'arrête en cas d'erreur d'initialisation et à la première erreur de paramétrage de direction.

La fonction portwrite d'arrête à la première erreur d'écriture.

La fonction closegpio libère les GPIOs

Ces fonctions retournent 0 si tout s'est bien passé.

Fichier de codage des fonctions pigpios.c

#include <stdio.h>
#include "pigpio.h"
#include "pigpios.h"

unsigned port[8] = { 17 , 27 , 22 , 5 , 6 , 13 , 19 , 26};

int opengpio() {
	int retour = gpioInitialise();
	if (retour < 0) {
		return retour;
	}
	for(int i=0;(i<8)&&(retour>=0);i+=1) {
		retour=gpioSetMode(port[i], PI_OUTPUT);
	}
	return retour;
}

int portwrite(unsigned char value) {
	int retour=0;
	for(int i=0;(i<8)&&(retour>=0);i+=1) {
		int bitvalue= (value >> i) & 1;
		retour=gpioWrite(port[i],bitvalue);
	}
	return retour;
}

void closegpio() {
	gpioTerminate();
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include "pigpios.h"

int main(int argc, char** argv) {
	int res;
	unsigned char valeur=0x55;
	if (argc == 2) {
		valeur = strtol(argv[1],NULL,0);
	}	
	res = opengpio();
	if (res == -1) {
		fprintf(stderr,"ERREUR ouverture : %d\n",res);
		return EXIT_FAILURE;
	}
	portwrite(valeur);	
	closegpio();
	return EXIT_SUCCESS;
}
					

En cas d'erreur lors de l'ouverture ou bien lors de l'initialisation de la direction, le programme s'arrête.

Utilisation en C avec la méthode client/serveur

Cette méthode utilise l'API pipigpiod_if2

L'accès au serveur des GPIOs se fait au travers d'une connexion réseau qui peut être locale ou distante.

Les différentes étapes sont :

  1. Etablir la connexion réseau avec la fonction pigpio_start
  2. Définir la direction des GPIos avec la fonction set_mode
  3. Ecrire des valeurs sur les GPIOS avec la fonction gpio_write
    ou Lire les valeurs de GPIOs avec la fonction gpio_read
  4. Libérer les GPIOs et terminer la connexion réseau avec la fonction pigpio_stop

Exemple de programme C avec la méthode client/serveur

Voir le codage des fonctions de gestions des gpios

Fichier de définition pigpios.h

#ifndef __PIGPIOSD_H
#define __PIGPIOSD_H

int opengpio(void);
int portwrite(unsigned char ); 
void closegpio(void);

#endif
					

La fonction opengpio établit la connexion réseau avec pigpio_start et retourne un identificateur de connexion qui sera utilisé par toutes les autres fonctions ou -1 en cas d'erreur. Cette fonction définit également les GPIos en sortie.

La fonction portwrite utilise la fonction pigpio_write qui écrit les valeurs sur les GPIOs et retourne une valeur négative en cas d'erreur qui termine la boucle à la première erreur.

La fonction closegpio utilise la fonction pigpio_stop pour libérer les GPIOs et terminer la connexion réseau.

Fichier de codage des fonctions pigpios.c

#include <stdio.h>
#include "pigpiod_if2.h"
#include "pigpiosd.h"

int fdpio;

unsigned port[8] = { 17 , 27 , 22 , 5 , 6 , 13 , 19 , 26};

int opengpio() {
	int retour=0;
	fdpio = pigpio_start("localhost", "8888");
	if (fdpio < 0) {
		return fdpio;
	}
	for(int i=0;(i<8)&&(retour>=0);i+=1) {
		retour=set_mode(fdpio,port[i], PI_OUTPUT);
	}
	return retour;
}

int portwrite(unsigned char value) {
	int retour=0;
	for(int i=0;(i<8)&&(retour>=0);i+=1) {
		int bitvalue= (value >> i) & 1;
		retour=gpio_write(fdpio,port[i],bitvalue);
	}
	return retour;
}

void closegpio() {
	pigpio_stop(fdpio);
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include "pigpiosd.h"

int main(int argc, char** argv) {
	int res;
	unsigned char valeur=0x55;
	if (argc == 2) {
		valeur = strtol(argv[1],NULL,0);
	}	
	res = opengpio();
	if (res == -1) {
		fprintf(stderr,"ERREUR ouverture : %d\n",res);
		return EXIT_FAILURE;
	}
	portwrite(valeur);	
	closegpio();
	return EXIT_SUCCESS;
}
					

Utilisation de l'application piscope

On va visualiser le signal fourni par un interrupteur mécanique sur une entrée (exemple GPIO26). Classiquement, l'interrupteur est connecté entre la masse et l'entrée avec une résistance connectée entre l'entrée et le +Vcc. Pour visualiser ce signal, on utilise l'application piscope qui peut être installée sur la raspberry ou bien sur l'ordinateur hôte.

Signal enregistré par l'application piscope lors de la fermeture de l'interrupteur qui donne un signal qui passe de 1 à 0.

Le passage de 0 à 1 (ouverture du contact) n'est pas instantané, on constate une suite de valeurs 0 et 1 supplémentaires, ces valeurs sont appelées rebonds, ce phénomène est d'origine mécanique lorsque le contact change de position, il peut se produire des oscillations qui impliquent ces suites de changement d'états. Ce phénomène n'apparaît que quelques fois et dépend de la technologie de fabrication de l'interrupteur. Dans certains cas cela peut produire des effets indésirables sur le programme. Il faut donc le connaître pour le corriger. La solution de l'électronicien consiste à ajouter un condensateur ou un filtre analogique. La solution de l'informaticien est d'ajouter un délai, puis une fois ce délai écoulé, vérifier que l'entrée a bien changé d'état.

Gestion des sorties PWM

Le signal PWM peut être réalisé sur une broche GPIO quelconque par logiciel, on parle de softPWM ou bien sur des broches qui supportent ce mode de fonctionnement au niveau matériel. Dans le cas du softPWM, c'est une tâche qui génère le signal PWM, dans le cas d'une broche dédiée, c'est un timer qui génère ce signal indépendamment du logiciel. Sur la raspberry PI, il y 4 GPIOs qui assurent cette fonctionnalité, les kernelID 12 et 18 sur PWM0, 13 et 19 sur PWM1.

On va s'intéresser au PWM matériel

Utilisation de WiringPi

Le fait que cette méthode soit obsolète, ne nous empêche pas de présenter cette solution.

Utilisation en ligne de commande

Cela se fait avec la commande gpio en deux étapes :

  1. Activer le mode pwm avec
    gpio -g mode ${kernelID} pwm
  2. Ecrire la valeur du rapport cyclique (entre 0 et 1023) avec
    gpio -g pwm ${kernedID} ${valeur}

Exemple de script shell

Voir le script shell avec la commande gpio sur la GPIO 18
#!/bin/sh

GPIO=18
VALEUR=512
MODE=0

while [ -n "$1" ]; do
case $1 in
	-m | --mode)
		MODE=1
		;;	
	*) 
		VALEUR=$1		
esac
shift
done

if [ "${MODE}" = 1 ] 
then
	echo "mode PWM sur ${GPIO}"
	gpio -g mode ${GPIO} pwm
else
	echo "${VALEUR} sur ${GPIO}"
	gpio -g pwm ${GPIO} ${VALEUR}
fi
					

La boucle while permet de traiter tous les paramètres de la ligne de commande en traitant soit l'option -m soit la valeur.

Il faut donc appeler le script deux fois, une première fois avec l'option -m, ensuite sans l'option avec la valeur du rapport cyclique entre 0 et 1023.

Utilisation en C

Pour utiliser le mode PWM, il faut suivre les étapes suivantes :

  1. Initialiser wiringpi avec la fonction wiringPiSetupGpio
  2. Définir le GPIO en sortie PWM avec la fonction pinMode et la valeur PWM_OUTPUT
  3. Enfin utiliser la sortie avec la fonction pwmWrite avec la valeur du rapport cyclique

Il ne faut pas oublier de modifier le Makefile en changeant le nom du programme exécutable ainsi que les variables CFLAGS et LDFLAGS.

CFLAGS= -Wall -I$(HOME)/raspberry/include
LDFLAGS=-lpthread -L$(HOME)/raspberry/lib -lwiringPi -lcrypt

Exemple de programme C

Voir le codage du programme C

Fichier de définition wpwm.h

#ifndef __WPWM_H
#define __WPWM_H

#define GPIO_DEFAUT 18
#define VAL_MAX 1023

int InitPWM(unsigned int);
void setCycle(unsigned int,unsigned int);

#endif
					

La fonction InitPWM initialise le système WiringPi avec l'utilisation des kernelID, puis paramètre la broche en sortie PWM en utisant la constante PWM_OUTPUT

La fonction écrit le rapport cyclique avec la fonction pwmWrite

Fichier de codage des fonctions wpwm.c

#include <stdio.h>
#include <wiringPi.h>
#include "wpwm.h"

int InitPWM(unsigned int wiringgpio) {
	int resultat=0;
	resultat = wiringPiSetupGpio();
	if (resultat == -1) {
		return resultat ;
	}
	pinMode(wiringgpio,PWM_OUTPUT);
	return resultat;
}

void setCycle(unsigned int wiringgpio,unsigned int cycle) {
	pwmWrite(wiringgpio, cycle) ;
}

					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include <wiringPi.h>
#include "wpwm.h"

int main(int argc, char** argv) {
	
	unsigned int valeur=VAL_MAX;
	unsigned int wgpio=WIRING_DEFAUT;
	int resultat ;
	
	if (argc == 2) {
		valeur = strtol(argv[1],NULL,0);
	}
	else if (argc == 3) {
		wgpio = strtol(argv[1],NULL,0);
		valeur = strtol(argv[2],NULL,0);
	}
	resultat = InitPWM(wgpio);
	if (resultat != 0) {
		fprintf(stderr,"Erreur\n");
		return EXIT_FAILURE;
	}
	setCycle(wgpio,valeur);
	return EXIT_SUCCESS;
}
					

Ce programme ne fonctionne pas en mode utilisateur mais uniquement en mode root avec sudo. Cela vient d'un problème d'accès au descripteur /dev/gpiomem qui, lui-même accède au descripteur /dev/mem qui, lui, n'est pas accessible à l'utilisateur.

Si le programme de test comprend un paramètre, il s'agit de la valeur du rapport cyclique, le kernedID du GPIO étant la valeur par défaut.

Si le programme de test comprend deux paramètres, le premier est le kernelID, le deuxième la valeur du rapport cyclique.

Utilisation de l'overlay pwm

Configuration

L'overlay pwm fait partie du système d'arborescence matérielle (Device Tree). Il permet de charger le ou les drivers spécifiques au matériel utilisé, ce qui est le cas du pwm dans ce mode de fonctionnement.

Les fichiers overlay se trouvent dans le répertoire de démarrage /boot/overlays, où l'on trouve le fichier pwm.dtbo.

Pour la phase de test, on peut gérer les overlays grâce à la commande dtoverlay en mode root :

Pour charger l'overlay et le driver automatiquement au démarrage, on modifie le fichier /boot/config.txt en ajoutant la ligne :

dtoverlay=pwm

Utilisation en ligne de commande

Le chargement crée un nouvelle arborescence /sys/class/pwm/pwmchip0 qui permet d'accéder à la broche 18 en mode pwm sur PWM0.

  1. Ecrire 0 dans /sys/class/pwm/pwmchip0/export active le PWM0 en créant le répertoire pwm0 qui contient les accès à la broche 18 en mode pwm
  2. Ecrire la valeur de la période en ns dans /sys/class/pwm/pwmchip0/pwm0/period définit la période du signal pwm
  3. Ecrire la valeur de la demi période en ns dans /sys/class/pwm/pwmchip0/pwm0/duty_cycle définit le rapport cyclique
  4. Ecrire 1 dans /sys/class/pwm/pwmchip0/pwm0/enable active le signal PWM.

Exemple de script shell

Voir le script de gestion du pwm avec l'accès utilisateur au système
#!/bin/sh

DEVICE="/sys/class/pwm/pwmchip0"
PWM=pwm0
PERIODE=2000000
VALEUR=100

if [ -n "$1" ]
then
	VALEUR="$1"
fi

if [ ! -d "$DEVICE" ]
then
	echo "$DEVICE absent"
	echo "il manque sans doute l'overlay"
	echo "sudo dtoverlay pwm"
	exit 1
fi

DEMIPERIODE=$(echo "scale=0;${PERIODE}*${VALEUR}/100" | bc -l)
echo "${DEMIPERIODE}/${PERIODE}"
if [ ! -d "${DEVICE}/${PWM}" ]
then
	echo 0 > ${DEVICE}/export
	sleep 1
fi
echo ${PERIODE} > ${DEVICE}/${PWM}/period
echo ${DEMIPERIODE} > ${DEVICE}/${PWM}/duty_cycle
echo 1 > ${DEVICE}/${PWM}/enable
					

On crée un signal PWM de fréquence 2000us sur le PWM0 (broche 18), le paramètre transmis correspond au rapport cyclique enter 0 et 100.

Après avoir défini la valeur du rapport cyclique, on vérifie si le driver est chargé, dans le cas contraire le script se termine.

Si le driver est chargé, on vérifie si l'export a déjà été effectué, dans la cas contraire, on exporte le pwm 0

Ensuite on calcule la demi-période en fonction de la période et de la valeur exprimée entre 0 et 100

Enfin on écrit ces valeurs puis on active le pwm.

Utilisation en C

En C on accède au système /sys/class/pwm/pwmchip0 en utilisant les fonctions d'ouverture, écriture et fermeture des fichiers.

Exemple de programme C

Voir le codage du programme C

Fichier de définition gcpwm.h

#ifndef __GCPWM_H
#define __GCPWM_H

#define PWM_DEFAUT 0
#define PWM_PERRIOD_DEFAUT 2000000UL
#define PWM_CYCLE_MAX 1023

int InitPWM(unsigned int);
int setPeriod(unsigned int);
int setCycle(unsigned int);
int enablePWM(unsigned int);

#endif
					

Toutes les fonctions effectuent une ouverture, écriture et fermeture sur le système.

La fonction InitPWM crée l'export du pwm qui peut être 0 ou 1. elle stocke également cette valeur pour qu'elle puisse être utilisée par les autres fonctions.

La fonction setPeriod initialise la période du pwm avec la valeur en ns.

La fonction setCycle initialise la demi-période du pwm avec la valeur en ns.

Enfin la fonction enablePWM active le pwm si la paramètre vaut 1, et le désactive si le paramètre vaut 0.

Fichier de codage des fonctions gcpwm.c

#include <stdio.h>
#include "gcpwm.h"

unsigned int pwmnum;

int InitPWM(unsigned int pwm) {
	int retour=0;
	FILE *fd;
	pwmnum = pwm ;
	fd=fopen("/sys/class/pwm/pwmchip0/export","w");	
	if (fd != NULL) {
		fprintf(fd,"%d\n",pwm);
		fclose(fd);
		return 1;
	}	
	else {
		return 0;
	}	
	return retour;
}

int setPeriod(unsigned int periode) {
	int retour=0;
	FILE *fd;
	char nom[50];
	sprintf(nom,"/sys/class/pwm/pwmchip0/pwm%d/period",pwmnum);
	fd=fopen(nom,"w");	
	if (fd != NULL) {
		fprintf(fd,"%d\n",periode);
		fclose(fd);
		return 1;
	}	
	else {
		return 0;
	}	
	return retour;	
}

int setCycle(unsigned int cycle)  {
	int retour=0;
	FILE *fd;
	char nom[50];
	sprintf(nom,"/sys/class/pwm/pwmchip0/pwm%d/duty_cycle",pwmnum);
	fd=fopen(nom,"w");	
	if (fd != NULL) {
		fprintf(fd,"%d\n",cycle);
		fclose(fd);
		return 1;
	}	
	else {
		return 0;
	}	
	return retour;
}

int enablePWM(unsigned int enable) {
	int retour=0;
	FILE *fd;
	char nom[50];
	sprintf(nom,"/sys/class/pwm/pwmchip0/pwm%d/enable",pwmnum);
	fd=fopen(nom,"w");	
	if (fd != NULL) {
		fprintf(fd,"%d\n",enable);
		fclose(fd);
		return 1;
	}	
	else {
		return 0;
	}	
	return retour;
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>&>
#include "gcpwm.h"

int main(int argc, char** argv) {
	
	unsigned int valeur=PWM_CYCLE_MAX;
	unsigned int pwm=PWM_DEFAUT;
	unsigned int rapportcycle=100;
	int resultat ;
	
	if (argc == 2) {
		rapportcycle = strtol(argv[1],NULL,0);
	}
	else if (argc == 3) {
		pwm = strtol(argv[1],NULL,0);
		rapportcycle = strtol(argv[2],NULL,0);
	}
	valeur = PWM_PERRIOD_DEFAUT * rapportcycle /100 ;
	resultat = InitPWM(pwm);
	sleep(1);
	if (resultat == 0) {
		fprintf(stderr,"Erreur\n");
		return EXIT_FAILURE;
	}
	setPeriod(PWM_PERRIOD_DEFAUT);
	if (resultat == 0) {
		fprintf(stderr,"Erreur\n");
		return EXIT_FAILURE;
	}
	setCycle(valeur);
	if (resultat == 0) {
		fprintf(stderr,"Erreur\n");
		return EXIT_FAILURE;
	}
	enablePWM(1);
	return EXIT_SUCCESS;
}
					

Le programme de test effectue l'ensemble des étapes de la gestion du pwm avec un arrêt du programme en cas d'erreur.

Utilisation de pigpiod

Configuration

Il ne faut pas oublier de démarrer le serveur :

sudo systemctl start pigpiod

Utilisation en ligne de commande

Cela se fait avec le client pigs :

pigs p ${kernedID} ${valeur}

La valeur est comprise entre 0 et 255

Exemple de script shell

Voir le script de gestion du pwm avec l'accès utilisateur au système
#!/bin/sh

GPIO=18
VALEUR=128

if [ -n "$1" ]
then
	VALEUR="$1"
fi

echo "PWM sur ${GPIO} avec ${VALEUR}"
pigs p ${GPIO} ${VALEUR}
					

Si aucun paramètre n'est transmis, c'est la valeur par défaut qui est utilisée.

Utilisation en C

Il ne faut pas oublier d'adapter les variables EXEC, CFLAGS et LDFLAGS du Makefile.

Exemple de programme C

Voir le codage du programme C

Fichier de définition pigpwm.h

#ifndef __PIGPWM_H
#define __PIGPWM_H

#define PWM_FREQUENCE 1000
#define PWM_CYCLE_MAX 100

int opengpio(void);
int gpiopwm(unsigned char ); 
void closegpio(void);

#endif
					

La fonction opengpio initialise la connexion au serveur avec les valeurs par défaut, configure le GPIO 18 en mode pwm0 (fonction secondaire ALT0), puis détermine la plage des valeurs du rapport cyclique ainsi que la fréquence du signal PWM.

La fonction gpiopwm affecte le rapport cyclique compris entre 0 et la valeur maximale définie par PWM_CYCLE_MAX.

Enfin la fonction closegpio libère la broche pwm et ferme la connexion réseau.

Fichier de codage des fonctions pigpwm.c

#include <stdio.h>
#include "pigpiod_if2.h"
#include "pigpwm.h"

int fdpio;

int opengpio() {
	int retour=0;
	fdpio = pigpio_start("localhost", "8888");
	if (fdpio < 0) {
		return fdpio;
	}
	retour = set_mode(fdpio, 18, PI_ALT0);
	if (retour < 0) {
		return retour;
	}
	retour = set_PWM_range(fdpio,18,PWM_CYCLE_MAX); 
	if (retour < 0) {
		return retour;
	}
	retour = set_PWM_frequency(fdpio,18,PWM_FREQUENCE); 
	return retour;
}


int gpiopwm(unsigned char cycle) {
	int retour = 0;
	retour = set_PWM_dutycycle(fdpio,18,cycle);
	return retour;
}

void closegpio() {
	pigpio_stop(fdpio);
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include "pigpwm.h"

int main(int argc, char** argv) {
	
	int res;
	unsigned char valeur=PWM_CYCLE_MAX;
	if (argc == 2) {
		valeur = strtol(argv[1],NULL,0);
	}	
	res = opengpio();
	if (res == -1) {
		fprintf(stderr,"ERREUR ouverture : %d\n",res);
		return EXIT_FAILURE;
	}
	gpiopwm(valeur);
	closegpio();
	return EXIT_SUCCESS;
}
					

Résultat de l'affichage fourni par piscope pour un rapport cyclique de 20%.

Gestion de l'interface SPI

Fonctionnement

L'interface SPI utilise une transmission série synchrone qui fonctionne en half-duplex ou full-duplex.

En half-duplex, l'échange des données se fait alternativement dans un sens puis dans un autre, c'est une suite de lecture et écriture.

En full-duplex, l'échange des données dans les deux sens est simultané, la lecture et l'écriture des données sont simultanées. C'est la cas de nombreux capteurs.

Configuration

Si l'interface SPI a été activée, on doit trouver des descripteurs de la forme /dev/spidevM.N avec M qui représente le numéro du bus SPI et N qui définit le numéro de sélection de circuit, comme par exemple, M=0 avec N=0 et N=1. On peut également vérifier dans le fichier config.txt si la ligne dtparam=spi=on est présente et non commentée.

Remarque : l'utilisation du descripteur avec les fonction d'ouverture, lecture, écriture et fermeture ne permet que le half-duplex. Pour le full-duplex, il faut utiliser un driver existant ou des librairies qui permettent l'échange de données comme WiringPI ou pigpiod.

Etude d'un exemple

On va donner une seconde vie à une carte acceléromètre/magnétomètre équipée du capteur LSM303D

On ne trouve pas de librairie ou driver linux pour ce composant, seulement une librairie arduino. Il faut donc développer un librairie qui contient l'ensemble des fonctions de gestions de ce capteur. On va donc créer la librairie pour la raspberry à partir des documents constructeurs disponibles.

De plus ce capteur utilise le mode full-duplex, ce qui exclut l'utilisation des descripteurs /dev/spidevM.N.

On crée d'abord un fichier de définition des registres du capteurs LSM303D_defs.h

Voir le code du fichier LSM303D_defs.h
#ifndef __LSM303D_DEFS_H
#define __LSM303D_DEFS_H

// registre de données , controle et statut
#define LSM303D_TEMP_OUT_L 	0x05
#define LSM303D_TEMP_OUT_H 	0x06
#define LSM303D_STATUS_M 	0x07
#define LSM303D_OUT_X_L_M	0x08
#define LSM303D_OUT_X_H_M	0x09
#define LSM303D_OUT_Y_L_M	0x0A
#define LSM303D_OUT_Y_H_M	0x0B
#define LSM303D_OUT_Z_L_M	0x0C
#define LSM303D_OUT_Z_H_M	0x0D
#define LSM303D_WHO_AM_I	0x0F
#define LSM303D_CTRL_0		0x1F
#define LSM303D_CTRL_1		0x20
#define LSM303D_CTRL_2		0x21
#define LSM303D_CTRL_3		0x22
#define LSM303D_CTRL_4		0x23
#define LSM303D_CTRL_5		0x24
#define LSM303D_CTRL_6		0x25
#define LSM303D_CTRL_7		0x26
#define LSM303D_STATUS_A 	0x27
#define LSM303D_OUT_X_L_A	0x28
#define LSM303D_OUT_X_H_A	0x29
#define LSM303D_OUT_Y_L_A	0x2A
#define LSM303D_OUT_Y_H_A	0x2B
#define LSM303D_OUT_Z_L_A	0x2C
#define LSM303D_OUT_Z_H_A	0x2D
// bits donnees status magnetometre
#define LSM303D_STATUS_M_ZXYMOR	0x80
#define LSM303D_STATUS_M_ZMOR	0x40
#define LSM303D_STATUS_M_YMOR	0x20
#define LSM303D_STATUS_M_XMOR	0x10
#define LSM303D_STATUS_M_ZYXMDA	0x08
#define LSM303D_STATUS_M_ZMDA	0x04
#define LSM303D_STATUS_M_YMDA	0x02
#define LSM303D_STATUS_M_XMDA	0x01
// bits donnees status accelerometre
#define LSM303D_STATUS_A_ZXYAOR	0x80
#define LSM303D_STATUS_A_ZAOR	0x40
#define LSM303D_STATUS_A_YAOR	0x20
#define LSM303D_STATUS_A_XAOR	0x10
#define LSM303D_STATUS_A_ZYXADA	0x08
#define LSM303D_STATUS_A_ZADA	0x04
#define LSM303D_STATUS_A_YADA	0x02
#define LSM303D_STATUS_A_XADA	0x01
// bits donnees controle 0
#define LSM303D_CTRL_0_BOOT	0x80
#define LSM303D_CTRL_0_FIFO_EN	0x40
#define LSM303D_CTRL_0_FTH_EN	0x20
#define LSM303D_CTRL_0_HP_CLICK	0x04
#define LSM303D_CTRL_0_HPIS1	0x02
#define LSM303D_CTRL_0_HPIS2	0x01
// bits donnees controle 1
#define LSM303D_CTRL_1_AODR_MASK	0xF0
#define LSM303D_CTRL_1_AODR_3		0x80
#define LSM303D_CTRL_1_AODR_2		0x40
#define LSM303D_CTRL_1_AODR_1		0x20
#define LSM303D_CTRL_1_AODR_0		0x10
#define LSM303D_CTRL_1_BDU		0x08
#define LSM303D_CTRL_1_AZEN		0x04
#define LSM303D_CTRL_1_AYEN		0x02
#define LSM303D_CTRL_1_AXEN		0x01
// bits donnees controle 2
#define LSM303D_CTRL_2_ABW_1	0x80
#define LSM303D_CTRL_2_ABW_0	0x40
#define LSM303D_CTRL_2_AFS_2	0x20
#define LSM303D_CTRL_2_AFS_1	0x10
#define LSM303D_CTRL_2_AFS_0	0x08
#define LSM303D_CTRL_2_AST	0x02
#define LSM303D_CTRL_2_SIM	0x01
// bits donnees controle 5
#define LSM303D_CTRL_5_TEMP_EN	0x80
#define LSM303D_CTRL_5_M_RES_1	0x40
#define LSM303D_CTRL_5_M_RES_0	0x20
#define LSM303D_CTRL_5_M_ODR_2	0x10
#define LSM303D_CTRL_5_M_ODR_1	0x08
#define LSM303D_CTRL_5_M_ODR_0	0x04
#define LSM303D_CTRL_5_LIR2	0x02
#define LSM303D_CTRL_5_LIR1	0x01
// bits donnees controle 6
#define LSM303D_CTRL_6_MFS1	0x40
#define LSM303D_CTRL_6_MFS0	0x20
// bits donnees controle 7
#define LSM303D_CTRL_7_AHPM_1	0x80
#define LSM303D_CTRL_7_AHPM_0	0x40
#define LSM303D_CTRL_7_AFDS	0x20
#define LSM303D_CTRL_7_T_ONLY	0x10
#define LSM303D_CTRL_7_MLP	0x04
#define LSM303D_CTRL_7_MD_1	0x02
#define LSM303D_CTRL_7_MD_0	0x01

#endif
					

Ce fichier définit les valeurs des registres ainsi que les valeurs des bits utilisés dans les registres d'état, de contrôle et de données.

Utilisation de WiringPi en C

La librairie WiringPI fournit deux fonctions de gestion de l'interface SPI qui sont :

Exemple de programme C

Voir le codage du programme C

Fichier de définition LSM303D.h

#ifndef __LSM303D_H
#define __LSM303D_H

#include "LSM303D_defs.h"

#define LSM303D_CANAL 	0
#define LSM303D_FREQUENCE 1000000L

// calibre +-2
#define DIV_2 16384.0

void LSM303DInit(void);
void LSM303DEcrireReg(unsigned char ,unsigned char);
unsigned char LSM303DLireReg(int);
int LSM303DAccPret(void);
short LSM303DLireAxe(int );

#endif
					

La fonction LSM303DInit initialise WiringPi, puis le circuit LSM303D en écrivant la valeur 0x17 dans le registre de contrôle 1 et 0 dans les autres registres de contrôle :

  • Fréquence d'échantillonnage de 3.125Hz
  • Acquisition continue
  • Validation des 3 axes de l'accéléromètre
  • Capteur de température désactivé
  • Filtres désactivés
  • Calibre accélération ±2g, calibre magnétomètre ±2 gauss

La fonction d'écriture LSM303DEcrireReg de valeur dans un registre utilisent 2 octets, lors de l'écriture on envoie le numéro du registre suivi de la valeur, données codées sur un octet.

La fonction de lecture LSM303DLireReg de la valeur d'un registre utilisent également 2 octets en envoyant le numéro du registre suivi de la valeur 0, la valeur lue est stockée dans le deuxième octet reçu.

La fonction LSM303DAccPret lit le registre de statut et teste si le bit 3 de ce registre est à 1, elle retourne 1 si ce bit est à 1.

La fonction LSM303DLireAxe effectue la lecture de la valeur d'un axe en partant du registre de base de cet axe.

Fichier de codage des fonctions LSM303D.c

#include <stdio.h>
#include <stdlib.h>
#include <wiringPiSPI.h>
#include "LSM303D.h"

void LSM303DEcrireReg(unsigned char registre,unsigned char valeur) {
	unsigned char buffer[2];
	buffer[0] = registre;
	buffer[1] = valeur;	
	wiringPiSPIDataRW(LSM303D_CANAL,buffer,2);
}

unsigned char LSM303DLireReg(int registre) {
	unsigned char resultat = 0;
	unsigned char buffer[2];
	buffer[0] = registre | 0x80 ;
	buffer[1] = 0;
	wiringPiSPIDataRW(LSM303D_CANAL,buffer,2);
	resultat = buffer[1];	
	return resultat;
}

void LSM303DInit(void) {
	wiringPiSPISetup(LSM303D_CANAL,LSM303D_FREQUENCE);
	LSM303DEcrireReg(LSM303D_CTRL_1,LSM303D_CTRL_1_AZEN|LSM303D_CTRL_1_AYEN|LSM303D_CTRL_1_AXEN | LSM303D_CTRL_1_AODR_0);
	LSM303DEcrireReg(LSM303D_CTRL_2,0);
	LSM303DEcrireReg(LSM303D_CTRL_3,0);
	LSM303DEcrireReg(LSM303D_CTRL_4,0);
	LSM303DEcrireReg(LSM303D_CTRL_5,0);
	LSM303DEcrireReg(LSM303D_CTRL_6,0);
	LSM303DEcrireReg(LSM303D_CTRL_7,0);
}

int LSM303DAccPret() {
  unsigned char valeur = LSM303DLireReg(LSM303D_STATUS_A);
  return ((valeur & LSM303D_STATUS_A_ZYXADA) == LSM303D_STATUS_A_ZYXADA);
}

short LSM303DLireAxe(int regbase) {
	short resultat =0;
	unsigned char lsb,msb;
	lsb=LSM303DLireReg(regbase);
	msb=LSM303DLireReg(regbase+1);
	resultat = (short)lsb | (((short)msb) << 8) ;	
	return resultat;
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "LSM303D.h" 

int main(int argc, char** argv) {
	
	unsigned char id;
	int ax,ay,az;
	double rax,ray,raz;
	int mx,my,mz;
	double rmx,rmy,rmz,mm;
	
	LSM303DInit();
	id = LSM303DLireReg(LSM303D_WHO_AM_I);
	printf("ID = %x\n",id);
	while (!LSM303DAccPret());
	ax = LSM303DLireAxe(LSM303D_OUT_X_L_A);
	ay = LSM303DLireAxe(LSM303D_OUT_Y_L_A);
	az = LSM303DLireAxe(LSM303D_OUT_Z_L_A);
	rax = (double)ax / DIV_2 ;
	ray = (double)ay / DIV_2 ;
	raz = (double)az / DIV_2 ;
	mx = LSM303DLireAxe(LSM303D_OUT_X_L_M);
	my = LSM303DLireAxe(LSM303D_OUT_Y_L_M);
	mz = LSM303DLireAxe(LSM303D_OUT_Z_L_M);
	rmx = (double)mx / DIV_2 ;
	rmy = (double)my / DIV_2 ;
	rmz = (double)mz / DIV_2 ;
	mm = sqrt(rmx*rmx+rmy*rmy+rmz*rmz);
	printf("x=%+03.2lf g,y=%+03.2lf g,z=%+03.2lf g\tx=%+03.2lf G,y=%+03.2lf G,z=%+03.2lf G,M=%+03.2lf G\n",rax,ray,raz,rmx,rmy,rmz,mm);
	return EXIT_SUCCESS;
}
					

Le programme de test initialise le circuit, affiche l'ID du capteur qui soit être de 0x49.

Ensuite on lit les valeurs des 3 axes de l'accéléromètre puis du magnétomètre. Avec le calibre ±2 exprimé sur 16 bits en virgule fixe, il convient de diviser par 16384 pour obtenir la valeur réelle.

Si le capteur est immobile et horizontale (axes x et y horizontaux), les valeurs affichées sont :

ID = 49
x=-0.00 g,y=+0.01 g,z=-0.91 g	x=+0.04 G,y=+0.09 G,z=+0.31 G,M=+0.33 G
					

Pour l'accélération, on obtient presque 0g pour les axes x et y, et presque 1g sur l'axe z. Ce résultat n'est pas suffisant pour vérifier les valeurs de l'accélération, mais les valeurs indiquées semblent correctes car le capteur indique bien l'accélération de l'apesanteur qui est dans de cas de 1g verticalement.

Pour le magnétomètre, c'est plus délicat, car le champ magnétique terrestre dépend de la position du capteur sur la planète. Il faut donc prendre en compte ces paramètres pour valider les valeurs mesurées. En prenant la valeur de 47µT=0.47g au centre de la France métropolitaine, la valeur de 0.33g peut sembler être correcte.

Valider les données de ce capteur (comme la plupart des capteurs) nécessite d'utiliser des bancs de mesures en laboratoire avec des instruments de mesures adaptés.

Utilisation de pigpiod en C

Comme pour les GPios, on utilise le système client/serveur.

Les différentes étapes sont donc :

  1. Etablir la connexion réseau avec la fonction pigpio_start
  2. Configurer le système SPI avec la fonction spi_open
  3. Transférer les données avec la fonction spi_xfer qui utilise un buffer pour l'émission et un buffer pour la réception.
  4. Fermer le système spi avec la fonction spi_close
  5. Libérer le périphérique SPI et terminer la connexion réseau avec la fonction pigpio_stop

Exemple de programme C

Voir le codage du programme C

Fichier de définition LSM303D.h

#ifndef __LSM303D_H
#define __LSM303D_H

#include "LSM303D_defs.h"

#define LSM303D_CANAL 	0
#define LSM303D_FREQUENCE 100000L

// calibre +-2
#define DIV_2 16384.0

int opengpio();
void closegpio();
void LSM303DEcrireReg(unsigned char ,unsigned char ) ;
unsigned char LSM303DLireReg(int ) ;
void LSM303DInit(void);
int LSM303DAccPret(void);
short LSM303DLireAxe(int );

#endif
					

La valeur de la fréquence de 100kHz a été choisi pour pouvoir utiliser piscope.

La fonction opengpio assure la connexion au serveur et la configuration du SPI. chaque étape fournit un identificateur qui sera utilisé par les autres fonctions.

La fonction closegpio termine la fonction SPI puis termine la connexion réseau

La fonction LSM303Dinit initialise le capteur, en utilisant la même configuration que précédemment.

La fonction LSM303DEcrire stocke la valeur du registre et la valeur de la donnée dans le buffer de transmission. Le buffer de réception est défini, mais non utilisé.

La fonction LSM303DLire stocke la valeur du registre et la valeur 0 dans le registre de transmission. Le deuxième octet du buffer de réception contient la réponse du capteur.

Fichier de codage des fonctions LSM303D.c

#include <stdio.h>
#include <stdlib.h>
#include "pigpiod_if2.h"
#include "LSM303D.h"

int fdpio;
int fdspi;

int opengpio() {
	fdpio = pigpio_start("localhost", "8888");
	if (fdpio < 0) {
		return fdpio;
	}
	fdspi =  spi_open(fdpio, LSM303D_CANAL, LSM303D_FREQUENCE, 0);
	return fdspi;
}

void closegpio() {
	spi_close(fdpio,fdspi);
	pigpio_stop(fdpio);
}

void LSM303DEcrireReg(unsigned char registre,unsigned char valeur) {
	char txbuf[2] , rxbuf[2];
	txbuf[0] = registre;
	txbuf[1] = valeur;	
	spi_xfer(fdpio, fdspi, txbuf, rxbuf, 2);
}

unsigned char LSM303DLireReg(int registre) {
	unsigned char resultat = 0;
	char txbuf[2] , rxbuf[2];
	txbuf[0] = registre | 0x80 ;
	txbuf[1] = 0;
	spi_xfer(fdpio, fdspi, txbuf, rxbuf, 2);
	resultat = rxbuf[1];	
	return resultat;
}

void LSM303DInit(void) {
	LSM303DEcrireReg(LSM303D_CTRL_1,LSM303D_CTRL_1_AZEN|LSM303D_CTRL_1_AYEN|LSM303D_CTRL_1_AXEN | LSM303D_CTRL_1_AODR_0);
	LSM303DEcrireReg(LSM303D_CTRL_2,0);
	LSM303DEcrireReg(LSM303D_CTRL_3,0);
	LSM303DEcrireReg(LSM303D_CTRL_4,0);
	LSM303DEcrireReg(LSM303D_CTRL_5,0);
	LSM303DEcrireReg(LSM303D_CTRL_6,0);
	LSM303DEcrireReg(LSM303D_CTRL_7,0);
}

int LSM303DAccPret() {
  unsigned char valeur = LSM303DLireReg(LSM303D_STATUS_A);
  return ((valeur & LSM303D_STATUS_A_ZYXADA) == LSM303D_STATUS_A_ZYXADA);
}

short LSM303DLireAxe(int regbase) {
	short resultat =0;
	unsigned char lsb,msb;
	lsb=LSM303DLireReg(regbase);
	msb=LSM303DLireReg(regbase+1);
	resultat = (short)lsb | (((short)msb) << 8) ;	
	return resultat;
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "LSM303D.h" 

int main(int argc, char** argv) {
	
	unsigned char id;
	int ax,ay,az;
	double rax,ray,raz;
	int mx,my,mz;
	double rmx,rmy,rmz,mm;
	
	int res = opengpio();
	if (res == -1) {
		fprintf(stderr,"ERREUR ouverture : %d\n",res);
		return EXIT_FAILURE;
	}	
	LSM303DInit();
	id = LSM303DLireReg(LSM303D_WHO_AM_I);
	printf("ID = %x\n",id);
	while (!LSM303DAccPret());
	ax = LSM303DLireAxe(LSM303D_OUT_X_L_A);
	ay = LSM303DLireAxe(LSM303D_OUT_Y_L_A);
	az = LSM303DLireAxe(LSM303D_OUT_Z_L_A);
	rax = (double)ax / DIV_2 ;
	ray = (double)ay / DIV_2 ;
	raz = (double)az / DIV_2 ;
	mx = LSM303DLireAxe(LSM303D_OUT_X_L_M);
	my = LSM303DLireAxe(LSM303D_OUT_Y_L_M);
	mz = LSM303DLireAxe(LSM303D_OUT_Z_L_M);
	rmx = (double)mx / DIV_2 ;
	rmy = (double)my / DIV_2 ;
	rmz = (double)mz / DIV_2 ;
	mm = sqrt(rmx*rmx+rmy*rmy+rmz*rmz);
	printf("x=%+03.2lf g,y=%+03.2lf g,z=%+03.2lf g\tx=%+03.2lf G,y=%+03.2lf G,z=%+03.2lf G,M=%+03.2lf G\n",rax,ray,raz,rmx,rmy,rmz,mm);
	closegpio();
	return EXIT_SUCCESS;
}
					

On utilise piscope pour visualiser les échanges sur la liaison SPI.

La figure suivante présente un extrait des échanges lors de la demande de l'identificateur du capteur

On a bien un transfert simultané dans les deux sens. Les 8 premiers bits correspondent à l'émission de la valeur du registre qui est 0x0F avec le bit 8 à 1 pour la lecture (MOSI). Les 8 derniers bits correspondent à la réception de la valeur transmise par le composant 0x49 (MISO).

Gestion de l'interface I2C

Fonctionnement

L'interface I2C utilise une transmission série synchrone qui fonctionne en half-duplex.

Configuration

Si l'interface I2C a été activée, on doit trouver des descripteurs de la forme /dev/i2c-M avec M qui représente le numéro du bus I2C. On peut également vérifier dans le fichier config.txt si la ligne dtparam=i2c_arm=on est présente et non commentée.

Etude d'un exemple

On utilise une carte capteur de luminosité équipée du capteur bh1745

Ce composant utilise des convertisseurs ADC dont le gain et la résolution sont configurables au travers d'une constante de temps comme c'est le cas des convertisseurs à rampe.

Avec ce composant on trouve une librairie python ainsi que des programmes de démonstration.

La libraire python nous donne une information sur la gestion des LEDs, qu'il faut analyser pour pouvoir écrire la librairie en C. On utilise la sortie INT du capteur pour allumer les LEDs, Cette sortie INT est activée en cas de dépassement de valeurs enregistrées. On fait donc en sorte que le dépassement soit toujours activé, ensuite il suffit d'activer cette sortie pour allumer les LEDs, et de la désactiver pour les éteindre. Je laisse le lecteur apprécier ce mode de fonctionnement.

Plutôt que d'essayer de transformer les programmes fournis,on va créer notre propre librairie. Pour cela, on crée d'abord un fichier de définition des registres du capteurs bh1745_defs.h.

Voir le code du fichier bh1745_defs.h
#ifndef __BH1745_DEFS_H
#define __BH1745_DEFS_H

#define BH1745_I2CADR	0x38
// registres RW
#define BH1745_SYSTEM_CTRL	0x40
#define BH1745_CTRL1		0x41
#define BH1745_CTRL2		0x42
#define BH1745_CTRL3		0x44
// registres donnees R
#define BH1745_RED_LSB		0x50
#define BH1745_RED_MSB		0x51
#define BH1745_GREEN_LSB	0x52
#define BH1745_GREEN_MSB	0x53
#define BH1745_BLUE_LSB		0x54
#define BH1745_BLUE_MSB		0x55
#define BH1745_CLEAR_LSB	0x56
#define BH1745_CLEAR_MSB	0x57
#define BH1745_DINT_LSB		0x58
#define BH1745_DINT_MSB		0x59
// autres registres RW
#define BH1745_INTERRUPT	0x60
#define BH1745_PERSISTENCE	0x61
#define BH1745_TH_LSB		0x62
#define BH1745_TH_MSB		0x63
#define BH1745_TL_LSB		0x64
#define BH1745_TL_MSB		0x65
// registre R ID
#define BH1745_ID 0x92
// donnees
#define BH1745_MANUFACTURER_ID 0xE0
// bits de controles
#define BH1745_SYSCTRL_SW_RST	0x80
#define BH1745_SYSCTRL_INT_RST	0x40
// bits de temps de mesure
#define BH1745_CTRL1_160ms	0x00
#define BH1745_CTRL1_320ms	0x01
#define BH1745_CTRL1_640ms	0x02
#define BH1745_CTRL1_1280ms	0x03
#define BH1745_CTRL1_2560ms	0x04
#define BH1745_CTRL1_5120ms	0x05
// bits de controle 2
#define BH1745_CTRL2_VALID	0x80
#define BH1745_CTRL2_RGBC_EN	0x10
#define BH1745_CTRL2_ADC_G_1	0x00
#define BH1745_CTRL2_ADC_G_2	0x01
#define BH1745_CTRL2_ADC_G_16	0x02
// bits de controle 3
#define BH1745_CTRL3_VALUE 0x02
// bits interrupt
#define BH1745_INTERRUPT_STATUS	0x80
#define BH1745_INTERRUPT_LATCH	0x10
#define BH1745_INTERRUPT_SOURCE	0xC0
#define BH1745_INTERRUPT_ENABLE	0x01
// gestion des LEDs
#define BH1745_LED_ON	1
#define BH1745_LED_OFF	0

#endif
					

Utilisation de WiringPi en C

La librairie WiringPI fournit deux fonctions de gestion de l'interface I2C qui sont :

Exemple de programme C

Voir le codage du programme C

Fichier de définition bh1745.h

#ifndef __BH1745_H
#define __BH1745_H

#include "bh1745_defs.h"

unsigned char bh1745manutactureId(void);
void bh1745Init(void);
void bh1745Mesure(void); 
int bh1745Pret(void) ;
unsigned short bh1745lire(unsigned int  );

void bh1745Led(int);

#endif
					

La fonction bh1745Init, initialise l'interface i2c, puis le capteur en écrivant les valeurs dans les différents registres de contrôle.

  • Effectuer un reset logiciel en écrivant 0x80, puis 0 dans le registre système
  • Configurer la constante de temps du convertisseur en écrivant 0x02 dans le registre de contrôle 1
  • Configurer le mode de fonctionnement et le gain du convertisseur en écrivant 0x11 dans le registre de contrôle 2
  • Ecrire 0x02 dans le registre de contrôle 3
  • Ecrire 0 dans les registres d'alarme haute et 0xff dans les registres d'alarme basse.
  • Désactiver les interruptions pour éteindre les LEDs

La fonction bh1745manutactureId lit le contenu du registre qui contient l'identification du capteur

La fonction bh1745Mesure effectue la demande d'une nouvelle mesure en écrivant dans le registre 3 qui ne modifie pas la configuration

La fonction bh1745Pret retourne 1 lorsque la mesure est disponible

La fonction bh1745lire retourne le contenu 16 bits de la valeur d'une couleur ou de la luminosité référencé par la valeur du premier registre.

La fonction bh1745Led allume ou éteint les LEDs.

Fichier de codage des fonctions bh1745.c

#include <stdio.h>
#include <stdlib.h>
#include "wiringPiI2C.h"
#include "bh1745.h"

int id;

void bh1745Init() {
	id = wiringPiI2CSetup(BH1745_I2CADR);
	wiringPiI2CWriteReg8(id,BH1745_SYSTEM_CTRL,BH1745_SYSCTRL_SW_RST);
	wiringPiI2CWriteReg8(id,BH1745_SYSTEM_CTRL,0);
	wiringPiI2CWriteReg8(id,BH1745_CTRL1,BH1745_CTRL1_640ms);
	wiringPiI2CWriteReg8(id,BH1745_CTRL2,BH1745_CTRL2_RGBC_EN | BH1745_CTRL2_ADC_G_2);
	wiringPiI2CWriteReg8(id,BH1745_CTRL3,BH1745_CTRL3_VALUE);
	wiringPiI2CWriteReg8(id,BH1745_TH_LSB,0);
	wiringPiI2CWriteReg8(id,BH1745_TH_MSB,0);
	wiringPiI2CWriteReg8(id,BH1745_TL_LSB,0xff);
	wiringPiI2CWriteReg8(id,BH1745_TL_MSB,0xff);
	wiringPiI2CWriteReg8(id,BH1745_INTERRUPT,0);
}

unsigned char bh1745manutactureId() {
	unsigned char mid;
	mid = wiringPiI2CReadReg8(id,BH1745_ID);
	return mid;
}

void bh1745Mesure() {
	wiringPiI2CWriteReg8(id,BH1745_CTRL3,BH1745_CTRL3_VALUE);
}

int bh1745Pret() {
	int valide = wiringPiI2CReadReg8(id,BH1745_CTRL2);
	return valide & BH1745_CTRL2_VALID;
}

unsigned short bh1745lire(unsigned int LSBReg) {
	unsigned short valeur=0;
	unsigned short lsb,msb;
	lsb = wiringPiI2CReadReg8(id,LSBReg);
	msb = wiringPiI2CReadReg8(id,LSBReg + 1);
	valeur = lsb + (msb << 8);
	return valeur;
}

void bh1745Led(int etat) {
	if (etat) {
		wiringPiI2CWriteReg8(id,BH1745_INTERRUPT,BH1745_INTERRUPT_ENABLE);
	}
	else {
		wiringPiI2CWriteReg8(id,BH1745_INTERRUPT,0);
	}
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "bh1745.h"

int main(int argc, char** argv) {
	
	unsigned short red,green,blue,clear;
	
	bh1745Init();
	unsigned char manufacture = bh1745manutactureId();
	printf("id=%x\n",manufacture);
	usleep(500000L);
	while (!bh1745Pret());
	red = bh1745lire(BH1745_RED_LSB);
	green = bh1745lire(BH1745_GREEN_LSB);
	blue = bh1745lire(BH1745_BLUE_LSB);
	clear = bh1745lire(BH1745_CLEAR_LSB);	
	printf("%u;%u;%u;%u\n",red,green,blue,clear);
	return EXIT_SUCCESS;
}
					

La valeur de la luminosité affichée correspond à une valeur très approchée de la luminosité en lux, valeur qui dépend des caractéristiques du capteur comme la sensibilité en fonction de la longueur d'onde ainsi que la sensiblité en fonction de l'angle du rayonnement. Comme pour tous les capteurs, il faudrait faire des tests en utilisant des bancs de mesures (source lumineuse + appareil de mesure).

Utilisation du descripteur /dev/i2c-1 en C

La transmission I2C est half-duplex, elle peut dont être utilisée avec le descripteur.

Le périphérique 1 est celui qui est disponible sur le bornier des GPIos.

On utilise les fonctions de gestions des fichiers ouverture, lecture, écriture et fermeture

Exemple de programme C

Voir le codage du programme C

Fichier de définition bh1745.h

#ifndef __BH1745_H
#define __BH1745_H

#include "bh1745_defs.h"

int bh1745manutactureId(void);
int bh1745Init(void);
int bh1745Mesure(void); 
int bh1745Pret(void) ;
int bh1745lire(unsigned int  );
void bh1745close(void);

#endif
					

Le code source c contient deux fonctions bh1745EcrireReg et bh1745LireReg qui ne sont pas dans le fichier h, ces fonctions peuvent être considérées comme privées car utilisées seulement par les autre fonctions de ce fichier.

La fonction bh1745Init réalise l'ouverture du descripteur, la configuration du périphériques avec ioctl en configurant l'adresse du périphérique sur le bus I2C. Ensuite elle réalise la configuration du capteur avec une suite d'écriture des registres de contrôle avec les mêmes valeurs que celles utilisées par la solution wiringpi. L'écriture est réalisée avec la fonction bh1745EcrireReg qui écrit un buffer de 2 octets, le premier est le numéro du registre et le second la valeur à écrire. Cette fonction s'arrête à la première erreur rencontrée et retourne le code -1.

Cette version fournit également les fonctions bh1745Mesure, bh1745Pret, bh1745lire de la version wiringpi en utilisant les fonctions d'écriture et lecture d'un registre.

Elle fournit en plus la fonction bh1745close qui consiste à fermer le descripteur.

La gestion des LEDs n'est pas implémentée dans cette version, mais il est facile de l'ajouter.

Fichier de codage des fonctions bh1745.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include "bh1745.h"

int fdi2c;

int bh1745EcrireReg(unsigned char reg,unsigned char valeur) {
	int resultat = 0;
	unsigned char buffer[2];
	buffer[0] = reg;
	buffer[1] = valeur;
	resultat=write(fdi2c,buffer,2);	
	return resultat;
}

int bh1745LireReg(unsigned char reg) {
	int resultat = 0;
	unsigned char buffer[2];
	buffer[0] = reg;
	resultat=write(fdi2c,buffer,1);
	if (resultat != 1) {
		return -1;
	}
	resultat = read(fdi2c,buffer,1);
	if (resultat != 1) {
		return -1;
	}
	resultat = (int)buffer[0];
	return resultat;
}

int bh1745Init() {
    int resultat=0;
	fdi2c=open("/dev/i2c-1", O_RDWR);
	if (fdi2c < 0) {
		return -1;
	}
	resultat = ioctl(fdi2c, I2C_SLAVE, BH1745_I2CADR);  
	if (resultat < 0) {
		return -1;
	}
	resultat = bh1745EcrireReg(BH1745_SYSTEM_CTRL,BH1745_SYSCTRL_SW_RST);
	if (resultat != 2) {
		return -1;
	}
	resultat = bh1745EcrireReg(BH1745_CTRL1,BH1745_CTRL1_640ms);
	if (resultat != 2) {
		return -1;
	}
	resultat = bh1745EcrireReg(BH1745_CTRL2,BH1745_CTRL2_RGBC_EN|BH1745_CTRL2_ADC_G_2);
	if (resultat != 2) {
		return -1;
	}
	resultat = bh1745EcrireReg(BH1745_SYSTEM_CTRL,0);
	if (resultat != 2) {
		return -1;
	}
	resultat = bh1745EcrireReg(BH1745_CTRL3,BH1745_CTRL3_VALUE);
	if (resultat != 2) {
		return -1;
	}
	return resultat;
}

int bh1745manutactureId() {
	int resultat = 0 ;
	resultat = bh1745LireReg(BH1745_ID);
	return resultat;
}

int bh1745Mesure() {
	int resultat = 0;
	resultat = bh1745EcrireReg(BH1745_CTRL3,BH1745_CTRL3_VALUE);
	return resultat ;
}

int bh1745Pret() {
	int resultat =0;
	resultat = bh1745LireReg(BH1745_CTRL2);
	if (resultat < 0) {
		return resultat;
	}
	return resultat & BH1745_CTRL2_VALID;
}

int bh1745lire(unsigned int LSBReg) {
	int valeur = 0;
	int lsb,msb;
	int resultat =0;
	resultat = bh1745LireReg(LSBReg);
	if (resultat < 0) {
		return resultat;
	}
	lsb = resultat ;
	resultat = bh1745LireReg(LSBReg + 1);
	if (resultat < 0) {
		return resultat;
	}	
	msb = resultat;
	valeur = ((msb & 0xff) << 8) + (lsb & 0xff) ;
	return valeur;
}

void bh1745close() {
	close(fdi2c);
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include "bh1745.h"

int main(int argc, char** argv) {
	
	unsigned short red,green,blue,clear;
	int resultat=0;
	
	resultat = bh1745Init();
	printf("resultat : %d\n",resultat);
	unsigned char manufacture = bh1745manutactureId();
	printf("id=%x\n",manufacture);
	while (!bh1745Pret());	
	red = bh1745lire(BH1745_RED_LSB);
	green = bh1745lire(BH1745_GREEN_LSB);
	blue = bh1745lire(BH1745_BLUE_LSB);
	clear = bh1745lire(BH1745_CLEAR_LSB);	
	printf("%u;%u;%u;%u\n",red,green,blue,clear);
	bh1745close();
	return EXIT_SUCCESS;
}
					

Utilisation de pigpiod en C

Comme pour SPI, on utilise le système client/serveur.

Les différentes étapes sont donc :

  1. Etablir la connexion réseau avec la fonction pigpio_start
  2. Configurer le système SPI avec la fonction i2c_open
  3. Ecrire dans un registre avec la fonction i2c_write_byte_data
    ou lire la valeur d'un registre avec la fonction i2c_read_byte_data
  4. Fermer le système spi avec la fonction i2c_close
  5. Libérer le périphérique I2C et terminer la connexion réseau avec la fonction pigpio_stop

Exemple de programme C

Voir le codage du programme C

Fichier de définition bh1745.h

#ifndef __BH1745_H
#define __BH1745_H

#include "bh1745_defs.h"

int opengpio();
void closegpio();
void bh1745Init(void);
unsigned char bh1745manutactureId(void);
void bh1745Mesure(void); 
int bh1745Pret(void) ;
unsigned short bh1745lire(unsigned int  );

#endif
					

La fonction opengpio initialise le périphérique I2C-1 avec l'adresse du capteur après avoir établi la connexion réseau.

La fonction closegpio libère le périphérique I2C et termine la connexion réseau.

Les fonctions accèdent aux registres avec les fonctions de lecture i2c_read_byte_data et d'écriture i2c_write_byte_data.

Fichier de codage des fonctions bh1745.c

#include <stdio.h>
#include <stdlib.h>
#include "pigpiod_if2.h"
#include "bh1745.h"

int fdpio;
int fdi2c;

int opengpio() {
	fdpio = pigpio_start("localhost", "8888");
	if (fdpio < 0) {
		return fdpio;
	}
	fdi2c =  i2c_open(fdpio,1,BH1745_I2CADR, 0);
	return fdi2c;
}

void closegpio() {
	i2c_close(fdpio,fdi2c);
	pigpio_stop(fdpio);
}

void bh1745Init() {
	i2c_write_byte_data(fdpio,fdi2c,BH1745_SYSTEM_CTRL,BH1745_SYSCTRL_SW_RST);
	i2c_write_byte_data(fdpio,fdi2c,BH1745_SYSTEM_CTRL,0);
	i2c_write_byte_data(fdpio,fdi2c,BH1745_CTRL1,BH1745_CTRL1_640ms);
	i2c_write_byte_data(fdpio,fdi2c,BH1745_CTRL2,BH1745_CTRL2_RGBC_EN | BH1745_CTRL2_ADC_G_2);
	i2c_write_byte_data(fdpio,fdi2c,BH1745_CTRL3,BH1745_CTRL3_VALUE);
}

unsigned char bh1745manutactureId() {
	unsigned char mid=0;
	mid = i2c_read_byte_data(fdpio,fdi2c,BH1745_ID);
	return mid;
}

void bh1745Mesure() {
	i2c_write_byte_data(fdpio,fdi2c,BH1745_CTRL3,BH1745_CTRL3_VALUE);
}

int bh1745Pret() {
	int valide = i2c_read_byte_data(fdpio,fdi2c,BH1745_CTRL2);
	return valide & BH1745_CTRL2_VALID;
}

unsigned short bh1745lire(unsigned int LSBReg) {
	unsigned short valeur=0;
	unsigned short lsb,msb;
	lsb = i2c_read_byte_data(fdpio,fdi2c,LSBReg);
	msb = i2c_read_byte_data(fdpio,fdi2c,LSBReg + 1);
	valeur = lsb + (msb << 8);
	return valeur;
}
					
Voir le codage du programme de test
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "bh1745.h"

int main(int argc, char** argv) {
	
	unsigned short red,green,blue,clear;
	
	int res = opengpio();
	if (res == -1) {
		fprintf(stderr,"ERREUR ouverture : %d\n",res);
		return EXIT_FAILURE;
	}	
	bh1745Init();
	unsigned char manufacture = bh1745manutactureId();
	printf("id=%x\n",manufacture);
	usleep(500000L);
	while (!bh1745Pret());
	red = bh1745lire(BH1745_RED_LSB);
	green = bh1745lire(BH1745_GREEN_LSB);
	blue = bh1745lire(BH1745_BLUE_LSB);
	clear = bh1745lire(BH1745_CLEAR_LSB);	
	printf("%u;%u;%u;%u\n",red,green,blue,clear);
	return EXIT_SUCCESS;
}
					

Affichage avec piscope de la lecture de l'indentifiant du capteur

On retrouve les signaux I2C en commençant par l'écriture de la valeur du registre, puis la lecture de la réponse qui est l'identifiant.

Pour l'écriture, on trouve l'adresse du composant 0x38 avec bit de poids faible (R/W) à 0 pour une écriture, suivi de ACK, suivi de la valeur du registre qui est 0x92, suivi de ACK

Ensuite pour la lecture, on trouve l'adresse du composant 0x38 avec le bit de poids faible (R/W) à 1 pour une lecture, suivi de ACK, suivi de la valeur de l'ID fourni par le capteur qui est 0xe0, suivi de NACK.