aide communication série entre Arduino et visual c#

Bonjour,

je suis un étudiant en Ingénieur électronique, et dans le cadre de mon stage il m'est demandé de programmer une presse.

Mon maitre de stage désire que cette presse puisse :

  • choisir la vitesse de descente
  • choisir le déplacement
  • avoir les données de pression de cellule de force

Ma question est la suivante,

je réussi a avoir les données de la cellule de force et d'en faire un graph en temps réel sur visual c#.

Mais... comment pouvoir envoyer vers Arduino la valeur de la vitesse et du déplacement rentré par l'utilisateur sur visual c# ?

Pour obtenir ce que l'arduino lit sur son entrée analogique, je lui dit que quand il a 4 sur le port il m'écrit sur le port la valeur de l'entrée analogique.
Mais comment lui dire d'écrire par exemple 4 et la valeur de la vitesse et le déplacement ?

Je possède une carte Arduino UNO et un moteur pas à pas

Merci

Bonjour,

J'ai trouvé quelque chose qui peut t'intéresser : http://csharp.simpleserial.com/

HadesDT:
Pour obtenir ce que l'arduino lit sur son entrée analogique, je lui dit que quand il a 4 sur le port il m'écrit sur le port la valeur de l'entrée analogique.
Mais comment lui dire d'écrire par exemple 4 et la valeur de la vitesse et le déplacement ?

Hum.
Précise déjà ce que tu as comme connexion.
Tu dis que tu réussi à transférer la valeur du port analogique. Précise comment.

En fait on ne sait pas exactement où tu en es, ce qui marche, ce qui ne marche pas.

Bonjour, désolé de ne pas avoir répondu plus tôt

Donc je vous post mon code Arduino qui fonctionne pour la lecture de l'entrée analogique et le code visual c# associé

