Affichage des articles dont le libellé est STM32. Afficher tous les articles
Affichage des articles dont le libellé est STM32. Afficher tous les articles

dimanche 7 décembre 2025

PYTHON : Initiation

 


PYTHON : Initiation


Ce mois-ci je propose une petite initiation au langage Python. Le but ici n'est pas de produire un tutoriel exhaustif, déjà très nombreux sur le WEB, mais de donner envie d'utiliser ce langage moderne.

La première version de Python est sortie en 1991. La version 2 date de 2000, la version 3 de 2008. C'est à l'heure actuelle un des langages les plus utilisés, aussi bien sur PC que sur plateforme embarquée. Pour ma part, Python représente une grande part des lignes de code que j'ai écrites aussi bien dans ma vie privée que professionnelle.

Python peut être utilisé pour une grande variété de projets :

  • automatisation
  • projets scientifiques
  • serveurs WEB
  • bases de données
  • communication
  • interface graphique
  • etc.

Sur ce blog il suffit de rechercher le mot "python" pour constater que j'ai déjà publié pas mal de petits scripts.

1. Cibles

Les plateformes sur lesquelles peut tourner Python sont nombreuses :

  • machine Windows, MacOs, Linux
  • cartes SBC comme la Raspberry PI, BeagleBone, Banana PI

Mais il existe aussi des versions de Python légères, comme MicroPython, qui peuvent se contenter de plateformes moins puissantes comme les ESP32, STM32.

Le principal intérêt de Python sur des plateformes aussi variées est la portabilité et on comprend vite l'intérêt d'utiliser un langage unique : limiter la formation.

A l'heure actuelle je développe essentiellement avec :

  • sur PC : Python, HTML, JavaScript
  • sur RASPBERRY PI : Python, HTML, JavaScript
  • sur ESP8266, ESP32, ARDUINO : C, C++, HTML, JavaScript

2. Installation

L'installation de Python est simple.

Sous Linux, en fonction de la distribution, il est souvent pré-installé. Pour le vérifier, lancer la commande suivante :

python -V

S'il est installé il va afficher sa version :

Python 3.12.3

Si ce n'est pas le cas, il faut utiliser la méthode d'installation de la distribution :

Sous Ubuntu : sudo apt-get install python 

Sous Fedora : sudo dnf install python3

Sous Windows il faudra télécharger le logiciel d'installation ici.

Sous Windows n'oubliez pas de cocher la case "Add Python 3.XX to PATH" :

2.1. Installation de packages

Installer le langage Python ne veut pas dire que l'on installe simplement un interpréteur nu avec des instructions basiques. Un nombre très important de librairies sont présentes : The Python Standard Library

Par contre d'autres librairies peuvent vous apporter des fonctionnalités avancées. Par exemple, on peut très bien développer un serveur WEB à l'aide du package http.server, présent dans la librairie standard, mais c'est un peu comme travailler avec un marteau et une enclume. Pour développer des application plus cossues, on a souvent recours à des frameworks comme Jango, ou CherryPy.

2.2. Pip

Certains sites proposent l'installation de leurs packages à l'aide de pip (Package Installer for Python).

C'est en effet un procédé très simple, qui permet d'installer des packages sans avoir à télécharger une archive et se préoccuper de l'endroit où il va falloir la décompresser.

Sous Ubuntu ou Debian : sudo apt install python3-pip

Sous Windows : Comment installer PIP sur Windows

On pourra ainsi installer par exemple le package de communication série PySerial comme ceci :

pip install pyserial

3. La console

Python met à votre disposition une console qui vous permettra d'exécuter du code sans avoir à créer de fichier source.

Il suffit pour cela de taper la commande python dans un terminal :

python

ou (sous Windows)

py

Python 3.12.3 (main, Nov  6 2025, 13:44:16) [GCC 13.3.0] on linux

Type "help", "copyright", "credits" or "license" for more information.

>>>

Ensuite on entre le code à exécuter : 

print("Hello Python")
Hello Python
>>> 

On quitte la console en tapant CTRL-D.

Cette console est un moyen très pratique de taper quelques lignes de code rapidement pour faire des essais.

4. Un tutoriel ?

Il existe énormément de tutoriels sur le langage Python. Je vous propose le tutoriel officiel pour démarrer.

Celui de W3school, en anglais, aborde des sujets supplémentaires :

  • mathplotlib
  • fichiers
  • base de données MySQL
  • etc.

Attention : il est assez déconseillé d'adopter de vieux tutoriels Python2 pour travailler en Python3, car les différences sont assez importantes.

5. Quel éditeur ?

Pour écrire du code, on utilise un éditeur de texte. Certains éditeurs évolués comme Visual Studio Code vous apporteront un certain confort, en particulier la coloration syntaxique.

L'écriture de code en Python n'utilise pas d'accolades pour définir les blocs de code comme en C ou C++, mais une indentation.

En C, on utilise des accolades :

for (i=0 ; i < 4 ; i++)
{
    printf("blabla\n");
    printf("%d\n", i);
}

L'équivalent en Python :

for i in range(4):
    print("blabla")
    print(i)

Ce code produira le résultat suivant :

blabla
0
blabla
1
blabla
2
blabla
3

Mais si vous écrivez :

for i in range(4):
    print("blabla")
print(i)

Ce code produira le résultat suivant :

blabla
blabla
blabla
blabla
3

Attention donc.

Pour l'indentation il va falloir faire un choix. Soit vous utilisez la tabulation, soit vous utilisez des espaces. La majeure partie des éditeurs modernes permettent d'insérer des espaces lorsque l'on insère une tabulation. En général, la taille d'une tabulation est configurable. Un nombre de 3 ou 4 espaces est en général une bonne valeur.

6. Domaines d'application

Les domaines d'application sont très nombreux. Je n'ai pas l'intention de tous les énumérer, mais en voici quelques exemples :

6.1. Instructions de base

Quand on débute en Python, on constate assez vite que la simplicité est au rendez-vous. De plus, Python emprunte au langage C certaines caractéristiques essentielles, mais aussi à JavaScript :

Commentaire :

# comment

Commentaire sur plusieurs lignes :

'''
comment using string literals
with triple quotes
'''

Déclaration d'un entier :

a = 100

Déclaration d'un entier en notation hexadécimale :

a = 0x44

Déclaration d'un caractère en notation hexadécimale :

a = "\x44"

Déclaration d'une chaîne de caractères :

s = "this is a string"
s = 'this is a string'
a = "\x44\x33"

On peut accéder à un caractère particulier dans la chaîne, comme en C :

t = "string"
print(t[2])

Les tableaux :

t = [0, 1, 2, 3]
print(t[2])

t = (0, 1, 2, 3)
print(t[2])

On remarque deux types de tableaux : le premier, appelé liste, modifiable (entre crochets), le deuxième, appelé tuple, non modifiable (entre parenthèses).

Tous les tableaux sont indexés en partant de ZÉRO, comme en C.

En Python on peut ajouter, modifier ou supprimer un élément de liste :

t = [0, 1, 2, 3]
t.append(5)    # ajoute un élément dont la valeur est 5
t[4] = 12        # modifie l'élément 4
t.pop(2)         # retire l'élément 2

Au final, le contenu sera : [0, 1, 3, 12]

Appel de fonction ou de méthode :

function(arguments)
object.method(arguments)

Un dictionnaire, que l'on peut assimiler à une structure C :

x = {"name" : "John", "age" : 36}

Les habitués du C ou de JavaScript ne seront donc pas complètement perdus.

On remarque que le type des variables n'est pas explicitement déclaré.

On peut parfaitement écrire :

a = 100
a = "abcdef"

La variable a change donc de type à volonté. C'est un point qui m'a fortement perturbé à mes débuts. On m'a répondu "simple question d'ouverture d'esprit". J'ai donc arrêté de me plaindre et me suis accroché.

En fait chaque fonction est libre de contrôler ses arguments :

range("3")

Produit une exception :

TypeError: 'str' object cannot be interpreted as an integer

6.1.1. Boucle for

En C on écrirait :

for (i=0 ; i < 4 ; i++)
{
    printf("i a pour valeur %d\n", i);
}

L'équivalent en Python :

for i in range(4):
    print("i a pour valeur", i)

Dans cet exemple les différences sont flagrantes :

  • pas d'accolades mais une simple indentation (tabulations ou espaces)
  • pas de point-virgule

6.1.2. Séquence

On l'aura compris, la fonction range() retourne une séquence de nombres, 0 à 4 dans l'exemple précédent.

range() peut prendre 3 arguments :

range(3, 10, 2) # retourne une séquence de 3 to 9, par pas de deux : [3, 5, 7, 9]

Si on désire une séquence sans logique précise, on peut l'écrire comme ceci :

for i in [0, 2, 4, 8, 13]:
    print("i a pour valeur", i)

[0, 2, 4, 8, 13] est aussi une séquence. On remarque déjà qu'en C ou C++ il serait plus difficile d'écrire l'équivalent.

c = ["Un", "problème", "sans", "solution", "est", "un", "problème", "mal", "posé"]

for i in range(len(c)):
    print("i vaut", i, "et c[i] vaut", c[i])

6.1.3. Typage

Dans les exemples précédents on a vu que [0, 2, 4, 8, 13] et ["Un", "problème", "sans", "solution", "est", "un", "problème", "mal", "posé"] sont des séquences.

Peut on mélanger les chaînes de caractères et les nombres dans une séquence ?

c = [1, "problème", "sans", "solution", "est", 1, "problème", "mal", "posé"]

Oui. Cette déclaration est tout à fait valide.

Il est même possible de déterminer le type de chaque élément :

c = [1, "problème", "sans", "solution", "est", 1, "problème", "mal", "posé"]
print(c)
for i in range(len(c)):
    print(type(c[i]))

Ce qui produit le résultat suivant :

[1, 'problème', 'sans', 'solution', 'est', 1, 'problème', 'mal', 'posé']
<class 'int'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'int'>
<class 'str'>
<class 'str'>
<class 'str'>

6.1.4. Slicing

La simplicité est encore plus évidente quand on aborde le slicing (découpage) :

alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print(alpha[4:16])

Ce qui produit le résultat suivant :

EFGHIJKLMNOP

En C on utiliserait une fonction comme strncpy() ou memcpy(), plus complexes et dangereuses pour les débutants, et qui nécessitent une allocation mémoire.

En Python, les lignes suivantes :

alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print(alpha[20:100])

Produisent le résultat suivant :

UVWXYZ

En clair, on a demandé une tranche de la chaîne s, située entre le 20ème caractère et le 100ème, alors que la chaîne a une longueur de seulement 26 caractères. On l'aura compris, Python est un langage qui apporte de la sécurité.

Comment récupérer le dernier élément d'une séquence ? comme ceci :

a = "abcdef"
print(a[-1])

6.1.5. Introspection

On appelle introspection la capacité à examiner les classes, fonctions et mots-clés pour savoir ce qu'ils sont, ce qu'ils font et ce qu'ils connaissent.

Écrivons une classe et un peu de code :

class student:
    name = ""
    present = False
    # constructeur
    def __init__(self, name):
        self.name = name
    # methods
    def enter(self):
        ''' the student enters the school '''
        self.present = True
    def exit(self):
        ''' the student leaves school '''

        self.present = False

john = student("john")
print("john:")
print(john)
print("id(john):")
print(id(john))
print('dir("john"):')
print(dir(john))
print('john.__class__:')
print(john.__class__)
print('dir(john.present):')
print(dir(john.present))
print('dir(john.enter):')
print(dir(john.enter))
print('callable(getattr(john, "enter")):')
print(callable(getattr(john, "enter")))

Ce qui produit le résultat suivant :

john:
<__main__.student object at 0x7024a0d6ef00>
id(john):
126360425639824
dir("john"):
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'enter', 'exit', 'name', 'present']
john.__class__:
<class '__main__.student'>
dir(john.present):
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
dir(john.enter):
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
callable(getattr(john, "enter")):
True

