2013-02-27 9 views
5

Sto provando a leggere da diverse porte seriali dai sensori tramite microcontrollori. Ogni porta seriale riceverà più di 2000 misurazioni (ogni misura è 7 byte, tutte in esadecimale). E stanno sparando allo stesso tempo. In questo momento sto interrogando da 4 porte seriali. Inoltre, traduco ogni misura in String e aggiungo ad un Stringbuilder. Al termine della ricezione dei dati, verranno inseriti in un file. Il problema è che il consumo della CPU è molto alto, dall'80% al 100%.Polling porta seriale e gestione dati

Sono andato anche se alcuni articoli e messo Thread.Sleep (100) alla fine. Riduce il tempo di CPU quando non ci sono dati in arrivo. Ho anche inserito Thread.Sleep alla fine di ogni polling quando BytesToRead è inferiore a 100. Aiuta solo in una certa misura.

Qualcuno può suggerire una soluzione per il polling dalla porta seriale e gestire i dati che ottengo? Forse aggiungendo ogni volta che ottengo qualcosa causa il problema?

//I use separate threads for all sensors 
private void SensorThread(SerialPort mySerialPort, int bytesPerMeasurement, TextBox textBox,  StringBuilder data) 
    { 
     textBox.BeginInvoke(new MethodInvoker(delegate() { textBox.Text = ""; })); 

     int bytesRead; 
     int t; 
     Byte[] dataIn; 

     while (mySerialPort.IsOpen) 
     { 
      try 
      { 
       if (mySerialPort.BytesToRead != 0) 
       { 
        //trying to read a fix number of bytes 
        bytesRead = 0; 
        t = 0; 
        dataIn = new Byte[bytesPerMeasurement]; 
        t = mySerialPort.Read(dataIn, 0, bytesPerMeasurement); 
        bytesRead += t; 
        while (bytesRead != bytesPerMeasurement) 
        { 
         t = mySerialPort.Read(dataIn, bytesRead, bytesPerMeasurement - bytesRead); 
         bytesRead += t; 
        } 
        //convert them into hex string 
        StringBuilder s = new StringBuilder(); 
        foreach (Byte b in dataIn) { s.Append(b.ToString("X") + ","); } 
        var line = s.ToString(); 

              var lineString = string.Format("{0} ----   {2}", 
                 line, 
                mySerialPort.BytesToRead); 
        data.Append(lineString + "\r\n");//append a measurement to a huge Stringbuilder...Need a solution for this. 

        ////use delegate to change UI thread... 
        textBox.BeginInvoke(new MethodInvoker(delegate() { textBox.Text = line; })); 

        if (mySerialPort.BytesToRead <= 100) { Thread.Sleep(100); } 
       } 
      else{Thread.Sleep(100);} 

      } 
      catch (Exception ex) 
      { 
       //MessageBox.Show(ex.ToString()); 
      } 
     } 


    } 
+1

Non utilizzare un loop. Utilizzare su eventi ricevuti, se possibile, o un timer multimediale, in caso contrario. –

+0

@HansPassant Sì. – Timtianyang

+0

@DannyVarod Qual è il timer multimediale per questo caso? – Timtianyang

risposta

6

questo non è un buon modo per farlo, è molto meglio lavorare sull'evento DataReceived.

fondamentalmente con le porte seriali c'è un processo a 3 fasi che funziona bene.

  • Ricevere i dati dalla porta seriale
  • attesa fino ad ottenere un pezzo rilevante di dati
  • Interpretare i dati

così qualcosa come

class DataCollector 
{ 
    private readonly Action<List<byte>> _processMeasurement; 
    private readonly string _port; 
    private SerialPort _serialPort; 
    private const int SizeOfMeasurement = 4; 
    List<byte> Data = new List<byte>(); 

    public DataCollector(string port, Action<List<byte>> processMeasurement) 
    { 
     _processMeasurement = processMeasurement; 
     _serialPort = new SerialPort(port); 
     _serialPort.DataReceived +=SerialPortDataReceived; 
    } 

