Réalisation d'un potentiostat pour mon TIPE

Bonjour a tous
Je suis un élève en TSI qui doit réaliser un instrument de mesure qui se nomme un potentiostat pour faire de la spectrométrie d'impédance. Pour faire simple, cela consiste a envoie une tension sinusoïdale à une cellule électrochimique et à mesurer sa réponse en courent. J'en l'ai réalisé un mais j'ai un problème, il me donne des mesures aberrants et je ne sais comment faire pour régler le problème.

Le schéma de mon montage

Le code pour l'envoie de la tension et l'acquisition de la réponse en courent

#include <LiquidCrystal.h>
#include <math.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

int a = 10;
float ct = A0;
float c = 0;

int val = 0;
float freq = 40;      // fréquence du signal (Hz)
float amplitude = 5; // amplitude PWM
float offset =350;    // centre du signal

int slowFactor = 10;        // ralentissement acquisition
int samples = 100;         // nombre de points par période

unsigned long t0;

void setup() {

  TCCR1B = TCCR1B & B11111000 | B00000001;

  Serial.begin(9600);

  pinMode(a, OUTPUT);
  pinMode(ct, INPUT);

  t0 = millis();
}

void loop() {

  for (int i = 0; i < samples; i++) {

    float t = (millis() - t0) / 1000.0; // temps en secondes


    val = offset + amplitude * sin(2 * PI * freq * t);

    

    analogWrite(a, val);

    delay(10 * slowFactor);             // ralentit la sortie

    c = analogRead(ct);

    Serial.print(val);
    Serial.print("  ");
    Serial.println(c);
  }
}

code python pour visualiser l'entré et la sortie

import numpy as np
import csv
import os
import serial 
import matplotlib.pyplot as plt


os.chdir(os.path.dirname(__file__))


def read_serial_data(port, baudrate):
    ser = serial.Serial(port, baudrate)
    data = []
    try:
        while True:
            line = ser.readline().decode('utf-8').strip()

            if line:
                values = line.split()  # sépare les nombres par espace
                for v in values:
                    data.append(float(v))
    except KeyboardInterrupt:
        pass
    finally:
        ser.close()
    return np.array(data)


    
# Change ton port avec celui sur lequel ton Arduino est connecté
raw_data = read_serial_data('COM3', 9600)

data_entrees = raw_data[::2]
data_sorties = raw_data[1::2]

with open('data.csv', mode='w', newline='') as file:
    writer = csv.writer(file)
    writer.writerow(['Entrees', 'Sorties'])
    for entree, sortie in zip(data_entrees, data_sorties):
        writer.writerow([entree, sortie])



plt.figure()
plt.plot(data_entrees, label='Entrees(mV)')
plt.plot(data_sorties, label='Sorties(uA)')
plt.xlabel('temps (s)')
plt.ylabel('Amplitude')
plt.legend()
plt.grid()
plt.savefig('data_plot.png')
plt.show()

Merci d'avance pour vos réponse

Cordialement

salut

commence déjà par vérifier que le 3,1kHz est bien présent sur D10 : tu te sers d'analogWrite, la fréquence ne sera pas celle que tu désires mais celle «prévue» par l'équipe Arduino

ensuite vérifie que la tension que tu «fabriques» est bien celle dont a besoin ton système

Tu cherches à générer une sinusoïde à 40Hz si j'ai bien compris.
Tu veux avoir 100 échantillons par période de la sinusoïde.
Il faudrait donc que chaque itération de la boucle for dure 250μs or, dans la boucle, je vois un delay() qui actuellement fait 100ms
Le calcul de l'amplitude devrait aussi tenir compte du fait qu'il y a 100 échantillons par période.

quelle sont les altairnatives à l'analgWrite ?

Le delay était pour ralentir le débit des valeurs envoyer à l'ordinateur pour avoir des tracés compréhensibles

Passer le debit de 99600 baud à 115200 ( ou plus si le matériel le supporte ) ne dispensrait-il de placer un delai ? ?

Edit : bien évidement lire 9600.

bonne question de vais testé

Oui mais cela ralenti la boucle qui génère le sinus.
Si tu veux faire un sinus à 40Hz composé de 100 échantillons il faut que la boucle tourne à 4kHz tu n'as pas le choix. À toi d'adapter la vitesse de communication pour que cela fonctionne.

