Arduino + C#

In un precedente articolo avevamo visto come costruire un’applicazione in Java che consentisse di visualizzare i valori letti da alcuni sensori e di comandare un certo numero di uscite su Arduino. In questo articolo invece vediamo come fare la stessa cosa ma in C#, infatti sia i collegamenti che lo sketch rimangono gli stessi, com’è possibile vedere nello schema riportato qui sotto :

Schema sensore kty-120 ArduinoI pin usati come entrate sono A0 ( sensore di temperatura ), A1 ( trimmer da 10K ) e A2 ( trimmer da 1k ), mentre per le uscite sono i pin 5, 6 e 7. Arduino quindi,ad intervalli regolari, registra i valori dei sensori e dello stato delle uscite e li memorizza in una stringa inviata attraverso la porta seriale, contemporaneamente rimane in attesa che arrivino comandi dal PC. L’applicazione invece deve ricevere questa stringa, scomporla in modo da ottenere i valori ricevuti e visualizzare il tutto sulla finestra.

A proposito della stringa inviata da Arduino ed elaborata dal programma sul PC, come si vede dalla figura qui sotto, essa è composta da dei caratteri di delimitazione per ogni valore  letto su ogni pin di entrata e uscita, più una parte che permette di identificare la scheda in modo univoco. Logicamente questo è solo un esempio personale, ognuno poi sceglie il proprio modo.

 

 

Qui sotto vediamo come si presenta l’applicazione sul PC. Oltre ai pulsanti per le uscite e le progressBar per i valori delle entrate, vediamo in basso una RichTextBox che tiene traccia dei dati in arrivo sulla seriale.

 

Interfaccia c# Arduino

 

 

Funzionamento del programma PC

Passiamo ora ad una breve descrizione del sorgente del programma sul PC. Il modo per elaborare la stringa ricevuta da Arduino è praticamente lo stesso di quello visto per Java, a parte qualche differenza e il modo in cui vengono gestite le porte seriali. Qui non c’è bisogno di importare nessuna libreria, in quanto tutto ciò che serve per l’occorrenza è già incluso nel .NET Framework. In particolare c’è già un componente chiamato serialPort che provvede, tramite pochi settaggi, a stabilire una connessione sulla porta seriale selezionata. Ma scendiamo più nel dettaglio.  Per prima cosa dobbiamo avere un elenco delle porte seriali disponibili su l PC e per questo motivo che attraverso l’esecuzione di SerialPort.GetPortNames() all’interno di un ciclo for() riempiamo il ComboBox1. La porta scelta verrà aperta e gestita dalla funzione LeggiDati() la quale richiama,tramite un delegate ( una specie di puntatore a un metodo ), la procedura RicDati() che comincia ad elaborare la stringa in arrivo. Qui ad esempio vengono ricavati i valori letti dai sensori racchiusi attraverso i corrispondenti tag di inizio e fine. Un esempio lo si può vedere qui sotto.

Ricava il valore della temperatura

				// recupero del valore misurato dal sensore di temperatura su A0
				//
				if (line.IndexOf("@") > 0 && line.IndexOf("#") > 0) {
					int inizio = line.IndexOf("@")+1;
					int fine = line.IndexOf("#");
					String val = line.Substring(inizio,4);
					try {
						int j;
						Int32.TryParse(line.Substring(inizio,2), out j);
						pr0.Value =j;
					} catch(FormatException xe)  {
						this.Refresh();
					}
					lb0.Text =  val + " °C";
				}

 

Ricava lo stato dell’uscita 5 di Arduino 

	if(line.Contains("p5on")) {
					
					panel5.BackColor = Color.Orange;
					
				} else if (line.Contains("p5off")) {
					
					panel5.BackColor = Color.Gray;
				}

 

Di seguito invece il sorgente completo del programma in C#

/*
 * Creato da SharpDevelop.
 * Utente: mario
 * Data: 09/06/2018
 * Ora: 12:24
 * 
 * 
 */
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Data;
using System.Text;
using System.IO;


namespace ArduinoC_1
{
	/// <summary>
	/// Description of MainForm.
	/// </summary>
	public partial class MainForm : Form
	{
	
		private int cl = 0;
		private delegate void RicDatiEvent(string line);
		
		public MainForm()
		{
			InitializeComponent();
		}
		
		
		void MainFormLoad(object sender, EventArgs e)
		{
			//
			// riempio il combobox con l'elendo delle porte seriali disponibili
			//
			foreach (string item in System.IO.Ports.SerialPort.GetPortNames())
			{
				comboBox1.Items.Add(item);
			}
			
			button1.Enabled =true;
			button2.Enabled =false;
			btn5.Enabled =false;
			btn6.Enabled =false;
			btn7.Enabled =false;
			btnTutti.Enabled =false;
			btnNessuno.Enabled = false;
			
		}
		
		//
		// qui la connessione alla porta seriale selezionata
		//
		void Button1Click(object sender, EventArgs e)
		{
			try {
				if(comboBox1.SelectedIndex > -1) {
					
					serialPort1.PortName = comboBox1.Text;
					String  impPorta = "Porta aperta: " + serialPort1.PortName +
						"  velocità: " + serialPort1.BaudRate.ToString() + " Baud";
					st.Text = impPorta;
					timer1.Enabled =true;
					button1.Enabled =false;
					button2.Enabled =true;
					btn5.Enabled =true;
					btn6.Enabled =true;
					btn7.Enabled =true;
					btnTutti.Enabled =true;
					btnNessuno.Enabled = true;
					serialPort1.Open();
					
					// gestore d'evento associato al ricevimento dati sulla seriale
					//
					serialPort1.DataReceived += LeggiDati;
					
				} else {
					MessageBox.Show("Selezionare la porta seriale!",
					                "Controllo scheda Arduino",MessageBoxButtons.OK);
				}
				
			}catch(System.IO.IOException) {
				MessageBox.Show("Errore sulla porta!\n Il programma verrà riavviato...",
				                "Controllo scheda Arduino",MessageBoxButtons.OK);
				Application.Restart();
				
			}
		}
		
		
		// 
		// Procedura eseguita quando vengono ricevuti i dati sulla seriale
		//
		void LeggiDati(object sender,  System.IO.Ports.SerialDataReceivedEventArgs e)
		{
			
			try {
				if(serialPort1.IsOpen) {
					string line = serialPort1.ReadLine();
					this.BeginInvoke(new RicDatiEvent(RicDati), line);
				}
				
			}catch (System.IO.IOException j) {
				
				MessageBox.Show("Errore sulla porta!\n Il programma verrà riavviato...",
				                "Controllo scheda Arduino",MessageBoxButtons.OK);
				Application.Restart();
			}
			
		}
		
		//
		// invio comandi sulla sporta seriale
		//
		void InviaDati(String comando) {
			
			if( serialPort1.IsOpen) {
				serialPort1.Write(comando);
			}
		}
		
		
		// metodo del delegate
		// scansione della stringa in arrivo da Arduino
		//
		private void RicDati(string line)
		{
			
			//
			// ricerca la stringa di riconoscimento
			//
			if (line.Contains("[ARDU328]")) {
				panel1.BackColor = Color.YellowGreen;
				label2.Text ="Arduino connesso...";
			} else {
				panel1.BackColor = Color.Gray;
				label2.Text ="Nessun collegamento";
			}
			
			//
			// verifica che la stringa sia arrivata per intero
			//

			if (line.IndexOf("*") == 0 &&  line.IndexOf("!") > 0) {
				
				//
				// recupero del valore misurato dal sensore di temperatura su A0
				//
				if (line.IndexOf("@") > 0 && line.IndexOf("#") > 0) {
					int inizio = line.IndexOf("@")+1;
					int fine = line.IndexOf("#");
					String val = line.Substring(inizio,4);
					try {
						int j;
						Int32.TryParse(line.Substring(inizio,2), out j);
						pr0.Value =j;
					} catch(FormatException xe)  {
						this.Refresh();
					}
					lb0.Text =  val + " °C";
				}
				
				//
				// recupero del valore di tensione applicato su A1
				//
				
				if (line.IndexOf("&") > 0 && line.IndexOf("%") > 0) {
					int inizio = line.IndexOf("&")+1;
					int fine = line.IndexOf("%");
					String val = line.Substring(inizio,4);
					try {
						int j;
						Int32.TryParse(line.Substring(inizio,1), out j);
						pr1.Value =j * 10;
					} catch(FormatException xe)  {
						this.Refresh();
					}
					lb1.Text =  val + " Volts";
				}
				
				//
				// recupero del valore di tensione applicato su A2
				//
				
				if (line.IndexOf("$") > 0 && line.IndexOf("/") > 0) {
					int inizio = line.IndexOf("$")+1;
					int fine = line.IndexOf("/");
					String val = line.Substring(inizio,4);
					try {
						int j;
						Int32.TryParse(line.Substring(inizio,1), out j);
						pr3.Value =j * 10;
					} catch(FormatException xe)  {
						this.Refresh();
					}
					lb3.Text =  val + " Volts";
				}
			
				//
				//
				// ricava lo stato delle uscite 5-6-7 di Arduino
				//
				
				if(line.Contains("p5on")) {
					
					panel5.BackColor = Color.Orange;
					
				} else if (line.Contains("p5off")) {
					
					panel5.BackColor = Color.Gray;
				}
				
				if(line.Contains("p6on")) {
					
					panel6.BackColor = Color.Orange;
					
				} else if (line.Contains("p6off")) {
					
					panel6.BackColor = Color.Gray;
				}
				
				if(line.Contains("p7on")) {
					
					panel7.BackColor = Color.Orange;
					
				} else if (line.Contains("p7off")) {
					
					panel7.BackColor = Color.Gray;
				}
			}
			//
			// la stringa ricevuta da Arduino viene scritta nella casella di testo
			//
			rc.Text += line;
		}
		
		
		
		//
		// chiusura della porta seriale e azzeramento dei controlli sulla finestra
		//
		void Button2Click(object sender, EventArgs e)
		{
			try {
				
				if(serialPort1.IsOpen) {
					serialPort1.Close();
					panel1.BackColor = Color.Gray;
					label2.Text ="Nessun collegamento";
					rc.Clear();
					pr0.Value = 0;
					pr1.Value = 0;
					pr3.Value = 0;
					lb0.Text ="";
					lb1.Text ="";
					lb3.Text ="";
					button1.Enabled =true;
					button2.Enabled =false;
					panel5.BackColor = Color.Gray;
					panel6.BackColor = Color.Gray;
					panel7.BackColor = Color.Gray;
					btn5.Enabled =false;
					btn6.Enabled =false;
					btn7.Enabled =false;
					btnTutti.Enabled =false;
					btnNessuno.Enabled = false;
					st.Text ="";
					timer1.Enabled =false;
				}
			}catch(System.IO.IOException) {
				
				MessageBox.Show("Errore sulla porta!\n Il programma verrà riavviato...",
				                "Controllo scheda Arduino",MessageBoxButtons.OK);
				Application.Restart();
			}
			
		}
		
		//
		// ogni pulsante invia il comando corrispondente alle uscite di Arduino
		//
		void BtnNessunoClick(object sender, EventArgs e)
		{
			InviaDati("usciteOFF");
		}
		
		void Btn5Click(object sender, EventArgs e)
		{
			InviaDati("p5");
		}
		
		void Btn6Click(object sender, EventArgs e)
		{
			InviaDati("p6");
		}
		
		void Btn7Click(object sender, EventArgs e)
		{
			InviaDati("p7");
		}
		
		void BtnTuttiClick(object sender, EventArgs e)
		{
			InviaDati("usciteON");
		}

		void Timer1Tick(object sender, EventArgs e)
		{
			rc.Clear();
		}
	}
}

 

Lo sketch di Arduino

Per quanto riguarda il programma eseguito da Arduino, vediamo che la lettura dei sensori avviene ogni 5 mS  l’uno dall’altro e trasmessi sulla seriale, insieme allo stato delle uscite, attraverso la variabile pack. Ogni 150 mS invece Arduino verifica se il programma dal PC ha inviato i comandi necessari per commutare i pin di uscita 5-6-7. Notiamo, inoltre,come per ogni sensore collegato ai pin di entrata venga usata una funzione apposita che converte il valore analogico letto in forma adatta ad essere inviato sul PC. Per il resto penso non ci sia bisogno di ulteriori spiegazioni.