Examinons de plus près :

print("john:")
print(john)
john:
<__main__.student object at 0x7024a0d6ef00>

print(john) dit que john est un objet de la classe student situé dans le module __main__, et que son identifiant est 0x7024a0d6ef00

print("id(john):")
print(id(john))
id(john):
126360425639824

id(john) nous donne l'identifiant de l'objet john : 126360425639824 (0x7024a0d6ef00 en décimal)

print('dir("john"):')
print(dir(john))
dir("john"):
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'enter', 'exit', 'name', 'present']

dir(john) retourne les membres de l'objet john. Les méthodes que nous avons écrites sont présentes, parmi d'autres membres.

print('john.__class__:')
print(john.__class__)
john.__class__:
<class '__main__.student'>

john.__class__ contient le nom de la classe de l'objet john : __main__.student

print('dir(john.present):')
print(dir(john.present))
dir(john.present):
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']

dir(john.present) retourne les attributs de l'attribut present de l'objet john.

print('dir(john.enter):')
print(dir(john.enter))
dir(john.enter):
['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

dir(john.enter) retourne les attributs de la méthode enter() de l'objet john.

print('callable(getattr(john, "enter")):')
print(callable(getattr(john, "enter")))
callable(getattr(john, "enter")):
True

callable(getattr(john, "enter")) permet de savoir si le membre enter de l'objet john est appelable.

On peut remarquer sur cette ligne l'utilisation de simples et doubles quotes imbriquées :

print('callable(getattr(john, "enter")):')

Ce qui permet de ne pas fermer la chaîne sur la première double quote de "enter".

On aurait pu écrire aussi :

print("callable(getattr(john, 'enter')):")

Ou, comme en C :

print("callable(getattr(john, \"enter\")):")

En Python on peut indifféremment utiliser la simple ou la double quote pour encadrer une chaîne, comme en JavaScript.

On constate qu'en Python tout est orienté objet, qu'il s'agisse de classe, d'instances, d'attributs et méthodes. Je vous laisse imaginer les possibilités offertes par toutes ces méthodes d'introspection.

6.1.6. Et les pointeurs ?

En Python, la notion de pointeur n'existe tout simplement pas. C'est certainement une bonne nouvelle pour les débutants.

6.2. La ligne série

Quand on travaille avec un ARDUINO ou un ESP, il est assez courant d'avoir recours au moniteur série pour afficher des information sur l'écran du PC.

Mais c'est assez limité. Que faire si l'on a besoin d’interagir avec le logiciel embarqué ? La ligne série, accessible via le connecteur USB de la carte, semble le moyen le plus simple.

J'ai déjà traité ce sujet ici : Commander un ARDUINO par la ligne série ou BLUETOOTH

Pour communiquer en série à partir de Python, il vous faudra installer le package PySerial.

Certains d'entre vous ont peut être déjà développé des logiciel de communication série en C à l'aide de Win32 sous Windows, ou termios sous Linux. Sous Windows, on peut très vite constater que cela peut très vite provoquer des problèmes en fonction de la cible. On logiciel peut très bien fonctionner sur certains PC et mal fonctionner sur d'autres (c'est du vécu). Il faut vraiment être un spécialiste de Win32 pour s'en sortir. Sous Linux c'est beaucoup moins le cas.

Mais dans tous les cas, avec PySerial votre code fonctionnera aussi bien sur Windows que MacOs ou Linux. Seule l'ouverture de la ligne série diffère :

Sous Linux :

ser = serial.Serial('/dev/ttyUSB0')

Sous Windows :

ser = serial.Serial('COM1')

6.3. L'interface graphique

En matière d'interface graphique, on peut en Python se contenter de TkInter, qui produit des résultats honorables mais assez peu esthétiques :

Interface graphique Tkinter python

On peut également utiliser WxPython, PyGTK, ou PyQT, qui sont des interfaces Python à WxWindows, GTK et QT.

On peut citer également PyGame, une librairie de dessin toute simple, sans widgets.

Mais à l'heure actuelle ces librairies sont de moins en moins utilisées, la création d'interfaces graphiques se faisant principalement en HTML, JavaScript et CSS.

6.4. Le serveur WEB

Il existe une grande variété de serveurs WEB écrits en PYTHON :

  • http.server
  • CherryPy
  • Django
  • Flask
  • etc

6.4.1. http.server

http.server fait partie de la librairie standard Python. Examinons cet exemple simple :

from http.server import BaseHTTPRequestHandler, HTTPServer
import time
 
hostName = "127.0.0.1"
serverPort = 8080
 
class MyServer(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
        self.wfile.write(bytes("<body>", "utf-8"))
        self.wfile.write(bytes("<p>This is my web server</p>", "utf-8"))
        self.wfile.write(bytes("</body></html>", "utf-8"))
 
if __name__ == "__main__":        
    webServer = HTTPServer((hostName, serverPort), MyServer)
    print("Server started at http://%s:%s" % (hostName, serverPort))
 
    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass
 
    webServer.server_close()
    print("Server stopped.")

Si l'on lance ce serveur en local il affichera sur la console :

Server started at http://127.0.0.1:8080

Dans le navigateur on entrera :

http://localhost:8080/index

ou :

http://localhost:8080/

Le navigateur affichera simplement : This is my web server

On peut ajouter une URL supplémentaire nommée alternative :

from http.server import BaseHTTPRequestHandler, HTTPServer
import time

hostName = "127.0.0.1"
serverPort = 8080

class MyServer(BaseHTTPRequestHandler):
    def do_GET(self):
        if(self.path == '/' or self.path == '/index'):
                self.send_response(200)
                self.send_header("Content-type", "text/html")
                self.end_headers()
                self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
                self.wfile.write(bytes("<body>", "utf-8"))
                self.wfile.write(bytes("<p>This is my web server</p>", "utf-8"))
                self.wfile.write(bytes("</body></html>", "utf-8"))
        elif(self.path == '/alternative'):
                self.send_response(200)
                self.send_header("Content-type", "text/html")
                self.end_headers()
                self.wfile.write(bytes("<html><head><title>My Web Server</title></head>", "utf-8"))
                self.wfile.write(bytes("<body>", "utf-8"))
                self.wfile.write(bytes("<p>This is another URL</p>", "utf-8"))
                self.wfile.write(bytes("</body></html>", "utf-8"))

if __name__ == "__main__":
    webServer = HTTPServer((hostName, serverPort), MyServer)
    print("Server started at http://%s:%s" % (hostName, serverPort))

    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass

    webServer.server_close()
    print("Server stopped.")

Dans le navigateur on entrera :

http://localhost:8080/alternative

Le navigateur affichera alors : This is another URL

Comme on le voit, la librairie est assez spartiate. On a franchement l'impression de travailler à très bas niveau. 

6.4.2. CherryPy

Cherrypy propose une approche intéressante :

import cherrypy
 
class HelloWorld(object):
    @cherrypy.expose
    def index(self):
        return "Hello from CherryPy!"
 
if __name__ == '__main__':
    cherrypy.quickstart(HelloWorld())

Chaque méthode de la classe HelloWorld est exposée à l'aide du mot clé (appelé décorateur) @cherrypy.expose. Elle sera donc appelée lorsque l'URL nommée index sera entrée dans la barre d'adresse du navigateur, mais également l'URL nommée /.

Si l'on lance ce serveur en local il affichera sur la console :

[06/Dec/2025:15:54:57] ENGINE Listening for SIGTERM.
[06/Dec/2025:15:54:57] ENGINE Listening for SIGHUP.
[06/Dec/2025:15:54:57] ENGINE Listening for SIGUSR1.
[06/Dec/2025:15:54:57] ENGINE Bus STARTING
CherryPy Checker:
The Application mounted at '' has an empty config.
[06/Dec/2025:15:54:57] ENGINE Started monitor thread 'Autoreloader'.
[06/Dec/2025:15:54:57] ENGINE Serving on http://127.0.0.1:8080
[06/Dec/2025:15:54:57] ENGINE Bus STARTED

A noter que le port par défaut du serveur est le 8080.

Dans le navigateur on entrera :

http://localhost:8080/index

ou :

http://localhost:8080/

Le navigateur affichera simplement : Hello from CherryPy!

On peut ajouter une URL supplémentaire nommée alternative :

import cherrypy
 
class HelloWorld(object):
    @cherrypy.expose
    def index(self):
        return "Hello from CherryPy!"
 
    @cherrypy.expose
    def alternative(self):
        return "Another Hello from CherryPy!"

if __name__ == '__main__':
    cherrypy.quickstart(HelloWorld())

Dans le navigateur on entrera :

http://localhost:8080/alternative

Le navigateur affichera alors : Another Hello from CherryPy!

Simplissime ! Au final, pour ceux qui connaissent, comparer http.server à CherryPy revient à comparer les classes WebServer et AsyncWebServer quand on travaille sur ESP32. Quand on a goûté à CherryPy ou AsyncWebServer, il est difficile de revenir à http.server ou WebServer. On s'habitue vite au luxe.

Comme vous le savez peut être, AsyncWebServer est capable de gérer une page HTML en utilisant une template (un modèle). En Python la gestion des templates peut être confiée à Jinja2.

7. Générer un exécutable sous Windows

Quand on travaille avec Python, il est assez facile de distribuer ses logiciels à des utilisateurs Linux. En effet, Python est très souvent pré-installé sur la majeure partie des distributions.

Sous Windows, ce n'est pratiquement jamais le cas. Mais il existe un moyen : PyInstaller permet de générer un exécutable, un .exe !

Oui, vous avez bien lu. Vous pourrez ainsi distribuer votre logiciel de manière extrêmement simple, et de plus sans divulguer vos fichiers sources. La suite ici.

8. Conclusion

D'après de nombreux spécialistes Python est un langage de haut niveau, simple et facile à apprendre. De plus, de nombreuses librairies sont disponibles, ce qui simplifie encore plus le développement d'applications.

Malgré cela il a un inconvénient majeur : du fait que c'est un langage interprété, certaines erreurs de syntaxe sont détectées lors de l'exécution. Cela veut dire clairement que la phase de tests est un élément crucial dans le processus de développement. Mais cela ne veut pas dire que cela le rend moins fiable pour autant. En matière de développement, la phase de tests est en général aussi coûteuse que la phase de codage, quel que soit le langage. Il ne viendrait pas à l'idée d'un développeur professionnel de négliger ce fait.

Tout langage nécessite un apprentissage, qui requiert en général une semaine de labeur. Pour accéder à Python il n'en sera pas autrement, mais l'investissement sera très vite rentabilisé, bien au delà de ce que peut offrir C ou C++.


Cordialement

Henri

jeudi 29 décembre 2022

STM32 BLUE PILL vs ARDUINO NANO

 


STM32 BLUE PILL vs ARDUINO NANO


Bonjour, aujourd'hui nous allons examiner de plus près la carte BLUE PILL, équipée d'un microcontrôleur STM32F103C8T6.

J'avais déjà parlé de cette carte ici :

STM32 et ARDUINO

A mon sens, la BLUE PILL est une carte très avantageuse par rapport à l'ARDUINO NANO.

Ce tableau décrit les possibilités du STM32F103C8T6 48 pins. D'autres modèles plus puissants intègrent plus de mémoire FLASH, plus de périphériques, etc.

FonctionARDUINO NANOBLUE PILL
Broches3040
Processeur8 bits32 bits
Alimentation du microcontrôleur5V2V - 3.6V
Horloge16MHz72MHz
Mémoire FLASH32Ko64Ko
Mémoire RAM2Ko20Ko
Mémoire EEPROM1Ko4Ko (1)
GPIOs22 (6 PWM)32 (15 PWM) (2)
UARTs13
I2C12
SPI12
CAN01
USB01
JTAG01
ADC1 ADC 10 bits2 ADC 12 bits

(1) l'EEPROM est émulée en mémoire FLASH, comme sur un ESP32.

(2) les GPIOs sont tolérantes au 5V, il n'y a donc pas lieu d'ajouter des protections particulières.

Les 20Ko de RAM sont un avantage certain. Lorsque je développe du logiciel sur une NANO, je suis plus souvent confronté à un manque d'espace en RAM (données) qu'à un manque d'espace en FLASH (programme).

Egalement, la présence de 3 UARTs dispense d'utiliser des objets du genre SoftwareSerial, peu performants, lorsque l'on désire intégrer des périphériques du genre modem, BlueTooth, DfPlayer.

Le prix de la BLUE PILL est sensiblement équivalent à celui de l'ARDUINO NANO sur AliExpress. C'est dire si j'envisage sérieusement de l'utiliser en lieu et place de l'ARDUINO NANO dans mes projets.

Nous allons tout d'abord étudier la carte, et surtout sa mise en oeuvre.

1. La librairie STM32DUINO

Si l'on veut travailler avec la même facilité qu'avec l'ARDUINO, il faut installer un gestionnaire de cartes supplémentaire, comme pour un ESP32. Pour cela, dans l'IDE ARDUINO, il faut ouvrir les préférences (menu Fichiers / Préférences) et ajouter l'URL suivante (URL de gestionnaire de cartes supplémentaires).

https://github.com/stm32duino/BoardManagerFiles/raw/master/package_stmicroelectronics_index.json

Si ce champ comporte déjà une ou plusieurs URLs, il faudra utiliser un séparateur, qui est la virgule.

2. Les connecteurs

2.1. Le connecteur USB

Ce connecteur est bien relié aux GPIOs PA11 et PA12, comme prévu dans la datasheet. On pourrait penser que l'on peut l'utiliser comme le port USB de l'ARDUINO NANO (chargement du code, moniteur série), mais il n'en est rien.

En effet, pour que l'USB soit utilisable pour le chargement du code, il faudrait qu'il soit initialisé par un bootloader. Or il n'y a pas de bootloader prévu pour cette carte. Le chargement est bien réalisé par un bootloader mais il est interne au microcontrôleur, et utilise l'UART1 (PA9 & PA10).

Certains ont cependant écrit un bootloader : https://how2electronics.com/stm32-bootloader-programming-usb/

2.2. UART1 : PA9 et PA10

Ces deux broches permettent le chargement du code. Il faut sélectionner les bonnes options dans le menu Outils :
  • Type de carte : Generic STM32F1 series
  • Board part number : BluePill F103C8
  • Upload Method : STM32CubeProgrammer (Serial)

Ensuite le raccordement de la carte est simple. Il requiert un convertisseur USB / Série : 

Il faut bien entendu utiliser un modèle de convertisseur adapté au 3.3V, comme celui-ci, qui est équipé d'un cavalier permettant de choisir entre 5V et 3.3V :

FT232RL 5V/3.3V

Remarque : si la carte est alimentée par le connecteur USB ou la broche 5V, le fil rouge est inutile.

Avant le chargement il faut déplacer le cavalier boot0 de la carte BLUE PILL sur 1. Avant chaque chargement du code il faudra appuyer sur le bouton RESET.

Si l'on veut que la carte démarre directement l'application, il faudra replacer le cavalier boot0 sur 0.

2.3. Le connecteur SWD

Le connecteur à 4 broches en bout de carte peut être relié à un boîtier ST-LINK, afin de charger le code : 




Le câblage se fait avec 4 fils seulement : 3.3V, GND, SWDIO, SWDCLK.

La vitesse de chargement est supérieure à celle de la liaison série, et il n'y a pas besoin d'appuyer sur le bouton RESET.

Il faut sélectionner les bonnes options dans le menu Outils :
  • Type de carte : Generic STM32F1 series
  • Board part number : BluePill F103C8
  • Upload Method : STM32CubeProgrammer (SWD)

Le boîtier ST-LINK a un autre avantage : il peut permettre de deboguer son application à l'aide d'un debugger, GDB par exemple.

2.3. Moniteur série

Que l'on utilise l'une ou l'autre méthode de chargement, le moniteur série (Serial) utilise l'UART1, donc PA9 et PA10. L'utilisation d'un boîtier ST-LINK ne dispense donc pas de l'achat d'un convertisseur USB / Série si l'on veut afficher des informations sur le moniteur série.

2.4. Alimentation

La carte peut être alimentée de plusieurs manières : 

  • par le connecteur USB, en 5V. La broche 5V, dans ce cas, fournira cette même tension
  • par la broche 5V de la carte
  • par la broche 3.3V de la carte
  • par le convertisseur USB / série. On connectera la broche VCC à la broche 3.3V de la carte
  • par le boîtier ST-LINK. On connectera la broche VCC à la broche 3.3V de la carte

Si la carte est alimentée par l'USB ou la broche 5V, le 3.3V nécessaire au STM32 est fourni par un régulateur RICHTEK RT9193, un bon régulateur LDO, capable de fournie 300mA. Son courant de repos est de 90µA.

Contrairement à une carte ARDUINO il n'y a aucune protection par diode entre le 5V USB et la broche 5V. Cela veut dire que si l'on alimente la carte par la broche 5V, et que l'USB est branché, le 5V de la carte est directement relié au 5V USB. Il peut y avoir danger si les deux tensions sont vraiment différentes.

3. L'ADC

Par rapport à l'ADC de l'ARDUINO, celui de la BLUE-PILL est assez différent :

  • la tension de référence est de 3.3V, la tension d'alimentation du microcontrôleur
  • la résolution est supérieure : 12 bits, donc 4096 points, au lieu de 1024
  • le temps d'échantillonnage est nettement inférieur
  • la broche AREF n'existe pas, sauf sur de plus grosses cartes

3.1. Résolution

La résolution d'un ATMEGA328P est de 10 bits, contre 12 bits pour le STM32, ce qui produira des mesures 4 fois plus précises.

3.2. Temps d'échantillonnage

Le temps d'échantillonnage du STM32 est largement inférieur à celui d'un ATMEGA328P :
  • 740000 samples/s au maximum pour le STM32F401, assez proche du STM32F103C8T6
  • 38470 samples/s pour l'ATMEGA 328P en poussant l'horloge de l'ADC au maximum
On constate aisément que la vitesse d'acquisition sera nettement supérieure avec un STM32.

4. Exercice

Je propose de construire un petit voltmètre, équipé d'un afficheur 4 digits à 7 segments du genre TM1637.

4.1. Schéma

Le schéma est réalisé à l'aide de KICAD :

Le montage est alimenté par le connecteur USB, car le TM1637 a besoin de 5V pour pouvoir fonctionner.

Le chargement du code peut se faire via le connecteur SERIAL ou par le connecteur SWD en bout de carte.

L'entrée à mesurer peut recevoir une tension variable entre 0V et 3.3V (un simple potentiomètre suffit).

Si l'on désire mesurer des tensions plus importantes, il suffit d'utiliser un pont diviseur à résistances :

U2 = U * R2 / (R2+R1)

4.2. L'IDE ARDUINO

Il faut bien entendu installer le package STM32DUINO, si ce n'est pas déjà fait.

Ensuite il faudra installer la librairie suivante :

TM1637 : https://github.com/RobTillaart/TM1637_RT

Celle-ci est installable depuis l'IDE ARDUINO.

Pour récupérer le projet voir plus bas :  4.3. Téléchargements.

4.3. Le code

Le code ressemble beaucoup à ce que l'on pourrait écrire si la carte était une NANO, aux définitions des GPIOs près : 

bluepill-voltmeter.ino

Le croquis utilise 21332 octets (32%) de l'espace de stockage de programmes. Le maximum est de 65536 octets.

Les variables globales utilisent 852 octets (4%) de mémoire dynamique, ce qui laisse 19628 octets pour les variables locales. Le maximum est de 20480 octets.

Comme on peut le constater, le code occupe un espace bien supérieur au même code compilé pour l'ARDUINO NANO : 

Le croquis utilise 4896 octets (15%) de l'espace de stockage de programmes. Le maximum est de 30720 octets.

Les variables globales utilisent 331 octets (16%) de mémoire dynamique, ce qui laisse 1717 octets pour les variables locales. Le maximum est de 2048 octets.

Ceci est dû au fait que le STM32F103C8T6 est un microcontrôleur 32 bits, mais probablement aussi à la librairie STM32DUINO.

Mais, qu'il s'agisse de mémoire FLASH ou RAM, la quantité restante est supérieure à celle qu'offre la NANO : 

  • FLASH : 44204 octets
  • RAM : 19628 octets
Nous serons donc beaucoup plus à l'aise pour développer de grosses applications, ou utiliser des librairies de taille conséquente.

4.4. Téléchargements

Pour télécharger le projet : https://bitbucket.org/henri_bachetti/bluepill-voltmeter

Cette page donne toutes les informations nécessaires :

https://riton-duino.blogspot.com/p/migration-sous-bitbucket.html

5. Photos


En haut : le convertisseur USB / Série.

A gauche : la BLUE PILL.

A droite : L'afficheur TM1637.

Au milieu : le petit potentiomètre bleu permettant d'ajuster la tension à mesurer.

Le multimètre affiche 3.034V, alors que le montage affiche 3.014V.

Ceci est dû au fait que je n'ai pas mesuré la tension de 3.3V produite par la carte. Or elle vaut 3.33V, contre 3.31V dans le code. En corrigeant cette petite erreur, tout rentre dans l'ordre.

A ce sujet, il faut rappeler que le STM32F103C8T6 possède bien une broche AVDD, permettant de fixer la tension de référence analogique. Mais sur la BLUE PILL, cette broche est reliée au 3.3V. Pour faire des mesures précises, il convient donc de mesurer cette tension avec un multimètre suffisamment précis.

6. Conclusion

Ces cartes, une fois bien prises en main, ont un potentiel comparable à celui de l'ARDUINO NANO, et même largement supérieur si l'on veut profiter des possibilités supplémentaires : 

  • grande quantité de mémoire
  • plusieurs liaisons série
  • double bus I2C, SPI
  • etc.


Cordialement

Henri


jeudi 2 juillet 2020

INA138 et MAX4372 : Current Monitoring



INA138 et MAX4372

Current Monitoring


Nous allons aujourd'hui mettre en œuvre L'INA138 et le MAX4372, deux amplificateurs de courant.

Ces composants permettent d'amplifier la tension présente aux bornes d'un shunt de mesure afin d'envoyer une tension mesurable sur l'entrée analogique d'un microcontrôleur.

La sortie de l'INA138 est un générateur de courant. Le gain est donc réglable à l'aide d'une simple résistance.

La sortie du MAX4372 est un générateur de tension. Le gain est fixe et dépend du modèle :

  • MAX4372T : 20
  • MAX4372F : 50
  • MAX4372H : 100

La première question que l'on peut se poser : pourquoi utiliser un amplificateur analogique alors que des modèles numériques existent ? :

  • INA219 (12 bits)
  • INA226 (16 bits)

Ces composants nous apporteraient une précision supérieure, mais ils ont un inconvénient majeur : leur temps de conversion est élevé, 140µS au minimum. Si l'on désire s'affranchir du bruit il est même recommandé d'adopter un temps de conversion supérieur : 1.1ms ou 8.244ms.

Deuxièmement le dialogue se fait en I2C, ce qui n'améliore pas les choses.

Bref le handicap est important si l'on a des variations de courant rapides à observer.

Petit avantage supplémentaire pour l'INA 138 : il consomme 25µA, contre 330µA pour l'INA226. Le MAX4372 est à peine plus gourmand : 30µA.

D'autre part, lorsque l'on mesure un courant, la précision n'est pas le critère principal. Il s'agit souvent de mesurer la consommation d'un montage ou d'un composant et de tracer sa variation dans le temps. Une précision de 0.1% ou 0.5% est totalement inutile.

Le montage suivant pourra être utilisé pour mesurer un courant avec une rapidité qui dépendra uniquement de celle de l'ADC du microcontrôleur.

On peut facilement obtenir plus de 38000 échantillons par seconde avec un ATMEGA328 :

https://riton-duino.blogspot.com/2018/11/arduino-les-performances-de-ladc.html

Certains gros STM32 peuvent offrir plus d'un million d'échantillons par seconde !

https://riton-duino.blogspot.com/2019/04/stm32-ladc.html

La deuxième question que l'on peut se poser : un simple AOP monté en amplificateur ne pourrait-il pas faire le même travail ?

La réponse est non, car l'INA138 et le MAX4372 peuvent accepter sur leurs entrée des tensions largements supérieures à leur tension d'alimentation, ce que ne pourra pas tolérer un AOP.

1. L'INA138

Ce composant est capable de mesurer un courant côté "high side" (rail positif) uniquement. Il peut être alimenté entre 2.7 V à 36 V.
Un autre modèle, l'INA168, peut être alimenté entre 2.7 V à 60 V.


La résistance shunt RS produit une chute de tension proportionnelle au courant traversant la charge. Il convient donc de choisir sa valeur judicieusement, afin de ne pas perturber la charge.
Pour une mesure précise, il est recommandé également de ne pas dépasser 0.5V entre les entrées de l'INA138.

La résistance RL permet de fixer le gain de l'amplificateur (voir figure 9 de la datasheet) :
  • RL=50KΩ pour un gain de 10
  • RL=100KΩ pour un gain de 20
  • RL=250KΩ pour un gain de 50
  • etc.
Imaginons que l'on désire mesurer un courant maximal d'un ampère.
Si l'on admet 100mV de chute de tension dans le shunt, celui-ci aura une valeur de :

RS = 0.1V / 1A = 0.1Ω

Si l'on fixe le gain à 20, la résistance RL doit avoir une valeur de 100KΩ.

La tension maximale en sortie de l'INA138 vaudra :

Vo = Is * RS * RL / 5KΩ = 1A * 0.1Ω * 100KΩ / 5kΩ = 2V

L'entrée analogique du microcontrôleur mesurera donc 2V à pleine échelle.

On peut utiliser différentes références de tension (VREF) pour l'ADC :
  • référence par défaut : 5V ou 3.3V suivant la tension d'alimentation
  • référence interne 1.1V
  • référence interne 2.56V (ARDUINO MEGA)
Si l'on utilise la référence par défaut, la précision s'en ressentira légèrement.
Avec une référence 3.3V, la perte de précision sera faible.
Avec une référence 5V on perdra un bit sur les 10 de l'ADC. On peut augmenter le gain à 50 avec une résistance RL de 200KΩ. La tension maximale à mesurer sera de 5V. C'est limite.

Si l'on utilise la référence interne 1.1V l'ADC sera saturé. Il conviendra de diminuer la valeur du gain. On le fixera à 10 avec une résistance de 50KΩ. La tension maximale à mesurer sera de 1V.
Enfin, avec la référence interne 1.1V, si l'on conserve le gain de 20, le courant maximal que l'on pourra mesurer sera de 0.55A.

Sur une MEGA, la référence 2.56V sera le meilleur choix.

Il faut également tenir compte de la puissance dissipée par le shunt. La modèle que j'ai utilisé est une résistance 100mΩ CMS 2010 pouvant dissiper 1W.
Le courant maximal qu'elle pourra supporter est de :

I = √(P / R) = 3.16A

Comme c'est une limite absolue à ne pas dépasser, si l'on veut éviter un échauffement excessif, il serait bon d'appliquer une marge de sécurité.
Dans le cas où l'on dépasse le courant maximal admissible, il faudra adopter une résistance de puissance plus importante, mais cela se fera au détriment de la chute de tension dans RS :

U = R * I = 0.1 * 3.16 = 0.316V

On est loin des 100mV que l'on s'était fixé au départ. Il serait plus judicieux d'adopter un shunt de plus faible valeur, et d'augmenter le gain.

On peut résumer avec un petit tableau :

VREF
RS RL Gain Courant maxi Résolution
(ADC 10 bits)
5V 0.1Ω 200KΩ 50 1A 0.97mA
5V 0.1Ω 100KΩ 20 2.5A 2.44mA
5V
0.1Ω (*) 50KΩ 10 5A 4.88mA
5V
0.02Ω 200KΩ 50 6.25A 6.1mA
3.3V 0.1Ω 200KΩ 50 0.66A 0.64mA
3.3V 0.1Ω 100KΩ 20 1.65A 1.61mA
3.3V
0.1Ω (*) 50KΩ 10 3.3A 3.22mA
3.3V
0.02Ω 200KΩ 50 4.125A 4mA
1.1V 0.1Ω 200KΩ 50 0.22A 0.21mA
1.1V 0.1Ω 100KΩ 20 0.55A 0.53mA
1.1V 0.1Ω 50KΩ 10 1.1A 1.07mA
2.56V 0.1Ω 100KΩ 20 1.28A 1.07mA

(*) Ces deux configurations nécessiteront l'adoption d'un shunt d'au moins 3W, ou de diminuer sa valeur et d'augmenter le gain (voir ligne suivante dans le tableau).

Bien entendu avec d'autres microcontrôleurs possédant par exemple un ADC 12 bits ou des références internes de valeurs différentes ces paramètres seront différents.

On peut utiliser une référence de tension externe si l'on désire une plus grande précision ou une stabilité en température supérieure.


2. Le MAX4372

Le MAX4372 est plus simple. Il n'y a aucune résistance de réglage, mais les calculs restent les mêmes, et le tableau précédent reste valable.


3. Les schémas

3.1. L'INA138

Voici le schéma que j'utilise :
La sortie de l'INA138 est redirigée vers l'entrée d'un AOP OPA363 monté en suiveur (gain = 1), ceci afin de diminuer fortement l'impédance de sortie.
Cet étage est nécessaire si l'on désire faire des mesures de courant subissant des variations rapides ou utiliser plusieurs canaux ADC simultanément. Voir cet article :

Un condensateur de 100nF permet de découpler l'alimentation.

3.2. Le MAX4372

Voici le schéma que j'utilise :
Le MAX4372 a une impédance de sortie très faible, il n'a nul besoin d'un AOP suiveur pour abaisser celle-ci.

Ici aussi un condensateur de 100nF permet de découpler l'alimentation.

Que dire de plus ? Il est temps de passer aux essais.

3.3. Les branchements

Le connecteur IN-OUT des deux modules a le brochage suivant :
  • 1 : sortie
  • 2 : alimentation 5V ou 3.3V (l'OPA363 supporte au maxi 5.5V)
  • 3 : GND
  • 4 et 5 : V-
  • 6 et 7 : V+
La tension d'alimentation pour la charge doit être appliquée sur les broches 6-7. La charge est câblée entre les broches 4-5 et GND.

Les broches 4-5 et 6-7 sont doublées afin d'augmenter le courant admissible.

Sur ce schéma la charge est une résistance de 47Ω, elle consommera donc environ 100mA sous 5V.

On pourrait imaginer remplacer cette charge par un microcontrôleur quelconque pour en mesurer la consommation. Il est facilement imaginable également que le microcontrôleur reçoive lui-même l'information à mesurer sur une de ses propres entrées analogiques. Il pourra donc mesurer sa propre consommation en temps réel.

Un module de ce type peut être mis en place pendant la phase de test d'un projet et retiré ensuite, une fois que la consommation est connue.
On peut par exemple prévoir un connecteur femelle à 7 points sur la carte pour enficher le module, et le remplacer ensuite par un cavalier entre les pins 5 et 6.

4. Le sketch

4.2. L'INA138

Voici un petit sketch pour un ARDUINO :

#define VREF          1.073
#define SHUNT         0.100
#define INA138_GAIN   19

void setup()
{
  Serial.begin(115200);
  analogReference(INTERNAL);
}

void loop()
{
  unsigned int adc = analogRead(A0);
  Serial.print(F("ADC: ")); Serial.println(adc);
  float voltage = adc * VREF / 1023;
  Serial.print(F("voltage: ")); Serial.print(voltage, 3);  Serial.println(" V");
  float current = voltage / SHUNT / INA138_GAIN;
  Serial.print(F("current: ")); Serial.print(current, 3); Serial.println(" A");
  delay(2000);
}


On voit que la tension de référence vaut 1.073V. Cette tension est mesurée sur la sortie AREF de l'ARDUINO.

Sur une PRO MINI cette broche n'existe pas, mais on pourra la fixer à 1.1V sans problème. On ajustera la valeur du gain dans le code afin de compenser.

Dans ce sketch la valeur du gain est de 19. Pourquoi cette valeur alors que sa valeur théorique est de 20 ?
Ce n'est qu'une histoire de précision de composants. Le shunt de 0.1Ω a été acheté chez un fournisseur sérieux. Il a une précision de 1%. La résistance RL que j'ai utilisé fait partie d'un jeu de résistances CMS dont j'ignore la précision (AliExpress).

J'ai donc ajusté la valeur du gain dans le code afin que le résultat affiché soit identique à la valeur du courant mesuré par mon multimètre.
Si l'on utilise une résistance RL à 1% la correction à réaliser sera plus faible.

L'ARDUINO, après avoir ajusté ces constantes affiche ceci :

ADC: 168
voltage: 0.176 V
current: 0.099 A
ADC: 167
voltage: 0.175 V
current: 0.098 A
ADC: 167
voltage: 0.175 V
current: 0.098 A
ADC: 166
voltage: 0.174 V
current: 0.098 A

Le voltage affiché est celui lu sur l'entrée A0. Comme le gain de l'INA138 est fixé à 20, la tension aux bornes du shunt est donc 20 fois inférieure.

La mesure est très stable.

4.2. Le MAX4372

Le sketch est légèrement différent (j'ai utilisé un MAX4372F avec donc un gain de 50) :

#define VREF          1.073
#define SHUNT         0.100
#define MAX4372_GAIN  50

void setup()
{
  Serial.begin(115200);
  analogReference(INTERNAL);
}

void loop()
{
  unsigned int adc = analogRead(A0);
  Serial.print(F("ADC: ")); Serial.println(adc);
  float voltage = adc * VREF / 1023;
  Serial.print(F("voltage: ")); Serial.print(voltage, 3);  Serial.println(" V");
  float current = voltage / SHUNT / MAX4372_GAIN;
  Serial.print(F("current: ")); Serial.print(current, 3); Serial.println(" A");
  delay(2000);
}

Ici il n'y a aucun besoin d'ajuster quoi que ce soit, mis à part la tension VREF.
Voici le résultat :

voltage: 0.514 V
current: 0.103 A
ADC: 487
voltage: 0.511 V
current: 0.102 A
ADC: 505
voltage: 0.530 V
current: 0.106 A
ADC: 490
voltage: 0.514 V
current: 0.103 A


La mesure est un peu moins stable, ceci étant dû probablement au gain plus élevé. Le modèle MAX4372T avec un gain de 20 serait probablement moins bruité.
Mais cela reste tout à fait exploitable.

5. Photos

Voici une photo des 2 modules :

INA138

MAX4372

Les 2 cartes font respectivement 23mm x 20mm et 20mm x 15mm.

On peut donc les utiliser comme des module ARDUINO classique, y compris sur une breadboard si le courant à mesurer est faible.

Si le courant à mesurer est supérieur à 1 ou 2 ampères il serait plus convenable d'opter pour un connecteur plus approprié.

6. Téléchargements

Ces deux projets sont téléchargeables ici :


7. Liens utiles

Les amplificateurs de courants.
Chez TEXAS :
Chez MAXIM :

8. Conclusion

La mesure de courant est largement facilitée par ces petits composants, avec un peit avantage pour le MAX4372, plus simple à mettre en oeuvre.
Il ne reste plus qu'à les exploiter sur une vraie application.

Une dernière chose : le MAX4372, l'INA138 et l'OPA363 se trouvent facilement sur AliExpress.
Le module INA138 a coûté 1.60€, le MAX4372 1.10€.


Cordialement
Henri

9. Mises à jour

03/07/2020 : le MAX4372