Ta communication à 9600 bauds, c'est un caractère par ms environ donc ça ne peut pas marcher.
Même à 115200 cela va coincer, c'est un caractère toutes les 100μs.
Il faudrait placer les résultats de conversion dans un tableau et sortir tout d'un seul coup après l'acquisition d'une période complète, par exemple.
Ne pas oublier aussi que analogRead() prend un certain temps.

La fréquence est fixe, tu pourrais la générer à partir d'un timer et filtrer (énergiquement) le signal carré pour n'en conserver que la fondamentale.
Une autre solution serait d'utiliser un module DDS pour lui faire synthétiser le sinus et ton application ne ferait qu'acquérir la tension et le courant.
Si tu continues sur le soft actuel, tu pourrais déjà gagner beaucoup de temp en précalculant ta table de sinus en calculant dans le setup() une table d'amplitude, sous forme d'entier, ce qui éviterait le calcul du sinus plus des multiplications de flottants.

comment tu on faire ça ? désolé mais je suis débutant sur arduino

Tu créé une variable de type "int val[100];"
tu la remplis dans la fonction setup, grâce à une boucle FOR de 0 à 99, chaque indice x permettant d'avoir t = x/(100*40)s, puisqu'il faut 40 sinus par seconde et que ta table d'onde contient 100 sous élément d'une période, d'un sinus( si je ne suis pas allé un peu vite :slight_smile: ).

Si tu veux être plus à l'aise, tu peux aller voir le site d'eskimon et un cours d'initiation au C/C++(je ne retrouve jamais celui qui est le plus conseillé ici).

Cela te prendra que quelque heures et t'en fera gagner au moins 10 fois plus, sur un projet de ce type.

merci beaucoup je vais jeter un oeil de suite

Cette ligne semble indiquer une utilisation d'un micro avr.

J'avais il y longtemps comparé l'écriture entre analogWrite et l'utilisation des registres.
Il n'y a pas photo : analogWrite est plus lent que les registres, pire analogWrite bégaye quand on change le rapport cyclique.

C'est ce qui devrait être le plus rapide, mais toutes les fréquences ne sont pas possible "directement".
Les fréquences sont le résultat de choix de divisions de l'horloge principale et avec un micro 8 bits le choix est limité.

et plus particulièrement :

Si je ne me trompe pas il y a un exemple de génération de fréquence qui utilise 2 timers pour obtenir des fréquences intermédiaires.

salut
je reviens tard mais je vois que l'on t'a déjà apporté des réponses et des astuces.

l'écriture directe dans les registres, beaucoup plus rapide et qui permet de faire «ce que tu veux» (dans la limite des possibilités du µC).

exact, mais à elle seule elle ne permet pas de définir la fréquence de la MLI (ou PWM) ─ ni la fonction elle-même ─ car elle n'agit que sur le PreScaler.

arme-toi de la doc du µC et étudies-en les chapitres concernant les Timers, tu verras que ce n'est pas si difficile à maîtriser.

