Comunicazione seriale tra Arduino e PC

Salve, ho realizzato un programma per monitorare tramite PC alcune variabili di sensori collegati ad Arduino. Per fare questo ho realizzato, lato Arduino, questa funzione:

void comunica()
{
  if (Serial.available() > 0)
  {
    Stringa = Serial.read();
    if (Stringa == 'T')
    {
      Serial.println(dstm, 1);
      Serial.println(tmpr, 1);
      Serial.println(umid, 1);
      Serial.println(indc, 1);
      Serial.println(ftrs, 0);
      Serial.println(bzonoff);
    }
    if (Stringa == 'B')
    {
      bzonoff = !bzonoff;
    }
  }
}

In pratica, inviando il carattere 'T', Arduino trasmette sulla seriale le 6 variabili indicate. Inviando invece il carattere 'B' la variabile bzonoff viene negata.
Verificando con il monitor seriale di Arduino, tutto funziona perfettamente.
Il problema si presenta con Visual Basic.

Nel Timer ho scritto queste istruzioni

 Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

        If ToR = 0 Then
            SerialPort1.Write("T")
            tmprf = tmprc * 9 / 5 + 32
            tmprk = tmprc + 273.15
            Label1.Text = dstm & " cm"
            Label2.Text = tmprc & " °C"
            Label3.Text = umid & " %"
            Label4.Text = indc & " °C"
            Label5.Text = ftrs & " mV"
            Label7.Text = tmprf & " °F"
            Label8.Text = tmprk & " K"
            
            If bzonoff = 0 Then
                PictureBox1.Image = My.Resources.ON01
                Label13.Text = "DISABILITA BUZZER"
            Else
                PictureBox1.Image = My.Resources.OFF01
                Label13.Text = "ABILITA BUZZER"
            End If
        ElseIf ToR = 1 Then
            SerialPort1.Write("B")
            ToR = 0
        End If

    End Sub

La variabile ToR (trasmette o riceve) vale sempre "0" e il programma, inviando il carattere "T", riceve i dati correttamente che visualizza sulle varie Label senza problemi.
Se clicco su una PictureBox e assegno a ToR il valore "1" il programma dovrebbe inviare sulla seriale il carattere "B"

 Private Sub PictureBox1_Click(sender As Object, e As EventArgs) Handles PictureBox1.Click
        ToR = 1
    End Sub

Questo purtroppo non avviene, o meglio avviene sporadicamente e dopo molto tempo non compatibile con la velocità di comunicazione impostata a 115200.
Non può trattarsi di un problema di buffer pieno in quanto il Timer ha un ritardo di 100 ms tra un evento e l'altro e quindi a conti fatti riuscerebbe ad inviare circa 1400 caratteri per volta mentre le mie 6 variabili sono equivalenti a meno di 30 caratteri.
Il codice di lettura è questo

Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
        Stringa = SerialPort1.ReadLine()
        dstm = Val(Stringa)
        Stringa = SerialPort1.ReadLine()
        tmprc = Val(Stringa)
        Stringa = SerialPort1.ReadLine()
        umid = Val(Stringa)
        Stringa = SerialPort1.ReadLine()
        indc = Val(Stringa)
        Stringa = SerialPort1.ReadLine()
        ftrs = Val(Stringa)
        Stringa = SerialPort1.ReadLine()
        bzonoff = Val(Stringa)

    End Sub

Ringrazio anticipatamente chi vorrà aiutarmi.
Carlo Calò

Buongiorno e benvenuto nella sezione Italiana del forum,

cortesemente, come prima cosa, leggi attentamente il REGOLAMENTO di detta sezione, (... e, per evitare future possibili discussioni/incomprensioni, prestando molta attenzione al punto 15), dopo di che, come da suddetto regolamento (punto 16.7), fai la tua presentazione NELL'APPOSITA DISCUSSIONE spiegando bene quali esperienze hai in elettronica e programmazione, affinché noi possiamo conoscere la tua esperienza ed esprimerci con termini adeguati.

Grazie,

Guglielmo

P.S.: Ti ricordo che, purtroppo, fino a quando non sarà fatta la presentazione nell’apposita discussione, nel rispetto del succitato regolamento nessuno ti risponderà (eventuali risposte o tuoi ulteriori post, verrebbero temporaneamente nascosti), quindi ti consiglio di farla al più presto. :wink:

Grazie Guglielmo
seguo molto il forum e a dir la verità ho spesso letto questa tua raccomandazione ma non avevo mai capito dove mettere le informazioni.
Adesso l'ho fatto. Grazie.

Carlo

1 Like

Fai un pò di prove:

  1. Prova ad eseguire da vb un SerialPort1.Flush() dopo l'invio oppure dopo aver ricevuto i dati.
  2. prova ad aumentare il tempo del timer1, vedi se con 1 sec fa lo stesso casino

Inoltre, qui:

If ToR = 0 Then
            SerialPort1.Write("T")
            tmprf = tmprc * 9 / 5 + 32

dai per scontato che tra la write e il codice successivo hai di sicuro letto con SerialPort1_DataReceived ??

Ciao nid69ita. Intanto ti ringrazio per la risposta. Ho risolto in parte il mio problema aumentando il parametro "Interval" del Timer a 500. In questo modo cliccando sulla picturebox il comando risponde "quasi subito" perché in ogni caso l'evento Tick viene generato ogni 500 ms. Evidentemente con un "Interval" troppo breve il buffer della seriale non si svuota tra un Tick e l'altro e risulta sempre pieno, anzi, con il trascorrere del tempo a questo punto, potrebbe saturarsi ed avere una perdita di dati.
Non ho molta dimestichezza con la porta seriale e vorrei realizzare un protocollo robusto e affidabile anche per applicazioni future. Non posso affidarmi al caso. In genere le applicazioni che realizzo (lavoro in una scuola tecnica e insegno elettronica) effettuano il monitoraggio di dati forniti da Arduino e fin qui tutto ok. L'invio dei comandi è invece, per quanto mi riguarda, molto più complicato.
Da Arduino al PC una "println(qualsiasi_cosa)" e una "val(qualsiasinumero)" funzionano alla perfezione.
Viceversa, per inviare un dato numerico, non un solo carattere, da PC ad Arduino ho dovuto fare ricorso ad un algoritmo, peraltro trovato sulla rete che interpreta i vari caratteri numerici in base al loro codice ASCII.
In allegato la funzione comunica() che trasmette e riceve. Con "L" trasformo i successivi 9 caratteri in valori compresi tra 0 e 255 per delle uscite PWM su un LED RGB.
Il tutto funziona ma mi sembra francamente molto macchinoso. Non oso pensare cosa dovrei fare se dovessi trasmettere un dato float.

void comunica()
{
  if (Serial.available() > 0)
  {
    Stringa = Serial.read();
    if (Stringa == 'T')
    {
      Serial.println(dstm, 1);
      Serial.println(tmpr, 1);
      Serial.println(umid, 1);
      Serial.println(indc, 1);
      Serial.println(ftrs, 0);
      Serial.println(bzonoff);
      Serial.println(r);
      Serial.println(g);
      Serial.println(b);
    }
    if (Stringa == 'B')
    {
      bzonoff = !bzonoff;
    }
    if (Stringa == 'L')
    {
      Stringa = Serial.read();
      r = 100 * (Stringa - 48);
      Stringa = Serial.read();
      r = 10 * (Stringa - 48) + r;
      Stringa = Serial.read();
      r = (Stringa - 48) + r;
      Stringa = Serial.read();
      g = 100 * (Stringa - 48);
      Stringa = Serial.read();
      g = 10 * (Stringa - 48) + g;
      Stringa = Serial.read();
      g = (Stringa - 48) + g;
      Stringa = Serial.read();
      b = 100 * (Stringa - 48);
      Stringa = Serial.read();
      b = 10 * (Stringa - 48) + b;
      Stringa = Serial.read();
      b = (Stringa - 48) + b;
    }
  }
}

La routine di trasmissione di VB prevede tre trackbar che su rilascio del mouse attivano la subroutine LedRGB()


    Private Sub TrackBarR_MouseUp(sender As Object, e As MouseEventArgs) Handles TrackBarR.MouseUp
        LedRGB()
    End Sub

    Private Sub TrackBarG_MouseUp(sender As Object, e As MouseEventArgs) Handles TrackBarG.MouseUp
        LedRGB()
    End Sub

    Private Sub TrackBarB_MouseUp(sender As Object, e As MouseEventArgs) Handles TrackBarB.MouseUp
        LedRGB()
    End Sub

    Private Sub LedRGB()
        Timer1.Enabled = False
        SerialPort1.Write("L")
        SerialPort1.Write(Format(TrackBarR.Value, "000"))
        SerialPort1.Write(Format(TrackBarG.Value, "000"))
        SerialPort1.Write(Format(TrackBarB.Value, "000"))
        LabelR.Text = Format(TrackBarR.Value, "000")
        LabelG.Text = Format(TrackBarG.Value, "000")
        LabelB.Text = Format(TrackBarB.Value, "000")
        Timer1.Enabled = True
    End Sub

Se hai numeri lunghezza fissa tipo "240" oppure "012" non è scandaloso convertire come fai.

In alternativa puoi lavorare con stringhe del C (non String che è un oggetto) array di char terminato da 0 o '\0' e poi ci sono già le funzioni che convertono da testo a numero, essendo in C è importante il tipo di risultato.
atoi() da testo a intero (16 bit su questa mcu), atol() da testo a long. atof() da testo a float (non ricordo se su arduino funziona atof() perchè pesante)
Anche l'oggetto String puoi accodare caratteri e poi convertire ma String su queste MCU con poca ram portano a problemi di frammentazione memoria heap.

P.S. ma la variabile Stringa che usi in arduino come è dichiarata ? char ?

Si. La variabile stringa l'ho definita come char. In un momento di incauto ottimismo ho pensato di poter usare un array char Stringa[20] ed un comando tipo Stringa=Serial.readln() che mi leggesse in un colpo solo la stringa fino al carattere finale escluso. Non esistendo il comando Serial.readln() ho provato con Serial.readStringUntil() con '\n' come carattere terminale dato che Visual Basic invia con SerialPort1.Writeline(Testo) proprio '\n' al termine della stringa e successivamente trattare la stessa con atoi() o atof() nel caso di numeri con la virgola. Ovviamente il tutto senza successo.

Perché questa istruzione restituisce un oggetto della classe String (con la S maiuscola) e non un char array.

Come spesso accade, il tentativo di evitare la classe String fa più danni della String stessa :wink:

Grazie per queste informazioni. Smanettando un po' nel Forum, ho trovato molta gente con il mio stesso problema e anche tante indicazioni ma nessuno che fornisse un piccolo esempio risolutivo. Alla fine ho caricato questo programmino su Arduino per verificare la conversione numerica della stringa trasmessa sul monitor seriale. Cioè ho trasformato la stringa in un intero e ho moltiplicato per 2, la stessa stringa l'ho anche trasformata in un float e ho moltiplicato per 3.

String Stringa;
char cmd;
int num;
float numf;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  if (Serial.available() > 0)
  {
    cmd = Serial.read();
    if (cmd == '*')
    {
      Stringa = Serial.readStringUntil('\n');
      delay(500);
      Serial.println(Stringa);
      num = 2 * atoi(Stringa.c_str());
      Serial.println(num);
      numf = 3 * atof(Stringa.c_str());
      Serial.println(numf, 3);
    }
  }
}

digitando

*Ciao

il monitor seriale risponde

Ciao
0
0.000

digitando invece

*24.42

il monitor seriale risponde

24.42
48
73.260

Alla grande!!!! :smiley:

Adesso devo vedere come fare lato Visual Basic. Grazie ancora

Risolto anche lato VB

Public Class Form1
    Dim DatiRicevuti As String = ""
    Dim Stringa As String, Stringnum As String, Stringfloat As String
    Dim Num As Integer
    Dim Numf As Double

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        SerialPort1.Close()
        ComboBox1.Items.AddRange(IO.Ports.SerialPort.GetPortNames())
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        If ComboBox1.Text <> "" Then
            Try
                SerialPort1.Encoding = System.Text.Encoding.Default
                SerialPort1.PortName = ComboBox1.Text
                SerialPort1.Open()
                MsgBox("Connesso!!")
                Timer1.Enabled = True
            Catch ex As Exception
                MsgBox("Errore di connessione")
                Timer1.Enabled = False
            End Try
        End If
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        End
    End Sub

    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        SerialPort1.Write("*")
        SerialPort1.WriteLine(TextBox1.Text)
    End Sub

    Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
        Stringa = SerialPort1.ReadLine()
        Stringnum = SerialPort1.ReadLine()
        Stringfloat = SerialPort1.ReadLine()
    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Label1.Text = Val(Stringa)
        Label2.Text = Val(Stringnum)
        Label3.Text = Val(Stringfloat)
        Label4.Text = Val(Stringfloat) * 2
    End Sub
End Class

Provo a inserire anche il form

form1

Se usi la classe String allora tanto vale che trai vantaggio da quello che offre usando i metodi messi a disposizione invece di fare un mix tra istruzioni C string standard e metodi di String

Questa invece è una possibile versione che non fa uso di String

Se uno legge la documentazione di serialport.readLine è scritto che toglie il NewLine, per cui il carattere newline inviato con arduino non verrà considerato "giusto". Però non dice che quando invii con arduino invierai due caratteri a fine stringa '\r' '\n', come da descrizione '\n' viene rimosso ma rimane un carattere invisibile '\r', questo confronto tra stringhe risulta falso perchè avendo un carattere terminale le due stringe sono diverse.
Puoi costatando leggendo la lunghezza della stringa inviata da arduino, se non ha carattere aggiuntivi la stringa ha lunghezza uguale ai caratteri inviati, se risulta più lunga di 1 è il carattere '\r'.
Leggi la lunghezza della stringa e rimuovi l'ultimo carattere dovresti risolvere i problemi.

Grazie per i due esempi, che analizzerò con molta attenzione.
Di solito le simulazioni le faccio con Tinkercad ma mi hai fatto conoscere anche Wokwi di cui ignoravo l'esistenza.
Il forum di Arduino è una miniera!! :wink:

Me ne ero accorto che c'era qualcosa che non andava quando ho stampato le stringhe ricevute sulle label del form. Pur avendo selezionato la posizione del testo centrato nella label, il programma visualizzava i dati verso l'alto. Era evidente un "ritorno carrello" in eccesso.

Il problema l'ho risolto con la funzione Val.

Mi sembra un piccolo bug :slight_smile:

while (Serial.available()) {
    char ch = (char) Serial.read();
    rxBuffer[pos++] = ch;  //Se ch è '\n' poi incrementa

    if (ch == '\n') {
      rxBuffer[pos] = '\0'; // Aggiungo il terminatore di stringa ma pos è posizione successiva a '\n'  per cui diventa \n\0
      pos = 0;

Non sono pignolo solo che magari sfugge a chi lo prova

Si incrementa, ma non comporta alcun problema.
I cosiddetti "white space" vengono ignorati nelle funzioni di conversione

Non lo sapevo nella mia implementazione non c'è controllo :yum: ma forse basterebbe *s >='0' && *s<='9',
Credevo desse risultati sbagliati :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.