Ensuite je vous posterai (dans un autre post pour eviter toutes confusion ce que j'ai essayé de faire pour commander le moteur mais qui ne marche pas :slight_smile:

Donc voici mon code Arduino :

void setup()  
{                

  Serial.begin(19254 );  
}

void loop()                    
{
  if(Serial.available())
  {
    int c = Serial.read();
    
    if (c == '4')
    {
    Serial.println(analogRead(A1));
    }  
  }
}

et le code visual c#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
using System.IO;
using Microsoft.Office.Core;
using Excel = Microsoft.Office.Interop.Excel;
using System.Windows.Forms.DataVisualization.Charting;
using Microsoft.Office.Interop.Excel;
using ApplicationForm = System.Windows.Forms.Application;
using System.Globalization;

namespace Acqu_donnee
{
    public partial class Form1 : Form
    {
        SerialPort Port1; //ouverture du port série

        public Form1()
        {
            InitializeComponent();
            for (int i = 1; i <= 50; i++)
            {
                try
                {
                    Port1 = new SerialPort("COM" + i, 19254); //Port1 trouve le port COM du PC
                    Port1.Open(); //Port1 est ouvert
                    Port1.ReadTimeout = 1000; //Delais pour l'écriture
                    Port1.WriteTimeout = 1000; //Délais pour la lecture

                    Port1.Write("4"); //Ecrit 4 sur le port 1

                    byte[] lus = new byte[8]; 
                    int nblu = Port1.Read(lus, 0, 8); 
                    if (nblu > 0)
                    {
                        return; // port trouvé
                    }
                }
                catch (Exception)
                {
                    Port1.Dispose();
                }

            }
        }

        public void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (Port1.IsOpen) Port1.Close(); //Ferme le port1 à la fermeture du programme
        }

        public void timer1_Tick(object sender, EventArgs e) //Evenement à chaque tick du Timer1
        {
            string r = ""; //défini r comme un string vide
            double x = 0; //défini x comme un double vide
            double value = 0; //Défini vélue comme un double vide
            if (Lect_mes.Checked) //Si Lect_mes est cochée
            {
                Port1.Write("4"); //on écrit 4 sur le port1
            }
            
            r = Port1.ReadLine(); //r = ce qu'il lit sur le port1
            try 
            {
                x = Convert.ToDouble(r); //converti un Char en Double
                value = Math.Round((x / 1024 * 5 * 2 * 196.2), 2); //On calcul la pression
                //1024 = 0...1023 échantillons (valeur renvoyée par le port1)
                //5 car on est sur la plage 0...5V
                //196.2 convertion Kg en Newton
                //2, on arrondis à 2 décimales
            }
            catch 
            {
                if (listBox1.Items.Count == 0)
                {
                    value = 0;
                }
                else
                {
                    value = double.Parse( listBox1.Items[listBox1.Items.Count - 1].ToString());
                }
            }
            listBox1.Items.Add(value); //Ajoute les valeurs de value dans la ListBox
            listBox1.SelectedIndex = listBox1.Items.Count - 1; //AutoScroll de la ListBox

            double xVal = listBox1.Items.Count * 0.02; //Valeur axe des x
            chart1.Series[0].Points.AddXY(xVal, value); //Ecriture des points sur le graph
            chart1.ChartAreas[0].AxisX.Maximum = xVal; // Maximum de l'axe des x est la dernière valeur de XVal
            if (chart1.Series[0].Points.Count > 250) // Permet d'afficher que 5 secondes sur l'axe des x
            {
                chart1.Series[0].Points.RemoveAt(0);
            }
            
           chart1.ChartAreas[0].AxisX.Minimum = chart1.Series[0].Points[0].XValue;
              
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            string r = ""; //défini r comme un string vide
            double x = 0; //défini x comme un double vide
            double value = 0; //Défini vélue comme un double vide
            if (Lect_mes.Checked) //Si Lect_mes est cochée
            {
                Port1.Write("4"); //on écrit 4 sur le port1
            }

            r = Port1.ReadLine();
            try 
            {
                x = Convert.ToDouble(r); //converti un Char en Double
                value = Math.Round((x / 1023 * 5 * 2 * 196.2), 2); //On calcul la pression
                //1024 = 0...1023 échantillons (valeur renvoyée par le port1)
                //5 car on est sur la plage 0...5V
                //196.2 convertion Kg en Newton
                //2, on arrondis à 2 décimales
            }
            catch 
            {
                value = 0;
            }
            textBox1.Text = value.ToString() +' ' + 'N'; // Rajoute N après la mesure dans la TextBox
        }

        public void Lect_mes_CheckedChanged(object sender, EventArgs e) //Evenement quand Lect_mes change d'état
        {
            if (Lect_mes.Checked) //Si Lect-mes est coché on écrit 4 sur le Port1
            {                     //et on actionne les Timers
                Port1.Write("4");
                timer1.Enabled = true;
                timer2.Enabled = true;
            }
            else
            {
                timer1.Enabled = false; //Sinon on coupe les Timers
                timer2.Enabled = false;
            }
        }

        public void Del_ListBox_Click(object sender, EventArgs e)
        {
            listBox1.Items.Clear(); //Clear la ListBox
            chart1.Series[0].Points.Clear(); //Clear le Graph
            textBox1.Clear(); //Clear la TextBox
        }

        private void toExcel_Click(object sender, EventArgs e)
        {
            Excel.Application oXL;    //Définition d'une nouvelle application Excel
            Excel.Workbook oWB;       //Définition d'un classeur
            Excel.Worksheet oSheet;   //Définition d'une feuille

            object misValue = System.Reflection.Missing.Value;

            // initialisation feuille excel
            try
            {
                oXL = new Excel.Application(); //myExcelApp est créee par Excel.Application
                oXL.Visible = true;            //Ouvre Excel automatiquement après l'export
                FileInfo fi = new FileInfo(ApplicationForm.ExecutablePath);

                oWB = (Excel.Workbook)(oXL.Workbooks.Open(Path.Combine(fi.DirectoryName, "Acquisition.xls"))); //Ouvre le fichier Acquisition.xls
                oSheet = (Excel.Worksheet)oWB.ActiveSheet;

                Excel.Range oRange = (Excel.Range)oSheet.get_Range("A3", "B30000"); //Ecrit les données de la cellule A3 a B30000
                oRange.Clear();


                //écriture dans feuille excel
                for (int i = 0; i < Math.Min(listBox1.Items.Count - 1, 27997); i++)
                {
                    oXL.Cells[i + 3, 2] = listBox1.Items[i].ToString(); //On inscrit toutes les valeurs de la ListBox les une en dessous des autres à partir de B3
                    if (i == 0)
                    {
                        oXL.Cells[3, 1] = 0; //Permet d'écrire le temps dans A3
                    }
                    else
                    {
                        oXL.Cells[i + 3, 1] = i * 0.02; //Permet d'écrire le temps dans A4 jusque autant de mesure par pas de 0,02 (50hz)
                    }
                }
            }
            catch
            {
                MessageBox.Show("L'exportation n'a pas été effectuée"); //Si exportation Excel plante, message d'erreur
            }
        }
    }
}