par exemple, pour générer un signal à 10kHz/25% sur D10 :
D10 correspond (sur l'ATMega328) à PB2 : elle s'appelle aussi OC1B quand elle est «gérée» par le Timer1.
preScaler 1/1 → bits CS à '001'
■ mode 15 FastPWM (c'est un choix pour l'exemple)
■ → comptage max pour la période = 1599 car 10kHz=1/((1599+1)*62,5ns), à écrire dans OCR1A

ça donne (à mettre dans le setup) :

//           mode de la sortie OC1B pour PWM «positive»
//           ━┷
TCCR1A = 0b00100011 ;
//               ┯━
//             mode du Timer : ici 15
//            ━┷
TCCR1B = 0b00011001 ;
//              ┯━━
//              réglage du preScaler

OCR1A  = 1599 ; // comptage max pour 100µs
OCR1B  = 399 ;  // 25% de 1599

DDRB   = 0b00000100 ; // ne pas oublier de déclarer *PB2* en sortie

ensuite, tu modifies dans la boucle la valeur d'OCR1B pour agir sur la PWM, mais surtout sans utiliser analogWrite.


explore la doc, c'est la bible

Celle d'Atmel sur les micro 8 bit est un modèle de clarté avec à chaque fois des exemples d'application très clairs.

Le lien sur le site de Mike Gammon est aussi clair et très complet, avec des exemples d'application très utiles, enfin qui m'ont été utiles.

Il reste quand même une interrogation : quelle carte dite "Arduino" utilises-tu et quel microcontrôleur est monté dessus.
Ce qui compte ce n'est pas le nom du fabricant de la carte, c'est la référence du microcontrôleur.

@aleister1

j'ai supposé qu'il s'agit d'une Uno : ni A6 ni A7 n'apparaissent sur le symbole donc ce n'est à priori pas une Nano, et avec aussi peu de broches pas une Mega non plus.
quant aux noms de registres utilisés, ils ne peuvent que suggérer un AVR, donc il ne reste que peu de choix ...

il s'agit bien d'une ONU et merci encore pour vos retour mais il y a un point que je ne comprend pas. Comment je lie ma réponse si je ne doit pas utilisé analogWriter ? car les solution que vous me proposé sont uniquement pour l'entré en tension si je me trompe pas.

La PWM n'est pas le mode adapté pour générer une fréquence qui par définition n'a qu'un rapport cyclique 50/50. Il faut aller au plus simple et le plus simple est le mode CTC.
Problème il n'existe pas de fonction arduino pour ce mode, et la fonction aurait tous les risques d'être foireuse comme analogWrite.
l faut utiliser directement les registres.

Ces micro avr ont eu une vie avant Arduino.
Je ne voudrais pas dire de bêtise mais les micros avr sont sortis et ont été programmés en C bien avant la "naissance" d'Arduino, au moins 10 ans et probablement bien plus.

Regardes l'utilisation des timers en mode CTC.
La réponse t'a déjà été donné ; la datasheet du microcontrôleur
Recherche : "atmega328p datasheet".
Tu arriveras sur le site de Microchip qui est le propriétaire actuel d'Atmel.

L'autre source d'information est celle que je t'ai donné : Mike Gammon.

Avec ces deux sources tu aura des exemples d'application en C.
Sur le site de Mike Gammon tu trouveras la liste des fréquences obtenables avec un micro 8 bits de 16 MHz d'horloge comme sur la carte UNO.
Cela te permettra de vérifier que tu appliques correctement les formules.

je ne te propose aucune solution, j'essaie seulement de te mettre sur une voie te permettant de trouver la tienne.
d'ailleurs, d'autres t'ont aussi suggéré des astuces comme le pré-calcul (et la mise en tableau) des valeurs afin de gagner du temps : toutes ces idées sont à utiliser de concert car elles sont complémentaires, pas concurrentes.

si je te déconseille l'utilisation d'analogWrite c'est pour générer ton signal selon tes besoins, sans les paramètres imposés par l'IDE Arduino.
analogRead ne configure que l'ADC et n'a rien à voir avec analogWrite : cette fonction (analogRead) sert à la lecture d'un signal analogique alors qu'analogWrite n'a d'analogique que le nom, elle sert à «fabriquer» un signal numérique (la PWM est un signal rectangulaire de fréquence fixe et de rapport cyclique variable, rien d'analogique là-dedans).

Pour compléter les précédentes réponses, tu ne peut pas lire avec analogWriter ou équivalent, puisque cette fonctionnalité veut dire que tu génère une tension.
Pour lire une tension, c'est la fonctionnalité analogRead ou équivalent.

Donc que tu utilises analogWriter ou une solution équivalent, cela ne change rien.

De mon point de vue, tu as trop de partie non maitrisée, pour tout faire d'un coup.
je pense que tu pourrais dans un premier temps, utiliser analogWriter, seul et voir ce que cela fait quitte à utiliser delayMicroseconds.
As tu un fréquence mètre a ta disposition ou un oscilloscope ?
-Modifier ce programme pour utiliser une table d'onde.
-Puis utiliser un Timer au lieu de monopoliser la loop.

Faire une version sans delay est aussi très intéressante, pour comprendre comment faire plusieurs fonctions en même temps, dans la loop, avec une machine à état, dite machine de @J-M-L :rofl: