Capteurs et afficheurs
Mesure des grandeurs physiques avec l'ADC de l'ESP32, visualise des données sur un écran OLED SSD1306 et intègre un capteur de température numérique DS18B20.
ADC — Entrées analogiques
L'ESP32 dispose de 18 canaux ADC 12 bits. La valeur renvoyée va de 0 (0 V) à 4095 (3.3 V).
⚠️ Attention : ADC2 incompatible WiFi
Les pins ADC2 (GPIO 0, 2, 4, 12-15, 25-27) ne fonctionnent pas quand le WiFi est actif. Pour des mesures fiables avec WiFi, utilise uniquement ADC1 (GPIO 32-39).
OLED SSD1306 — Afficheur I2C
Le SSD1306 est un afficheur OLED 128×64 pixels, communiquant en I2C. Le module ssd1306 est inclus dans MicroPython pour ESP32.
| Pin SSD1306 | Pin ESP32 | Description |
|---|---|---|
| VCC | 3.3 V | Alimentation |
| GND | GND | Masse |
| SDA | GPIO 21 | Données I2C |
| SCL | GPIO 22 | Horloge I2C |
DS18B20 — Température numérique 1-Wire
Le DS18B20 mesure la température de -55 à +125 °C avec une précision de ±0.5 °C. Il communique sur un seul fil (1-Wire).
| Pin DS18B20 | Connexion |
|---|---|
| GND (broche 1) | GND |
| DATA (broche 2) | GPIO 27 + résistance 4.7 kΩ vers 3.3 V |
| VDD (broche 3) | 3.3 V |
Résumé des connexions
ADC (analogique)
- GPIO 32-39 → ADC1 (WiFi OK)
- GPIO 0-27 → ADC2 (sans WiFi)
- Résolution : 12 bits (0-4095)
I2C (OLED)
- SDA → GPIO 21
- SCL → GPIO 22
- Adresse : 0x3C
1-Wire (DS18B20)
- DATA → GPIO 27
- Résistance 4.7 kΩ vers 3.3 V
- Plusieurs capteurs possibles
Exemples commentés
Chaque exemple est précédé de son contexte. Le code est affiché directement — bouton copier + lien GitHub disponibles.
📊 Entrées analogiques
ADC 12 bits, potentiomètre, cellule photo, capteur LM35.
1 Le convertisseur Analogique/Numérique (ADC)
Plusieurs broches de l'ESP32 peuvent mesurer une tension analogique entre 0 et 3,3 V. Le convertisseur ADC (Analog-to-Digital Converter) transforme cette tension en nombre entier.
La résolution détermine la précision :
- 9 bits : 2⁹ = 512 valeurs, de 0 à 511. Résolution : 3,3 / 512 ≈ 6,4 mV
- 12 bits : 2¹² = 4096 valeurs, de 0 à 4095. Résolution : 3,3 / 4096 ≈ 0,8 mV
from machine import ADC, Pin
adc = ADC(Pin(35))
adc.atten(ADC.ATTN_11DB) # plage 0 à 3.6 V
adc.width(ADC.WIDTH_9BIT) # résolution 9 bits (0 à 511)
raw = adc.read()
tension = raw * 3.3 / 511 # conversion en volts entree-analogique.py — Lecture ADC et conversion en tension .python cours-exemples/analogique/entree-analogique.py 12 lignes
GitHub
from machine import Pin, ADC
from time import sleep
from machine import Pin
adc = ADC(Pin(35)) # cree l'objet ADC, pour une entree analogique
adc.atten(ADC.ATTN_11DB) # set 11dB input attenuation (range 0.0v - 3.6v)
adc.width(ADC.WIDTH_9BIT) # set 9 bit return values (returned range 0-511)
for x in range(100): # boucle pendant 10 s
raw = adc.read() # lecture de raw (entre 0 et 511)
print(raw)
sleep(.1)2 Potentiomètre et cellule photo-électrique (LDR)
Un potentiomètre est une résistance variable : sa rotation fait varier la tension de 0 à 3,3 V. La valeur lue (0 à 511) peut être mappée vers n'importe quelle plage, par exemple : raw / 64 donne 0 à 7,98 → contrôle du nombre de NeoPixels allumés.
Une LDR (Light Dependent Resistor, cellule photo-électrique) varie avec la lumière : 100 Ω éclairée, 500 kΩ dans l'obscurité. Montée en pont diviseur de tension avec une résistance fixe, la tension varie avec l'éclairement. On compare à un seuil pour détecter l'obscurité :
tension = adc.read() * 3.3 / 512
led.value(tension < 2.0) # allume LED si sombre entree-analogique-neopixel.py — Potentiomètre contrôle les NeoPixels .python cours-exemples/analogique/entree-analogique-neopixel.py 18 lignes
GitHub
from machine import Pin, ADC
from time import sleep
from machine import Pin
from neopixel import NeoPixel
adc = ADC(Pin(35)) # cree l'objet ADC
adc.atten(ADC.ATTN_11DB) # set 11dB input attenuation (range 0.0v - 3.6v)
adc.width(ADC.WIDTH_9BIT) # set 9 bit return values (returned range 0-511)
np = NeoPixel(Pin(26), 8)
for x in range(100): # boucle pendant 10 s
raw = adc.read()
print("raw : {0:3d}, tension : {1:f}, raw/64 : {2:f}".
format(raw, raw * 3.3 / 511, raw/64))
for led in range(8):
np[led]=(0, 0, 10*(raw/64 > led)) # allume 0-8 leds du neopixel
np.write()
sleep(.1) cellule-photo.py — LDR — LED s'allume dans l'obscurité .python cours-exemples/analogique/cellule-photo.py 14 lignes
GitHub
from machine import Pin, ADC
from time import sleep
from machine import Pin
adc = ADC(Pin(32)) # cree l'objet ADC, pour une entree analogique
adc.atten(ADC.ATTN_11DB) # set 11dB input attenuation (range 0.0v - 3.6v)
adc.width(ADC.WIDTH_9BIT) # set 9 bit return values (returned range 0-511)
led = Pin(23, Pin.OUT)
for x in range(100):
tension = adc.read()* 3.3 / 512 # calcul de la tension (9 bits)
led.value(tension < 2.0) # allume LED si tension < 2.0
print("Tension : ", tension)
sleep(.1)🖥️ Afficheur OLED
SSD1306 128×64 — texte, formes, graphiques et courbes.
1 Connexion I2C et commandes de base
L'écran OLED SSD1306 (128 × 64 pixels, monochrome) communique avec l'ESP32 via le bus I2C (2 fils : SCL sur broche 22, SDA sur broche 21). La bibliothèque ssd1306.py doit être chargée sur l'ESP32 au préalable (elle n'est pas incluse par défaut dans MicroPython).
from machine import I2C, Pin
from ssd1306 import SSD1306_I2C
i2c = I2C(-1, Pin(22), Pin(21))
display = SSD1306_I2C(128, 64, i2c)
display.fill(0) # effacer l'écran (0 = noir)
display.text("Bonjour !", 1, 10, 1) # texte blanc en (x=1, y=10)
display.pixel(64, 32, 1) # pixel blanc au centre
display.hline(0, 0, 128, 1) # ligne horizontale en haut
display.rect(4, 4, 60, 28, 1) # rectangle vide
display.fill_rect(4, 36, 20, 15, 1) # rectangle plein
display.show() # OBLIGATOIRE : envoyer le buffer à l'écran Important : display.show() est l'appel qui transfère le buffer en mémoire vers l'écran physique. Rien n'est visible avant cet appel. Chaque modification du buffer doit être suivie d'un show().
oled-base.py — Texte, formes et commandes de base .python cours-exemples/oled/oled-base.py 145 lignes
GitHub
from machine import I2C, Pin
from time import sleep
from ssd1306 import SSD1306_I2C # module pour commander le OLED
i2c = I2C(-1, Pin(22), Pin(21)) # pin SCK et SDA du OLED
display = SSD1306_I2C(128, 64, i2c) # declaration taille ecran, pins
bp = Pin(25, Pin.IN) # poussoir sur pin 25 pour passer
# fonction pour attendre l'appui+relach. pour passer au graphique suivant
def attend_appui():
while bp.value() == False : pass ; sleep(.02) # attend appui poussoir
while bp.value() == True : pass ; sleep(.02) # attend le relachement
display.fill(0) # Remplit l'afficheur avec 0 -> OFF
display.show() # Mise a jour de l'affichage (envoie)
display.text("Presse Poussoir !", 1, 10, 1)
display.show() ; attend_appui() # la fonction attend l'appui/relachement
display.fill(1) # Remplit l'afficheur avec 1 -> ON
display.show() ; attend_appui()
display.fill(0) # Remplit l'afficheur avec 0 -> OFF
display.show() ; attend_appui()
# Affiche le texte en 1, 0 (x,y), et etat : 1 (blanc). (0 : noir)
display.text("Hello World !", 1, 0, 1)
display.show() ; attend_appui()
display.contrast(50) # contraste mis a 50 sur max = 255
attend_appui()
display.contrast(255) # contraste maxi
attend_appui()
display.invert(1) # inversion sortie (fond blanc)
attend_appui()
display.invert(0) # remise affichage normal
attend_appui()
# Affiche le texte en 1, 10 (x,y), et 1 signifie en blanc (0 : noir)
display.text("Tu vas bien ?", 1, 10, 1)
display.show() ; attend_appui()
# Affiche le texte en 1, 20 (x,y), et 1 signifie en blanc (0 : noir)
display.text("Estoy muy biene", 1, 20, 1)
display.show() ; attend_appui()
# Cela ecrit en couleur 0 (noir) le meme texte, donc ca l'efface
display.text("Tu vas bien ?", 1, 10, 0)
display.show() ; attend_appui()
# La hauteur d'une lettre est de 7 pixels
# On aurait pu afficher sur la ligne 8 et non 10, soit 1 pixel d'ecart
display.text("Tu vas bien ?", 1, 8, 1)
display.show() ; attend_appui()
# On aurait aussi pu afficher sur la ligne 7, mais on touche !
display.text("Tu vas bien ?", 1, 8, 0) # on efface
display.text("Tu vas bien ?", 1, 7, 1) # on ecrit trop pres !!
display.show() ; attend_appui()
display.fill(0) # Efface tout
display.show() ; attend_appui()
# Mets le pixel 3, 4 (x, y) a 1 (allume)
# .pixel(x,y,c)
display.pixel(127, 31, 1)
display.show() ; attend_appui()
# Retourne la valeur a 127,31 (x,y)
# .pixel(x,y,c)
c = display.pixel(127, 31) # affiche l'etat 1 ou 0 du pixel (x,y)
print("Etat du pixel :", c)
display.text("...voir shell... !", 1, 10, 1)
display.show() ; attend_appui()
c = display.pixel(126, 30) # affiche l'etat 1 ou 0 du pixel (x,y)
print("Etat du pixel :", c)
display.text("...encore... !", 1, 20, 1)
display.show() ; attend_appui()
# Trace une ligne horizontale, d'origine 0,0 (x,y),
# de longueur l= 128 (vers la droite)
# .hline(x,y,l,c)
display.fill(0) # Efface tout
display.hline(0, 0, 128, 1)
display.show() ; attend_appui()
# Trace une ligne verticale, d'origine 0,0 (x,y),
# de hauteur h= 63 (vers le bas)
# .hline(x,y,h,c)
display.vline(0, 0, 63, 1)
display.show() ; attend_appui()
# Trace une ligne, d'origine 0,63 (x1,y1), d'extremite 127,0 (x1,y1)
# .line(x1,y1,x2,y2,c)
display.line(0, 63, 127, 0, 1)
display.show() ; attend_appui()
# Trace un rectangle vide, d'origine 4,4 (x,y), de dimensions 68,28 (l,h)
# .rect(x,y,l,h,c)
display.rect(4, 4, 60, 28, 1)
display.show() ; attend_appui()
# Trace un rectangle plein, d'origine 4,36 (x,y), de dimensions 20,15 (l,h)
# .fill_rect(x,y,l,h,c)
display.fill_rect(4, 36, 20, 15, 1)
display.show() ; attend_appui()
# Pour de simples graphiques, nous pouvons creer une matrice, qui contient
# les elements de notre icone, comme celui-ci par exemple :
ICON = [
[ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
[ 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
[ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],]
display.fill(0) # Efface tout
x0, y0 = 60, 12
for y, row in enumerate(ICON): # prendre chaque ligne
for x, c in enumerate(row): # et prendre chaque colonne
display.pixel(x0+x, y0+y, c) # pixel = 1 ou 0, centre (x0,y0)
display.show()
attend_appui()
for x in range(1,74):
display.scroll(-1, 0)
display.show()
sleep(.01)
attend_appui()
display.text("C'est fini !", 1, 10, 1)
display.show() ; attend_appui()
2 Applications — affichage temps réel et courbes
L'OLED est particulièrement utile pour les projets autonomes sans ordinateur : afficher des mesures, des graphiques ou des menus sans avoir besoin du port série.
mesure-vitesse-OLED.py affiche en temps réel la vitesse calculée entre deux capteurs — le résultat apparaît directement sur l'écran de la carte.
Pour les courbes (sinus.py), on calcule y = sin(α) et on adapte l'échelle : la fonction adaptEchelle(y, -1, 1, 60, 4) transforme la valeur y (entre -1 et 1) en coordonnée pixel (entre 60 et 4, car l'axe y de l'écran est inversé) :
def adaptEchelle(e, e1, e2, s1, s2):
return (e - e1) * (s2 - s1) / (e2 - e1) + s1 mesure-vitesse-OLED.py — Mesure de vitesse affichée en temps réel .python cours-exemples/oled/mesure-vitesse-OLED.py 59 lignes
GitHub
############## mesure vitesse chute bille ################
from machine import Pin, I2C
from time import sleep, sleep_us, sleep_ms, ticks_ms, ticks_us
# choix de la bibliotheque en fonction du type d'ecran OLED
from ssd1306 import SSD1306_I2C # module pour commander le OLED
i2c = I2C(-1, Pin(22), Pin(21)) # pin SCK et SDA du OLED
display = SSD1306_I2C(128, 64, i2c) # declaration taille ecran, pins
from sh1106 import SH1106_I2C
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=400000)
display = SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)
display.sleep(False)
display.fill(0) # Remplit l'afficheur avec 0 -> OFF
display.show() # Mise a jour de l'affichage (envoie)
relais = Pin(2, Pin.OUT)
capteur1 = Pin(25, Pin.IN)
capteur2 = Pin(34, Pin.IN)
dcy = Pin(36, Pin.IN)
relais.value(1) # commande relais pour maintien bille
display.text("Appuyer sur DCY ...", 1, 0, 1)
display.show() ;
# attente de l'appui sur le poussoir DCY
while not dcy.value(): # attente a l'etat bas
pass
relais.value(0) # lacher de la bille par ouverture relais
display.fill(0)
display.text("Attente C1...", 1, 0, 1)
display.show() ;
# attente du passage devant le capteur 1
while not capteur1.value(): # attente a l'etat bas
sleep_ms(1)
start = ticks_ms() # start sur front montant 1
display.text("C1 OK", 20, 10, 1)
display.text("Attente C2...", 1, 20, 1)
display.show() ;
# attente du passage devant le capteur 2
while not capteur2.value(): # attente a l'etat bas
sleep_ms(1)
stop = ticks_ms() # stop sur front montant 2
display.text("C2 OK", 20, 30, 1)
delta = stop - start
display.text("Temps C1-C2 : ", 1, 40, 1)
display.text(str(delta/1000)+ "s", 10, 50, 1)
display.show()
sinus.py — Tracé sinus et courbes de Lissajous .python cours-exemples/oled/sinus.py 55 lignes
GitHub
from machine import I2C, Pin
from time import sleep
from math import pi, sin # import de pi et sin
from adapt import adaptEchelle # notre fonction pour l'affichage
OLED = 1 # 1 : 1.3 pouces, 0 : 0.96 pouces
# choix de la bibliotheque en fonction du type d'ecran OLED
if OLED == 0 :
from ssd1306 import SSD1306_I2C # module pour commander le OLED
i2c = I2C(-1, Pin(22), Pin(21)) # pin SCK et SDA du OLED
display = SSD1306_I2C(128, 64, i2c) # declaration taille ecran, pins
else :
from sh1106 import SH1106_I2C
i2c = I2C(scl=Pin(22), sda=Pin(21), freq=400000)
display = SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)
display.sleep(False)
bp = Pin(25, Pin.IN) # poussoir sur pin 25 pour passer
# fonction pour attendre l'appui+relach. pour passer au graphique suivant
def attend_appui():
while bp.value() == False : pass ; sleep(.02) # attend appui poussoir
while bp.value() == True : pass ; sleep(.02) # attend le relachement
display.fill(0) # Remplit l'afficheur avec 0 -> OFF
display.show() # Mise a jour de l'affichage (envoie)
for x in range (128) : # 128 points car c'est la taille du OLED
alpha = x / 128 * 2 * pi # alpha va de 0 a 2*pi en 128 points
y = sin(alpha) # y va de -1 a 1
s = int(adaptEchelle(y, -1, 1, 60, 4)) # s va de 60 a 4
display.pixel(x, s, 1)
display.show() ; attend_appui() # on affiche d'un coup les 128 points
for x in range (128) :
alpha = x / 128 * 4 * pi # alpha de 0 a 4*pi, soit 2 periodes
y = sin(alpha)
s = int(adaptEchelle(y, -1, 1, 60, 4))
display.pixel(x, s, 1)
display.show() ; attend_appui()
display.fill(0)
for t in range(240): # t prend 240 valeurs
alpha = t / 240 * 2 * pi # alpha va de 0 a 2.pi en 240 valeurs
x = sin(alpha * 2)
y = sin(alpha * 3)
sx = int(adaptEchelle(x, -1, 1, 2, 125)) # sx va de 2 a 125
sy = int(adaptEchelle(y, -1, 1, 60, 4)) # sy va de 60 a 4
display.pixel(sx, sy, 1)
display.show() ; attend_appui()
display.fill(0)
display.text("C'est fini !", 1, 10, 1)
display.show()
🌡️ Température DS18B20
Capteur 1-Wire numérique — un ou plusieurs sur le même fil.
1 Le bus 1-Wire et le capteur numérique
Le DS18B20 est un thermomètre numérique intelligent : il embarque son propre convertisseur et communique via le protocole 1-Wire (un seul fil de données + masse). Contrairement à un capteur analogique, le signal transmis est numérique, donc insensible aux perturbations électriques même sur des câbles de plusieurs mètres.
Points clés :
- Chaque capteur possède un code ROM unique sur 8 octets gravé en usine
- Grâce à ce code, plusieurs capteurs peuvent coexister sur la même broche
- Une résistance de tirage de 4,7 kΩ est nécessaire entre +3,3V et le fil de données
from onewire import OneWire
from ds18x20 import DS18X20
ds = DS18X20(OneWire(Pin(27)))
roms = ds.scan() # trouver tous les capteurs
ds.convert_temp() # déclencher la mesure
sleep_ms(500) # attendre la conversion (750 ms max)
temp = ds.read_temp(roms[0]) # lire le résultat en °C capteur-DS18B20.py — Un seul capteur DS18B20 .python cours-exemples/temperature/capteur-DS18B20.py 19 lignes
GitHub
from machine import Pin
from onewire import OneWire
from time import sleep, sleep_ms
from ds18x20 import DS18X20
ds_pin = Pin(27) # definie pin capteur
ds_capteur = DS18X20(OneWire(ds_pin)) # cree l'objet ds_capteur
roms = ds_capteur.scan() # scanne l'ensemble des capteurs
print('Found DS devices : ', roms) # affiche les codes ROM trouves
ds_capteur.convert_temp() # lance la lecture
sleep_ms(500) # temporisation pour lecture
for x, rom in enumerate(roms) :
print(" ")
print("Capteur : ", x)
print("Code ROM : ", rom) # affiche code ROM capteur
print(ds_capteur.read_temp(rom)) # affiche la temperature
ensemble-DS18B20.py — Plusieurs capteurs sur un fil .python cours-exemples/temperature/ensemble-DS18B20.py 27 lignes
GitHub
from time import time, sleep
from esp32 import Partition , ULP
from onewire import OneWire
from ds18x20 import DS18X20
from machine import Pin, ADC, DAC, PWM
ds_pin = Pin(27) # definie pin capteur
ds_sensor = DS18X20(OneWire(ds_pin))
def read_ds_sensor(s): # notre fonction de mesure
roms = ds_sensor.scan()
ds_sensor.convert_temp()
sleep(.5)
if s=="INT": # INT : capteur interieur
rom = bytearray(b'(\xa6d\x16\xa8\x01<\xc5') # son code ROM
if s=="EXT": # EXT : capteur Exterieur
rom = bytearray(b'(\xff\xa3\x9ed\x15\x01\xb7') # son code ROM
temp = ds_sensor.read_temp(rom) # on lit le capteur choisi
return temp # on retourne la temperature
tempINT = read_ds_sensor("INT")
print('Capteur Interieur - sur carte : ', tempINT, 'degres')
tempEXT = read_ds_sensor("EXT")
print('Capteur Exterieur - WaterProof : ', tempEXT, 'degres')
Programmation temps réel
Timing précis, séquenceurs non-bloquants et interruptions.