NB : y a deux timer dans le code car j'ai une acquisition à 50hz dans une listbox et une a 10hz dans une textbox :wink:
Voila, ceci fonctionne très bien pour lire l'entrée analogique A1

Et voila ce que je voulais faire pour commander le moteur :

#include <Stepper.h>

const int steps = 20;  // change this to fit the number of steps per revolution
                                     // for your motor
Stepper myStepper(steps, 4,5,7,8); 

void setup()  
{                

  Serial.begin(19254);  
}

void loop()                    
{
  if(Serial.available())
  {
    int c = Serial.read();
    int Speed = 0;
    int Distance = 0;
    if (c == '0')
    {
      Speed = Serial.read();  
    }
    else
    {
      Speed = Speed;
    }
    if (c == '1')
    {
      Distance = Serial.read();
    }
    else
    {
      Distance = Distance;
    }
  
    if (c == '4')
    {
    Serial.println(analogRead(A1));
    myStepper.setSpeed(Speed);
    double Deplacement = Distance / 0.005;
    myStepper.step(Deplacement);
    myStepper.step(-Deplacement);
    }  
  }
}

et visual c# (je rajoute ceci)

        private void Speed_button_Click(object sender, EventArgs e)
        {
            Port1.Write("0");
            string Speed;
            Speed = Speed_TextBox.Text;
            Port1.Write(Speed);
        }

        private void Depl_button_Click(object sender, EventArgs e)
        {
            Port1.Write("1");
            string Deplacement;
            Deplacement = Depl_TextBox.Text;
            Port1.Write(Deplacement);
        }

A votre avis qu'est ce qu'il faudrait que je fasse ?

Pas grand chose ne fonctionne à part l'acquisition sur l'entrée analogique

Mais... Cette acquisition fonctionne si je retire toute la commande du moteur, donc la je sèche vraiment ... Comment faire?

Personne pour m'aider ?

Vais essayé d'etre un peu plus clair :slight_smile:

Si je met le code du début à savoir :

void setup()  
{                

  Serial.begin(19254 );  
}

void loop()                    
{
  if(Serial.available())
  {
    int c = Serial.read();
    
    if (c == '4')
    {
    Serial.println(analogRead(A1));
    }  
  }
}

L'acquisition de l'entrée analogique fonctionne très bien, mon graph se fait en temps réel etc sur visual c#

Si je rajoute un peu de code pour la commande du moteur :

#include <Stepper.h>

const int steps = 20;  
Stepper myStepper(steps, 4,5,7,8); 
int EnA=3;
int EnB=6;

void setup()  
{                
  Serial.begin(19254); 
  pinMode(EnA, OUTPUT);
  pinMode(EnB, OUTPUT);
}

void loop()                    
{
  if(Serial.available())
  {
    int c = Serial.read();
     
    if (c == '4')
    {
    digitalWrite(EnA,HIGH);
    digitalWrite(EnB,HIGH);
    Serial.println(analogRead(A1));
    } 
motor();
  }
}

void motor()
{
  myStepper.setSpeed(60);
  myStepper.step(steps);
  myStepper.step(-steps);
}

Maintenant avec ce code :

J'ai 1 ou 2 acquisitions sur visual c# (au lieu de 50 par secondes) puis il me dit qu'il a pas sur lire le port et que l'opération has timeout.
Ce qui me parait logique car la boucle loop s'éffectue tout le temps, donc il va ecrire sur le portsérie l'entrée analogique et après il lance le moteur, mais celui ci prend un certain temps à faire son cycle.

Donc ma question est :
Comment arriver à dire a la carte tu fais le cycle du moteur ET tu envoies en continu les info sur le port série de l'entrée analogique ?

Merci de m'aider car je suis totalement bloqué et je trouve rien sur internet :frowning:

Je ne sais pas ce que fais ce code "Stepper" mais de toute évidence c'est lui qui te faire perdre ton temps.

Déjà tu peut probablement découper le code de motor() en 3 phases

int motor_state = 0;
motor()
{
  switch( motor_state )
  {
  case 1:
    myStepper.setSpeed(60);
    ++motor_state;
    break;
  case 2:
    myStepper.step(steps);
    ++motor_state;
    break;
  case 3:
    myStepper.step(-steps);
    motor_state = 0;
    break;
  }
}

Sur Arduino tu n'as pas de multi-tâche.
Il faut donc découper chaque tâche en sous-tâche élémentaire de durée très courte de façon a pouvoir entremêler les sous tâches. Une machine d'état pour chaque tâche est souvent utile.

Si çà ne marche pas mieux c'est que l'appel à mySTepper.step(xxx) prend trop de temps. N'est-il pas possible de scinder en plusieurs appels plus petits ?

oui j'ai remarqué que Stepper.step me prennait beaucoup de temps,

j'ai fait quelque recherche et j'ai trouvé qu'il était plus facile de commander un pas a pas avec Arduino + L297 + L298 + pont de diode en HH + moteur.

Le l297 permet de faire la sequence mystepper (enfin si on peut dire) et il y a que 3 I/O digital de l'arduino qui sont utilisée

une qui permet de choisir le sens de rotation, 1 ou 0
une qui permet de choisir le step, sur celle ci faut mettre une clock, je sais pas encore trop comment la faire, si tu as une idée :slight_smile:
une qui permet de dire si c'est un pas entier ou demi, 1 ou 0

Donc cette méthode devrait me permettre de plus perdre de temps, t'en penses quoi ?

J'ai fais un essai avec un servo pour savoir si en mettant des 1 ou 0 etc si ca réagissait, et la je ne perd plus de temps, j'ai la lecture de l'analogique sur c# et le servo qui est controler, donc si je suis cette logique je devrait arriver a ce que je veux :slight_smile:

Edit, voila le schéma utilisé

Les bornes 3,4,5 de l'arduino sont connectées aux bornes 17,18,19 du L297
la borne 15 est connectée à un potentiometre (cela permet de définir le max dans le L297)
la borne 10 est mise aux 5V, car pas besoin de pouvoir jouer dessus
La borne reset est connectée avec celle de l'arduino

13 et 14 à la masse et 1 et 15 du l298 aussi car pas besoin de pouvoir mesuré le courant

Le reste comme c'est mit

Je n'ai pas d'expérience avec les moteurs pas à pas mais je comprend le principe.
A la fin même si tu fais ce nouveau montage, tu devras donner sur CLOCK un nombre d'impulsion égal au nombre de pas.
On peut peut-être arriver à faire générer à l'Arduino un nombre précis d'impulsions via timer et interruptions...Je vais y réfléchir

Mais ne peux-tu pas commencer par faire moins de pas dans chaque appel à step() ?

Question subsidiaire : Pourquoi choisir un baudrate non standard de 19254 ?
La valeur standard la plus proche est 19200.

Les valeurs les plus courantes sont :
75, 120, 240, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 256000
Et 31250 pour le MIDI

Certains drivers sur PC, à une lointaine époque :), ne savaient pas générer d'autres valeurs.

car je fais en sorte que le pc trouve le port COM de la carte automatiquement, donc je les ai mit a un débit non standard comme ca un périf extérieur du pc ne peux pas venir perturber la détection de la carte :slight_smile:

barbudor:
Je n'ai pas d'expérience avec les moteurs pas à pas mais je comprend le principe.
A la fin même si tu fais ce nouveau montage, tu devras donner sur CLOCK un nombre d'impulsion égal au nombre de pas.
On peut peut-être arriver à faire générer à l'Arduino un nombre précis d'impulsions via timer et interruptions...Je vais y réfléchir

Mais ne peux-tu pas commencer par faire moins de pas dans chaque appel à step() ?

oui, mais peut être que ca lui fera perdre moins de temps de faire 1 ou 0 très rapidement sur une seule I/O digital ? Mais comment arriver a faire cette clock ?

et pour faire moins de pas dans chaque appel de step je ne sais pas trop comment faire (je n'ai jamais touché au moteur PaP :()

HadesDT:
car je fais en sorte que le pc trouve le port COM de la carte automatiquement, donc je les ai mit a un débit non standard comme ca un périf extérieur du pc ne peux pas venir perturber la détection de la carte :slight_smile:

Ca n'a pas de sens ce que tu dis.
Le baudrate c'est toi qui le règle dans l'objet SerialPort, membre baudrate.

Revenons aux moteurs pas à pas.
De ton code je déduit :

  • Ton moteur à besoin de 20 pas pour faire un tour complet (cf le constructeur de Serial)
  • Tu le règle à la vitesse de 60 tours par minute, donc 1 tour par seconde
  • Quand tu appelles myStepper.step(20) tu lui fait faire un tour, en 1 seconde.

Donc tu ne peux revenir sur ton code de liaison série qu'une fois par seconde.

Il faut donc réduire le nombre de pas dans myStepper.step(), en faire moins (1 par 1 par exemple) à chaque tour de loop() jusqu'à atteindre 20.

Essaye quelque chose comme :

  1. Déplace le setSpeed(60) dans setup(). Rien a faire dans loop.

  2. Change motor() comme çà :

int motor_count = 0;
int motor_dir = 1;
void motor()
{
  myStepper( motor_dir );
  if ( ++motor_count >= step )
  {
    motor_dir = - motor_dir;// on change de sens
    motor_count = 0;
  }
}

Le temps d'exécution d'un step passe de 1 seconde à 1/20ème.

Pour aller plus loin, si c'est nécessaire, il va falloir être plus malin. Probablement se passer de la classe Stepper et refaire à la main le séquencement des pas.

Donc tu ferais ceci dans le code?

#include <Stepper.h>

const int steps = 20;  
Stepper myStepper(steps, 4,5,7,8); 
int EnA=3;
int EnB=6;
int motor_count = 0;
int motor_dir = 1;

void setup()  
{                
  Serial.begin(19254); 
  pinMode(EnA, OUTPUT);
  pinMode(EnB, OUTPUT);
  myStepper.setSpeed(60);
}

void loop()                    
{
  if(Serial.available())
  {
    int c = Serial.read();
     
    if (c == '4')
    {
    digitalWrite(EnA,HIGH);
    digitalWrite(EnB,HIGH);
    Serial.println(analogRead(A1));
    } 
motor();
  }
}

void motor()
{
 myStepper.step( motor_dir );
  if ( ++motor_count >= steps )
  {
    motor_dir = - motor_dir;// on change de sens
    motor_count = 0;
  }
}

Et si on utilise la deuxième solution, comment ferais tu pour générer une clock pour 20 steps ? :slight_smile:

Oui à la première question. Qu'est-ce que çà donne ?
Il se pourrait que le déplacement du moteur soit un peu saccadé avec cette méthode.

Pour la 2eme question, c'est pas tant un problème de hard qu'un problème de soft où il faut arriver à faire 2 choses en même temps.
Toutefois pour le 2nd cas, il faut que je réfléchisse mais je pense qu'on doit pouvoir utiliser une sortie de timer pour générer les impulsions (comme pour le PWM). Le problème est de s’arrêter à temps après un nombre finit d'impulsion.

Je testerai demain matin car la carte est au boulot :frowning:

pour la deuxième solution, je pensais a une librairie timerone si j'ai bon souvenir ou on peut lui dire que tout les x temps il fait une fonction, mais je me rappelle plus la synthaxe de cette librairies

int clock = 13;
boolean clock_etat;
void cmd_digitalOUT()
{
clock_etat = digitalRead(clock);
   if (clock == LOW)
        digitalWrite(clock,HIGH);
   else
       digitalWrite(clock,LOW);
}

Mais je me rappel plus la synthaxe pour dire que tout les x temps le timer effectue cette fonction

Merde, moi qui m’apprêtait à écrire une lib similaire, c'est déjà fait :stuck_out_tongue_closed_eyes:
C'est pas marrant cette plateforme où tout le monde a déjà tout fait.

En bref, oui pour gérer proprement en "multi-tâche" le mouvement du moteur pas-à-pas c'est une bonne solution. Tu as besoin d'un appel de fonction toute les 50ms pour faire bouger ton moteur dans un sens où l'autre.
Par contre je te déconseille d'utiliser Stepper() associé avec TimerOne car Stepper.step() fait appel à la fonction millis() et attend que le temps passe. Or il est probable que TimerOne appelle les fonctions de timer depuis la routine d'interruption. Il est interdit d'avoir du code bloquant dans une routine d'interruption, surtout une qui attend que le temps passe, parce que tant que tu es dans la fonction d'interruption de TimerOne, millis() n'avance plus.

Donc il faudra que tu étudie le code de Stepper pour comprendre la séquence de gestion d'un moteur pas à pas (il y a 4 états qu'il faut enchaîner). Ou bien utiliser ton schéma alternatif.

je vais partir sur le schéma alternatif car les composants sont dispo au boulot.

je vais essayer de retrouver la syntaxe du timerone :slight_smile:

merci de ton aide et je te tiens au courant des 2 méthode :slight_smile: