Problème lors du suivi de la charge d'un circuit RC

Bonjour,
Enseignant de sciences physiques, je souhaiterais monter un petit projet de suivi de la charge d'un circuit RC par Arduino avec commande de la carte Arduino (Uno) par Python. Je ne souhaite pas utiliser pyfirmata et préfère interagir directement via Serial (dans un but pédagogique mais aussi pour découvrir le fonctionnement de Serial). La charge dure quelques millisecondes (capacité de 47nF et résistance de 100kohms) et la communication via le port Série étant trop lente, je stocke les mesures sur l'Arduino afin de les transmettre en un bloc à la fin des mesures. Chaque mesure code sur 2 octets l'instant de la mesure et la tension mesurée (codée sur 10 bits par l'Arduino).
Le programme Python lance la mesure et récupère les données pour les tracer. Je place ci-dessous les deux codes :

Code Arduino :

int voltage = 0;    // tension mesurée

unsigned long start_time;
unsigned long time_actuel;

#define MESURE 101   //les informations envoyées par le programme Python pour lancer les subroutines
#define FLUSH_BUFFER 102
#define N 300   //le nombre de mesures

uint8_t buffer[4 * N]; //chaque mesure utilise 4 bytes

//===============================================================================
//  Subroutines
//===============================================================================


void FlushBuffer() //vide le port Série
{
  while (Serial.available()>0) Serial.read();
}

void Mesure()
{
  int n;
  int debutExperience;
  unsigned long delta_t;
  int delta_t_int;
  debutExperience = 0;
  digitalWrite(8,LOW); //le "générateur", pin8, est remis à zéro
  delay(1000); //on attend 1 seconde la décharge éventuelle du condensateur
  start_time = micros();
  for (n=0; n<N; n++){
    time_actuel = micros();  
    if  ((time_actuel-start_time>3000) && debutExperience == 0 ){ 
      digitalWrite(8,HIGH); //au bout de 3ms on allume le "générateur"
      debutExperience = 1;
    }
    delta_t=micros()-start_time;
    delta_t_int=(int) (delta_t);
    voltage=analogRead(0);
    buffer[4*n]=(delta_t_int>>8)&0xFF; //octet de poids fort pour l'instant t
    buffer[4*n+1]=delta_t_int & 0xFF; //octet de poids faible pour l'instant t
    buffer[4*n+2]=(voltage>>8)&0xFF; //octet de poids fort pour la tension
    buffer[4*n+3]= voltage & 0xFF; //octet de poids faible pour la tension
    delay(0.5); //on attend 0.5ms entre deux mesures
  }
  Serial.write(buffer,4*N); //une fois les N mesures faites, on envoie au programme Python
}


//===============================================================================
//  Initialization
//===============================================================================
void setup() 
{ 
  Serial.begin (500000);      
  while (Serial.available()>0) Serial.read(); //on teste la communication via le port Série
  char c;
  c = 0;
  Serial.write(c);
  c = 255;
  Serial.write(c);
  c = 0;
  Serial.write(c);
}

//===============================================================================
//  Main
//===============================================================================
void loop() 
{
  char commande;
  if (Serial.available()>0) {
    commande = Serial.read();
    if (commande == MESURE) {
      Mesure();
    }
    else if (commande == FLUSH_BUFFER){
      FlushBuffer();
    }
    }
   delay(0.0001);
}

Et du côté de Python :

import matplotlib.pyplot as plt
import serial


class Arduino():
    def __init__(self,port):
        self.ser = serial.Serial(port,baudrate=500000)
        c_recu = self.ser.read(1) #on teste la communication via le port Série
        while ord(c_recu)!=0:
            c_recu = self.ser.read(1)
        c_recu = self.ser.read(1)
        while ord(c_recu)!=255:
            c_recu = self.ser.read(1)
        c_recu = self.ser.read(1)
        while ord(c_recu)!=0:
            c_recu = self.ser.read(1)
        print("Communication établie")            
        
##############################################################################
#Création des variables de commande
##############################################################################        
        self.MESURE = chr(101).encode() 
        self.FLUSH_BUFFER = chr(102).encode()        
        
        
    def close(self):
        #libération du port série
        self.ser.close()
        

    def flushBuffer(self):
        #nettoyage du buffer
        self.ser.write(self.FLUSH_BUFFER)   
            
    
    def mesure(self):
        #mesure sur 300 points
        self.ser.write(self.MESURE)
        liste_recus = []
        N=300
        liste_recus = self.ser.read(4*N)
        temps = []
        voltage = []
        for n in range(N):
            temps_1n = (liste_recus[4*n]) 
            temps_2n = (liste_recus[4*n+1])
            volt_1n = (liste_recus[4*n+2])
            volt_2n = (liste_recus[4*n+3])          
            temps.append(float(temps_1n*0x100+temps_2n)/1000)
            voltage.append(float(volt_1n*0x100+volt_2n)*5/1024)
        return temps,voltage

port="/dev/cu.usbmodem1421"
ard=Arduino(port)

ard.flushBuffer()

charge=ard.mesure()

ard.close()
 
plt.figure()
plt.plot(charge[0],charge[1],'+')
plt.show()

Le programme fonctionne très bien à la première exécution comme le montre la 1ère image attachée. La charge affichée correspond au comportement attendu. Par contre, quand je relance le programme dans la foulée, j'obtiens du bruit proche de 5V.
Je dois débrancher la connexion USB et la rebrancher pour que je puisse retrouver une belle charge de condensateur quand je relance le programme (mais à l'exécution suivante je retombe sur du bruit).

Voici les différentes modifications que j'ai essayées mais aucune n'a modifié le comportement :

  • j'ai relié physiquement la masse de la carte Arduino à une masse EDF via un fil,
  • j'ai testé les programmes avec une autre carte Arduino Uno et sur un autre ordinateur portable,
  • j'ai modifié la durée entre deux mesures.

La seule modification qui a fonctionné était de brancher une led entre la sortie 8 et la masse (je voulais tester le bon fonctionnement de la commande digitalWrite) : dans ce cas j'obtiens à chaque exécution une bonne charge (mais la tension en régime stationnaire est de 2,5V et non 5V).

Je suspecte un comportement capacitif de la sortie digitale mais je vous avoue que c'est un peu flou. Est-ce que quelqu'un aurait une idée ou aurait déjà rencontré de tels problèmes ?
Je vous remercie par avance pour le temps que vous prendrez à réfléchir à ce problème (qui est peut-être très simple).

bruit.png

belle charge.png

bruit.png

belle charge.png

Bonjour,

Je viens d'essayer ta manip et j'ai exactement le même problème que toi. Ca marche une fois et ensuite on a n'importe quoi. Obligé de couper l'alim de la carte pour que ça refonctionne.

J'ai donc branché un oscillo aux bornes du condensateur pour essayer d'analyser et la ... ça fonctionne à tous les coups. C'est à y perdre son latin!

Une petite remarque en passant: delay() prend un unsigned long comme argument, donc delay(0.5) est équivalent à delay(0)

Bonsoir

j'essaie de comprendre le symptôme avant d'aller plus loin.

bruit ? vous parlez du 'bruit de quantification' quand la tension à acquérir se situe entre 4,9125 et 4,9175, ce bruit qui fait que le chiffre de droite est souvent 'instable' sur un multimètre.

Il me semble inhérent à la conversion A/N mais est perceptible .....ou pas..... selon la valeur de la tension à mesurer et la valeur réelle de la référence selon la position d ela valeur entre deux 'échelons' successifs.

Est-ce gênant pour la manip?
Dans un contexte pédagogique j'y vois au contraire l'occasion de montrer un bruit de quantification !

tomprog appelle ça du bruit, mais en fait en gros on obtient n'importe quoi.
Au lieu de la belle courbe 2 dans son post on obtient la courbe 1 (ou n'importe quelle variante de cette courbe).

OK,
Il faudrait montrer ce que cela donne parce que sur le 'zoom' qu'il a montré ne je vois qu'un bruit de quantification normal

Ce n'est pas un zoom, c'est la même courbe. Enfin ce devrait être la même courbe.

sur les images je ne peut m'empêcher de voir ((je n'ai pas vu le code )
d'une part une courbe entre 0V et 5V
d'autre part un zoom entre 4,9V et 5V (fin de charge vers l'asymptote)

En fait dans la courbe du haut le condensateur a une charge initiale de 4,8975 V et se charge vers 5V, il n'est pas initialement déchargé (contrairement à la situation ou l'alimentation a été préalablement coupée)

J'ai un doute sur le nombre réel envoyé en paramètre à delay()

Oui, c'est comme si le condensateur ne se déchargeait pas ou comme si le convertisseur était bloqué autours de 5V, mais c'est très bizarre car ça fonctionne bien la première fois et quand je branche un oscilllo ça fonctionne bien à tous les coup.

@affich
Tu as bien fait progresser le schmilblic en remarquant que c’était comme si le condensateur n’était pas déchargé.

Je viens de voir d'ou vient le problème: la pin de charge/décharge du condensateur n'est pas mise en sortie donc on travaille uniquement en activant/désactivant la résistance de pullup.

En mettant la pin en sortie tout est ok.

tout s'explique !
@kamill j'allais te proposer d'observer à l'oscilloscope l'effet de :

  digitalWrite(8,LOW); //le "générateur", pin8, est remis à zéro
  delay(1000); //on attend 1 seconde la décharge éventuelle du condensateur

la seconde est très largement suffisante pour décharger 47nF à travers 100k ... à condition de bouccler le circuit à la masse... :slight_smile: Tel quel le DigitalWrite(8,LOW) était loin d'avoir l'effet espéré !!

Ce qui était quand-même déroutant c'est que quand je branchais l'oscillo sur le condensateur ça fonctionnait. Sans doute que la résistance de la sonde était suffisante pour décharger le condensateur.

Oui en déchargeant 47nF sur 1,1 MOhm pendant 1 seconde ça suffisait largement
(RC=47ms, 5*RC= 235ms)

En mettant la sonde sur X10 la décharge n'aurait pas été complète

Ma sonde est toujours sur X10. Je ne sais même pas s'il y a une autre position :slight_smile:

Merci tout d'abord pour vos différentes réponses ! Effectivement je n'avais pas déclaré la pin8 en mode OUTPUT. Cela fonctionne bien maintenant.
Si on ne déclare pas le mode OUTPUT, la mise à LOW de la pin8 ne met pas cette sortie à la masse ?
En tout cas je comprends mieux le résultat obtenu en présence de la LED qui déchargeait le condensateur.

(PS : Effectivement je ne savais pas que l'argument de delay() était un unsigned long, j'utiliserai delayMicroseconds() ).

tomprog:
Si on ne déclare pas le mode OUTPUT, la mise à LOW de la pin8 ne met pas cette sortie à la masse ?

Non la pin est en entrée donc à l'état haute impédance. Par contre si on met une entrée à HIGH, ça connecte une résistance de pullup ce qui explique que la charge du condensateur fonctionne.
Par contre comme le condensateur ne se décharge jamais ça ne fonctionne qu'une seule fois.

D'accord merci !

Bonjour,

Je te propose une version un peu différente mais qui fait la même chose : les paramètres sont transmis à partir de Python.

Code Arduino

// Déclaration des variables du programme
unsigned long baud = 250000; // flux de la liaison série
int delai = 1000;             // délai d'attente pour être sûr de décharger le condensateur
 
// Variables d'acquisition
int timeRes;    // période d'échantillonnage en ms
int dureeAcquisition ; // durée totale de l'acquisition  en ms, saisi dans Python
unsigned long nbrPoints; // sera calculé à partir des données précédentes
unsigned long cpt = 1;
int topDepart = 0; //variable pour lancer l'acquisition
String envoi;


unsigned long tempsZero;
unsigned long tempsCourant;
unsigned long tempsPrec;
 
// Broches de branchement au circuit
byte const mesureTension = 0; //broche analogique de mesure de la tension aux bornes de C
byte const resistancepullup = 5;
byte const chargeCondo = 3;   //broche pour décharger initialement le condensateur
 
void setup()
{
    Serial.begin(baud);
 
    //configure la broche commandePin en tant que sortie
    pinMode(resistancepullup,INPUT_PULLUP);

    pinMode(chargeCondo, OUTPUT);
 
    //on décharge le condensateur en le mettant à 5V comme l'alimentation de la résistance pull-up
    //on a donc 5V en entrée du circuit et 5V en sortie --> il se décharge
    digitalWrite(chargeCondo, HIGH);
 
    //on attend delai ms pour être sur de l'avoir déchargé
    delay(delai);    
    tempsPrec = 0;
    tempsZero = millis();

    reglages();
    nbrPoints=dureeAcquisition/timeRes;
}
 
//  Boucle principale
void loop()
{        
    tempsCourant = millis() - tempsZero;
    if (tempsCourant >= tempsPrec && cpt <= nbrPoints)
    {
        lecture();
        cpt++;
    }     
}

void reglages()
{
  while (topDepart == 0) {
    envoi = Serial.readString();

    while (Serial.available() == 0 );
    if (Serial.available() != 0)
    {
      timeRes = Serial.parseInt(); //on enregistre la valeur saisie dans timeRes
      delay(100); //délai de temporisation
    }

    while (Serial.available() == 0 );
    if (Serial.available() != 0)
    {
      dureeAcquisition = Serial.parseInt(); //on enregistre la valeur saisie dans dureeAcquisition
      delay(100); //délai de temporisation
    }

    while (Serial.available() == 0 );
    if (Serial.available() != 0)
    {
      topDepart = 1;
      tempsPrec = 0;
      tempsZero = millis();
    }
  }
}
void lecture()
{
    //on met à 0V le générateur pour voir la charge
    digitalWrite(chargeCondo, LOW);
 
    float tension;
    // Lecture et conversion des données
    tension = analogRead(mesureTension); // mesure analogique    
 
    // Envoie les données
    Serial.print(tempsCourant / 1000.0, 3); // temps en seconde
    Serial.print(';');
    Serial.println(tension); // donne la valeur recalculée de tension aux bornes de C
 
    tempsPrec += timeRes;
}

Code Python

"""
Observation de la charge d'un condensateur

Auteur : Nicolas Le Boulaire
Date de création : 24/04/2020
Date de dernière mise à jour : 04/05/2020

Programme qui permet de récupérer les données issues du moniteur série d'Arduino,
de les afficher graphiquement et de modéliser les points expérimentaux à partir
de la courbe attendue pour connaître les tensions de seuil
Montage utilisé : sortie numérique comme générateur programmable
Pour les bornes sur l'Arduino, se référer au programme .ino de même nom

Entrée : Tension analogique aux bornes du condensateur
Résultat : graphique et constante de temps du circuit
"""
# Importation des modules utilisés
import serial
import time
import matplotlib.pyplot as plt
import numpy as np
from math import exp, log
from scipy.optimize import curve_fit

# Définition de la fonction pour la modélisation
def fonction( x, a, b):
    """
    Fonction qui permet de définir une fonction pour optimiser la modélisation
    de valeurs expérimentales
    Entrées : la variable x et les paramètres de la fonction a et b
    Résultat : valeur calculée de la fonction au point x
    """
    return a *(1- np.exp(-1/b*x)) #a=tension de charge E, b=tau=cste de temps

# Initialisation des variables
arduino = None
rawdata = []
nbrPoints = 0
tension = []
temps = []

def acquisition(nbrPoints):
    # Ouverture de la liaison série
    portcom=input("Saisir le numéro de port (COM) de l'Arduino :")
    portcom="COM"+portcom
    try:
        arduino = serial.Serial(portcom, baudrate=250000, timeout=3)
    except Exception as e:
        print("Vérifier le port série:", e)
        return

    time.sleep(1)
    periode=input("Saisir la période d'échantillonnage (en ms) : ")
    arduino.write(periode.encode("ascii"))
    time.sleep(1)
    duree=input("Saisir la durée totale (en ms) : ")
    arduino.write(duree.encode("ascii"))
    time.sleep(1)
    acquisition=input("Saisir S pour lancer l'acquisition : ")
    arduino.write(acquisition.encode("ascii"))

    nbrPoints=int(duree)//int(periode)

    # Réception et stockage des données
    print("Début de l'acquisition...")
    compt = 0
    while compt < nbrPoints:
        data_read = arduino.readline().decode()[:-2]
        rawdata.append(data_read)
        #print(compt, rawdata[compt], sep=" - ")
        compt += 1
    print("Fin de l'acquisition.")
    arduino.close()



acquisition(nbrPoints)
def write(L):
    file=open("chargecondo.txt", mode="w")
    for i in range(len(L)):
        file.write(L[i]+"\n")
    file.close()

# Remplissage des variables temps et tension à partir de l'acquisition
write(rawdata)
temps,tensionanalog=np.loadtxt("chargecondo.txt", delimiter=";",unpack=True)

# Calcul de la vraie tension issue de la valeur analogique de l'Arduino
tensionvolt=tensionanalog*5.0/1024.0

#Modélisation des données expérimentales
params, covar = curve_fit(fonction, temps, tensionvolt)

# Affichage des variables modélisées
E=params[0]
tau=params[1]
print("E = {:.4} V".format(E))
print("Tau = {:.3E} s".format(tau))

# Création des grandeurs pour l'affichage
modele = []
asymptote=[]
for val in temps:
    modele.append(fonction(val, *params))
    asymptote.append(E*val/tau)


#Affichage des données expérimentales + modélisées
xmin ,xmax = temps.min(), temps.max()
ymin, ymax = tensionvolt.min(), tensionvolt.max()
dx = (xmax - xmin) * 0.1
dy = (ymax - ymin) * 0.1
plt.xlim(xmin - dx, xmax + dx)
plt.ylim(ymin - dy, ymax + dy)

plt.plot(temps, tensionvolt, '+', label="valeurs expérimentales")
plt.plot(temps, modele, linewidth=1.5,
            label="modele : Uc={:.4}*(1-exp(-t/{:.3E}))".format(*params))
plt.plot(temps, asymptote, "--")
plt.annotate(r"L'asymptote coupe Uc=E en t=$\tau$={:.3E} s".format(tau),
                xy=(tau,E), xytext=(0.2*(xmax-xmin), E/2),
                arrowprops=dict(facecolor='black', arrowstyle='-|>'))
plt.axhline(E, linestyle="--", linewidth=1.5)
plt.annotate("Uc=E".format(E), xy=(0,E), xytext=(xmin-dx/2, 0.9*(ymax-ymin)),
                arrowprops=dict(facecolor='black', arrowstyle='-|>'))


plt.legend()
plt.grid()
plt.title("Modélisation de la charge d'un condensateur")
plt.xlabel('Temps (s)')
plt.ylabel('U(condensateur) (V)')

plt.savefig("charge_condensateur.png")
plt.show()

Si ça peut donner d'autres pistes.