Systèmes informatique
Fermer ×

Raspberry pi et python

Modules python pour les entrées-sorties

Il existe plusieurs modules python dédiés à la gestion des entrées sorties et bus de communication (I2C, SPI), parmi ces modules, on trouve :

Pour les exemples, on utilise les mêmes GPIOs que ceux du chapitre précédent, ainsi que les mêmes capteur I2C et SPI.

bit76543210
kernedID du GPIO26191365222717

Programmation des GPIOs

Notez que je décline toutes responsabilités quant aux conséquences que pourraient avoir l'utilisation des programmes présentés. Ceux-ci pourraient être erronés ou obsolètes.

Module RPI.GPIO

Ce module fournit un ensemble de fonction de configuration et d'accès aux GPIOs, après avoir importé le module avec :

import RPi.GPIO as GPIO

On peut utiliser les fonctions :

  1. GPIO.setmode(mode) : définit le type d'identifiant à utiliser pour les GPIOs, GPIO.BCM permet d'utiliser les kerbelID, GPIO.BOARD permet d'utiliser directement les numéros du connecteur.
  2. GPIO.setup(numero,direction) : numero correspond au kernelID ou bien au numéro de broche suivant la configuration utilisée. direction vaut GPIO.IN pour une entrée ou GPIO.OUT pour une sortie
    • valeur=GPIO.input(numero) : lit la valeur du GPIO en entrée avec valeur qui peut prendre une des valeurs 0, GPIO.LOW, False, 1, GPIO.HIGH, True.
    • GPIO.output(numero,valeur) : affecte une valeur au GPIO en sortie en utilisant les mêmes possibilités de valeurs.
  3. GPIO.cleanup(liste gpios) : désactive les gpios de la liste
Voir le codage du programme de test des GPIOs avec RPI

import RPi.GPIO as GPIO
from time import sleep

gpios=( 17 , 27, 22 , 5 , 6 , 13 , 19 , 26 )

def initGPIOS(gpios):
	GPIO.setmode(GPIO.BCM)
	for i in range(8):
		GPIO.setup(gpios[i], GPIO.OUT)
		
def EcrirePort(gpios,valeur):
	masque = 1 ;
	for i in range(8):
		GPIO.output(gpios[i], masque & valeur)
		masque <<= 1
			
def main(args):
	ch_val=args[1]
	valeur = int(ch_val,0)
	initGPIOS(gpios)
	EcrirePort(gpios,valeur)
	sleep(1)
	GPIO.cleanup(gpios)
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

La liste des gpios utilise une liste non modifiable.

Pour affecter la valeur de chaque bit de l'octet à chaque sortie, on utilise un masque que l'on décale d'un bit à chaque itération. Cela permet de tester chaque bit de l'octet et de positionner la sortie en fonction de l'état de ce bit.

La valeur de l'octet à écrire est transmis en ligne de commande, puis convertie en entier, Le deuxième paramètre indique la base de conversion, 0 signifie que la base est automatiquement détectée. Un chaîne qui commence par 0x indique une notation hexadécimale, par 0 indique une notation octale, sinon c'est la base 10 qui est utilisée.

La fonction sleep permet de maintenir le résultat avant de tout éteindre en désactivant les GPIOs. Elle est utile pour ce programme de test. Elle est importée à partir du module time

Module GPIOZero

Ce module fournit un ensemble de fonction de configuration et d'accès aux GPIOs, après avoir importé le module avec :

from gpiozero import LED

On peut utiliser les GPIOs en sortie avec les fonctions :

  1. led=LED(numero) : numero correspond au kernelID
  2. ensuite, on peut utiliser :
    • led.value=valeur : affecte une valeur à la sortie avec valeur qui peut être 0,False, 1,True
    • led.on() : pour positionner la sortie à 1
    • led.off() : pour positionner la sortie à 0

En utilisant le module

from gpiozero import Button

On peut utiliser les GPIOs en entrée avec les fonctions :

  1. bouton=Button(numero) : numero correspond au kernelID
  2. ensuite, on peut utiliser :
    • bouton.is_pressed() : qui donne True si l'entrée est à 0, car les boutons sont souvent positionnés par rapport à la masse.
Voir le codage du programme de test des GPIOs avec GPIOZero

from gpiozero import LED
from time import sleep

gpios=( 17 , 27, 22 , 5 , 6 , 13 , 19 , 26 )

def initLEDS(gpios):
	led=[]
	for i in range(8):
		led.append(LED(gpios[i]))
	return led

def EcrirePort(led,valeur):
	masque = 1;
	for i in range(8):
		led[i].value = valeur & masque
		masque <<= 1
			
def main(args):
	ch_val=args[1]
	valeur=int(ch_val,0)
	led=initLEDS(gpios)
	EcrirePort(led,valeur)
	sleep(1)
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

Lors de l'initialisation, on crée le tableau des objets GPIOs de type LED pour des sorties. Pour créer ce tableau d'objets LED, on crée d'abord un tableau vide, puis on ajoute chaque objet LED avec la méthode append.

Pour affecter chaque bit de l'entier, on procède comme précédemment avec un masque qui permet de sélectionner chaque bit.

La fonction sleep permet de maintenir le résultat avant de tout éteindre en désactivant les GPIOs, lors de la destruction implicite de l'objet lorsque le programme se termine.

Module pigpio

Ce module est un client du serveur pigpiod qu'il faut démarrer avant d'utiliser ce module avec la commande en mode root :

systemctl start pigpiod

On importe le module avec, par exemple :

import pigpio as PIGPIO

Ensuite on peut utiliser les fonctions :

  1. pi=PIGPIO.pi() pour établir la connexion avec le serveur en local. Dans le cas de connexion distante, il faut utiliser des paramètres qui sont le nom ou l'adresse ip du serveur et le numéro de port.
  2. pi.set_mode(numero,direction) : définit le GPIO de kernelID numero en entrée si direction vaut PIGPIO.INPUT ou en sortie si direction vaut PIGPIO.OUTPUT
  3. ensuite on utilise le GPIO soit en entrée soit en sortie
    • valeur=pi.read(numero) : la fonction renvoie soit 0 soit 1 à partir du GPIO de kernelID numero.
    • pi.write(numero,valeur) : affecte le niveau de sortie en fonction de valeur qui prend une des valeurs 0, False, 1, True.
  4. pi.stop() pour mettre fin à la connexion avec le serveur. La déconnexion ne désactive pas les GPIOs.

Enfin, si le serveur n'est plus utilisé par d'autres applications, on pourra l'arrêter avec la commande en root :

systemctl stop pigpiod
Voir le codage du programme de test des GPIOs avec pigpio

import pigpio as PIGPIO

gpios=( 17 , 27, 22 , 5 , 6 , 13 , 19 , 26 )

def initGPIOS(pi,gpios):
	for i in range(8):
		pi.set_mode(gpios[i], PIGPIO.OUTPUT)

def EcrirePort(pi,gpios,valeur):
	masque = 1
	for i in range(8):
		bit = bool(valeur & masque)
		pi.write(gpios[i],bit)
		masque <<= 1
	
def main(args):
	ch_val=args[1]
	valeur = int(ch_val,0)
	pi = PIGPIO.pi()
	if not pi.connected:
		print("Erreur connexion")
		return -1
	pi = initGPIOS(pi,gpios)
	EcrirePort(pi,gpios,valeur)
	pi.stop()
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

Lors de l'établissement de la connexion, il convient de vérifier si la connexion a bien été établie avant de continuer. En cas de problème de connexion on termine le programme avec un code d'erreur.

La valeur transmise, après avoir appliqué le masque pour sélectionner un bit de l'octet, doit être convertie en booléen pour être acceptée par la fonction d'écriture.

Après déconnexion au serveur les GPIOs restent en l'état.

Module periphery

Ce module utilise le descripteur /dev/gpiochip0 du système. Après avoir importer le module :

from periphery import GPIO

on peut utiliser les fonctions :

  1. gpio=GPIO(descripteur,numero,direction) : avec descripteur qui vaut "/dev/gpiochip0", numero qui est le kernelID du GPIO, et la direction qui vaut "in" pour une entrée et "out" pour une sortie. Cette fonction renvoie un objet qui représente le GPIO.
    • gpio.write(valeur) : affecte le GPIO corresondant, avec valeur qui est un booléen.
    • valeur=gpio.read() : renvoie une valeur de type booléen
  2. gpio.close() : termine l'accès au GPIO.
Voir le codage du programme de test des GPIOs avec periphery

from periphery import GPIO

gpios=( 17 , 27, 22 , 5 , 6 , 13 , 19 , 26 )

def initLEDS(gpios):
	leds=[]
	for i in range(8):
		led = GPIO("/dev/gpiochip0", gpios[i], "out")
		leds.append(led)
	return leds

def EcrirePort(led,valeur):
	masque = 1
	for i in range(8):
		bit = bool(valeur & masque)
		led[i].write(bit)
		masque <<= 1
		
def closeLEDS(led):
	for i in range(8):
		led[i].close()
			
def main(args):
	ch_val=args[1]
	valeur=int(ch_val,0)
	led=initLEDS(gpios)
	EcrirePort(led,valeur)
	closeLEDS(led)
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

Programmation des sorties PWM

PWM matériel

Ici on utilise le mode PWM matériel des broches GPIOs qui supportent cette fonction, on choisit le GPIO de kernelId 18.

Module RPI.GPIO

A partir du module

import RPi.GPIO as GPIO

Il suffit d'utiliser les fonctions spécifiques au mode PWM

  1. GPIO.setmode(mode) : définit le type d'identifiant à utiliser pour les GPIOs, GPIO.BCM permet d'utiliser les kerbelID, GPIO.BOARD permet d'utiliser directement les numéros du connecteur.
  2. GPIO.setup(numero,direction) : numero correspond au kernelID ou bien au numéro de broche suivant la configuration utilisée. direction vaut GPIO.OUT car la broche est obligatoirement en sortie.
    • pwm=GPIO.PWM(numero,frequence) : avec numero qui est le nunéro kernelID et frequence qui est la fréquence de récurrence du signal PWM en Herz.
    • pwm.start(valeur) : définit le rapport cyclique du signal PWM entre 0 et 100.
    • pwm.stop() : termine le mode PWM et libère le GPIO.
  3. GPIO.cleanup(liste gpios) : désactive les gpios de la liste
Voir le codage du programme de test d'une sortie PWM avec RPI

import RPi.GPIO as GPIO
from time import sleep
	
def main(args):
	ch_val=args[1]
	valeur = float(ch_val)
	GPIO.setmode(GPIO.BCM)
	GPIO.setup(18,GPIO.OUT)
	pwm = GPIO.PWM(18, 100)
	pwm.start(valeur) 
	sleep(1)
	pwm.stop()
	GPIO.cleanup()
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))	
					

Le rapport cyclique est transmis en ligne de commande et converti en entier.

Module GPIOZero

On importe PWMLED du module gpiozero

from gpiozero import PWMLED

on utilise les fonctions

  1. pwm=PWMLED(numero) : construit l'objet pwm avec numero qui correspond au kernelID
  2. pwm.value=valeur : affecte une valeur à la sortie avec valeur qui est un réel compris entre 0 et 1
Voir le codage du programme de test d'une sortie PWM avec GPIOZero

from gpiozero import PWMLED
from time import sleep

def main(args):
	ch_val=args[1]
	valeur=float(ch_val)
	pwm = PWMLED(18)
	pwm.value=valeur
	sleep(1)
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

L'argument transmis en ligne de commande est converti en nombre réel qui doit être compris entre 0 et 1.

L'objet est détruit en fin de programme, la fonction sleep permet de maintenir l'affichage pour visualiser le résultat.

Module pigpio

Ce module est un client du serveur pigpiod qu'il faut démarrer avant d'utiliser ce module avec la commande en mode root :

systemctl start pigpiod

On importe le module avec, par exemple :

import pigpio as PIGPIO

Ensuite on peut utiliser les fonctions :

  1. pi=PIGPIO.pi() pour établir la connexion avec le serveur en local. Dans le cas de connexion distante, il faut utiliser des paramètres qui sont le nom ou l'adresse ip du serveur et le numéro de port.
  2. pi.set_mode(numero,PIGPIO.ALT2) : définit le mode PWM du GPIO en utilisant la fonctionnalité secondaire ALT2
  3. set_PWM_range(18,valeurmax) : définit la plage de valeurs pour le rapport cyclique. entre 0 et valeurmax
  4. set_PWM_frequency(18,frequence) : définit la fréquence de récurrence sur signal PWM
  5. pi.stop() pour mettre fin à la connexion avec le serveur. La déconnexion ne désactive pas le GPIO.
Voir le codage du programme de test d'une sortie PWM avec pigpio

import pigpio as PIGPIO

def initPWM(pi):
	pi.set_mode(18, PIGPIO.ALT2)
	pi.set_PWM_range(18, 100)
	pi.set_PWM_frequency(18,1000)

def EcrireValeur(pi,valeur):
	pi.set_PWM_dutycycle(18,valeur) 
	
def main(args):
	ch_val=args[1]
	valeur = int(ch_val,0)
	pi = PIGPIO.pi()
	if not pi.connected:
		print("Erreur connexion")
		return -1
	initPWM(pi)
	EcrireValeur(pi,valeur)
	pi.stop()
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

Dans la fonction initPWM, on définit le PWM0 de la broche 18 en mode PWM en utilisant la fonction ALT2 du GPIO. Ensuite on définit une plage de réglage du rapport cyclique enter 0 et 100. Enfin on définit la fréquence à 1000Hz.

Le rapport cyclique est défini par l'argument transmis en ligne de commande.

Module periphery

Ce module utilise l'overlay pwm qu'il faut installer en mode root

dtoverlay pwm

C'est l'installation par défaut qui définit le PWM0 du GPIO 18.

Après avoir importer le module :

from periphery import PWM

on peut utiliser les fonctions :

  1. pwm=PWM(circuit,canal) : avec circuit qui vaut 0 et canal qui vaut 0, cela correspond à PWM0 sur le GPIO 18.
  2. pwm.frequency=valeur : permet de définir la fréquence du signal PWM
  3. pwm.duty_cycle=valeur : permet de définir le rapport cyclique avec une valeur réeele comprise entre 0 et 1
  4. pwm.enable() : activation du PWM
  5. pwm.close() : termine l'accès au GPIO.

Après les test si le mode pwm n'est plus utilisé, il peut désinstaller en mode root

dtoverlay -r pwm
Voir le codage du programme de test d'une sortie PWM avec periphery

from periphery import PWM
from time import sleep


def initPWM():
	pwm= PWM(0, 0)
	pwm.frequency = 100
	pwm.duty_cycle = 0.5
	pwm.enable()
	return pwm
	
def EcrireValeur(pwm,valeur):
	pwm.duty_cycle = valeur
	
			
def main(args):
	ch_val=args[1]
	valeur=float(ch_val)
	pwm = initPWM()
	EcrireValeur(pwm,valeur)
	sleep(1)
	pwm.close()
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

Lors de l'initialisation le PWM est défini avec une fréquence de 100Hz et un rapport cyclique de 1/2.

Programmation du bus I2C

Fichier de définitions et programme principal

On reprend la carte capteur de luminosité équipée du capteur bh1745 étudiée dans le chapitre précédent.

On adapte le fichier de définitions des registres au python sous la forme d'attributs statiques (ou attributs de classe). On va également écrire un programme de test du capteur qui ne dépend pas du module utilisé mais seulement d'une classe de gestion du capteur, classe qui, elle, est dépendante du module utilisé.

Voir le codage du fichier de définitions et le programme de test

Classe de définitions des registres


class BH1745_defs():
	I2CADR = 0x38
	SYSTEM_CTRL = 0x40
	CTRL1 = 0x41
	CTRL2 = 0x42
	CTRL3 = 0x44
	RED_LSB = 0x50
	RED_MSB = 0x51
	GREEN_LSB = 0x52
	GREEN_MSB = 0x53
	BLUE_LSB = 0x54
	BLUE_MSB = 0x55
	CLEAR_LSB = 0x56
	CLEAR_MSB = 0x57
	DINT_LSB = 0x58
	DINT_MSB = 0x59
	INTERRUPT = 0x60
	PERSISTENCE = 0x61
	TH_LSB = 0x62
	TH_MSB = 0x63
	TL_LSB = 0x64
	TL_MSB = 0x65
	ID = 0x92
	MANUFACTURER_ID = 0xE0
	SYSCTRL_SW_RST = 0x80
	SYSCTRL_INT_RST = 0x40
	CTRL1_160ms = 0x00
	CTRL1_320ms = 0x01
	CTRL1_640ms = 0x02
	CTRL1_1280ms = 0x03
	CTRL1_2560ms = 0x04
	CTRL1_5120ms = 0x05
	CTRL2_VALID = 0x80
	CTRL2_RGBC_EN = 0x10
	CTRL2_ADC_G_1 = 0x00
	CTRL2_ADC_G_2 = 0x01
	CTRL2_ADC_G_16 = 0x02
	CTRL3_VALUE = 0x02
	INTERRUPT_STATUS = 0x80
	INTERRUPT_LATCH = 0x10
	INTERRUPT_SOURCE = 0xC0
	INTERRUPT_ENABLE = 0x01
	LED_ON = 1
	LED_OFF = 0	
					

Programme de test du capteur


from bh1745_defs import BH1745_defs
from bh1745 import BH1745

def main(args):
	bh1745 = BH1745(1)
	bh1745.device_init()
	identifiant = bh1745.manutactureId()
	print(hex(identifiant))
	pret = bh1745.Pret()
	while (not pret):
		pret = bh1745.Pret()
	red = bh1745.lire(BH1745_defs.RED_LSB);
	green = bh1745.lire(BH1745_defs.GREEN_LSB);
	blue = bh1745.lire(BH1745_defs.BLUE_LSB);
	clear = bh1745.lire(BH1745_defs.CLEAR_LSB);
	print(f"valeurs {red};{green};{blue};{clear}");	
	bh1745.device_close()
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))
					

Ce programme utilise la classe BH1745_defs et la classe BH1745. Cette classe offre les fonctionnalités suivantes

  • device_init() : qui initialise les registres du capteur
  • manutactureId() : qui retourne l'identifiant du capteur
  • Pret() : qui renvoie True lorsque des données sont disponibles
  • lire(registre) : qui lit la valeur d'un canal de couleur et renvoie un entier positif sur 16bits
  • device_close() : qui termine la connexion I2C

Module smBus

On utilise le module smBus en important

from smbus import SMBus

Après on peut utiliser les fonctions

  1. objetI2C=smBus(numero) : crée l'objet objetI2C avec numero qui est le numéro du port I2C, qui est 1.
    • objetI2C.write_byte_data(adresseI2C,registre,valeur) : écrit une valeur dans le registre du capteur situé à l'adresse adresseI2C
    • valeur=objetI2C.read_byte_data(adresseI2C,registre) : renvoie le contenu du registre du capteur
Voir le codage de la classe BH1745 avec smBus

from bh1745_defs import BH1745_defs
from smbus import SMBus

class BH1745():
	def __init__(self,numero=0):
		self.numport = numero
		self.i2c = SMBus(numero)
	
	def device_init(self):
		self.i2c.write_byte_data(BH1745_defs.I2CADR, BH1745_defs.SYSTEM_CTRL , BH1745_defs.SYSCTRL_SW_RST)
		self.i2c.write_byte_data(BH1745_defs.I2CADR, BH1745_defs.SYSTEM_CTRL , 0 )
		self.i2c.write_byte_data(BH1745_defs.I2CADR, BH1745_defs.CTRL1 , BH1745_defs.CTRL1_640ms)
		self.i2c.write_byte_data(BH1745_defs.I2CADR, BH1745_defs.CTRL2 , BH1745_defs.CTRL2_RGBC_EN | BH1745_defs.CTRL2_ADC_G_2)
		self.i2c.write_byte_data(BH1745_defs.I2CADR, BH1745_defs.CTRL3 , BH1745_defs.CTRL3_VALUE)
		
	def device_close(self):
		self.i2c.close()
		
	def manutactureId(self):
		valeur = self.i2c.read_byte_data(BH1745_defs.I2CADR, BH1745_defs.ID)
		return valeur
		
	def Mesure(self):
		self.i2c.write_byte_data(BH1745_defs.I2CADR, BH1745_defs.CTRL3 , BH1745_defs.CTRL3_VALUE )
		
	def Pret(self):
		valeur = self.i2c.read_byte_data(BH1745_defs.I2CADR, BH1745_defs.CTRL2)
		return bool(valeur & BH1745_defs.CTRL2_VALID)
		
	def lire(self,LSBReg):
		lsb = self.i2c.read_byte_data(BH1745_defs.I2CADR, LSBReg)	
		msb = self.i2c.read_byte_data(BH1745_defs.I2CADR, LSBReg + 1)	
		valeur = lsb + (msb << 8);
		return valeur
			

La méthode de calcul de la valeur à partir du msb et lsb ne fonctionne que pour des valeurs positives en python.

Module pigpio

On utilise le module pigpio en important

import pigpio as PIGPIO

Après on peut utiliser les fonctions

  1. pi=PIGPIO.pi() pour établir la connexion avec le serveur en local. Dans le cas de connexion distante, il faut utiliser des paramètres qui sont le nom ou l'adresse ip du serveur et le numéro de port.
  2. objetI2C=pi.i2c_open(numero, adresseI2C) : numero est le numéro du bus I2C (1) et adresseI2C l'adresse I2C du capteur
    • pi.i2c_write_byte_data(objetI2C,registre,valeur) : écrit une valeur dans le registre du capteur représenté par objetI2C
    • valeur=self.pi.i2c_read_byte_data(objetI2C,register) : renvoie le contenu du registre du capteur
  3. pi.i2c_close(objetI2C) : termine l'utilisation de l'I2C
  4. pi.stop() : déconnexion du serveur
Voir le codage de la classe BH1745 avec avec pigpio

from bh1745_defs import BH1745_defs
import pigpio as PIGPIO

class BH1745():
	def __init__(self,numero=0):
		self.numport = numero
		self.pi =   PIGPIO.pi()
		self.i2c =  self.pi.i2c_open(numero, BH1745_defs.I2CADR)
	
	def device_init(self):
		self.pi.i2c_write_byte_data(self.i2c, BH1745_defs.SYSTEM_CTRL , BH1745_defs.SYSCTRL_SW_RST)
		self.pi.i2c_write_byte_data(self.i2c, BH1745_defs.SYSTEM_CTRL , 0 )
		self.pi.i2c_write_byte_data(self.i2c, BH1745_defs.CTRL1 , BH1745_defs.CTRL1_640ms)
		self.pi.i2c_write_byte_data(self.i2c, BH1745_defs.CTRL2 , BH1745_defs.CTRL2_RGBC_EN | BH1745_defs.CTRL2_ADC_G_2)
		self.pi.i2c_write_byte_data(self.i2c, BH1745_defs.CTRL3 , BH1745_defs.CTRL3_VALUE)
		
	def device_close(self):
		self.pi.i2c_close(self.i2c)
		self.pi.stop()
		
	def manutactureId(self):
		valeur = self.pi.i2c_read_byte_data(self.i2c, BH1745_defs.ID)
		return valeur
		
	def Mesure(self):
		self.pi.i2c_write_byte_data(self.i2c, BH1745_defs.CTRL3 , BH1745_defs.CTRL3_VALUE )
		
	def Pret(self):
		valeur = self.pi.i2c_read_byte_data(self.i2c, BH1745_defs.CTRL2)
		return valeur & BH1745_defs.CTRL2_VALID
		
	def lire(self,LSBReg):
		lsb = self.pi.i2c_read_byte_data(self.i2c, LSBReg)	
		msb = self.pi.i2c_read_byte_data(self.i2c, LSBReg + 1)	
		valeur = lsb + (msb << 8);
		return valeur
			

Module periphery

On importe le module :

from periphery import I2C

on peut utiliser les fonctions :

  1. objetI2C=I2C(descripteur) : avec descripteur qui vaut "/dev/i2c-1".
  2. reponse=objetI2C.transfer(liste message) : listemessage est une liste composée de données du type I2C.Message(liste,mode) où liste est une liste de valeurs et mode indique s'il s'agit d'une lecture (read=True) ou d'une écriture (pas de paramètre).
    Le contenu de la liste de message est à construire avant l'utilisation de la fonction transfert.
    La réponse est transmise dans les messages, ce qui fait que la réponse est une liste de message de liste de données.
  3. i2c.close() : termine l'accès au bus I2C.
Voir le codage de la classe BH1745 avec periphery

from bh1745_defs import BH1745_defs
from periphery import I2C

class BH1745():
	def __init__(self,numero=0):
		self.numport = numero
		self.i2c = I2C("/dev/i2c-" + str(numero))
	
	def device_init(self):
		self.i2c.transfer(BH1745_defs.I2CADR, [ I2C.Message([ BH1745_defs.SYSTEM_CTRL , BH1745_defs.SYSCTRL_SW_RST ]) ])
		self.i2c.transfer(BH1745_defs.I2CADR, [ I2C.Message([ BH1745_defs.SYSTEM_CTRL , 0 ])])
		self.i2c.transfer(BH1745_defs.I2CADR, [ I2C.Message([ BH1745_defs.CTRL1 , BH1745_defs.CTRL1_640ms ])])
		self.i2c.transfer(BH1745_defs.I2CADR, [ I2C.Message([ BH1745_defs.CTRL2 , BH1745_defs.CTRL2_RGBC_EN | BH1745_defs.CTRL2_ADC_G_2 ])])
		self.i2c.transfer(BH1745_defs.I2CADR, [ I2C.Message([ BH1745_defs.CTRL3 , BH1745_defs.CTRL3_VALUE ])])
		
	def device_close(self):
		self.i2c.close()
		
	def manutactureId(self):
		msg = [ I2C.Message([ BH1745_defs.ID] ) , I2C.Message([0x00], read=True) ]
		self.i2c.transfer(BH1745_defs.I2CADR,  msg )
		return msg[1].data[0]
		
	def Mesure(self):
		self.i2c.transfer(BH1745_defs.I2CADR,[ I2C.Message([ BH1745_defs.CTRL3 , BH1745_defs.CTRL3_VALUE ]) ] )
		
	def Pret(self):
		msg = [ I2C.Message([ BH1745_defs.CTRL2]) , I2C.Message([0x00], read=True) ]
		self.i2c.transfer(BH1745_defs.I2CADR,  msg  )
		return (msg[1].data[0]) & BH1745_defs.CTRL2_VALID
		
	def lire(self,LSBReg):
		msg = [ I2C.Message([ LSBReg ] ) , I2C.Message([0x00], read=True)]
		self.i2c.transfer(BH1745_defs.I2CADR,  msg )
		lsb = msg[1].data[0]
		msg = [ I2C.Message([ LSBReg + 1 ])	, I2C.Message([0x00], read=True)]
		self.i2c.transfer(BH1745_defs.I2CADR,  msg )	
		msb = msg[1].data[0]
		valeur = lsb + (msb << 8);
		return valeur	
			

En écriture on crée une liste qui contient un seul message de deux valeurs qui sont le registre et la valeur à écrire

En lecture on crée une liste de deux message, le premier message contient le registre, le deuxième message contient 0 avec le paramètre read à True. Ceci pour indiquer une écriture suivi d'une lecture. La réponse est stockée dans le deuxième message qui contient un octet ce qui donne msg[1].data[0]

Programmation du bus SPI

Fichier de définitions et programme principal

On utilise la carte acceléromètre/magnétomètre équipée du capteur LSM303D étudiée dans le chapitre précédent.

Comme pour le capteur précédent, on adapte le fichier de définitions des registres au python sous la forme d'attributs statiques (ou attributs de classe). On va également écrire un programme de test du capteur qui ne dépend pas du module utilisé mais seulement d'une classe de gestion du capteur, classe qui, elle, est dépendante du module utilisé.

Voir le codage du fichier de définitions et le programme de test

Classe de définitions des registres


class LSM303D_defs():
	DIV_2 = 16384
	TEMP_OUT_L = 0x05
	TEMP_OUT_H = 0x06
	STATUS_M = 0x07
	OUT_X_L_M = 0x08
	OUT_X_H_M = 0x09
	OUT_Y_L_M = 0x0A
	OUT_Y_H_M = 0x0B
	OUT_Z_L_M = 0x0C
	OUT_Z_H_M = 0x0D
	WHO_AM_I = 0x0F
	CTRL_0 = 0x1F
	CTRL_1 = 0x20
	CTRL_2 = 0x21
	CTRL_3 = 0x22
	CTRL_4 = 0x23
	CTRL_5 = 0x24
	CTRL_6 = 0x25
	CTRL_7 = 0x26
	STATUS_A = 0x27
	OUT_X_L_A = 0x28
	OUT_X_H_A = 0x29
	OUT_Y_L_A = 0x2A
	OUT_Y_H_A = 0x2B
	OUT_Z_L_A = 0x2C
	OUT_Z_H_A = 0x2D
	STATUS_M_ZXYMOR = 0x80
	STATUS_M_ZMOR = 0x40
	STATUS_M_YMOR = 0x20
	STATUS_M_XMOR = 0x10
	STATUS_M_ZYXMDA = 0x08
	STATUS_M_ZMDA = 0x04
	STATUS_M_YMDA = 0x02
	STATUS_M_XMDA = 0x01
	STATUS_A_ZXYAOR = 0x80
	STATUS_A_ZAOR = 0x40
	STATUS_A_YAOR = 0x20
	STATUS_A_XAOR = 0x10
	STATUS_A_ZYXADA = 0x08
	STATUS_A_ZADA = 0x04
	STATUS_A_YADA = 0x02
	STATUS_A_XADA = 0x01
	CTRL_0_BOOT = 0x80
	CTRL_0_FIFO_EN = 0x40
	CTRL_0_FTH_EN = 0x20
	CTRL_0_HP_CLICK = 0x04
	CTRL_0_HPIS1 = 0x02
	CTRL_0_HPIS2 = 0x01
	CTRL_1_AODR_MASK = 0xF0
	CTRL_1_AODR_3 = 0x80
	CTRL_1_AODR_2 = 0x40
	CTRL_1_AODR_1 = 0x20
	CTRL_1_AODR_0 = 0x10
	CTRL_1_BDU = 0x08
	CTRL_1_AZEN = 0x04
	CTRL_1_AYEN = 0x02
	CTRL_1_AXEN = 0x01
	CTRL_2_ABW_1 = 0x80
	CTRL_2_ABW_0 = 0x40
	CTRL_2_AFS_2 = 0x20
	CTRL_2_AFS_1 = 0x10
	CTRL_2_AFS_0 = 0x08
	CTRL_2_AST = 0x02
	CTRL_2_SIM = 0x01
	CTRL_5_TEMP_EN = 0x80
	CTRL_5_M_RES_1 = 0x40
	CTRL_5_M_RES_0 = 0x20
	CTRL_5_M_ODR_2 = 0x10
	CTRL_5_M_ODR_1 = 0x08
	CTRL_5_M_ODR_0 = 0x04
	CTRL_5_LIR2 = 0x02
	CTRL_5_LIR1 = 0x01
	CTRL_6_MFS1 = 0x40
	CTRL_6_MFS0 = 0x20
	CTRL_7_AHPM_1 = 0x80
	CTRL_7_AHPM_0 = 0x40
	CTRL_7_AFDS = 0x20
	CTRL_7_T_ONLY = 0x10
	CTRL_7_MLP = 0x04
	CTRL_7_MD_1 = 0x02
	CTRL_7_MD_0 = 0x01	
					
				

Programme de test du capteur


from lsm303d_defs import LSM303D_defs
from lsm303d import LSM303D
import math

def main(args):
	lsm303d = LSM303D(0)
	lsm303d.Init()
	identifiant = lsm303d.LireReg(LSM303D_defs.WHO_AM_I)
	print(f"ID = {hex(identifiant)}")
	
	ax = lsm303d.LireAxe(LSM303D_defs.OUT_X_L_A)
	ay = lsm303d.LireAxe(LSM303D_defs.OUT_Y_L_A)
	az = lsm303d.LireAxe(LSM303D_defs.OUT_Z_L_A)
	rax = ax / LSM303D_defs.DIV_2 
	ray = ay / LSM303D_defs.DIV_2 
	raz = az / LSM303D_defs.DIV_2 
	mx = lsm303d.LireAxe(LSM303D_defs.OUT_X_L_M)
	my = lsm303d.LireAxe(LSM303D_defs.OUT_Y_L_M)
	mz = lsm303d.LireAxe(LSM303D_defs.OUT_Z_L_M)
	rmx = mx / LSM303D_defs.DIV_2 
	rmy = my / LSM303D_defs.DIV_2 
	rmz = mz / LSM303D_defs.DIV_2 
	mm = math.sqrt(rmx*rmx+rmy*rmy+rmz*rmz)
	print(f"x={rax:.3f} m/s2,y={ray:.3f} m/s2,z={raz:.3f} m/s2 x={rmx:.3f} g,y={rmy:.3f} g,z={rmz:.3f} g, M={mm:.3f}")
	lsm303d.device_close()
	return 0

if __name__ == '__main__':
	import sys
	sys.exit(main(sys.argv))	
					

Ce programme utilise la classe LSM303D_defs pour la définition des registres et la classe LSM303D pour l'accès aux registres du capteur. Cette dernière fournit les fonctionnalités de gestion du capteur :

  • Init() : qui initialise les registres du capteur
  • valeur=LireReg(registre) : qui lit le contenu d'un registre
  • LireAxe(regsitreAxe) : qui lit la valeur de 16bits signés d'un axe des capteurs (accéléromètre et magnétomètre)

Module SPIdev

On importe le module

import spidev

qui fournit les fonctions

  1. spi=spidev.SpiDev(bus,numero) : crée un objet spi où bus est le numéro du bus SPI (ici 0) et numero est le numéro de la broche de sélection (ici CE0).
  2. spi.max_speed_hz=valeur : valeur est la valeur de la fréquence en Hz
  3. spi.mode=valeur : valeur est le mode du bus SPI compris entre 0 et 3 (ici 0)
  4. reponse=spi.xfer(liste valeurs) : effectue un transfert (écriture/lecture) en utilisant une liste de valeur en paramètre, et fournissant une liste de valeurs en réponse.
  5. spi.close() : termine la connexion SPI
Voir le codage de la classe LSM303D du bus SPI avec SPIdev

from lsm303d_defs import LSM303D_defs
import spidev

class LSM303D():
	def __init__(self,numero=0):
		self.numport = numero
		self.spi = spidev.SpiDev(0,numero)
		self.spi.max_speed_hz = 1000000
		self.spi.mode = 0

	def device_close(self):
		self.spi.close()
		
	def EcrireReg(self,registre,valeur):
		self.spi.xfer([registre , valeur])

	def LireReg(self,registre):
		datas = self.spi.xfer([(registre | 0x80) ,0])
		return datas[1]
		
	def Init(self):
		self.EcrireReg(LSM303D_defs.CTRL_1,LSM303D_defs.CTRL_1_AZEN|LSM303D_defs.CTRL_1_AYEN|LSM303D_defs.CTRL_1_AXEN | LSM303D_defs.CTRL_1_AODR_0)
		self.EcrireReg(LSM303D_defs.CTRL_2,0)
		self.EcrireReg(LSM303D_defs.CTRL_3,0)
		self.EcrireReg(LSM303D_defs.CTRL_4,0)
		self.EcrireReg(LSM303D_defs.CTRL_5,0)
		self.EcrireReg(LSM303D_defs.CTRL_6,0)
		self.EcrireReg(LSM303D_defs.CTRL_7,0)

	def AccPret(self):
		valeur = self.LireReg(LSM303D_defs.STATUS_A)
		bitpret = valeur & LSM303D_defs.STATUS_A_ZYXADA
		return bitpret == LSM303D_defs.STATUS_A_ZYXADA
		
	def LireAxe(self,regbase):
		lsb = self.LireReg(regbase)
		msb = self.LireReg(regbase+1)
		resultat = int.from_bytes([msb, lsb],byteorder='big',signed=True)
		return resultat
			

La conversion des deux octets qui composent le lsb et msb d'un entier signé 16 bits, ne peuvent plus être convertis avec un simple décalage. Il faut donc utiliser la méthode from_bytes de l'objet int en précisant l'ordre des octets ainsi que le type signé ou non signé.

Module pigpio

On utilise le module pigpio en important

import pigpio as PIGPIO

Après on peut utiliser les fonctions

  1. pi=PIGPIO.pi() pour établir la connexion avec le serveur en local. Dans le cas de connexion distante, il faut utiliser des paramètres qui sont le nom ou l'adresse ip du serveur et le numéro de port.
  2. objetSPI=pi.spi_open(numero, frequence, configuration) : numero est le numéro du bus SPI (ici 0) frequence la fréquence de transmission en Hz, configuration qui contient le mode, la broche de sélection et d'autres paramètres (ici 0)
  3. (taille,donnees)=pi.spi_xfer(objetSPI,liste valeurs) : liste valeurs est une liste contenant le registre et la valeur, la réponse comprend deux paramètres qui sont la taille des données renvoyées et la liste des données renvoyées.
  4. pi.spi_close(objetSPI) : termine l'utilisation du SPI
  5. pi.stop() : déconnexion du serveur
Voir le codage de la classe LSM303D du bus SPI avec pigpio

from lsm303d_defs import LSM303D_defs
import pigpio as PIGPIO

class LSM303D():
	def __init__(self,numero=0):
		self.numport = numero
		self.pi =   PIGPIO.pi()
		self.spi = self.pi.spi_open(numero, 100000, 0)

	def device_close(self):
		self.pi.spi_close(self.spi)
		self.pi.stop()
		
	def EcrireReg(self,registre,valeur):
		(count, rx_data) = self.pi.spi_xfer(self.spi, [ registre , valeur])
		return count
		
	def LireReg(self,registre):
		(count, rx_data) = self.pi.spi_xfer(self.spi, [ (registre | 0x80), 0])
		return rx_data[1]
		
	def Init(self):
		self.EcrireReg(LSM303D_defs.CTRL_1,LSM303D_defs.CTRL_1_AZEN|LSM303D_defs.CTRL_1_AYEN|LSM303D_defs.CTRL_1_AXEN | LSM303D_defs.CTRL_1_AODR_0)
		self.EcrireReg(LSM303D_defs.CTRL_2,0)
		self.EcrireReg(LSM303D_defs.CTRL_3,0)
		self.EcrireReg(LSM303D_defs.CTRL_4,0)
		self.EcrireReg(LSM303D_defs.CTRL_5,0)
		self.EcrireReg(LSM303D_defs.CTRL_6,0)
		self.EcrireReg(LSM303D_defs.CTRL_7,0)

	def AccPret(self):
		valeur = self.LireReg(LSM303D_defs.STATUS_A)
		bitpret = valeur & LSM303D_defs.STATUS_A_ZYXADA
		return bitpret == LSM303D_defs.STATUS_A_ZYXADA
		
	def LireAxe(self,regbase):
		lsb = self.LireReg(regbase)
		msb = self.LireReg(regbase+1)
		resultat = int.from_bytes([msb, lsb],byteorder='big',signed=True)
		return resultat