    private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e) 
    { 
     while(_serialPort.BytesToRead > 0) 
     { 
      var count = _serialPort.BytesToRead; 
      var bytes = new byte[count]; 
      _serialPort.Read(bytes, 0, count); 
      AddBytes(bytes); 
     } 
    } 

    private void AddBytes(byte[] bytes) 
    { 
     Data.AddRange(bytes); 
     while(Data.Count > SizeOfMeasurement)    
     { 
      var measurementData = Data.GetRange(0, SizeOfMeasurement); 
      Data.RemoveRange(0, SizeOfMeasurement); 
      if (_processMeasurement != null) _processMeasurement(measurementData); 
     } 

    } 
} 

Nota: Aggiungere I byte continuano a raccogliere i dati finché non hai abbastanza da contare come misura, o se ottieni una raffica di dati, li divide in int o misurazioni separate .... in modo da poter ottenere 1 byte una volta, 2 il successivo e 1 altro il successivo, e prenderà quindi una svolta in una misura. La maggior parte del tempo se il micro invia in uno scoppio, si arriverà a uno, ma a volte si otterrà diviso in 2.

poi da qualche parte si può fare

var collector = new DataCollector("COM1", ProcessMeasurement); 

e

private void ProcessMeasurement(List<byte> bytes) 
      { 
       // this will get called for every measurement, so then 
       // put stuff into a text box.... or do whatever 
      } 
+0

Devo interpretare i dati e aggiornare l'interfaccia utente in un altro thread? – Timtianyang

+0

no, non è necessario, si potrebbe fare tutto con la richiamata. –

+0

ricorda che il tuo computer è in grado di gestire molti più dati di quelli che possono arrivare su una porta seriale a tutta velocità! –

1

Credo che quello che si dovrebbe fare è l'aggiunta di un gestore di eventi per elaborare i dati in entrata:

mySerialPort.DataReceived += new SerialDataReceivedEventHandler(mySerialPort_DataReceived); 

Questo elimina Sollecita la necessità di eseguire un thread separato per ogni porta seriale che ascolti. Inoltre, ogni gestore DataReceived verrà chiamato precisamente quando ci sono dati disponibili e consumerà solo il tempo di CPU necessario per elaborare i dati, quindi cederà all'applicazione/sistema operativo.

Se ciò non risolve il problema di utilizzo della CPU, significa che stai facendo troppa elaborazione. Ma a meno che tu non abbia delle porte seriali molto veloci, non riesco a immaginare che il codice che hai lì porrà un problema.

+0

Darò una prova ad un gestore di eventi. Ma so che le porte seriali alimenteranno i dati continuamente (dai microcontrollori), non sta sondando una soluzione migliore per questo? – Timtianyang

+0

Non proprio, no. Il polling è solo una buona idea quando l'origine dati non è in grado di carpire l'attenzione del thread o quando il sovraccarico del meccanismo di gestione degli eventi è considerevole. Nel tuo caso il sovraccarico è minuscolo, data la velocità della porta seriale rispetto alla velocità della tua CPU. – ReturningTarzan

2

Prima di tutto è consigliabile leggere Using Stopwatches and Timers in .NET. Puoi abbattere qualsiasi problema di prestazioni con questo e dire esattamente quale parte del tuo codice sta causando il problema.

Utilizzare SerialPort.DataReceived Event per attivare il processo di ricezione dati.

Processo di ricezione e processo di manipolazione dei dati separati. Memorizza i tuoi dati, quindi elabora.

Non modificare l'interfaccia utente dal ciclo di lettura.

+0

Intendi solo rendere l'evento Receiving il più breve possibile e quindi utilizzare un altro thread per interpretare i dati e aggiornare l'IU? – Timtianyang

+1

@Timtianyang: Si tratta di [Separazione delle preoccupazioni] (http://en.wikipedia.org/wiki/Separation_of_concerns) e [Programmazione modulare] (http://en.wikipedia.org/wiki/Modular_programming). Il codice di Keith Nicholas è un buon esempio di come strutturare il tuo codice. – rumburak