Aller au contenu

Circuitpython

Introduction

Circuitpython est un dérivé de micropython, adaptation de python pour les microcontroleurs.

Installation

Pour le raspberry Pico et les SAMD21, l'installation se fait en copiant un fichier UF2 sur le microcontroleur démarré en mode boot.

Télécharger l'UF2 du Raspberry Pico

Pour les ESP32, on peut flasher le fichier bin avec un outil web

Télécharger le bin pour le LOLIN S3

Utilisation
  • CrĂ©er et modifier simplement un fichier nommĂ© code.py Ă  la racine du système de fichier CIRCUITPY après avoir branchĂ© la board sur votre PC.

Remarque

L'IDE mu-editor est prévue spécifiquement pour interfacer les boards CIRCUITPY. Une extension Circuitpython est également disponible dans VScode.

Ajout de librairies
  • CrĂ©er un dossier lib qui contiendra les librairies supplĂ©mentaires.

Remarque

De nombreuses librairies sont diffusées dans les bundle officiels et communautaires. D'autres se trouvent sur github. Pour un gain de place et de ressources du microcontrolleur, elles sont précompilées dans un format mpy.

  • Pour les installer facilement, installer l'outil circup

    pip install circup
    

  • Une fois votre board CIRCUITPY connectĂ©e, l'installation d'une librairie se fait en une commande.

  • Par exemple, pour installer la librairie adafruit_ble_radio :

    circup install adafruit_ble_radio
    

Périphériques d'entrée
Joystics analogique Ă  2 axes

  • Protocole de communication : 2x entrĂ©es analogiques
Exemple d'utilisation
import time
import board
import analogio
ax = analogio.AnalogIn(board.GPIO0)
ay = analogio.AnalogIn(board.GPIO1)

def range_map(x, in_min, in_max, out_min, out_max):
    return (x - in_min) * (out_max - out_min) // (in_max - in_min) + out_min

while True:
    x=range_map(ax.value, 0, 4095, -127, 127)
    y=range_map(ay.value, 0, 4095, -127, 127)
    print("x", x, "y", y)
    time.sleep(0.1)

Remarque

Il n'y a que 4 Analog In avec le RP2040. Il peut ĂŞtre utile d'utiliser un multiplexeur comme le module hc4067.

Encodeurs rotatifs

Exemple d'utilisation
import rotaryio #pour l'encodeur rotatif
import digitalio #pour le bouton
import board

encoder = rotaryio.IncrementalEncoder(board.GPIO10, board.GPIO9)

button = digitalio.DigitalInOut(board.GPIO12)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP

button_state = None
last_position = None

while True:
    position = encoder.position
    if last_position is None or position != last_position:
        print(position)
    last_position = position
    if not button.value and button_state is None:
        button_state = "pressed"
    if button.value and button_state == "pressed":
        print("Appui sur le bouton.")
        button_state = None
Capteur de température, humidité, pression, et composés organiques volatils - BME680

circup install adafruit-bme680
Exemple d'utilisation
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import busio
import adafruit_bme680

# Create sensor object, communicating over the board's default I2C bus
i2c = busio.I2C(scl=board.GPIO0, sda=board.GPIO1)  # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
bme680 = adafruit_bme680.Adafruit_BME680_I2C(i2c, debug=False)

# change this to match the location's pressure (hPa) at sea level
bme680.sea_level_pressure = 1013.25

# You will usually have to add an offset to account for the temperature of
# the sensor. This is usually around 5 degrees but varies by use. Use a
# separate temperature sensor to calibrate this one.
temperature_offset = -5

while True:
    print("\nTemperature: %0.1f C" % (bme680.temperature + temperature_offset))
    print("Gas: %d ohm" % bme680.gas)
    print("Humidity: %0.1f %%" % bme680.relative_humidity)
    print("Pressure: %0.3f hPa" % bme680.pressure)
    print("Altitude = %0.2f meters" % bme680.altitude)

    time.sleep(1)
Capteur de gaz - MQ2, MQ131...

Une sortie digitale (D0) envoie un signal lorsqu'un seuil est dépassé (réglable sur le potentiomètre).

  • Exemples de capteurs (voir cet article de seedstudio):

    • MQ131 : Capteur d'ozone
    • MQ135 : QualitĂ© de l'air intĂ©rieur (NH3,NOx, alcools, benzene, fumĂ©e, CO2...)
Exemple de mesure analogique avec moyennage
import time
import board
from analogio import AnalogIn

capteur = AnalogIn(board.GPIO0)

samples = 370

while True:
    t = time.monotonic()
    val = 0
    for repeat in range(samples):
        val += capteur.value
    moy = val / samples
    print("({:f} , {:f})".format(t,moy))
Capteur de luminosité - GY302 - BH1750

circup install adafruit-circuitpython-bh1750
Exemple d'utilisation
import time
import board
import busio
import adafruit_bh1750

i2c = busio.I2C(scl=board.GPIO0, sda=board.GPIO1)
sensor = adafruit_bh1750.BH1750(i2c)

while True:
    print("%.2f Lux"%sensor.lux)
    time.sleep(1)
Gyroscope GY-91 - MPU9250 = MPU6500 + AK8963

  • Librairie et documentation

  • Protocole de communication : I2C

  • Installation : Copier le dossier robohat_mpu9250 depuis github dans le dossier lib

Exemple d'utilisation
import board
import busio
from robohat_mpu9250.mpu9250 import MPU9250
from robohat_mpu9250.mpu6500 import MPU6500
from robohat_mpu9250.ak8963 import AK8963

from time import sleep

i2c = busio.I2C(scl=board.GPIO6, sda=board.GPIO7)

mpu = MPU6500(i2c, address=0x68)
ak = AK8963(i2c)

sensor = MPU9250(mpu, ak)

print("Reading in data from IMU.")
print("MPU9250 id: " + hex(sensor.read_whoami()))

while True:
    print('Acceleration (m/s^2): ({0:0.3f},{1:0.3f},{2:0.3f})'.format(*sensor.read_acceleration()))
    print('Magnetometer (gauss): ({0:0.3f},{1:0.3f},{2:0.3f})'.format(*sensor.read_magnetic()))
    print('Gyroscope (degrees/sec): ({0:0.3f},{1:0.3f},{2:0.3f})'.format(*sensor.read_gyro()))
    print('Temperature: {0:0.3f}C'.format(sensor.read_temperature()))
    sleep(2)
GPS GY-NEO6MV2

Exemple d'utilisation
import time
import board
import busio

import adafruit_gps

uart = busio.UART(tx=board.GPIO0, rx=board.GPIO1, baudrate=9600, timeout=10)

gps = adafruit_gps.GPS(uart, debug=False)  # Use UART/pyserial

# Turn on the basic GGA and RMC info (what you typically want)
gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")

# Turn on just minimum info (RMC only, location):
# gps.send_command(b'PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
# Turn off everything:
# gps.send_command(b'PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')
# Turn on everything (not all of it is parsed!)
# gps.send_command(b'PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0')

# Set update rate to once a second (1hz) which is what you typically want.
gps.send_command(b"PMTK220,1000")

# Main loop runs forever printing the location, etc. every second.
last_print = time.monotonic()
while True:
    gps.update()
    current = time.monotonic()
    if current - last_print >= 1.0:
        last_print = current
        if not gps.has_fix:
            print("Waiting for fix...")
            continue
        print("=" * 40)  # Print a separator line.
        print(
            "Fix timestamp: {}/{}/{} {:02}:{:02}:{:02}".format(
                gps.timestamp_utc.tm_mon,  # Grab parts of the time from the
                gps.timestamp_utc.tm_mday,  # struct_time object that holds
                gps.timestamp_utc.tm_year,  # the fix time.  Note you might
                gps.timestamp_utc.tm_hour,  # not get all data like year, day,
                gps.timestamp_utc.tm_min,  # month!
                gps.timestamp_utc.tm_sec,
            )
        )
        print("Latitude: {0:.6f} degrees".format(gps.latitude))
        print("Longitude: {0:.6f} degrees".format(gps.longitude))
        print(
            "Precise Latitude: {:2.}{:2.4f} degrees".format(
                gps.latitude_degrees, gps.latitude_minutes
            )
        )
        print(
            "Precise Longitude: {:2.}{:2.4f} degrees".format(
                gps.longitude_degrees, gps.longitude_minutes
            )
        )
        print("Fix quality: {}".format(gps.fix_quality))
        # Some attributes beyond latitude, longitude and timestamp are optional
        # and might not be present.  Check if they're None before trying to use!
        if gps.satellites is not None:
            print("# satellites: {}".format(gps.satellites))
        if gps.altitude_m is not None:
            print("Altitude: {} meters".format(gps.altitude_m))
        if gps.speed_knots is not None:
            print("Speed: {} knots".format(gps.speed_knots))
        if gps.track_angle_deg is not None:
            print("Track angle: {} degrees".format(gps.track_angle_deg))
        if gps.horizontal_dilution is not None:
            print("Horizontal dilution: {}".format(gps.horizontal_dilution))
        if gps.height_geoid is not None:
            print("Height geoid: {} meters".format(gps.height_geoid))
Périphérique de sortie
Ecran SSD1306 ou SH1106

Attention

Les écrans SSD1306 et sh1106 sont indifférenciables en apparence. Tester l'autre librairie si la première ne fonctionne pas.

  • Documentation CircuitPython sur les Ă©crans OLED SSD1306 et sh1106 et sur l'utilisation plus gĂ©nĂ©rale de DisplayIO

  • Librairies pour l'utilisation des Ă©crans SSD1306, ou sh1106, pour l'affichage de texte et pour la crĂ©ation de menus

  • Protocole de communication : I2C ou SPI selon le matĂ©riel

  • Installation :

    circup install adafruit-circuitpython-displayio-ssd1306 adafruit_display_text
    

Exemple d'utilisation
import displayio
import terminalio
import adafruit_displayio_ssd1306
from adafruit_display_text import label

# On efface le contenu de l'Ă©cran
displayio.release_displays()

# On déclare un port I2C sur les pins PG0 et GP0
i2c = busio.I2C(scl=board.GP1, sda=board.GP0)

# On déclare notre écran de 128x32 pixels sur l'I2C à l'addresse 0x3C
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32)
# On gère l'affichage du texte sur l'écran
text_group = displayio.Group(max_size=5)
# On écrit sur un ligne située à 3 pixel du haut
text_area = label.Label(terminalio.FONT, text="Mon texte", color=0xFFFFFF, x=0, y=3)
text_group.append(text_area)
# On écrit sur un ligne située à 14 pixel du haut
text_area = label.Label(terminalio.FONT, text="Mon 2ème texte", color=0xFFFFFF, x=0, y=14)
text_group.append(text_area)
# On écrit sur une ligne située à 25 pixel du haut
text_area = label.Label(terminalio.FONT, text="Ma 3ème ligne", color=0xFFFFFF, x=0, y=25)
text_group.append(text_area)
display.show(text_group)
Lecteur de carte SD

Exemple de code CircuitPython
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time

import adafruit_sdcard
import board
import busio
import digitalio
import microcontroller
import storage

# Use any pin that is not taken by SPI
SD_CS = board.D0

led = digitalio.DigitalInOut(board.D13)
led.direction = digitalio.Direction.OUTPUT

# Connect to the card and mount the filesystem.
spi = busio.SPI(clock=board.GPIO0, MOSI=board.GPIO1, MISO=board.GPIO2)
cs = digitalio.DigitalInOut(SD_CS)
sdcard = adafruit_sdcard.SDCard(spi, cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")

# Use the filesystem as normal! Our files are under /sd

print("Logging temperature to filesystem")
# append to the file!
while True:
    # open file for append
    with open("/sd/temperature.txt", "a") as f:
        led.value = True  # turn on LED to indicate we're writing to the file
        t = microcontroller.cpu.temperature
        print("Temperature = %0.1f" % t)
        f.write("%0.1f\n" % t)
        led.value = False  # turn off LED to indicate we're done
    # file is saved
    time.sleep(1)
Communications mobiles avec SIM800L

Se connecter au Web, envoyer ou recevoir appels ou SMS.

Exemple d'utilisation : Connection au WEB
import time
import board
import busio
import digitalio
import adafruit_requests as requests
from adafruit_fona.adafruit_fona import FONA
import adafruit_fona.adafruit_fona_network as network
import adafruit_fona.adafruit_fona_socket as cellular_socket

print("FONA Webclient Test")

TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"
JSON_URL = "http://api.coindesk.com/v1/bpi/currentprice/USD.json"

try:
    from secrets import secrets
except ImportError:
    print("GPRS secrets are kept in secrets.py, please add them there!")
    raise

# Create a serial connection for the FONA connection
uart = busio.UART(tx=board.GPIO0, rx=board.GPIO1)
rst = digitalio.DigitalInOut(board.GPIO2)

# Use this for FONA800 and FONA808
fona = FONA(uart, rst)

# Initialize cellular data network
network = network.CELLULAR(
    fona, (secrets["apn"], secrets["apn_username"], secrets["apn_password"])
)

while not network.is_attached:
    print("Attaching to network...")
    time.sleep(0.5)
print("Attached!")

while not network.is_connected:
    print("Connecting to network...")
    network.connect()
    time.sleep(0.5)

print("Network Connected!")

print("My IP address is:", fona.local_ip)
print("IP lookup adafruit.com: %s" % fona.get_host_by_name("adafruit.com"))

# Initialize a requests object with a socket and cellular interface
requests.set_socket(cellular_socket, fona)

# fona._debug = True
print("Fetching text from", TEXT_URL)
r = requests.get(TEXT_URL)
print("-" * 40)
print(r.text)
print("-" * 40)
r.close()

print()
print("Fetching json from", JSON_URL)
r = requests.get(JSON_URL)
print("-" * 40)
print(r.json())
print("-" * 40)
r.close()

print("Done!")
Example d'affichage des données d'un capteur
# On déclare les librairies nécessaires
# Les installer au préalable avec :
# circup install adafruit_bme680 adafruit_display_text adafruit_displayio_ssd1306 adafruit_sdcard
import board
import busio
import time
import adafruit_bme680
import digitalio
import adafruit_sdcard
import storage
import terminalio
import displayio
from adafruit_display_text import label
import adafruit_displayio_ssd1306

# On efface le contenu de l'Ă©cran
displayio.release_displays()
# On déclare un bus I2C sur les pins GPIO1 et GPIO2
i2c = busio.I2C(scl=board.GPIO1, sda=board.GPIO0)
# On déclare le BME680 sur l'I2C
bme680 = adafruit_bme680.Adafruit_BME680_I2C(i2c)
# On déclare notre écran de 128x32 pixels sur l'I2C à l'addresse 0x3C
display_bus = displayio.I2CDisplay(i2c, device_address=0x3C)
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32)
# On indique ici la pression (hPa) mesurée au niveau de la mer
bme680.sea_level_pressure = 1013.25
# On déclare un bus SPI sur les pins GPIO2, GPIO3, GPIO4 et GPIO5
spi = busio.SPI(clock=board.GPIO2, MOSI=board.GPIO3, MISO=board.GPIO4)
cs = digitalio.DigitalInOut(board.GPIO5)
#On déclare une carte SD connectée sur le SPI
sdcard = adafruit_sdcard.SDCard(spi, cs)
#On déclare un système de fichier VfsFat sur la carte SD
vfs = storage.VfsFat(sdcard)
#On le monte dans un dossier /sd
storage.mount(vfs, "/sd")

#On lance une boucle qui tourne en permanence
while True:
    #On récupère un tuple comprenant tous les paramètres mesurés par le BME680
    result=(bme680.temperature,bme680.gas,bme680.relative_humidity,bme680.pressure,bme680.altitude)
    #On affiche le tuple sur le serial pour Ă©ventuellement pouvoir le tracer en direct avec l'icone "Graphique" de Mu-Editor
    print(result)
    #On ouvre un fichier test.txt pour continuer Ă  le remplir ("a"=append)
    with open("/sd/test.txt", "a") as f:
        #On y écrit nos paramètres, séparés d'un espace, et formatés avec plus ou moins de décimales
        f.write("%0.1f %d %0.1f %0.3f %0.2f\r\n" % result)
    # On gère l'affichage du texte sur l'écran
    text_group = displayio.Group()
    # On écrit la température sur un ligne située à 3 pixel du haut
    text = "Temp : {:.2f} C".format(bme680.temperature)
    text_area = label.Label(terminalio.FONT, text=text, color=0xFFFFFF, x=0, y=3)
    text_group.append(text_area)
    # On écrit la pression sur un ligne située à 14 pixel du haut
    text = "Pres : {:.2f} hPa".format(bme680.pressure)
    text_area = label.Label(terminalio.FONT, text=text, color=0xFFFFFF, x=0, y=14)
    text_group.append(text_area)
    # On écrit l'humidité sur un ligne située à 25 pixel du haut
    text = "Humi : {:.2f} %".format(bme680.relative_humidity)
    text_area = label.Label(terminalio.FONT, text=text, color=0xFFFFFF, x=0, y=25)
    text_group.append(text_area)
    display.show(text_group)
    #on attend 2 secondes avant de recommencer une mesure.
    time.sleep(2)