Systèmes informatiques
Fermer ×

Raspberry pico

Architecture de la raspberry pico

image issue du site raspberypi

La Raspberry pico est une carte équipée du microcontrôleur à faible coût RP2040 qui peut être programmée à l'aide d'un SDK dédié ou bien à l'aide de l'IDE Arduino ou encore en python adapté au microcontrôleur : le micropython. La description complète de cette carte est disponible sur la page raspberry rubrique microcontrôleur.

Cette carte dispose de 29 GPIOs qui assurent plusieurs fonctions : entrée sortie binaire, UART, I2C, SPI, PWM , PIO (Programmable Input Output), SIO (Special Input Output).

La broche GPIO25, qui n'apparaît pas, est reliée à une LED sur la carte (LED_BUILTIN de l'exemple blink d'Arduino). Sur la carte certains GPIOs sont dédiés à ces fonctions par défaut. Les 8 blocs (slices) PWM possèdent chacun deux canaux A et B qui sont répartis sur les broches du circuit. Pour chaque bloc les canaux A et B occupent deux GPIOs successifs. C'est cette configuration qui sera utilisée dans les exemples qui seront présentés dans les paragraphes suivants.

Alimenter la carte en maintenant l'appui sur le bouton BOOTSEL permet de positionner la carte en mode programmation. Ce protocole permet de transformer la liaison USB en un espace de stockage (RPI-RP2) accessible depuis l'ordinateur. Le dépôt d'un fichier d'extension uf2 (produit par les outils de développement) dans cet espace de stockage programme la carte et la redémarre. Ce redémarrage ferme l'espace de stockage. Pour la reprogrammer, il faut débrancher la carte et la reconnecter en appuyant sur BOOTSEL. Ce protocole n'est pas nécessaire avec l'IDE Arduino.

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.

Programmation avec le SDK C

Installation

Le SDK est disponible sur le dépôt github. L'installation est parfaitement décrite sur le document Getting started with raspberry pico. Après avoir installé le SDK, il est possible d'essayer le programme blink qui fait clignoter la led de la carte (nommée ici PICO_DEFAULT_LED_PIN).

Il existe également de nombreux exemples sur ce dépôt github.

Création d'un nouveau projet

La création de nouveau projet est également très bien documentée au chapitre 8 de "Getting started with raspberry pico". On propose ici de créer un répertoire $RASPBERRY_PICO_ROOT/projets au même niveau de l'arborescence du répertoire $RASPBERRY_PICO_ROOT/pico-sdk ($RASPBERRY_PICO_ROOT est la racine de l'environnement de travail avec le SDK). Puis dans ce répertoire projets :
  1. Créer le répertoire de l'application (exemple blink)
  2. Dans ce nouveau répertoire : copier le fichier $RASPBERRY_PICO_ROOT/pico-sdk/external/pico_sdk_import.cmake dans le répertoire du projet
  3. Créer le fichier CMakeLists.txt en s'inspirant de celui de l'exemple blink.
  4. Créer le fichier blink.c qui contient l'application de clignotement de la LED interne à la carte.
  5. Il ne faut pas oublier d'initialiser la variable d'environnement PICO_SDK_PATH avec le chemin du répertoire pico-sdk (absolu de préférence).
  6. Après avoir écrit le code source de blink.c :
    1. Créer le répertoire build
    2. Dans le répertoire build faire :
      cmake ..
      make
  7. Si tout s'est bien passé, le fichier blink.uf2 est créé.
  8. Copier ce fichier dans l'espace de stockage de la respberry pico (RPI-RP2) en utilisant le glisser-déposer ou avec la commande cp.
  9. L'espace de stockage n'est plus accessible et la led interne clignote. L'application fonctionne.

Note : la fonction main possède une valeur de retour qui implique normalement l'utilisation de return. Dans le chapitre 1.2. Anatomy of a SDK Application du document pdf Raspberry Pi Pico C/C++ SDK, on trouve une note qui précise que la valeur de retour n'est pas utilisée et que le processus est bloqué à la fin de la fonction.

De plus une application définitive signale toutes les erreurs et ne bloque pas le processus. L'affichage sur la sortie standard est remplacé par un écran spécifique ou bien des leds.

L'accès aux GPIOs utilisent des fonctions dont les prototypes sont définies dans pico/stdlib.h :

Voir les fichiers du projet blink

Exemple :

On commence par l'exemple du chenillard qui utilise les GPIOs de 6 à 13.

Voir les fichiers du projet chenillard

Contenu du fichier CMakeLists.txt


cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(perga_projet C CXX ASM)
set(CMAKE_C_STANDARD 11)

set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(chenillard
chenillard.c
)

pico_enable_stdio_usb(chenillard 1)
pico_enable_stdio_uart(chenillard 1)
pico_add_extra_outputs(chenillard)

target_link_libraries(chenillard pico_stdlib)
					

Contenu du fichier chenillard.c


#include "pico/stdlib.h"

const int leds[8] = {6,7,8,9,10,11,12,13};

void initport(uint8_t masque) {
	int pindir;
	for(int i=0;i<8;i+=1) {
		pindir = (masque >> i) & 1;
		gpio_init(leds[i]);
		if (pindir == 0) {
			gpio_set_dir(leds[i], GPIO_IN);
		}
		else {
			gpio_set_dir(leds[i], GPIO_OUT);
		}
	}  
}

void writeport(uint8_t valeur) {
	int pinvalue;
	for(int i=0;i<8;i+=1) {
		pinvalue = (valeur >> i) & 1;
		gpio_put(leds[i], pinvalue );
	} 
}

