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.
Notez que je décline toutes responsabilités quant aux conséquences que pourraient avoir l'application des méthodes et conseils suivants ainsi que l'utilisation des programmes présentés. Ceux-ci pourraient être erronés ou obsolètes.
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.
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.
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.
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.
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
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
kernedID du GPIO | 26 | 19 | 13 | 6 | 5 | 22 | 27 | 17 |
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 :
Cela se fait avec les commandes echo et cat, ${kernelid} contient le numéro du GPIO :
echo ${kernelid} > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio${kernelid}/directionen sortie :
echo out > /sys/class/gpio/gpio${kernelid}/direction
cat /sys/class/gpio/gpio${kernelid}/valueen sortie :
echo 0 > /sys/class/gpio/gpio${kernelid}/valueou
echo 1 > /sys/class/gpio/gpio${kernelid}/value
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
On peut modifier le Makefile de hello pour l'adapter à ce projet en changeant le nom du programme exécutable
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;
}
}
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;
}
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 :
A chaque étape, le programme se termine en cas d'erreur.
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
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
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.
+-----+-----+---------+------+---+---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 :
gpio -g mode kernelID direction
gpio -g write kernelID valeurou 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
#!/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.
Wiringpi fournit un ensemble de fonction C pour accéder aux 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 chaque 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;
}
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;
}
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
La commande gpioinfo fournit le tableau des gpios, cette fois-ci, on ne parle plus d'id mais de numéro de ligne.
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 :
gpioset gpiochip0 listeavec liste qui est une suite de numéros de ligne avec la valeur 0 ou 1 sous la forme ligne=valeur.
gpioget gpiochip0 listeavec liste une suite de lignes séparées par un espace
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.
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.
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 :
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
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
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);
}
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;
}
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.
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);
}
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.
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 :
sudo apt install pigpiod libpigpio-dev pigpio-tools
sudo apt install libpigpiod-if2-1 libpigpiod-if-dev
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
Ecriture et lecture avec pigs :
pigs w kernelID valeur
pigs r kernedID
En ligne de commande, il faut utiliser la commande pigs qui utilise la méthode client/serveur.
bash
#!/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.
Cette méthode utilise l'API pigpio
L'accès au GPIOs se fait avec les étapes :
Le programme réalisé doit être exécuté en root ou en mode utilisateur avec sudo
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();
}
#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.
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 :
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);
}
#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;
}
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.
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
Le fait que cette méthode soit obsolète, ne nous empêche pas de présenter cette solution.
Cela se fait avec la commande gpio en deux étapes :
gpio -g mode ${kernelID} pwm
gpio -g pwm ${kernedID} ${valeur}
#!/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.
Pour utiliser le mode PWM, il faut suivre les étapes suivantes :
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
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
/pre>La fonction InitPWM initialise le système WiringPi avec l'utilisation des kernelID, puis paramètre la broche en sortie PWM en utilisant 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) ;
}
#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.
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 :
sudo dtoverlay -h pwmpour obtenir les informations sur l'overlay
sudo dtoverlay pwmpour charger et activer l'overlay et le driver
sudo dtoverlay -r pwmpour désactiver l'overlay
Pour charger l'overlay et le driver automatiquement au démarrage, on modifie le fichier /boot/config.txt en ajoutant la ligne :
dtoverlay=pwm
Le chargement crée un nouvelle arborescence /sys/class/pwm/pwmchip0 qui permet d'accéder à la broche 18 en mode pwm sur PWM0.
#!/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.
En C on accède au système /sys/class/pwm/pwmchip0 en utilisant les fonctions d'ouverture, écriture et fermeture des fichiers.
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;
}
#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.
Il ne faut pas oublier de démarrer le serveur :
sudo systemctl start pigpiod
Cela se fait avec le client pigs :
pigs p ${kernedID} ${valeur}
La valeur est comprise entre 0 et 255
#!/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.
Il ne faut pas oublier d'adapter les variables EXEC, CFLAGS et LDFLAGS du Makefile.
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);
}
#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%.
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.
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.
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
#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.
La librairie WiringPI fournit deux fonctions de gestion de l'interface SPI qui sont :
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 :
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;
}
#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.
Comme pour les GPios, on utilise le système client/serveur.
Les différentes étapes sont donc :
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;
}
#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).
L'interface I2C utilise une transmission série synchrone qui fonctionne en half-duplex.
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.
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.
#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
La librairie WiringPI fournit deux fonctions de gestion de l'interface I2C qui sont :
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.
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);
}
}
#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 sensibilité 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).
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
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);
}
#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;
}
Comme pour SPI, on utilise le système client/serveur.
Les différentes étapes sont donc :
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;
}
#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'identifiant 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.