Inviare Byte da c# ed inserirli in struct in arduino

Buongiorno a tutti,
sto cercando di inviare 180 byte da una applicazione in C# verso il mio arduino uno per poi inserirli in una struct di uguale grandezza, il problema è che non conosco bene il C#.
Quello che faccio è :
Lato C#

Apertura porta

SerialPort port = new SerialPort(ports[ck], 115200, Parity.None, 8, StopBits.One);
                    
                    port.Open();

Creo il mio array da un memory mapped file

Scrivo il mio array sulla seriale

port.Write(bytes, 0, bytes.Length);
         port.DiscardOutBuffer();

Per debug faccio inviare ad arduino tutti i byte che ho inviato e li scrivo in console

Console.Write("Data output : \n");
                                    int k = 0;
                                    for (i = 0; i < bytes.Length; i++)
                                    {
                                    if (k % 12 == 0 && k != 0)
                                        {
                                            Console.Write("\n");
                                        }
                                    Console.Write(String.Format("0x{0:X}", bytes[i]) + " ");
                                    k++;
                                    }

Lato arduino

byte Data[180]; //180 byte di dati in ingresso

//dichiaro la mia struct MemData... non la riporto per brevità

MemData Gtr2; 

void setup() {
  Serial.begin(115200);

  digitalWrite(12,HIGH);//led test
  delay(1000);
  digitalWrite(12,LOW);
}

void loop() {

 if(Serial.available()){
    for(int i = 0; i<sizeof(Data) ; i++)
  {
    Data[i] = Serial.read();
   }
   

memcpy(&Gtr2, Data, sizeof(Data));

for(int i = 0; i<sizeof(Data) ; i++)
  {
    
    Serial.print(((unsigned char*)&Gtr2)[i],HEX);
   }
     
 

  delay(1000);
   }
}

Su arduino creo il mio array di dati in ingresso Data[180], la mia struttura tramite

struct MemData{
...
...
};

Nel Loop comincio la lettura,ho fatto due prove, una volta con il ciclo for e una volta con la funzione Serial.readBytes(Data,sizeof(Data));

Copio i dati presenti nell'array nella struct con memcpy(&Gtr2, Data, sizeof(Data));

Ed in fine invio i dati sulla seriale un byte alla volta per effettuare la verifica dei dati inseriti nella struct con
Serial.print(((unsigned char*)&Gtr2),HEX);[/b]
Purtroppo facendo partire l'applicazione C# quello che ottengo è uno stream di dati in uscita diverso da quello in ingresso, in particolare quello riportato nel file allegato.
La logica di questo progetto è piuttosto semplice ma credo di fare errori nell'invio o ricezione dei tipi di dato, o nel modo in cui sto inviando il tutto.
Sapreste darmi un consiglio o correggere il mio errore?
data_stream.JPG

Senza info ? e cosa contiene MemData ?
Tra input e output mi pare fai qualche conversione. spedisci 0, visualizzi poi 0x30 che è carattere '0' e non valore 0
Spedisci 0x64 e caso strano ottieni 0x36 ovvero '6' e 0x34 ovvero '4'

Grazie mille per la risposta,
hai ragione per questo avevo cambiato nel c# la funzione

port.Write(bytes, 0, bytes.Length);

in

for (i = 0; i < bytes.Length; i++)
                                    {
                                        port.Write(new byte[] { bytes[i] }, 0, 1);
                                        
                                        
                                    }
                                    port.DiscardOutBuffer();

da quello che leggo qui c# - Can I use SerialPort.Write to send byte array - Stack Overflow utilizzando la funzione nel ciclo vado a scrivere un byte alla volta.
Il problema è che se in arduino nella lettura seriale inserisco serial.read()-'0' e invio indietro quello che ricevo, sul C# ottengo nella console per ogni byte inviato :
0x44 0x30
0x44 0x31
0x44 0x32

ovvero mantengo la formattazione incrementale dal 0x30 in più il byte 0x44.

Se invece utilizzo la funzione Serial.write(((unsigned char*)&Gtr2));[/b] i valori sono corretti fino al byte n° 63 dopo di che diventano tutti 0xFF, per caso arduino ha un buffer di ingresso limitato a 64 bytes?
per inviare 180 byte devo fare 3 cicli da 60?

MemData è una struct di 180 byte, ogni 4 byte c'è una informazione

struct MemData{
      float userInput[6]; // This structure allows for a number of parameters to be
      // passed from the user to the exteral application via control input
      // in the game. The ISIInputType enum describes which element of this
      // array corresponds to which in-game control. Note that this data
      // is floating point, and can be configured in the game to be driven
      // by an analog input device (joystick). The user may also map the
      // control to a keyboard key, or a digital controller button. This
      // means that the value may be anywhere between 0.0 and 1.0. Also note
      // that these values will not be debounced; if the user
      // maps the "External Signal Up" control to a key on the keyboard,
      // the coresponding value in this array will remain 1.0 for as long
      // as the user holds the key down.
       
      float rpm; // Engine speed, Radians Per Second.
      float maxEngineRPS; // For use with an "analog" rpm display.
      float fuelPressure; // KPa
      float fuel; // Current liters of fuel in the tank(s).
      float fuelCapacityLiters; // Maximum capacity of fuel tank(s).
      float engineWaterTemp; //
      float engineOilTemp; //
      float engineOilPressure; //
       
      float carSpeed; // meters per second
      long numberOfLaps; // # of laps in race, or -1 if player is not in
      // race mode (player is in practice or test mode).
       
      long completedLaps; // How many laps the player has completed. If this
      // value is 6, the player is on his 7th lap. -1 = n/a
       
      float lapTimeBest; // Seconds. -1.0 = none
      float lapTimePrevious; // Seconds. -1.0 = none
      float lapTimeCurrent; // Seconds. -1.0 = none
      long C_position; // Current position. 1 = first place.
      long numCars; // Number of cars (including the player) in the race.
      long gear; // -2 = no data available, -1 = reverse, 0 = neutral,
      // 1 = first gear... (valid range -1 to 7).
       
      //float tireTemp[ TIRE_LOC_MAX ][ TREAD_LOC_MAX ]; // Temperature of three points
      float tirefrontleft[3]; //tire values from [0]=left to [2]=right
      float tirefrontright[3]; //tire values from [0]=left to [2]=right
      float tirerearleft[3]; //tire values from [0]=left to [2]=right
      float tirerearright[3]; //tire values from [0]=left to [2]=right
      // across the tread of each tire.
      long numPenalties; // Number of penalties pending for the player.
       
      float carCGLoc[3]; // Physical location of car's Center of Gravity in world space, X,Y,Z... Y=up.
      //float carOri[ ORI_MAXIMUM ]; // Pitch, Yaw, Roll. Electronic compass, perhaps?
      float pitch;
      float yaw;
      float roll;
      //float localAcceleration[3]; // Acceleration in three axes (X, Y, Z) of car body (divide by
      // 9.81 to get G-force). From car center, +X=left, +Y=up, +Z=back.
      float lateral; //Force left-right
      float vertical; //force up-down
      float longitudinal; //force faster, slower
      };

Quello che faccio è leggere da un file mappato in memoria sul mio PC ed invio i suoi byte su arduino, qui faccio la raccolta dei byte e copio la memoria dell array in quella della struttura in modo che chiamando ad esmpio Gtr2.rpm ottengo ad esempio i dati relativi a quel campo e cosi via.

Buongiorno,
sono riuscito a risolvere il problema, da quello che ho visto è solo un modo di visualizzare i dati che invio da arduino al C#.
Infatti anando a leggere i byte presenti sulla mia struttura dati, mi accorgo che sono corretti.
Ora mi si presenta un nuovo problema, il numero di byte che posso ricevere sulla seriale.
Noto che posso riceverne correttamente massimo 62, tutti i successivi vengono posti a 0xff.
Da quello che vedo sulla documentazione, la Serial.read() restituisce -1 se non c'e' nulla da leggere, da cui il valore 0xff. Ma io ne sto inviando 180 tramite un ciclo for nell'applicazione C#.
Come posso fare per ovviare a questo problema?
Inizialmente avevo posto una condizione if(Serial.available()){} ma avevo lo stesso questo problema.

Su internet ho visto che consigliano semplicemente di leggere i dati in modo da non riempire il buffer, ma è quello che credo di fare, o no?

Devo instaurare una sorta di handshaking ogni 60 byte e trasmettere 3 volte per ottenere il mio array di 180 elementi?

Vorrei essere in grado di trasmettere i miei byte il più velocemente possibile.
Avete qualche consiglio da darmi?

Arduino ha un buffer di 64 byte.
Secondo me il problema è il for

