Introduzione.
In questo articolo, sarà discusso tutto ciò che riguarda l’utilizzo della Classe SerialComunication, inclusa nel namespace Windows.Devices. Vedremo in che modo è possibile con tale classe, leggere e inviare dati dalla porta seriale. A livello hardware, faremo uso della scheda Raspberry Pi2, Arduino Uno, più un sensore di temperatura DHT11 e un semplice Diodo Lied entrambi cablati sui pin della scheda Arduino Uno. Gestiremo il sensore di temperatura per la visualizzazione dei dati sulla Raspberry, mentre il led ci servirà per dimostrare in che modo possiamo inviare un comando dalla Raspberry ad Arduino mediante la classe SerialComunication. Di per se, e tutto decisamente semplice, lo scopo dell’articolo e dimostrare come poter scambiare informazioni tra due device connessi mediante la porta seriale.
Hardware necessario.
Dopo questa breve introduzione, passiamo allo step successivo. Per la realizzazione del nostro circuito, abbiamo bisogno del seguente materiale hardware:
- Raspberry Pi2 con installata la versione 10.0.10586.0 su sd card , dotata di cavo di alimentazione.
- Cavo Hdmi così da connettere la Raspberry Pi2 a un monitor.
- Monitor dotato di ingresso HDMI.
- Cavo ethernet.
- Adattatore ethernet-USB 3.0
- Breadboard, ovvero la base necessaria per il montaggio componenti e cablaggio elettrico.
- Jumper maschio-maschio e maschio-femmina.
- Diodo led
- Scheda Arduino Uno
- Cavo di alimentazione usb tipo B lato scheda Arduino Uno e usb tipo la per la connessione sulla Raspberry.
Software necessario.
A livello Software, sono necessari:
- Windows 10 build 10.0.10586.0 installata su pc
- Visual Studio 2015 Update 2
- Windows SKD 10.0.10586.0 (quest’ultimo e comunque incluso con VisualStudio 2015 update 2.
- Ide Arduino o Visual Micro for Arduino
- Windows 10 IoT core 10.0.10586.0 installato su sd card
Windows 10.
Prima di procedere con la creazione dei due progetti, ossia quello con Visual Studio 2015 per le UWP, e quello per Arduino, è necessario installare tutti i software indicati precedentemente. Per cui si partirà dall’installazione di Windows 10 che trovate a questo link.
Visual Studio 2015 update 2.
Installato il sistema operativo, e il momento di installare Visual Studio 2015 update2, trovate il link per il download qui.
Ide Arduino.
Terminata l’installazione di Visual Studio 2015, e l’ora di dedicarsi ad Arduino. Come specificato in precedenza, potete scegliere se avvalervi dell’ide Arduino che trovate a questo link.
Visual Micro for Arduino.
In alternativa, è possibile scaricare ed installare questo plug-in che vi permetterà di creare progetti per Arduino direttamente da Visual Studio 2015. Il pro che avete a disposizione l’intellisense per chi lo conosce, ossia un suggeritore automatico che vi aiuterà e non poco durante la scrittura del codice. Il contro e che a differenza dell’ide Arduino, non e incluso il debug, se intendete debuggare le vostre applicazioni dovrete acquistarlo separatamente.
Raspbrerry Pi2.
Ora che abbiamo installato tutto il necessario sul nostro pc, e ora di pensare alla scheda Raspberry Pi2. A questo link, e possibile scaricare Windows 10 IoT Core. Dobbiamo però, per l’installazione avere a portata di mano una micro sd di almeno 8gb, quindi eseguire l’installazione del sistema operativo su di essa, al termine inserire la sd card all’interno dell’alloggiamento dedicato sulla Raspberry. Per l’installazione di Windows 10 Iot Core, rimando alla seguente procedura, che spiega in maniera semplice ed esaustiva tutto il procedimento. Abbiamo installato tutti i componenti software necessari, e abbiamo anche tutta la componentistica hardware. Prima di dedicarci alla creazione dei progetti, è necessario cablare il circuito elettrico. Si tratta di collegare il sensore DHT11 e il diodo Led alla scheda Arduino.
Circuito elettrico.
Qui di seguito il circuito finale del nostro progetto, realizzato con Fritzing , un ottimo software per la realizzazione di schemi elettrici/elettronici.
Figura 1: il circuito elettrico.
I collegamenti sono i seguenti:
- Anodo del diodo led sul pin 13 della scheda Arduino uno
- Katodo del diodo led sul pin gnd della scheda Arduino uno
- il pin “S” del sensore DHT11, (pin a sinistra) sul pin 8 della scheda Arduino uno
- il pin centrale del sensore DHT11, sul pin +5v della scheda Arduino uno
- il pin di destra del sensore DHT11, sul pin gnd della scheda Arduino uno
- Infine colleghiamo sulla breadboard, due cavi di colore rosso e nero in modo da portare l’alimentazione a tutti i componenti precedenti
- Ultima cosa, colleghiamo il cavo usb menzionato in precedenza, tra una porta usb della Raspberry pi2 con l’uscita usb tipo b della scheda Arduino uno
Questi sono tutti i collegamenti necessari, e giunto ora il momento di dedicarsi alla creazione del progetto di prova.
Progetto di prova.
Avviamo Visual Studio 2015, dal menù file selezioniamo nuovo progetto, selezioniamo un progetto di tipo universale e sviluppato in C# come mostrato in figura, e lo denominiamo Prova comunicazione seriale.
Figura 2: il progetto di prova.
Ci verrà chiesto quale versione della build vogliamo che il nostro progetto supporti, alla seguente finestra di dialogo lasciamo tutto inalterato e confermiamo con tasto ok.
Figura 3: la versione minima e quella di target della build di Windows.
In questo caso, abbiamo lasciato come versione minima supportata la 10.0.10240, e quella di destinazione la 10.0.10586.
Confermato con il tasto ok, saremo condotti nella schermata denominata App.xaml.cs. Ma prima di scrivere codice per la parte grafica e per la gestione della comunicazione seriale, dobbiamo abilitare la capability necessaria, questo per far si di poter fare uso della classe SerialDevice e DeviceInformation, senza l’impostazione delle capability, riceveremo un eccezione a Runtime, mandando in crash l’applicativo. In altre parole, le capability sono necessarie per dichiarare che nella nostra applicazione facciamo uso di dispositivi e/o funzionalità di sistema. Per abilitarle, solitamente basta un doppio click con il mouse sul file in esplora soluzioni Package.appxmanifest, ma in questo caso trattandosi di una capability particolare, dobbiamo inserire tutto da codice. In esplora soluzioni, selezioniamo con un click del mouse il file menzionate prima, tasto F7 e saremo condotti nell’editor di codice come visibile in figura.
Figura 4: la sezione Applications e Capabilities del file Package.appxmanifest.
La parte che interessa e noi e quella tra i tag Capabilities, e li che andremo ad abilitare l’utilizzo delle Classi SerialDevice e DeviceInformation. Inseriamo il codice che segue.
<DeviceCapability Name=”serialcommunication”>
<Device Id=”any”>
< Function Type=”name:serialPort” />
</Device>
</DeviceCapability>
Dopo le modifiche, la sezione Capabilities deve assumere questo aspetto.
Figura 5: il file Package.appxmanifest dopo la modifica
Dopo queste modifiche, salviamo e chiudiamo il file Package.appxmanifest. Occupiamoci adesso dell’interfaccia grafica. In esplora soluzioni, doppio click con il mouse sul file MainPage.xaml, saremo condotti nella schermata dove dovremo definire mediante codice xaml la nostra interfaccia grafica. Per riassumere brevemente, il linguaggio xaml, detto anche codice dichiarativo, e utilizzato in diverse tecnologie, ha fatto il suo esordio con applicazioni Silverlight e Wpf dalla versione 3.5 del dotnet framework, e viste la potenzialità e stato evoluto e utilizzato tutt’ora anche per applicazioni mobile, desktop e universali come quella che svilupperemo in questo articolo, necessario quindi per definire tutti gli oggetti dell’interfaccia della nostra applicazione. E molto simile per chi la conosce alla sintassi xml, quindi racchiuso tra tag, dove sono possibili ulteriori personalizzazioni di ogni singolo oggetto mediante le proprietà che vedremo a breve. Qui di seguito, lascio il link alla documentazione ufficiale Microsoft per chi volesse approfondire ogni dettaglio del linguaggio xaml. Inseriamo il codice seguente all’interno del file MainPage.xaml.
<Page
x:Class=”Prova_comunicazione_seriale.MainPage”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”using:Prova_comunicazione_seriale”
xmlns:d=”http://schemas.microsoft.com/expression/blend/2008″
xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006″
mc:Ignorable=”d”>
<Grid Background=”CornflowerBlue”
Margin=”0,0,-104,0″>
<Grid.RowDefinitions>
<RowDefinition Height=”*”/>
<RowDefinition Height=”*”/>
<RowDefinition Height=”*”/>
<RowDefinition Height=”*”/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition Width=”Auto”/>
<ColumnDefinition Width=”Auto”/>
</Grid.ColumnDefinitions>
<!–Riga 0–>
<TextBlock x:Name=”tbkAllarmi”
Grid.Row=”0″
Grid.ColumnSpan=”3″
Grid.Column=”0″
HorizontalAlignment=”Left”
VerticalAlignment=”Center”
Text=”Errori”/>
<Button x:Name=”btnSerialConnect”
Background=”AliceBlue”
Grid.Row=”0″
Grid.Column=”4″
Content=”Connect”
Click=”ButtonClick”/>
<Button x:Name=”btnSerialDisconnect”
Background=”AliceBlue”
Grid.Row=”0″
Grid.Column=”5″
Content=”Disconnect”
Click=”ButtonClick”/>
<!–Riga 0–>
<TextBlock x:Name=”tbkNomeComponente”
Grid.Row=”1″
Grid.Column=”0″
HorizontalAlignment=”Left”
VerticalAlignment=”Center”
Text=”Componete rilevato”/>
<!–Riga 2–>
<ListBox x:Name=”lstSerialDevices”
Grid.Row=”2″
Grid.ColumnSpan=”6″>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text=”{Binding Id}”/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!–Riga 3–>
<Button x:Name=”btnAccendiled”
Background=”AliceBlue”
Grid.Row=”3″
Grid.Column=”0″
Content=”Accendi led”
Click=”ButtonClick”/>
<Button x:Name=”btnSpegniled”
Background=”AliceBlue”
Grid.Row=”3″
Grid.Column=”1″
Content=”Spegni led”
Click=”ButtonClick”/>
<TextBlock x:Name=”tbkStatusLed”
Grid.Row=”3″
Grid.Column=”2″
VerticalAlignment=”Center”
HorizontalAlignment=”Center”/>
<Button x:Name=”btnPulse1000ms”
Background=”AliceBlue”
Grid.Row=”3″
Grid.Column=”4″
Content=”Pulse 1000 ms”
Click=”ButtonClick”/>
<Button x:Name=”btnPulse2000ms”
Background=”AliceBlue”
Grid.Row=”3″
Grid.Column=”5″
Content=”Pulse 2000 ms”
Click=”ButtonClick”/>
</Grid>
< /Page>
Se tutto è stato eseguito in maniera corretta, questa e la schermata che visualizzeremo in fase di designer.
Figura 6: la schermata del file MainPage.xaml dopo la scrittura del codice dichiarativo.
Si tratta veramente di qualcosa di molto semplice a livello di interfaccia, lo scopo è dimostrare come inviare e ricevere dati tramite porta seriale. Occupiamoci adesso della scrittura del codice gestito, useremo in questo esempio C#. Tasto F7, saremo condotti nell’editor di codice, incolliamo il codice C# che segue.
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Devices.SerialCommunication;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
// Il modello di elemento per la pagina vuota è documentato all’indirizzo http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x410
namespace Prova_comunicazione_seriale
{
/// <summary>
/// Pagina vuota che può essere usata autonomamente oppure per l’esplorazione all’interno di un frame.
/// </summary>
public sealed partial class MainPage : Page
{
/// <summary>
/// Private variables
/// </summary>
private SerialDevice serialPort = null;
DataWriter dataWriteObject = null;
DataReader dataReaderObject = null;
private ObservableCollection<DeviceInformation> listOfDevices;
private CancellationTokenSource ReadCancellationTokenSource;
public MainPage()
{
InitializeComponent();
btnAccendiled.IsEnabled = false;
btnSpegniled.IsEnabled = false;
listOfDevices = new ObservableCollection<DeviceInformation>();
ListAvailablePorts();
}
private async void ListAvailablePorts()
{
try
{
string aqs = SerialDevice.GetDeviceSelector();
var dis = await DeviceInformation.FindAllAsync(aqs);
for (int i = 0; i < dis.Count; i++)
{
listOfDevices.Add(dis[i]);
}
lstSerialDevices.ItemsSource = listOfDevices;
btnAccendiled.IsEnabled = true;
btnSpegniled.IsEnabled = true;
lstSerialDevices.SelectedIndex = -1;
}
catch (Exception ex)
{
tbkAllarmi.Text = ex.Message;
}
}
private async void ButtonClick(object sender, RoutedEventArgs e)
{
var buttonClicked = sender as Button;
switch(buttonClicked.Name)
{
case “btnSerialConnect”:
SerialPortConfiguration();
break;
case “btnSerialDisconnect”:
SerialPortDisconnect();
break;
case “btnAccendiled”:
if (serialPort != null)
{
dataWriteObject = new DataWriter(serialPort.OutputStream);
await ManageLed(“2”);
}
if (dataWriteObject != null)
{
dataWriteObject.DetachStream();
dataWriteObject = null;
}
break;
case “btnSpegniled”:
if (serialPort != null)
{
dataWriteObject = new DataWriter(serialPort.OutputStream);
await ManageLed(“1”);
}
if (dataWriteObject != null)
{
dataWriteObject.DetachStream();
dataWriteObject = null;
}
break;
case “btnPulse1000ms”:
if (serialPort != null)
{
dataWriteObject = new DataWriter(serialPort.OutputStream);
await ManageLed(“3”);
}
if (dataWriteObject != null)
{
dataWriteObject.DetachStream();
dataWriteObject = null;
}
break;
case “btnPulse2000ms”:
if (serialPort != null)
{
dataWriteObject = new DataWriter(serialPort.OutputStream);
await ManageLed(“4”);
}
if (dataWriteObject != null)
{
dataWriteObject.DetachStream();
dataWriteObject = null;
}
break;
}
}
private async void SerialPortConfiguration()
{
var selection = lstSerialDevices.SelectedItems;
if (selection.Count <= 0)
{
tbkAllarmi.Text = “Seleziona un oggetto per la connessione seriale!”;
return;
}
DeviceInformation entry = (DeviceInformation)selection[0];
try
{
serialPort = await SerialDevice.FromIdAsync(entry.Id);
serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.BaudRate = 9600;
serialPort.Parity = SerialParity.None;
serialPort.StopBits = SerialStopBitCount.One;
serialPort.DataBits = 8;
serialPort.Handshake = SerialHandshake.None;
tbkAllarmi.Text = “Porta seriale correttamente configurata!”;
ReadCancellationTokenSource = new CancellationTokenSource();
Listen();
}
catch (Exception ex)
{
tbkAllarmi.Text = ex.Message;
btnAccendiled.IsEnabled = false;
btnSpegniled.IsEnabled = false;
}
}
private void SerialPortDisconnect()
{
try
{
CancelReadTask();
CloseDevice();
ListAvailablePorts();
}
catch (Exception ex)
{
tbkAllarmi.Text = ex.Message;
}
}
private async Task ManageLed(string value)
{
var accendiLed = value;
Task<UInt32> storeAsyncTask;
if (accendiLed.Length != 0)
{
dataWriteObject.WriteString(accendiLed);
storeAsyncTask = dataWriteObject.StoreAsync().AsTask();
UInt32 bytesWritten = await storeAsyncTask;
if (bytesWritten > 0)
{
tbkAllarmi.Text = “Valore inviato correttamente”;
}
}
else
{
tbkAllarmi.Text = “Nessun valore inviato”;
}
}
private async void Listen()
{
try
{
if (serialPort != null)
{
dataReaderObject = new DataReader(serialPort.InputStream);
while (true)
{
await ReadData(ReadCancellationTokenSource.Token);
}
}
}
catch (Exception ex)
{
tbkAllarmi.Text = ex.Message;
if (ex.GetType().Name == “TaskCanceledException”)
{
CloseDevice();
}
else
{
tbkAllarmi.Text = “Task annullato”;
}
}
finally
{
if (dataReaderObject != null)
{
dataReaderObject.DetachStream();
dataReaderObject = null;
}
}
}
private async Task ReadData(CancellationToken cancellationToken)
{
Task<UInt32> loadAsyncTask;
uint ReadBufferLength = 1024;
cancellationToken.ThrowIfCancellationRequested();
dataReaderObject.InputStreamOptions = InputStreamOptions.Partial;
loadAsyncTask = dataReaderObject.LoadAsync(ReadBufferLength).AsTask(cancellationToken);
UInt32 bytesRead = await loadAsyncTask;
if (bytesRead > 0)
{
tbkStatusLed.Text = dataReaderObject.ReadString(bytesRead);
}
}
private void CancelReadTask()
{
if (ReadCancellationTokenSource != null)
{
if (!ReadCancellationTokenSource.IsCancellationRequested)
{
ReadCancellationTokenSource.Cancel();
}
}
}
private void CloseDevice()
{
if (serialPort != null)
{
serialPort.Dispose();
}
serialPort = null;
btnAccendiled.IsEnabled = false;
btnSpegniled.IsEnabled = false;
listOfDevices.Clear();
}
}
}
Diamo uno sguardo più da vicino al codice precedente. All’avvio dell’applicazione viene eseguito il costruttore della classe MainPage.
Viene prima di tutto richiamato il medoto InitializeComponent(), che si occuperà della gestione per l’interfaccia grafica. Vengono disabilitati i button di richiesta accensione e spegnimento led. Successivamente richiamato il metodo ListAvailablePorts(), il quale si occupa di verificare se nel nostro caso sulla Raspberry Pi2 vi sono componenti collegati. In caso positivo, avremo nel controllo listBox, tutti gli Id dei componenti rilevati. Al click sul button btnSerialConnect, viene richiamato il metodo asincrono SerialPortConfiguration(), che si occupa di eseguire tutta la configurazione della porta seriale, e se tutto va a buon fine, avremo un messaggio che indicherà che la configurazione e avvenuta correttamente. A questo punto siamo pronti per collegarci con uno dei dispositivi connessi alla Raspberry. Sarà richiamato anche un altro metodo, Listen(), il quale lancerà un Task denominato ReadData(). Questo task, resterà in ascolto in modo da andare a leggere ogni valore in entrata sulla porta seriale e visualizzerà tutto a video. Il Task ManageLed(), viene richiamato al click su uno dei controlli button. Come potete osservare, questo task vuole in ingresso un parametro di tipo string, che servirà per eseguire una determinata azione sulla scheda Arduino Uno, la scheda Arduino invierà una conferma di azione eseguita, che sarà intercettata dal Task ReadData(), così da essere visualizzata nella nostra applicazione. Passiamo ora all’ultimo step, ossia la procedura per l’esecuzione del nostro esempio creato con Visual Studio.
Compilazione e debug del progetto di prova.
In esplora soluzioni, click con il mouse sul nome del progetto, tasto destro e selezioniamo il il comando “Proprietà”. Saremo condotti in un altra schermata, dove ci saranno una serie di schede. A noi interessano nell’ordine la schede “Applicazione” e “Debug”. Sulla prima scheda, lasciamo tutto come in figura.
Figura 7: la scheda applicazione.
Passiamo ora alla scheda “Debug”, qui eseguire alcune modifiche, ossia:
-
Dispositivo di destinazione
-
Indirizzo ip del dispositivo di destinazione
-
Modalità di autenticazione
Su dispositivo di destinazione impostiamo “Computer remoto”.
Su indirizzo ip, dobbiamo inserire l’indirizzo ip fornito dalla scheda Raspberry pi2.
Ultima impostazione, nella modalità di autenticazione, inseriamo “Universale(protocollo non crittografato).
Di seguito la schermata Debug con le impostazioni.
Figura 8: la scheda debug.
Ora siamo pronti per eseguire il debug della nostra applicazione sulla Raspberry pi2. Tasto F5, e se tutto è stato eseguito correttamente, questo e ciò che visualizzeremo a video.
Figura 9: la schermata del progetto di prova.
Conclusioni.
In questa prima parte, abbiamo fatto una panoramica sulla parte hardware necessaria, visto quali componenti software vanno installati, e terminato con la creazione del progetto ed eseguito infine il debug dell’applicazione con Visual Studio 2015 update2. Ovviamente manca ancora la parte inerente alla scheda Arduino uno, ossia lo sketch di codice per poter scambiare dati tra le due schede, lo vedremo nella seconda e ultima parte.