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à.