#define pin5 5
#define pin6 6
#define pin7 7
#define kty A0
#define tensione1 A1
#define tensione2 A2
unsigned long tempo = 0;
unsigned long tver = 0;
unsigned long tver1 = 0;
String u5, u6, u7;
String id = "[ARDU328]"; // Stringa di riconoscimento
String pack;
float temperatura, va1, va2;
String rx;


void setup() {

  pinMode(pin5, OUTPUT);
  pinMode(pin6, OUTPUT);
  pinMode(pin7, OUTPUT);
  pinMode(kty, INPUT);
  pinMode(tensione1, INPUT);
  pinMode(tensione2, INPUT);

  digitalWrite(pin5, HIGH);
  digitalWrite(pin6, HIGH);
  digitalWrite(pin7, LOW);

  Serial.begin(57600);

}

// Misura la temperatura su A0 e imposta il valore da inviare al PC
//
void misuraTempertatura() {

  float temp = analogRead(kty);
  float ukty = 4.9 * temp / 1023.0 ;
  float a = 0.00001874 * 1000;
  float b = 0.007884 * 1000;
  float c = 1000 - 3300 * ukty / (5 - ukty);
  float delta = b * b - 4 * a * c;
  float delta1 = sqrt (delta);
  float x2 = (-b + delta1) / (2 * a);
  float temp1 = x2 + 25 ;
  temperatura = temp1;


}

// Misura la tensione su A1 e imposta il valore da inviare al PC
//
void misuraTensioneA1() {

  const float riferimento = 5;
  const float R1 = 1000;
  const float R2 = 10000;
  const float re = 1023.0 * (R2 / (R1 + R2));
  int valore = analogRead(tensione1);
  float vx = (valore / re) * riferimento;
  va1 = vx;

}

// Misura la tensione su A2 e imposta il valore da inviare al PC
//
void misuraTensioneA2() {

  const float riferimento = 5;
  const float R1 = 1000;
  const float R2 = 1000;
  const float re = 1023.0 * (R2 / (R1 + R2));
  int valore = analogRead(tensione2);
  float vx = (valore / re) * riferimento;
  va2 = vx;

}


void loop() {

  tempo = millis();

  if (tempo > tver + 100) { // Ogni 100 millisecondi rileva i valori dei sensori

    misuraTensioneA1();
    delay(5);
    misuraTensioneA2();
    delay(5);
    misuraTempertatura();
    delay(5);

    // Legge lo stato delle uscite 5-6-7 e imposta il valore da inviare al PC
    //
    if (digitalRead(pin5) == HIGH) {
      u5 = "p5on";
    } else {
      u5 = "p5off";
    }
    if (digitalRead(pin6) == HIGH) {
      u6 = "p6on";
    } else {
      u6 = "p6off";
    }
    if (digitalRead(pin7) == HIGH) {
      u7 = "p7on";
    } else {
      u7 = "p7off";
    }

    // Stringa inviata al pc con tutti i valori dei sensori e delle uscite 5-6-7
    //
    pack += "*" + id  + "@" + (String) temperatura + "#" + "&" + (String) va1 +  "%"  + "$" +  (String) va2 +  "/"  + u5 +  u6 + u7 + "!" ;
    Serial.println(pack);
    pack = "";

    tver = millis();
  }


  // Ogni 150 millisecondi legge (se ci sono) i comandi in arrivo dal PC e li esegue
  //
  if (tempo > tver1 + 150) {


    if (Serial.available() > 0)  {

      rx = Serial.readString();

      if (rx.equals("p5")) {

        digitalWrite(pin5, not digitalRead(pin5));
      }
      if (rx.equals("p6")) {

        digitalWrite(pin6, not digitalRead(pin6));
      }
      if (rx.equals("p7")) {

        digitalWrite(pin7, not digitalRead(pin7));
      }
      
      if (rx.equals("usciteON")) {

        digitalWrite(pin5, HIGH);
        digitalWrite(pin6, HIGH);
        digitalWrite(pin7, HIGH);
      }
      if (rx.equals("usciteOFF")) {

        digitalWrite(pin5, LOW);
        digitalWrite(pin6, LOW);
        digitalWrite(pin7, LOW);
      }

    }
    tver1 = millis();
  }

}

 

Qui il video del programma in funzione

 

Qui è possibile scaricare il sorgente del programma e lo sketch Arduino

ArduinoC#1

sketch Arduino