PROGETTO “In Pole Position per la Ferrari”
(Camera commercio di Torino e Unione Industriale di Torino)
Area tematica: Progettazione
Istituto: ITIS “Enzo Ferrari” di Susa (To)
Classe: 5^Elettronica
Autori: SCEUSA Giulio e PILLONI Ivan
Sensore di temperatura con interfaccia grafica realizzata in Visual C
Il progetto che abbiamo deciso di realizzare è un dispositivo costituito da:
- una parte hardware a microprocessore che gestisce l’acquisizione analogica della temperatura rilevata dal sensore e,
tramite porta seriale, comunica al computer i dati digitali,
- una parte software realizzata in Visual C che gestisce l’interfaccia grafica .
Abbiamo scelto questo progetto perché comprendeva una parte hardware di Elettronica e soprattutto una parte
software di Sistemi, materia che trovo particolarmente interessante; inoltre mi permetteva di applicare e
approfondire quello che stavamo studiando come ad esempio: l’ADC, i circuititi di condizionamento, gli amplificatori
operazionali, la programmazione.
Per la realizzazione di questo progetto ho cominciato a definire uno schema elettrico per la parte hardware.
Abbiamo iniziato svolgendo i calcoli per il condizionamento.
Schema a blocchi
Sensore
di
Temperatura
Circuito
di
Condizionamento
Conversione
Analogico
Digitale
Gestione Dati
Tramite
Computer
Sensore
Abbiamo scelto come sensore di temperatura il circuito integrato LM335 perché è stato utilizzato in una delle
esercitazioni di laboratorio. Preciso che in questo anno scolastico abbiamo svolto poche esercitazioni di laboratorio e
le abbiamo svolte in aula perché il laboratorio era inagibile .Il sensore, percorso da una corrente di 1mA, fornisce in
uscita una tensione direttamente proporzionale alla temperatura, cioè se aumenta la temperatura aumenta anche la
tensione di uscita. Nella mia applicazione ho deciso di utilizzarlo per monitorare le variazioni di temperatura nel range
-30°C +50°C con un uscita che rispettivamente vale 2.456V e 3.256V.
Dal data sheet ho ricavato che devo fornire una corrente di 1mA. Ho deciso di non fornire una corrente costante di 1
mA ma di fornirla solo alla temperatura ambiente di 20°C dove mi serve una buona precisione. Quando la temperatura
sale e scende la corrente varia introducendo un errore che ho comunque deciso di correggere via software.
Per fornire una corrente di 1mA a 20°C ho utilizzato una tensione di 9V e una Rserie di (47//6,8) kohm.
Data Sheet
Circuito di condizionamento
Abbiamo iniziato dallo schema elettrico generale
utilizzato durante le lezioni in aula
Dopo aver deciso di utilizzare l’operazionale in singola alimentazione con Val = 9V abbiamo fissato la resistenza di
reazione R2=10 kohm poi abbiamo calcolato i parametri di progetto |q| ed m:
− ,
) = | |: 2.456
5 ∗ 2.456
| | =
= 15.35
0.8
5 :( ,
=
5
= 6.25
0.8
Applicando il principio della sovrapposizione degli effetti e collegando a massa il pin di ingresso non invertente
Utilizziamo Val=9V ed R2=10 kohm e il valore di |q|=15,35V calcolato in precedenza;
Siccome il parametro q è negativo colleghiamo il pin di ingresso invertente a a+Val e calcolo il valore di R1:
1=
90
∗ 2
=
= 5.863
| |
15.35
ℎ
Verifichiamo se il guadagno è minore, uguale o maggiore di m per capire se dobbiamo attenuare, amplificare o
nessuna delle due cose :
1+
#$
#%
<?
1 +
%()
*.+,-)
= 2.7056 <? 6,25
il guadagno è minore di m
Colleghiamo la parte inventente a massa e la parte non invertente all’uscita del mio sensore e visto che il guadagno
viene minore di m dobbiamo aggiungere R3 che calcoliamo :
2
( 1 ∗ 3)
( 1 + 3)
10
5.863 + 3
8.2 = 1 +
7.2 =
5.863 ∗ 3
0.5863 ∗ 3
5.863 + 3
=1+
3.078 3 = 5.863 + 33.078 3 −
3=
5.863
= 2.821/ ℎ
3.078
= 5.863
Questo è lo schema definitivo del circuito di
condizionamento :
Per avere un’ulteriore conferma dei calcoli abbiamo
verificato il circuito simulandolo e abbiamo notato che
tutto funzionava quando avevamo 3.256V l’uscita era
circa 5V e quando avevamo 2.256V avevamo qualche mV
dovuto alla singola alimentazione comunque
approssimabile comunque a 0V.
Simulatore Pspice - schematics
Grafico
ingresso_uscita
Sulle y c’è tensione
in ingresso al uP al
variare della
temperatura (x)
Come si vede in figura 1 per questo progetto abbiamo utilizzato come microprocessore il PIC16F73 con un
convertitore ADC a 8 bit, MAX232 per interfacciare l’uscita del microprocessore con la porte seriale, l’integrato LM324
che al suo interno contiene 4 operazionali, il sensore di temperatura LM335 e come si può vedere due morsettiere per
un’ eventuale espansione futura tipo aggiungendo un sensore di umidità e di pressione atmosferica.
Dopo aver realizzato lo schema elettrico e quindi aver capito quali e quanti componenti servivano abbiamo realizzato
il circuito stampato con il programma Ivex winboard PBC.
Nello schema le piste
verdi rappresentano
quelle che verranno
stampate sul rame
mentre quelle rosse
rappresentano i ponti
esterni, quindi i ponti
che passano sul lato
componenti della
basetta.
Realizzato il circuito
stampato abbiamo
eseguito una prova su
polistirolo per verificare di
aver assegnato il giusto
spazio ad ogni
componente.
Dopo aver realizzato il circuito stampato con winboard e aver testato le dimensioni dei componenti sul polistirolo
siamo passati alla realizzazione vera e propria della basetta:
-abbiamo stampato su carta il circuito;
-abbiamo attaccato con dello scotch il foglio di carta blu con la parte liscia sulla carta in modo che il toner si trasferisse
sulla parte ruvida;
-abbiamo tolto lo scotch e applicato la parte ruvida della carta blu, cioè quella con il toner, sulla parte di rame della
basetta;
-abbiamo passato il ferro da stiro sul foglio di carta blu in modo che il toner si trasferisse dalla carta alla basetta:
questo ha creato uno strato protettivo dall’azione corrosiva dell’acido;
-abbiamo verificato che tutte le piste fossero state riportate correttamente sul rame e ho corretto eventuali
imprecisioni con un pennarello indelebile;
- abbiamo immerso la basetta nell’acido (cloruro ferrico) e ho aspettato che corrodesse il rame;
- abbiamo pulito con del solvente il lato rame della basetta in modo da cancellare il toner.
La basetta con i componenti
saldati (vista dall’alto).
basetta vista dal lato
saldature
Elenco Componenti
Il costo della realizzazione è stimabile sui 35€ per i
componenti.
COMPONENTE
R1
R1’
R2
R3
R3’
R4
R5
R6
R7
R8
R9
R10
R11
C1
C2
C3
C4
C5
C6
C7
C8
C9
C10
Quarzo
LM324
PIC16F73
MAX 232
VALORE
5Kohm
820ohm
10Kohm
2Kohm
820ohm
47Kohm
6.8Kohm
83Kohm
10Kohm
10Kohm
10Kohm
10Kohm
10Kohm
470nF Poliestere
10uF 16V elettrolitico
22pF
22pF
1uF 100V
1uF 100V
1uF 100V
1uF 100V
100mF
10mF
7.3728MHz
Vista
della scatola aperta
terminata.
Vista della scatola chiusa
terminata.
Difficoltà incontrate
La prima difficoltà che abbiamo dovuto affrontare è stata che non riuscivamo a trovare uno schema elettrico già
pronto quindi abbiamo deciso di scegliere quello che ci sembrava più plausibile e fare noi tutti i calcoli che poi ci
hanno portato a disegnare lo schema di figura1
In seguito abbiamo iniziato ad occuparci della parte di programma relativa al software che gestiva l’acquisizione che è
stato realizzato in C#.
Il programma è suddiviso in 4 schermate:
1) Visualizzazione temperatura corrente;
2) Selezione porta seriale e velocità di trasmissione(Baud Rate);
3) Tabella dati e media dei valori presi;
4) Grafico dei valori acquisiti.
Schermata 1
Viene acquisita la temperatura tramite la porta seriale e visualizzata al
centro della finestra .
Schermata 2
Tramite due barre a tendina posso scegliere la velocità, e quale porta
seriale utilizzare in caso c’è ne fossero collegate più di una.
Schermata 3
La schermata contiene una tabella di tutti i valori acquisiti di
temperatura e rispettivo orario di acquisizione, un tasto per
eliminare questi dati salvati e quindi dalla tabella e una finestra
in cui viene calcolata la media dei valori acquisisti.
Schermata 4
In questa schermata viene visualizzato il grafico dei valori acquisisti e
salvati precedentemente con sull’asse delle y la temperatura e
sull’asse delle x il tempo.
Qui di seguito riportiamo il rispettivo codice di cui mi sono servito per realizzare le rispettive schermate .
Schermata 1
//Per usare le classi StreamWriter e StreamReader
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//Inizio del mio codice
//Variabile globale
string nomePorta;
/*Dichiara la variabile orario della struct System.DateTime e usa la
* proprietà Now per inizializzarla con l'ora corrente*/
public DateTime orario = DateTime.Now;
//Ricava la path della cartella release
internal string pathExe = Application.StartupPath;
//Dichiara l'oggetti dati della classe StreamWriter e lo inizzializza con null
internal StreamWriter scrivi = null;
//Contatore dati acquisiti
int cont = 0;
internal bool abilitascrittura = true;
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
//Chiude porta seriale
serialPort1.Close();
}
private void mnuSelezione_Click(object sender, EventArgs e)
{
//Dichiaro un oggetto della classe Form2
Form2 frmPortName = new Form2();
//Mostra finestra di dialogo
//ShowDialog serve a non farmi tornare al Form1 finche non chiudo la finestra
frmPortName.ShowDialog(this);
//Verifica se si è chiusa la finestra di dialogo cliccando <OK>
if (frmPortName.DialogResult == DialogResult.OK)
{
//Verifica la presenza di errori
try
{
//Ricava il nome della porta selezionata e lo salva nella variabile globale selezionata
nomePorta = frmPortName.cmbPort.SelectedItem.ToString();
//Assegna il contenuto di nome porta alla proprietà PortName di serialPort1
serialPort1.PortName = nomePorta;
//Assegna la velocità selezionata alla proprietà BaudRate
serialPort1.BaudRate = Convert.ToInt32(frmPortName.cmbBaud.SelectedItem);
//Apre il port seriale
serialPort1.Open();
}
//Se ci sono errori esegue il codice dentro di esso
catch (NullReferenceException)
{
MessageBox.Show("PORTA SERIALE INESISTENTE");
return;
}
catch (ArgumentOutOfRangeException)
{
MessageBox.Show("VELOCITà NON CONSENTITA");
return;
}
finally
{
//Verifica se la porta selezionata è aperta
if (serialPort1.IsOpen == true)
//Srive nella barra del titolo
this.Text = nomePorta + " " + "APERTA";
/*Inizializza l'oggeto dati della classe StreamWriter con il suo metodo costruttore che richiede
parametri d'ingresso :
* 1)nome e path del file di testo sotto forma di stringa
* 2)un valore booleano indicante la modalità di apertura del file :
* false ---> creazione di un file vuoto
* true --->aggiornamento di un file già esistente */
scrivi = new StreamWriter(pathExe + "\\Dato.txt", false);
//Chiude l'oggetto dati
scrivi.Close();
}
}
}
private void mnuChiudiPort_Click(object sender, EventArgs e)
{
//Chiude port seriale
serialPort1.Close();
//Scrive nella barra del titolo
this.Text = nomePorta + " " + "CHIUSA";
//Svuoto label
lblTemperatura.Text = " ";
}
private void serialPort1_DataReceived(object sender,
System.IO.Ports.SerialDataReceivedEventArgs e)
{
//Legge il dato dal buffer
int temperatura = serialPort1.ReadByte();
//Visualizza il valore dellla temperatura con 2 cifre dopo la virgola
lblTemperatura.Text = string.Format("{0:F2}", (temperatura * 0.2353) - 20 );
//Incrementa il contatore di valori acquisiti
cont++;
//Scrive, se è abilitata la scrittura del file,in dati.txt un valore ogni 5 acquisiti
if (cont == 5 & abilitascrittura == true)
{
//Aggiorna il contenuto di dati.txt
scrivi = new StreamWriter(pathExe + "\\dato.txt", true);
//Scrive la tensione concatenandola allo spazio vuoto
scrivi.Write(lblTemperatura.Text + " ");
//Scrive l'orario di acquisiszione della temperatura e poi va a capo
scrivi.WriteLine(orario.ToLongTimeString());
//Azzera il contatore dei valori acquisiti
cont = 0;
//Chiude l'oggetto dati
scrivi.Close();
}
}
private void mnuDati_Click(object sender, EventArgs e)
{
//Attiva il form di dialogo non modale frmLeggiFile
Form3 frmLeggiFile = new Form3();
frmLeggiFile.Show();
}
private void timer1_Tick(object sender, EventArgs e)
{
//Ricava l'orario corrente e lo visualizza
orario = DateTime.Now;
lblOrario.Text = orario.ToLongTimeString();
}
private void mnuGrafico_Click(object sender, EventArgs e)
{
//Attiva il form di dialogo non modale frmGrafico
Form4 frmGrafico = new Form4();
frmGrafico.Show();
}
}
}
Schermata 2
//Namespace della classse serialport
using System.IO.Ports;
namespace WindowsFormsApplication1
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e)
{
//Carica i valori di velocità nel combobox
cmbBaud.Items.Add(50);
cmbBaud.Items.Add(300);
cmbBaud.Items.Add(600);
cmbBaud.Items.Add(1200);
cmbBaud.Items.Add(2400);
cmbBaud.Items.Add(4800);
cmbBaud.Items.Add(9600);
cmbBaud.Items.Add(19200);
cmbBaud.Items.Add(38400);
cmbBaud.Items.Add(57600);
cmbBaud.Items.Add(115200);
//Imposta indice dell'elemento da selezionare,6 perchè inizia a contare da zero
cmbBaud.SelectedIndex = 6;
//Imposta elemento selezionato
cmbBaud.SelectedItem = cmbBaud.SelectedIndex;
//Fornisce un elenco delle porte seriali del pc
string[] ports = SerialPort.GetPortNames();
//Visualizza porte com nel listbox,foreasch = per ogni
foreach (string port in ports)
{
cmbPort.Items.Add(port);
}
//Verifica se è stata rilevata almeno una porta seriale
if (cmbPort.Items.Count != 0)
{
//Imposta indice della porta da selezionare
cmbPort.SelectedIndex = 0;
//Imposta la porta selezionata di default
cmbPort.SelectedItem = cmbPort.SelectedIndex;
}
else
{
MessageBox.Show("NESSUNA PORTA SERIALE RILEVATA");
//disattiva cmbBaud
cmbBaud.Enabled = false;
}
}
}
}
Schermata 3
//Per usare le classi StreamWriter e StreamReader
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
}
//Dichiara un oggetto della classe Form1
Form1 frmPricipale = new Form1();
private void btnchiudi_Click(object sender, EventArgs e)
{
//Chiudo Form3
this.Close();
}
private void Form3_Load(object sender, EventArgs e)
{
string dato, Tin;
int n,numVal = 0;
double media = 0;
//Disabilita la scrittura di dati.txt
frmPricipale.abilitascrittura = false;
//Svuota listBox
lstValori.Items.Clear();
//Scrive intestazione listBox
lstValori.Items.Add("Temperatura
Orario");
/*Dichiara l'oggetto leggi della classe StramReader e lo inizializza con il metodo costruttore
* che richiede in ingresso il nome e la path del file di testo sotto dforma di stringa*/
StreamReader leggi = new StreamReader(frmPricipale.pathExe + "\\dato.txt");
/*Legge il file usando la proprità EndOfStream della classe StreamReader che assume il valore
* true quando non vi sono dati da leggere */
while (!leggi.EndOfStream)
{
//Legge una riga di caratteri da dati.txt
dato = leggi.ReadLine();
//Ricava la posizione, nella stringa dato,dell'ultima occorrenza di uno spazio vuoto
n = dato.LastIndexOf(" ");
//Estrae da dato una sottostringa di n caratteri,a partire dal primo carattere (carattere 0)
Tin = dato.Substring(0,n);
//Conta il numero dei valori letti
numVal++;
/*Aggiunge a lstValori il valore della tensione concatenandolo con degli spazi e con
* la sottostringa di dato contenente l'orario di acquisizione, cioè la sottostringa che
* inizia dal carattere n+1 */
lstValori.Items.Add(" " + Tin + "
" + dato.Substring(n + 1));
//Ricava la somma delle tensioni
media = media + Convert.ToDouble(Tin);
}
//Ricava e visualizza la media delle tensioni
lblMedia.Text = string.Format("{0:f2}", media / numVal) + " " + "°C";
//Azzera il conteggio del numero di valori letti
numVal = 0;
//Chiudo l'oggetto leggi
leggi.Close();
//Riabilita la scrittura di dato.txt
frmPricipale.abilitascrittura = true;
}
private void btnSvuota_Click(object sender, EventArgs e)
{
//Ricrea e svuota il file dati.txt
frmPricipale.scrivi = new StreamWriter(frmPricipale.pathExe + "\\dato.txt",false);
//Chiude oggetto dati
frmPricipale.scrivi.Close();
//Svuota il listBox
lstValori.Items.Clear();
}
}
}
Schermata 4
// namespace implementato dal file WebChart.dll
using WebChart;
//Per usare le classi StreamWriter e StreamReader
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form4 : Form
{
// dichiara un oggetto della classe WebChart.ChartEngine
ChartEngine engine;
public Form4()
{
InitializeComponent();
// mio codice
InitializeCharts();
}
private ChartEngine CreateEngine()
{
// dichiara oggetto classe ChartEngine
ChartEngine engine = new ChartEngine();
engine.Charts = new ChartCollection(engine);
// imposta colore sfondo cornice grafico
engine.Background = new ChartInterior(Color.Black);
// imposta colore sfondo grafico
engine.PlotBackground = new ChartInterior(Color.White);
// imposta posizione legenda
engine.Legend.Position = LegendPosition.Bottom;
// imposta altezza legenda
engine.Legend.Width = 25;
// abilita visualizzazione legenda
engine.HasChartLegend = true;
// visualizza reticolo orizzontale
engine.GridLines = GridLines.Horizontal;
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Near;
format.FormatFlags = StringFormatFlags.LineLimit;
engine.YAxisFont = new ChartText(format);
// mostra valori ascisse
engine.ShowXValues = true;
// mostra valori ordinate
engine.ShowYValues = true;
// imposta colore reticolo orizzontale
engine.Border.Color = Color.Green;
return engine;
}
private void InitializeCharts()
{
engine = CreateEngine();
engine.Charts.Add(AddChart());
}
//Dichiara un oggetto della classe Form1
Form1 frmPricipale = new Form1();
private Chart AddChart()
{
// istogramma
// Chart c = new StackedColumnChart();
Chart c = new SmoothLineChart();
string dato, Tin;
int n, numVal = 0;
/*Dichiara l'oggetto leggi della classe StramReader e lo inizializza con il metodo costruttore
* che richiede in ingresso il nome e la path del file di testo sotto forma di stringa*/
StreamReader leggi = new StreamReader(frmPricipale.pathExe + "\\dato.txt");
/*Legge il file usando la proprità EndOfStream della classe StreamReader che assume il valore
* true quando non vi sono dati da leggere */
while (!leggi.EndOfStream)
{
//Legge una riga di caratteri da dato.txt
dato = leggi.ReadLine();
//Ricava la posizione, nella stringa dato,dell'ultima occorrenza di uno spazio vuoto
n = dato.LastIndexOf(" ");
//Estrae da dato una sottostringa di n caratteri,a partire dal primo carattere (carattere 0)
Tin = dato.Substring(0, n);
//Conta il numero dei valori letti
numVal++;
c.Data.Add(new ChartPoint(dato.Substring(n+1),Convert.ToSingle(Tin)));
}
// imposta colore linee grafico
c.Line.Color = Color.Black;
// imposta larghezza linee grafico
c.Line.Width = 1;
// imposta colore riempimento marker
c.Fill.Color = Color.Blue;//blu
// imposta forma marker
c.LineMarker = new CircleLineMarker(6, c.Fill.Color, c.Line.Color);
// altre forme dei marker dei punti
//c.LineMarker = new TriangleLineMarker(6, c.Fill.Color, c.Line.Color);
//c.LineMarker = new DiamondLineMarker(6, c.Fill.Color, c.Line.Color);
//c.LineMarker = new XLineMarker(6, c.Fill.Color, c.Line.Color);
// imposta messaggio in legenda
// c.Legend = "Ascisse = Tempo
Ordinate = Temperatura ";
return c;
}
private void picGrafico_Paint(object sender, PaintEventArgs e)
{
engine.Size = picGrafico.Size;
picGrafico.Image = engine.GetBitmap();
}
}
}
Conclusioni
Grazie a questo progetto abbiamo potuto approfondire gli argomenti studiati in
classe e imparare cose nuove sulla creazione di un termometro elettronico sia
hardware sia software
Inoltre abbiamo preso coscienza dei tempi di realizzazione (circa 100 ore in tre
mesi) nonostante il progetto non fosse di elevata difficoltà.