if(Serial.available()){
    for(int i = 0; i<sizeof(Data) ; i++)
  { Data[i] = Serial.read();
   }

Tu li ti chiedi se c'e' un carattere ma poi nel for leggi per forza tot caratteri, indipendentemente dal fatto ci siano dati.
Intanto potresti provare a cambiare il for in un while, dove esci per i arrivato a sizeof ma il i++ lo fai solo se serial.available oppure se il dato letto non è -1 in quanto Serial.read() ritorna -1 se il dato non è stato letto

Grazie mille nid69ita,
questa sera o domani proverò questa modifica e ti farò sapere come andrà.
Da quello che capisco io invio 180Byte dal PC ma solo 64 vengono posti nel buffer e letti, nel frattempo la scrittura da C# si arresta in attesa di uno spazio libero?
non appena uno di questi byte viene letto si libera uno spazio nel buffer, la scrittura da seriale ricomincia automaticamete?
Inserendo il while e la condizione incrementale sul if(Serial.available()) , il codice mi dovrebbe rallentare notevolmente perchè dopo i primi 64 elementi la scrittura deve attendere ogni volta che venga reso disponibile un registro in cui inserire il byte?
oppure diventa una operazione trasparente visto che idealmente non si arriverebbe mai a riempire il buffer?

Non ti so rispondere. Attendi qualcuno più esperto di me su queste MCU.

Tieni conto che quando si programma con queste MCU, senza sistema operativo sotto, devi fare tutto tu.
Di solito, visto che la loop() è già un ciclo while infinito, non si "forza" la loop() a fare un pezzo della logica alla volta ( i tuoi blocchi for) ma si usano variabili logiche o di stato.
Esempio la loop legge 1 solo char e lo mette nel tuo buffer, aumenti di 1 il contatore. Quando hai letto tutti, solo allora accendi una variabile di stato che permette di "saltare" la lettura e passi ad eseguire il pezzo di codice che analizza il buffer. E quando ha finito, azzeri il contatore.

Però non sò se risolve comunque il problema del buffer limitato. Penso dipenda anche dalla velocità della comunicazione. In teoria la read() libera un posto nel buffer.

void setup()
{ ...
 stato=RICEZIONE;
 counter=0;
}
void loop()
{ if(stato==RICEZIONE) 
 { if(Serial.available())
   { int c=Serial.read();
     if(c>=0) 
     { Data[counter] = (char)c;
       counter++;
     }
     if(counter==sizeof(Data)) stato=ANALISI;
   }
 }
 else  // stato==ANALISI
 { ... analisi del buffer...
    counter=0;
    stato=RICEZIONE;
 }
}

Rebel_88:
Inserendo il while e la condizione incrementale sul if(Serial.available()) , il codice mi dovrebbe rallentare notevolmente perchè dopo i primi 64 elementi la scrittura deve attendere ogni volta che venga reso disponibile un registro in cui inserire il byte?

Dal punto di vista di Arduino, o meglio di un Atmega328P @16 MHz, la seriale è una cosa molto lenta, una lumaca visto che anche se la mandi a 115200 bps per ricevere un singolo carattere servono quasi 90 us, una eternità :slight_smile:
Su Arduino l'UART è gestita, sia in lettura che scrittura, tramite interrupt, ovvero il micro deve impegnare meno di 1us per prelevare il byte (8 bit ricevuti) dall'UART e porlo nel ring buffer, pertanto a meno di non bloccare l'esecuzione degli interrupt, alcune librerie lo fanno perché usano routine time critical di tipo atomico, non c'è nessun pericolo di perdersi dati per strada.
Nel tuo caso non devi fare altro che inserire nella loop il controllo di nuovi caratteri disponibili sulla seriale, la Serial.available(), e continuare a prelevare dal buffer i caratteri arrivati fino a che non lo svuoti.
La "while (Serial.available())" è quello che ti serve, se non ci sono nuovi caratteri il programma continua, se ci sono il buffer viene svuotato, a seconda di quanto dura la loop puoi avere da pochi byte fino a qualche decina, per andare in overflow del buffer dovresti sforare il ciclo loop di oltre 60 ms, se è così vuol dire che il codice è scritto male e va rivisto, la loop non deve durare più di qualche ms.
Inutile dire che è vietato l'uso della delay() nel codice, a meno che non siano pochi ms, se non vuoi perdere dati dalla seriale, una volta che il buffer è pieno i caratteri in arrivo vanno persi fino a che non si comincia a svuotare posizioni del buffer, è una coda di tipo fifo.
Nota tecnica, su Arduino non esiste un flag che segnala l'eventuale overflow del buffer seriale, ovvero che è arrivato almeno un carattere in più dei 64 che può contenere.
La Serial.available() ritorna il numero di caratteri presenti nel buffer, se questo valore è minore di 64 c'è la certezza che non è avvenuto un overflow, se ritorna 64, sopratutto se lo fa spesso, è possibile che c'è stato un overflow con relativa perdita dati, in questi casi è possibile aumentare la dimensione del buffer, ovviamente a scapito della ram disponibile per i dati.

Grazie mille per il vostro aiuto e per le vostre spiegazioni.
Appena avrò la possibilità di tornare su arduino (un paio di giorni) vi farò sapere come è andata!

Buongiorno,
ieri sera ho potuto finalmente provare il codice.
Ho inserito nel loop :

int i =0;
while(Serial.available())
{
 if(i>180) break;
   Data[i] = Serial.read();
   i++;
}
Serial.println(i);

Nel C# ho

for (i = 0; i < 180; i++)
         {
         port.Write(new byte[] { bytes[i] }, 0, 1);
                                          
           }
        port.DiscardOutBuffer();
string message = port.ReadLine();
Console.Write(" i : " + message);

Il problema è che se avvio il codice per acquisire i dati inviati dal pc, noto che il valore di i che torna indietro non è mai superiore a 63.
Anzi, quasi sempre, mi appare a schermo un numero inferiore o uguale a 63, e analizzando i dati all'interno della mia struttura, noto che sono corretti fino al byte 63.
Quindi vuol dire che il ciclo while esce per mancanza di caratteri sulla seriale.
Ho provato a seguire anche altri consigli dal forum, come :
Questo
oppure
Questo
Ma rimango sempre fisso alla mia lettura di pochi byte, addirittura di meno rispetto alla situazione in cui non c'era il ciclo while.
Se faccio una prova solo con lo sketch arduino ed invio a mano sulla seriale dei byte noto che il massimo numero che ottengo di i è 63.

Sono sicuro di sbagliare qualche cosa di banale ma al momento non capisco dove.
Sapreste aiutarmi?

Perchè mai usi stò comando ? port.DiscardOutBuffer();

Non sono esperto di C# ma se hai spedito e dall'altra Arduino stà leggendo più lentamente del PC, da PC gli azzeri questo buffer. Non mi pare utile . Prova a toglierlo.

Poi, ribadisco, che fare un programma in loop() con un blocco di codice, che sia while o for, che in pratica è come se si programmasse a blocchi come nei vecchi programmi in basic, a me pare una cosa non sensata su queste mcu, ma da implementare una logica a stati come ho scritto nel post #7

Fai una veloce prova, modifica il ciclo di lettura così :

while(Serial.available())
{
 if(i>180) break;
   Data[i] = Serial.read();
   i++;
   delay(1);
}

... difatti a volte capita che nel tempo di trasferimento dei dati dal buffer HW della porta al buffer SW ... la Serial.available() dia 0 (... nessun carattere disponibile) e tu esci dal while. Un piccolo ritardo può risolvere il problema.

Nota che in alcuni casi ho dovuto portare quel ritardo anche a 5 msec quindi ... fai un po' di prove prima di rinunciare.

Guglielmo

Ad esempio, se devi svuotare il buffer della seriale (... ad esempio per scartare tutto quello che potrebbe contenere dopo aver ricevuto ciò che interessa) devi usare un qualche cosa di simile :

while (Serial.available()) {
   Serial.read();
   delay(5);
}

... o il buffer rischi di NON svuotarlo :wink:

Guglielmo

Grazie mille ad entrambi,
@nid69ita hai perfettamente ragione, devo implementare una macchina a stati per il mio codice, il problema è che al momento non ho molto tempo da dedicargli e di conseguenza sto facendo delle prove veloci in attesa del week-end.

@gpb01 questa sera faccio delle prove con l'inserimento del delay, ma è una cosa che avrei voluto evitare.
La mia applicazione deve prendere uno stream di dati dal pc per poter pilotare degli oggetti esterni come motori, led ecc per far funzionare un cruscotto, se vado ad inserire dei delay di 1ms ad ogni byte farò il refresh di tutti i dati ogni 180ms distruggendo l'aspetto estetico e la funzionalità del progetto.

Questa sera proverò e vi farò sapere qualche cosa il prima possibile.

Vi è un modo diverso di scrivere la lettura della seriale ...
... utilizzando un ciclo "do ... while" ed al suo initerno invece di chiederesi "Serial.available()" ci si chiede "!Serial.available()" ... NON si usa alcun delay(), ma occorre necessariamente prevedere un carattere terminatore di sequenza e, per evitare blocchi, anche un meccanismo di timeout.

Guglielmo

Grazie mille Guglielmo,
questa sera metterò in pratica i tuoi suggerimenti.
Nel frattempo ho trovato questo post in cui si spiega il motivo del delay(1) (post #5), in sintesi :

That delay(1) is in that code to compensate for the time it takes for the character to be received by the hardware USART. You have to take into consideration that the serial data is being received at, more than likely, a slower speed than it takes for the microcontroller to process instructions.

Vi farò sapere i risultati.

Rebel_88:
Nel frattempo ho trovato questo post in cui si spiega il motivo del delay(1) (post #5), in sintesi : Vi farò sapere i risultati.

... è quanto ti dicevo nel post #12.

Però, se segui l'altra strada, lo eviti e velocizzi la ricezione.

Guglielmo

Si, ho dimenticato di scriverlo nel post precedente.
Volevo aggiungere proprio di aver trovato il procedimento suggerito da te in quel post e in più anche la spiegazione del motivo per il quale bisognerebbe aggiungere il delay.

Buongiorno,
eccomi qui nuovamente.
Ho apportato le modifiche consigliate, in particolare ho scritto sul ciclo while del C#
:

                                    /***** Send DATA  ****/
                                        for (i = 0; i < 180; i++)
                                        {
                                            port.Write(bytes, i, 1);
                                        }
                                        

                                        for (i = 0; i < 180; i++)
                                        {
                                            port.Read(data_in, i, 1);
                                        }
                                        
                                        /***** Print Sent DATA  ****/

                                        Console.WriteLine(" \nPrint Send ");
                                        k = 0;
                                        for (i = 0; i < 180; i++)
                                        {
                                            if (k % 12 == 0 && k != 0) Console.Write("\n");
                                            k++;
                                            Console.Write("{0:x} ", bytes[i]);
                                        }
                                        
                                        // Printing Received Data 
                                        Console.WriteLine("\nPrint Return");
                                        k = 0;
                                        for (i = 0; i < 180; i++)
                                        {
                                            //port.Read(cmd_in, i, 1);
                                            if (k % 12 == 0 && k != 0) Console.Write("\n");
                                            k++;
                                            Console.Write("{0:x} ", data_in[i]);
                                        }

mentre su Arduino

void setup() {
  Serial.begin(115200);

}

void loop() {
  while (i < 180){
    while (!Serial.available()) {}
    Data[i] = Serial.read();
    i++;
  };

  for (int j = 0 ; j < 180; j++)
  { 
    Serial.write(Data[j]);
  }

  i = 0;
}

Quello che ottengo è che vengono scritti più di 64 byte, credo che vengano scritti tutti e 180 i byte ma sulla console mi trovo una situazione particolare, come potete vedere dal file allegato.
In particolare i primi 4 bytes ricevuti sono gli ultimi 4 byte che ho inviato, dopo questa prima parte errata comincia il contenuto corretto.
Ho pensato che fosse ancora un problema di sincronizzazione tra scrittura e lettura e quei 4 byte rappresentano ancora la fase di trasmissione, ma data la differenza di velocità di computazione tra pc e Arduino me li ritrovo ancora sul buffer.
Per questo motivo ho inserito la funzione :

  /* Discard buffer*/ 
while (Serial.available()){
  discard = Serial.read();
  }

che mi consente di eliminare tutto quello che è ancora presente sul buffer.
In fine ho aggiunto anche una lettura seriale sul C#(e quindi anche una Serial.write(0xc) fittizia su arduino), essendo una funzione bloccante mi consente di mettere in pausa temporaneamente il ciclo while mentre effettuo le mie operazioni su arduino, prima di cominciare con il prossimo invio di dati.

Facendo girare l'applicazione con queste impostazioni sembra scrivere e leggere correttamente i dati. (allegato 2)

Dico "sembra" perché se provo a togliere il ciclo di lettura dati sul C#, quindi anche quello di scrittura sulla seriale in Arduino, il programma si blocca. Penso che ci sia qualche dettaglio relativo ai tempi.
Ho provato ad aggiungere dei delay(1-5) ma il problema rimane.
Avete qualche idea?

Error_1.JPG

ok_2.JPG