int main() {
	
	int valeur;
	valeur = 1 ;
	initport(0xff);
	while (true) {
		valeur = (valeur << 1) % 255; 
		writeport(valeur);
		sleep_ms(250);
	}
}
					

PWM

Le circuit dispose de 8 blocs PWM nommés slices, chacun possède deux canaux associés à deux GPIOs consécutifs. Les principales fonctions de gestion du pwm sont :
  • gpio_set_function(NUMGPIO,GPIO_FUNC_PWM) qui définit la fonction utilisée par le GPIO.
  • pwm_get_default_config() qui initialise la structure de donnée associé au bloc PWM.
  • pwm_gpio_to_slice_num(NUMGPIO) qui retourne le numéro de bloc (slice) associé au GPIO
  • pwm_init(bloc,config,booleen) où bloc est le numéro retourné par la fonction précédente, config est la structure de donnée associée au bloc PWM, et booleen qui vaut true pour démarrer le signal PWM lors de l'initialisation
  • pwm_set_gpio_level(NUMGPIO,valeur où valeur est le rapport cyclique compris entre 0 et 65536.
Voir le code du projet pwm

Contenu du fichier CMakeLists.txt


cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(perga_projets C CXX ASM)
set(CMAKE_C_STANDARD 11)

set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(pwm
pwm.c
)

pico_enable_stdio_usb(pwm 1)
pico_enable_stdio_uart(pwm 1)
pico_add_extra_outputs(pwm)

target_link_libraries(pwm hardware_pwm pico_stdlib)
					

On ajoute la librairie de gestion du pwm : hardware_pwm

Contenu du fichier pwm.c


#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include <math.h>

#define LED 25
#define PERIODE 500
#define DELAI_MS 2

int main() {
	unsigned long n;
	unsigned long y;
	gpio_set_function(LED, GPIO_FUNC_PWM);
	pwm_config config= pwm_get_default_config();
	uint slice = pwm_gpio_to_slice_num(LED); 	
	pwm_init(slice,&config,true);
	while (true) {
		pwm_set_gpio_level(LED,y);
		sleep_ms(DELAI_MS);
		y=32767*(1+sin(2*M_PI*n/PERIODE));
		n = (n + 1) % PERIODE;
	}
}
					

Le GPIO 25 correspond au PWM4 canal B.

Utilisation du timer

Les fonctions de gestion du timer sont définies dans hardware/timer.h, il faut également ajouter la librairie hardware_timer dans le fichier CMakeLists.txt. La principale fonction de gestion du timer est :
  • add_repeating_timer_ms(PERIODE,fonction,parametres fonctions,structure timer) avec PERIODE en ms, fonction qui correspond à la fonction appelée à chaque période, parametres un pointeur vers le paramètre de la fonction et la structure de données du timer
Voir le code du projet chenillard avec timer

Contenu du fichier CMakeLists.txt


cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(perga_projets C CXX ASM)
set(CMAKE_C_STANDARD 11)

set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(chenillard_timer
chenillard_timer.c
)

pico_enable_stdio_usb(chenillard_timer 1)
pico_enable_stdio_uart(chenillard_timer 1)
pico_add_extra_outputs(chenillard_timer)

target_link_libraries(chenillard_timer hardware_timer pico_stdlib)
					

La fonction etape_chenillard utilise une variable statique, qui est initialisée à 128 (0x80) au premier appel de la fonction. Ensuite le contenu de cette variable est conservé.

Le programme principal fait clignoter la LED 14, la fonction associée au timer commande le chenillard.

Contenu du fichier chenillard_timer.c


#include "pico/stdlib.h"
#include "hardware/timer.h"

#define LED 14
#define DELAI_LED_MS 200
#define PERIODE_MS 250

const int leds[8] = {6,7,8,9,10,11,12,13};

void initport(uint8_t masque) {
	int pindir;
	for(int i=0;i<8;i+=1) {
		pindir = (masque >> i) & 1;
		gpio_init(leds[i]);
		if (pindir == 0) {
			gpio_set_dir(leds[i], GPIO_IN);
		}
		else {
			gpio_set_dir(leds[i], GPIO_OUT);
		}
	}  
}

void writeport(uint8_t valeur) {
	int pinvalue;
	for(int i=0;i<8;i+=1) {
		pinvalue = (valeur>> i) & 1;
		gpio_put(leds[i], pinvalue );
	} 
}

bool etape_chenillard(struct repeating_timer *t) {
	static int valeur = 0x80 ;
	valeur = (valeur << 1) % 255; 
	writeport(valeur);	
	return true;
}

int main() {
	struct repeating_timer timer;
	gpio_init(LED);
	gpio_set_dir(LED, GPIO_OUT);
	initport(0xff);
    add_repeating_timer_ms(PERIODE_MS, etape_chenillard, NULL, &timer);
	while (true) {
		gpio_put(LED, 0 );
		sleep_ms(DELAI_LED_MS);
		gpio_put(LED, 1 );
		sleep_ms(DELAI_LED_MS);
	}
}
					

Gestion du bus I2C

Les fonctions de gestion du bus I2C sont définies dans hardware/i2c.h, il faut également ajouter la librairie hardware_i2c dans le fichier CMakeLists.txt. Les principales fonctions de gestion de l'I2C sont :
  • i2c_init(i2c,frequence) pour l'initialisation de la structure de données i2c (adresse de la structure de données) dont l'adresse mémoire doit correspondre à la structure d'un des deux bus I2C : i2c0 ou i2c1. La fréquence correspond à la fréquence de l'horloge SCL.
  • gpio_set_function(NUMGPIO,GPIO_FUNC_I2C) pour affecter les broches GPIOs au bus I2C.
  • i2c_write_blocking(i2c,ADRESSE,buffer,taille,terminaison) pour l'envoi de valeurs sur le bus I2C. i2c est l'adresse de la structure de données, ADRESSE est l'adresse du circuit esclave sur la bus, buffer est l'adresse de la zone mémoire à envoyer sur le bus, taille est le nombre d'octets, terminaison est true pour indiquer que la transmission n'est pas terminée.
  • i2c_read_blocking(i2c,ADRESSE,buffer,taille,terminaison) retourne le nombre d'octets reçus.
  • i2c_deinit(i2c) libère la structure de données.

Exemple avec le capteur de luminosité bh1745 déjà utilisé avec la raspberry pi et l'arduino.

Voir les fichiers de gestion du capteur bh1745

Contenu du fichier 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
					

On utilise le fichier de définitions bh1745_defs.h déjà utilisé avec la raspberry pi et arduino. On ne réécrit pas le code, on utilise le code des projets arduino et raspberry pi. Ce qui change se sont les fonctions de gestion du bus I2C.

La structure de données i2c est globale car elle est utilisée par toutes les fonctions de gestion du bus I2C.

Contenu du fichier bh1745.c


#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "bh1745.h"

const uint sda_pin = 4;
const uint scl_pin = 5;

i2c_inst_t *i2c = i2c0;

void bh1745EcrireReg(unsigned char reg,unsigned char valeur) {
	unsigned char buffer[2];
	buffer[0] = reg;
	buffer[1] = valeur;
	i2c_write_blocking(i2c,BH1745_I2CADR,buffer,2,false);
}

int bh1745LireReg(unsigned char reg) {
	int resultat = 0;
	unsigned char buffer[2];
	buffer[0] = reg;
	i2c_write_blocking(i2c,BH1745_I2CADR,buffer,1,true);
	int nread = i2c_read_blocking(i2c,BH1745_I2CADR,buffer,1,false);
	if (nread != 1) {
		return -1 ;
	}
	resultat = (int)buffer[0];
	return resultat;
}

int bh1745Init() {
	int resultat=0;
	i2c_init(i2c, 100000UL);
	gpio_set_function(sda_pin, GPIO_FUNC_I2C);
	gpio_set_function(scl_pin, GPIO_FUNC_I2C);
	bh1745EcrireReg(BH1745_SYSTEM_CTRL,BH1745_SYSCTRL_SW_RST);
	bh1745EcrireReg(BH1745_CTRL1,BH1745_CTRL1_640ms);
	bh1745EcrireReg(BH1745_CTRL2,BH1745_CTRL2_RGBC_EN|BH1745_CTRL2_ADC_G_2);
	bh1745EcrireReg(BH1745_SYSTEM_CTRL,0);
	bh1745EcrireReg(BH1745_CTRL3,BH1745_CTRL3_VALUE);
	return resultat;
}

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

int bh1745Mesure() {
	int resultat = 0;
	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() {
	i2c_deinit(i2c);
}
					
Voir les fichiers de gestion du projet luminosité

Contenu du fichier CMakeLists.txt


cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(perga_projets C CXX ASM)
set(CMAKE_C_STANDARD 11)

set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(luminosite
luminosite.c
bh1745.c
)

pico_enable_stdio_usb(luminosite 1)
pico_enable_stdio_uart(luminosite 0)
pico_add_extra_outputs(luminosite)

target_link_libraries(luminosite hardware_i2c pico_stdlib)
					

On ajoute le fichier bh1745.c dans la liste des fichiers sources et la librairie hardware_i2c dans la liste des librairies.

Pour utiliser la sortie standard qui correspond à la connexion USB de la carte raspberry pico, il faut, bien évidement, inclure stdio.h, puis initialiser la communication avec la fonction stdio_init_all(), après on peut utiliser les fonctions comme printf.

Du côté de l'ordinateur, il faut utiliser un terminal série avec une vitesse de 921600 bauds. L'identifiant du port série dépend du système d'exploitation, il peut être, par exemple, /dev/ttyACM0.

Contenu du fichier luminosite.c


#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "bh1745.h"

int main() {
	
	unsigned short red,green,blue,clear;
	
	stdio_init_all();
	bh1745Init();
	int id = bh1745manutactureId();
	printf("id = %d\n",id);
	while (true) {
		if (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);	
			sleep_ms(500);
		}	
	}
}
					

Gestion du bus SPI

Les fonctions de gestion du bus SPI sont définies dans hardware/spi.h, il faut également ajouter la librairie hardware_spi dans le fichier CMakeLists.txt. Les principales fonctions de gestion du SPI sont :
  • spi_init(spi,frequence) pour l'initialisation de la structure de données spi (adresse de la structure de données) dont l'adresse mémoire doit correspondre à la structure d'un des deux bus SPI : spi0 ou spi1. La fréquence correspond à la fréquence de l'horloge SCK.
  • gpio_set_function(NUMGPIO,GPIO_FUNC_SPI) pour affecter les broches GPIOs au bus SPI.
  • spi_write_blocking(spi,buffer,taille) pour l'envoi de valeurs sur le bus SPI. spi est l'adresse de la structure de données, buffer est l'adresse de la zone mémoire à envoyer sur le bus, taille est le nombre d'octets.
  • spi_read_blocking(spi,buffer,taille) retourne le nombre d'octets reçus.
  • spi_deinit(spi) libère la structure de données.

Exemple avec l'accéléromètre LSM303D déjà utilisé avec la raspberry pi et l'arduino.

Voir les fichiers de gestion du capteur LSM303D

Contenu du fichier LSM303D.h


#ifndef __LSM303D_H
#define __LSM303D_H

#include "LSM303D_defs.h"

// interface SPI
#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 );
void LSM303Dclose(void);

#endif
					

Contenu du fichier LSM303D.c


#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "LSM303D.h"

const uint miso_pin = 16;
const uint cs_pin = 17;
const uint sck_pin = 18;
const uint mosi_pin = 19;
spi_inst_t *spi = spi0;

void LSM303DEcrireReg(unsigned char registre,unsigned char valeur) {
	unsigned char buffer[2];
	buffer[0] = registre;
	buffer[1] = valeur;	
	gpio_put(cs_pin, 0);
	spi_write_blocking(spi, buffer, 2);
	gpio_put(cs_pin, 1);
}

unsigned char LSM303DLireReg(int registre) {
	unsigned char resultat = 0;
	unsigned char buffer[2];
	buffer[0] = registre | 0x80 ;
	buffer[1] = 0;
	gpio_put(cs_pin, 0);
	spi_write_blocking(spi, buffer, 2);
	int nread = spi_read_blocking(spi, 0, buffer, 2);
	gpio_put(cs_pin, 1);	
	resultat = buffer[1];	
	return resultat;
}

void LSM303DInit(void) {
	gpio_init(cs_pin);
	gpio_set_dir(cs_pin, GPIO_OUT);
	gpio_put(cs_pin, 1);
	gpio_set_function(sck_pin, GPIO_FUNC_SPI);
	gpio_set_function(mosi_pin, GPIO_FUNC_SPI);
	gpio_set_function(miso_pin, GPIO_FUNC_SPI);	
	spi_init(spi, 1000000UL);
	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;
}

void LSM303Dclose() {
	spi_deinit (spi);
}
					
Voir les fichiers de gestion du projet accéléromètre

Contenu du fichier CMakeLists.txt


cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(perga_projets C CXX ASM)
set(CMAKE_C_STANDARD 11)

set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(accelmag
accelmag.c
LSM303D.c
)

pico_enable_stdio_usb(accelmag 1)
pico_enable_stdio_uart(accelmag 0)
pico_add_extra_outputs(accelmag)

target_link_libraries(accelmag hardware_spi pico_stdlib)
					

On ajoute le fichier LSM303D.c dans la liste des fichiers sources et la librairie hardware_spi dans la liste des librairies.

Contenu du fichier accelmag.c


#include <stdio.h>
#include <math.h>
#include "pico/stdlib.h"
#include "LSM303D.h"

int main() {
	
	unsigned char id;
	int ax,ay,az;
	double rax,ray,raz;
	int mx,my,mz;
	double rmx,rmy,rmz,mm;
	
	stdio_init_all();
	LSM303DInit();
	id = LSM303DLireReg(LSM303D_WHO_AM_I);
	printf("ID = %x\n",id);
	while (true) {
		if (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);
		}
		sleep_ms(500);
	}
}
					

Programmation avec l'IDE d'Arduino

Installation de la carte

L'installation de la carte n'est pas forcément disponible dans l'IDE arduino, mais il est possible d'utiliser la solution arduino pico. L'installation peut se faire en ajoutant le lien https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json dans le gestionnaire de sites du menu préférences.

Ensuite on utilise le gestionnaire de carte pour ajouter Raspberry Pi Pico/RP2040.

La programmation de la carte nécessite le démarrage avec BOOTSEL uniquement lors de la première utilisation avec l'IDE. Pour les programmations suivantes c'est l'IDE qui gère automatiquement la programmation.

Exemples

On reprend les exemples utilisés avec l'arduino et la carte uno. Le code reste le même, il suffit de changer les valeurs des GPIOs pour utiliser les valeurs de la carte raspberry pico que ce soit pour le chenillard, le bus SPI ou encore le bus I2C.

Chenillard

Voir le code du programme chenillard

Contenu du fichier chenillard.ino


const int leds[8] = {6,7,8,9,10,11,12,13};

void initport(uint8_t masque) {
  int pindir;
  for(int i=0;i<8;i+=1) {
    pindir = (masque >> i) & 1;
    pinMode(leds[i], pindir);
  }  
}

void writeport(uint8_t valeur) {
  int pinvalue;
  for(int i=0;i<8;i+=1) {
    pinvalue = (valeur>> i) & 1;
    digitalWrite(leds[i], pinvalue);
  } 
}

int valeur;

void setup() {
  initport(0xff);
  valeur = 1;
}

void loop() {
  writeport(valeur);
  delay(200);
  valeur = (valeur << 1) % 255; 
}
					

La seule différence avec le programme de l'arduino uno est la liste des ID des 8 leds.

PWM

Voir le code du programme pwm

Contenu du fichier pwm.ino


#define LED 25
#define PERIODE 500
#define DELAI_MS 2

unsigned int n;
unsigned int y;

void setup() {
  pinMode(LED,OUTPUT);
  y=0;
}

void loop() {
  analogWrite(LED,y);
  delay(DELAI_MS);
  y=127*(1+sin(2*PI*n/PERIODE));
  n = (n + 1) % PERIODE;
}						
					

Il suffit juste de changer la valeur de la sortie GPIO

Chenillard avec timer

La version utilisée pour l'arduino uno ne fonctionne pas avec la raspberry pico car elle fait appel au timer matériel de l'Atmega. Il faut donc utiliser une librairie adaptée RPI_PICO_TimerInterrupt au processeur RP2040 de la carte raspberry pico. Cette Librairie s'installe à partir de l'IDE.

La portabilité des programmes Arduino sur tous les processeurs et plates-formes a ses limites.

Voir le code du programme chenillard avec timer

Contenu du fichier chenillard_timer.ino


// These define's must be placed at the beginning before #include "TimerInterrupt_Generic.h"
// _TIMERINTERRUPT_LOGLEVEL_ from 0 to 4
// Don't define _TIMERINTERRUPT_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system.
#define TIMER_INTERRUPT_DEBUG         0
#define _TIMERINTERRUPT_LOGLEVEL_     0

// Can be included as many times as necessary, without `Multiple Definitions` Linker Error
#include "RPi_Pico_TimerInterrupt.h"

#define PERIODE_US  250000UL
#define DELAI_MS 200

#define LED 14
const int leds[8] = {6,7,8,9,10,11,12,13};
uint8_t compteur;
int bascule=0;

// Init RPI_PICO_Timer
RPI_PICO_Timer ITimer0(0);

void initport() {
  for(int i=0;i<8;i+=1) {
    pinMode(leds[i],OUTPUT);
    digitalWrite(leds[i],LOW);
  }  
}

bool TimerChenillard(struct repeating_timer *t) {
  if (compteur == 0) {
    digitalWrite(leds[7],LOW);
  }
  else {
    digitalWrite(leds[compteur-1],LOW);
  }
  digitalWrite(leds[compteur],HIGH);
  compteur = (compteur + 1) % 8; 
  return true;
}

void setup() {
  pinMode(LED,OUTPUT);
  digitalWrite(LED,bascule);
  initport();
  compteur = 0 ;
  ITimer0.attachInterruptInterval(PERIODE_US, TimerChenillard);
}

void loop() {
  delay(DELAI_MS);
  bascule = (~bascule & 1);
  digitalWrite(LED,bascule);
}
					

Ce code, inspiré des exemples disponibles, ressemble à celui de la carte Arduino car l'interface est quasi identique. Ici on construit l'objet ITimer alors que pour la carte uno, il est déjà construit, il n'y pas de méthode Init comme c'est le cas pour la carte uno. La période est en microsecondes au lieu de millisecondes. La fonction d'interruption à une valeur de retour de type booléen alors qu'elle n'en a pas pour la carte uno.

I2C et luminosité

Voir le code du programme luminosite

Contenu du fichier luminosite.ino


#include <Wire.h>
#include "bh1745_defs.h"

#define GP_SDA  16
#define GP_SCL  17

void ecrireRegistre(byte registre, byte valeur) {
  Wire.beginTransmission(BH1745_I2CADR);
  Wire.write(registre);
  Wire.write(valeur);
  Wire.endTransmission();
}

int lireRegistre(byte registre) {
  uint8_t recu;
  Wire.beginTransmission(BH1745_I2CADR);
  Wire.write(registre);
  Wire.endTransmission();
  Wire.requestFrom(BH1745_I2CADR, 1);
  if (Wire.available() == 1) {
    recu = Wire.read();
    return recu;
  } 
  else {
    return -1;
  }
}

void bh1745Init() {
  ecrireRegistre(BH1745_SYSTEM_CTRL,BH1745_SYSCTRL_SW_RST);
  ecrireRegistre(BH1745_SYSTEM_CTRL,0);
  ecrireRegistre(BH1745_CTRL1,BH1745_CTRL1_320ms);
  ecrireRegistre(BH1745_CTRL2,BH1745_CTRL2_RGBC_EN);
  ecrireRegistre(BH1745_CTRL3,BH1745_CTRL3_VALUE);
  ecrireRegistre(BH1745_TH_LSB,0);
  ecrireRegistre(BH1745_TH_MSB,0);
  ecrireRegistre(BH1745_TL_LSB,0xff);
  ecrireRegistre(BH1745_TL_MSB,0xff);
  ecrireRegistre(BH1745_INTERRUPT,0);
}

unsigned char bh1745manutactureId() {
  unsigned char mid;
  mid = lireRegistre(BH1745_ID);
  return mid;
}

void bh1745Mesure() {
  ecrireRegistre(BH1745_CTRL3,BH1745_CTRL3_VALUE);
}

boolean bh1745Pret() {
  int valide = lireRegistre(BH1745_CTRL2);
  return ((valide & BH1745_CTRL2_VALID) == BH1745_CTRL2_VALID);
}

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

void bh1745Led(int etat) {
  if (etat) {
    ecrireRegistre(BH1745_INTERRUPT,BH1745_INTERRUPT_ENABLE);
  }
  else {
    ecrireRegistre(BH1745_INTERRUPT,0);
  }
}

unsigned short red,green,blue,luminosite;
boolean pret;

void setup() {
  Serial.begin(19200);
  Wire.setSDA(GP_SDA);
  Wire.setSCL(GP_SCL);
  Wire.setClock(100000);
  Wire.begin();
  bh1745Init();
  unsigned char manufacture = bh1745manutactureId();
  Serial.print("id=");
  Serial.println(manufacture,HEX);
}

void loop() {

  pret = bh1745Pret();
  if (pret) {
    red = bh1745lire(BH1745_RED_LSB);
    green = bh1745lire(BH1745_GREEN_LSB);
    blue = bh1745lire(BH1745_BLUE_LSB);
    luminosite = bh1745lire(BH1745_CLEAR_LSB);
    Serial.print("rouge=");
    Serial.print(red);
    Serial.print(",vert=");
    Serial.print(green);
    Serial.print(",bleu=");
    Serial.print(blue);
    Serial.print(",clear=");
    Serial.println(luminosite);
  }
  delay(200);
}
					

L'objet Wire utilise le port I2C 0, pour le port i2C 1, il faut utiliser l'objet Wire1.

L'utilisation des fonctions Wire.setSDA(ID) et Wire.setSCL(ID) ne sont utilisées que si on utilise pas les valeurs par défaut des GPIOs 4 et 5. Ce qui signifie que si on utilise le port I2C par défaut, il n'y a rien à changer dans le programme destiné à la carte uno.

SPI et accéléromètre

Voir le code du programme accelmag

Contenu du fichier luminosite.ino


#include <SPI.h>
#include "LSM303D_defs.h"

//#define NODEFAULT

#ifdef NODEFAULT
  #define GP_MISO 4
  #define GP_SS   5
  #define GP_SCK  6
  #define GP_MOSI 7
#else
  #define GP_SS SS
#endif

SPISettings LSM303Dsettings(1000000UL, MSBFIRST, SPI_MODE0);

// calibre +-2
#define DIV_2 16384.0

void LSM303DEcrireReg(unsigned char registre,unsigned char valeur) {
  digitalWrite(GP_SS, LOW); 
  SPI.transfer(registre);
  SPI.transfer(valeur);
  digitalWrite(GP_SS, HIGH); 
  delayMicroseconds(1);
}

unsigned char LSM303DLireReg(unsigned char registre) {
  unsigned char resultat = 0;
  digitalWrite(GP_SS, LOW); 
  SPI.transfer(registre | 0x80);
  resultat = SPI.transfer(0);
  digitalWrite(GP_SS, HIGH); 
  delayMicroseconds(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);
}

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

short LSM303DLireAxe(unsigned int regbase) {
  short resultat =0;
  unsigned char lsb,msb;
  lsb=LSM303DLireReg(regbase);
  msb=LSM303DLireReg(regbase+1);
  resultat = (short)lsb | (((short)msb) << 8) ; 
  return resultat;
}

unsigned char id;
int ax,ay,az;
double rax,ray,raz;
int mx,my,mz;
double rmx,rmy,rmz,mm;

void printData(const String nom,double valeur) {
  Serial.print(nom);
  Serial.print(valeur);
}

void setup() {
  Serial.begin(19200);
  pinMode(GP_SS, OUTPUT); 
  digitalWrite(GP_SS, HIGH); 
#ifdef NODEFAULT
  SPI.setRX(GP_MISO);
  SPI.setCS(GP_SS);
  SPI.setSCK(GP_SCK);
  SPI.setTX(GP_MOSI);
#endif
  SPI.begin(); 
  SPI.beginTransaction(LSM303Dsettings);
  LSM303DInit();
  id = LSM303DLireReg(LSM303D_WHO_AM_I);
  Serial.print("ID = ");
  Serial.println(id,HEX);
  delay(100);
}

void loop() {
    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);
    printData("ax=",rax);
    printData(" ay=",ray);
    printData(" az=",raz);
    printData(", mx=",rmx);
    printData(" my=",rmy);
    printData(" mz=",rmz);
    printData(", M=",mm);
    Serial.println("");
}
					

Comme pour le port I2C, l'objet SPI utilise le port SPI 0, l'objet SPI1 utilise le port SPI 1.

De la même façon, si on utilise le port SPI par défaut (16,17,18,19), il n'y a rien à changé par rapport au code destiné à la carte uno, si on utilise un autre port SPI, il faut donc définir les broches utilisées comme cela est réalisé dans le code. La broche de sélection de circuit SS est également définie par défaut.

Programmation en python

Installation et utilisation

On utilise micropython qui est une version de python adaptée aux microcontrôleurs

Pour installer le firmware micropython, il faut suivre la documentation sur le SDK python ou encore directement télécharger le firmware de la raspberry pico le tout disponible sur la page micropython du site raspberry, en faisant attention à bien choisir le firmware (uf2) en fonction du type de raspberry pico (avec ou sans wifi). Ensuite, il suffit de déposer le fichier uf2 sur la pico après l'avoir démarrée en activant le poussoir bootsel.

Pour l'utilisation, il est conseillé d'utiliser le logiciel recommandé Thony qui permet de se connecter à la carte et de tester les programmes python. Ce Comme cela est décrit dans le paragraphe 4.1.1. Blinking the LED from Thonny de la documentation Raspberry Pi Pico Python SDK, si on sauve le programme dans la pico avec le nom main.py, celui-ci s'exécutera automatiquement au démarrage de la pico.

Très Important : cette opération peut s'avérer être irréversible , car après on n'a plus accès à la pico avec le logiciel. De plus, recharger le firmware micropython ne résout pas le problème. On évitera donc d'appeler le programme principal python main.py.

La gestion des périphériques nécessitent d'utiliser un ensemble de modules micropython adaptés à la pico.

GPIOs

L'accès aux GPIOs se fait avec la classe Pin du module machine. Les principales méthodes sont :

  1. objetgpio=Pin(GPIO,direction,valeur initiale) : constructeur avec GPIO qui est le numéro du GPIO, direction qui vaut Pin.IN pour une entrée ou bien Pin.OUT pour une sortie, dans ce dernier cas, il est possible de préciser la valeur initiale.
    • valeur = objetgpio.value() : qui, dans le cas d'une entrée, renvoie la valeur de la broche 0 ou 1
    • objetgpio.value(valeur) : pour activer la sortie avec valeur
    • objetgpio.on() : pour activer à 1 la sortie
    • objetgpio.off() : pour activer à 0 la sortie.
Voir le code du programme blink
Voir le code du programme chenillard

from machine import Pin,Timer
import time

GPIOS =  (6,7,8,9,10,11,12,13 )

def initPort(gpios):
    leds=[]
    for i in range(8):
        led = Pin(gpios[i], Pin.OUT)
        leds.append(led)
    return leds
    
def EcrirePort(leds,valeur):
    masque = 1
    for i in range(8):
        leds[i].value(valeur & masque)
        masque <<= 1

leds=initPort(GPIOS)
valeur = 1
EcrirePort(leds,valeur)
while True:
    pwm4b.duty_u16(y)
    time.sleep_ms(DELAI_MS)
    y=int(32767*(1+sin(2*pi*n/PERIODE)))
    n = (n+1) % PERIODE
					

On a une fonction d'initialisation du port 8 bits qui renvoie un tableau d'objets GPIOs qui seront ensuite utilisés par la méthode d'écriture.

La liste est crée avant d'ajouter chaque objet à cette liste.

La fonction d'écriture utilise un masque pour sélectionner chaque bit de l'octet.

Le décalage d'un bit avec l'opérateur de décalage. Le modulo 255 permet de revenir au bit 0 après le bit 7.

A chaque fois que cela est possible, on doit donc remplacer la boucle infinie while True par une fonction appelée par un timer, ce qui a pour effet de ne pas bloquer le système.

La classe Timer du module machine qui fournit les méthodes

  1. objettimer=Timer() : constructeur vide ou bien avec les paramètres d'initialisation
  2. objettimer.init(mode,frequence,periode,fonction) : initialise le timer en mode appel unique Timer.ONE_SHOT ou bien périodique Timer.PERIODIC, frequence (nom freq) est la fréquence d'appel de la fonction en Hz ou periode (period) est la période en ms, fonction (nom callback) est la fonction qui sera appelée une seule fois ou périodiquement,
  3. objettimer.deinit() : désactive le timer
Voir le code du programme blink avec un timer
Voir le code du programme chenillard avec un timer

from machine import Pin,Timer
import time

GPIOS =  (6,7,8,9,10,11,12,13 )

def initPort(gpios):
    leds=[]
    for i in range(8):
        led = Pin(gpios[i], Pin.OUT)
        leds.append(led)
    return leds
    
def EcrirePort(leds,valeur):
    masque = 1
    for i in range(8):
        leds[i].value(valeur & masque)
        masque <<= 1

def chenille(x):
    global leds,valeur
    valeur = (valeur << 1) % 255
    EcrirePort(leds,valeur)


leds=initPort(GPIOS)
valeur = 1
EcrirePort(leds,valeur)
clignotement = Timer()
clignotement.init(mode=Timer.PERIODIC, freq=10, callback=chenille)
time.sleep(20)
clignotement.deinit()
					

Le timer est initialisé en mode périodique avec une fréquence de 10 Hz, la fonction appelée est la fonction chenille. Cette doit contenir un paramètre.

Après avoir lancer le timer, on attend 20 secondes avant de terminer le programme et désactiver le timer.

PWM

L'utilisation des sorties PWM se fait avec la classe PWM du module machine. Les principales fonctions sont :

  1. objetpwm=PWM(objetgpio,frequence,rapport_cyclique) : objetgpio est l'objet créé avec le constructeur Pin, frequence la fréquence de récurrence du signal en Hz, et rapport_cyclique le rapport cyclique initial entre 0 et 65535.
    • objetpwl.duty_u16(rapport_cyclique) : permet de régler le rapport cyclique
Voir le code du programme pwm qui fait varier la luminosité de la led de la carte

from machine import Pin,PWM
from math import *
import time

GPIOLED = const(25)
PERIODE = const(500)
DELAI_MS  = const(4)

pwm4b = PWM(Pin(GPIOLED), freq=100, duty_u16=0)
n = int(0)
y = int(0)
while True:
    pwm4b.duty_u16(y)
    time.sleep_ms(DELAI_MS)
    y=int(32767*(1+sin(2*pi*n/PERIODE)))
    n = (n+1) % PERIODE
					

On utilise la LED de la carte qui correspond au PWM4B.

Ce programme produit une variation sinusoïdale de la luminosité en sachant que la luminosité n'est pas linéaire avec la tension.

La période de la sinusoïde est de 500×4ms soit 2 secondes

La valeur du signal de sortie est comprise entre 0 et la tension maximale.

I2C

L'utilisation du bus I2C de fait avec la classe I2C du module machine. Les principales fonctions sont :

  1. objeti2c=IEC(numero) : constructeur avec numero qui est l'identifiant du bus I2C
    • objeti2c.writeto(adresse,buffer) : écriture vers le composant correspondant à adresse avec buffer qui est du type bytearray.
    • buffer=objeti2c.readfrom(adresse,nb) : lecture depuis le composant correspondant à adresse avec nb qui est le nombre d'octets à lire, renvoie les données dans buffer qui est du type bytearray.

On reprend l'exemple de la raspberry pi avec le capteur BH1745

Voir le code du programme de test et du fichier de définition des constantes

Fichier de définition des constantes bh1745_defs.py


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 luminosite.py


from bh1745_defs import BH1745_defs
from bh1745 import BH1745

bh1745 = BH1745(0)
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()
					
Voir le code de la classe BH1745

from bh1745_defs import BH1745_defs
from machine import I2C

class BH1745():
    def __init__(self,numero=0):
        self.numport = numero
        self.i2c = I2C(numero)
        
    def device_init(self):
        buffer = bytearray([ BH1745_defs.SYSTEM_CTRL , BH1745_defs.SYSCTRL_SW_RST ] )
        self.i2c.writeto(BH1745_defs.I2CADR, buffer)
        buffer = bytearray([ BH1745_defs.SYSTEM_CTRL , 0 ])
        self.i2c.writeto(BH1745_defs.I2CADR, buffer )
        buffer = bytearray([ BH1745_defs.CTRL1 , BH1745_defs.CTRL1_640ms ]  )
        self.i2c.writeto(BH1745_defs.I2CADR, buffer )
        buffer = bytearray([ BH1745_defs.CTRL2 , BH1745_defs.CTRL2_RGBC_EN | BH1745_defs.CTRL2_ADC_G_2 ] )
        self.i2c.writeto(BH1745_defs.I2CADR, buffer)
        buffer = bytearray([ BH1745_defs.CTRL3 , BH1745_defs.CTRL3_VALUE ])
        self.i2c.writeto(BH1745_defs.I2CADR,buffer )
        
    def device_close(self):
        pass
        
    def manutactureId(self):
        buffer_in = bytearray([BH1745_defs.ID])
        self.i2c.writeto(BH1745_defs.I2CADR, buffer_in)
        buffer_out = self.i2c.readfrom(BH1745_defs.I2CADR,1)
        return buffer_out[0]
        
    def Mesure(self):
        buffer = bytearray([BH1745_defs.CTRL3 , BH1745_defs.CTRL3_VALUE])
        self.i2c.writevto(BH1745_defs.I2CADR, buffer )
        
    def Pret(self):
        buffer_in = bytearray([BH1745_defs.CTRL2])
        self.i2c.writeto(BH1745_defs.I2CADR, buffer_in)
        buffer_out = self.i2c.readfrom(BH1745_defs.I2CADR, 1)
        return bool(buffer_out[0] & BH1745_defs.CTRL2_VALID)
        
    def lire(self,LSBReg):
        buffer_in = bytearray([LSBReg])
        self.i2c.writeto(BH1745_defs.I2CADR, buffer_in)
        lsb = self.i2c.readfrom(BH1745_defs.I2CADR, 1)
        buffer_in = bytearray([LSBReg + 1])
        self.i2c.writeto(BH1745_defs.I2CADR, buffer_in)
        msb = self.i2c.readfrom(BH1745_defs.I2CADR,1)   
        valeur = lsb[0] + (msb[0] << 8);
        return valeur
					

Avant l'écriture, le buffer est construit à partir d'une liste qui contient le registre et la valeur.

Pour lire le contenu d'un registre, il faut effectuer une écriture avec la valeur du registre, suivi d'une lecture d'un octet. L'octet renvoyé est disponible à l'indice 0 du buffer de réception.

Pour la lecture d'une donnée on effectue l'écriture et l'écriture pour chaque registre de poids faible et de poids fort. Pour construire une valeur non signée à partir des deux octets, on utilise le décalage et l'addition

SPI

L'utilisation du bus SPI se fait avec la classe SPI du module machine. Les principales fonctions sont :

  1. objetspiSPI(numero, frequence) construit un objet SPI avec numero qui correspond à l'identifiant du bus SPI et frequence qui correspond à la fréquence de l'horloge SPI en Hz.
    • objetspi.write(buffer) : écrit le contenu de buffer qui est du type bytearray
    • buffer_out=objetspi.write_readinto(buffer_in) écrit le contenu de buffer_in avec une lecture simultanée dans buffer_in. Ces deux buffers sont du type bytearray

On reprend l'exemple de la raspberry pi avec le capteur LSM303D

Voir le code du programme de test et de définition des constantes

Fichier de définition des constantes lsm303d_defs.py


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 accelmag.py


from lsm303d_defs import LSM303D_defs
from lsm303d import LSM303D
import math

lsm303d = LSM303D(0)
lsm303d.Init()
identifiant = lsm303d.LireReg(LSM303D_defs.WHO_AM_I)
print(f"ID = {hex(identifiant)}")
while (True):
    if (lsm303d.AccPret()):
        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()
					
Voir le code de la classe LSM303D

from lsm303d_defs import LSM303D_defs
from machine import Pin,SPI
import struct

class LSM303D():
    def __init__(self,numero=0):
        self.numport = numero
        self.spi = spi = SPI(numero, 1000000)
        self.cs = Pin(SPI_CS,mode=Pin.OUT,value=1)

    def device_close(self):
        self.spi.deinit()
        
    def EcrireReg(self,registre,valeur):
        buffer = bytearray([ registre , valeur])
        try:
            self.cs(0)
            self.spi.write(buffer)
        finally:
            self.cs(1) 

    def LireReg(self,registre):
        buffer_out = bytearray([ registre  | 0x80 ,0 ])
        buffer_in = bytearray(len(buffer_out))
        try:
            self.cs(0)
            self.spi.write_readinto(buffer_out,buffer_in)
        finally:
            self.cs(1)
        return buffer_in[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)
        buffer = bytearray([msb,lsb])
        resultat = (struct.unpack(">h",buffer))
        return resultat[0]
					

Pour l'écriture, le buffer est constitué de la valeur du registre suivi du contenu du registre

Pour la lecture simultanée, le buffer d'écriture buffer_out est constitué du registre suivi de la valeur 0 qui correspond à la lecture des données. Le buffer de lecture buffer_in contient un premier octet (0xff) suivi de l'octet qui contient la réponse.

Il ne faut pas oublier de positionner le signal CS afin de sélectionner le circuit.

L'accès au bus utilise une gestion d'erreur déjà vue dans le chapitre sur la programmation en python. on a ajouté la directive finally qui permet de repositionner le signal cs même en cas d'erreur.

La conversion des deux octets en entier signé n'est pas possible avec int.bytes_from() en micropython. Ici on doit utiliser la fonction unpack du module struct. Cette fonction va décompacter le contenu du buffer dans une liste d'entiers signés 16 bits (symbole h) respectant le format big endian ( symbole >). La liste ne contient qu'un seul élément car le buffer ne contient que deux octets.