Програма на C# для відображення значень термометра через Serial Port

This entry was posted by Опубліковано: 18.05.2015

Спаяв термометр на мікроконтролері, але відчув якусь temperature незавершеність свого творіння 🙂

Захотілось надати йому уміння, ділитися своїми  даними з  комп’ютером. Декілька рядків коду і ось термометр уже передає значення через послідовний порт – тепер справа за комп’ютером…

Використав термінальну програму – значення температури успішно прийняв , але ж терміналку написав «хтось», а хочеться все своїми руками помацати…

Як справжній  чайник, кинувся переглядати творіння інших – насмикав звідти кусків коду… Те, що вийшло, на малюнку справа.

А тепер, покроково, опишу «процес творення», може комусь згодиться.

 

Назви компонетів форми

Назви компонетів форми

Розжовувати буду ретельно, орієнтуючись на програмера свого рівня. Іншими словами на того, чий рівень обмежується вмінням створити проект з формою, знає як перетягувати компоненти на форму і  де змінювати їхні властивості.

Вирішуємо, який буде зовнішній вигляд програми.

Там має бути:

  • вікно viknoMain з маленьким віконцем textBox (де має відображатись температура)
  • відповідні написи (lable1 і lable2 зрозуміло для чого)
  • та кнопкою start ( яка буде запускати приймання даних).

 

Візуалізація роботи програми

 

Загальна схема програми

Загальна схема програми

Спочатку підключаємо простори імен, дотримуючись принципу чим більше тим краще 🙂 Головне не забути останній рядок. Його можна писати і не вручну, а знайти компонент на панелі Toolbox і потягнути на форму, а у властивостях поміняти назву – тоді, автоматично додасться простір імен і створиться екземпляр класу SerialPort.

Але я списував у тих, хто це робив вручну:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;

Далі створюємо екземпляр послідовного порта comPort = new SerialPort(); і налаштовуємо його параметри:

namespace Serial
{
 public partial class viknoMain : Form
 {
 private SerialPort comPort = new SerialPort();

 private string _baudRate = "9600";
 private string _parity = "None";
 private string _stopBits = "One";
 private string _dataBits = "8";
 private string _portName = "COM1";
 private string temp; 

Наступний кусок коду – це конструктор нашої форми, в якому відбувається ініціалізація компонентів(тобто запуск, присвоєння початкових значень і приведення у бойову готовність) .

public viknoMain()
{
InitializeComponent();
comPort.DataReceived += new SerialDataReceivedEventHandler(comPort_DataReceived);
}

Підсвічений рядок, буде виловлювати подію в нашому послідовному порті:

comPort.DataReceived  +=  new  SerialDataReceivedEventHandler (comPort_DataReceived);

Делегат. Спроба використовуючи аналогії, пояснити, що це таке.

Як для чайника, досить таки складна писанина. Тут використовується об’єкт делегата SerialDataReceivedEventHandler. Поняття делегата, для мене виявилось досить складним, навіть після прочитання підручників.  В таких випадках, кличу на допомогу  фантазію, для того, щоб відшукати аналогію в реальному житті. Поділюсь своєю маячнею 🙂 :

  1. Уявімо собі транспортне підприємство, яке підписало договір на обслуговування з фірмою, що займається бухгалтерським програмним забезпеченням.
  2. Раптом на бухгалтерській фірмі стається подія – з ними розірвали договір оренди. Тепер вже їм потрібні послуги транспортної фірми,  для переїзду в інше місце. Тобто, найманець має виступити в ролі наймача, а наймач в цьому випадку стане найманцем.
  3. Далі події розвиватимуться так: директор бухгалтерської фірми уповноважить свого працівника(делегата, представника), щоб він провів переговори з транспортною компанією про надання послуг з перевезення.
  4. Представник (делегат) провівши переговори визначиться, який саме транспорт потрібно замовити.

Тепер розберемо наш код та прослідкуємо аналогію:

  1. Наш екземпляр  форми viknoMain, при своєму створенні сам створює екземпляр класу SerialPort – comPort:
    private  SerialPort  comPort = new  SerialPort();
    – подібно до того, як транспортна фірма найняла сторонню фірму для виконання деяких робіт.
  2. Повертаємось до рядка який ми розбираємо. Запис comPort.DataReceived означає,  що в об’єкті comPort відбулась подія  –  отримання даних DataReceived
    – подібно до того, як на бухгалтерській фірмі відбулась подія розірвання договору оренди офісу власником.
  3. Записом +=  ми  додаємо (призначаємо) події обробку, тобто, як програма має відреагувати.
    – аналогія: розпорядження про призначення, яке підпише директор.
    А яку саме обробку – визначить делегат new  SerialDataReceivedEventHandler (new – тому, що делегат це об’єкт і його спочатку треба створити)
    – аналогія: імя представника, тобто конкретного працівника, який буде займатись питанням найму перевізника.
  4. І нарешті, в дужках (comPort_DataReceived)  вказується конкретний метод, який належить нашій формі viknoMain і який власне обробить подію появи даних в буфері послідовного порту
    – подібно до того, як представник бухгалтерської фірми підбере в транспортній компанії вантажний автомобіль з вантажниками, які і здійснять перевезення.

Отже, завдання представника, у випадку аналогії з фірмами: здійснити зворотнє замовлення послуг, бо перевізник теж хоче заробити і документально зобов’язаний оформити свою послугу для звітності. Можливо і в C# зроблено так, щоб зворотній виклик відбувався з допомогою делегата – для “звітності” (тобто, для уникнення безладу).

Висновок: делегат здійснює зворотній виклик методу, що належить основному об’єкту  –  для потреб другорядного об’єкту, який мав би бути допоміжним, але йому теж знадобилась допомога.

Пояснення відноситься до категорії “на хлопський розум” – але навіщо такі складнощі з делегатом, мені вдалось пояснити самому собі, тільки таким чином.

Після всього написаного, зроблю справжнє свинство – витру нашого делегата 🙂 :

comPort.DataReceived  +=  comPort_DataReceived;

А програма все одно буде працювати!!!  Тіньова економіка є і у Visual Studio?. . .   🙂

Наступним іде обробник події клацання по кнопці Старт. Заготовку для нього створило Visual Studio автоматично, після подвійного клацання по кнопці start на формі, тому запис додавання до події делегата, який визначає обробника захований в файлі Form1.Designer.cs :

//
// start
//
this.start.Location = new System.Drawing.Point(157, 236);
this.start.Name = "start";
this.start.Size = new System.Drawing.Size(75, 23);
this.start.TabIndex = 2;
this.start.Text = "Старт";
this.start.UseVisualStyleBackColor = true;
this.start.Click += new System.EventHandler(this.start_Click);

Тут this, означає наш поточний об’єкт, тобто форму viknoMain. В його компоненті start виявлена подія Click, для якої, ми додаємо створеного екземпляра делегата System.EventHandler. А вже він в свою чергу визначає, що обслуговувати подію буде метод start_Click (), який належить нашій формі viknoMain (тобто this).
Конструкція запису така сама, як у випадку з подією в компорті, до якої я намагався знайти аналогію.

Код обробника події Click:

private void start_Click(object sender, EventArgs e)
{
    try
      {
        if (comPort.IsOpen == true) comPort.Close();

        //встановлюємо властивості нашого SerialPort Object
        comPort.PortName = _portName;
        comPort.BaudRate = int.Parse(_baudRate);
        comPort.DataBits = int.Parse(_dataBits);
        comPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), _stopBits);
        comPort.Parity = (Parity)Enum.Parse(typeof(Parity), _parity);

        //тепер відкриваємо порт
        comPort.Open();
      }
    //якщо зловили виключну ситуацію ex то відобразимо її властивість Message
    catch (Exception ex)
      {
        MessageBox.Show(ex.Message);
      }
}

В коді обробника події можна побачити оператор try і catch – вони призначені для виловлювання виключних ситуацій, або простіше кажучи дурниць, які я, як початківець міг наробити. Якщо, щось таке буде виявлено, то виникне виключна ситуація, на на екземпляр якої, вказує посилання ex: catch (Exception ex). В блоці коду оператора catch, ми викликаємо діалогове вікно, яке відобразить властивість Message екземпляра виключної ситуації ex, в якій і буде описана причина виникнення виключної ситуації.

Саме для уникнення чогось подібного, ми перевіряємо чи порт відкритий через властивість IsOpen, якщо відкритий – то закриваємо: метод Close().

Далі йде налаштування компорту, відповідно до налаштувань нашого передавача. Конкретні значення, ми написали раніше, але це були просто придуманні нами змінні типу string, а зараз ми присвоюємо значення властивостям нашого екзепляра послідовного порту, при цьому приводячи значення змінних до типів властивостей.

Після налаштування відкриваємо  компорт (тобто, встановлюємо з’єднання) з допомогою методу Open().

Код обробника події в буфері послідовного порту:

void   comPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            comPort.NewLine = "\x00D";
            try
            {

                temp = comPort.ReadLine();
                
                textBox1.Invoke(new EventHandler(delegate
                {
                    textBox1.Text = temp;

                }));
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);

           }
        }

Мій термометр використовує для позначення кінця значення температури, символ Enter, hex-код якого 0x0D.

За значення кінця передачі або читання в послідовному порті, відповідає властивість класу  NewLine, якому ми і присвоюємо потрібне нам значення: comPort.NewLine = “\x00D”; . Тепер метод ReadLine() буде знати де закінчується значення. Хочу зазначити, що спочатку я, орієнтуючись на інших, не врахував цю особливість і відповідно отримував значення шматками, які хаотично вискакували у віконці textBox.

Цікавим є наступний рядок програми:

По перше, він злегка зачепить поняття потоків, а саме, метод Invoke() вказує, що обєкт делегата (те що в дужках) слід виконати в тому ж потоці, що і потік основний, тобто в потоці нашої форми viknoMain.

Якщо, за коментувати цей метод, і залишити тільки саму суть, тобто рядок:

textBox1.Text = temp;

то завдяки конструкції try – catch ми спіймаємо виключну ситуацію. Вискочить повідомлення, де буде написано, що виключна ситуація сталася через спробу отримати доступ з потоку, якому не належить наш компонент textBox1.

Аналогія? Це ніби кур’єр, робота якого тільки доставити лист, вирішив на підставі цього листа зробити якість правки в бухгалтерії 🙂 – його справа залишити листа в канцелярії (в делегата); далі лист попаде до бухгалтера, той і зробить потрібні зміни.

Друге,  цей метод виконує делегата EventHandler, який призначений для  обробки подій, які не створюють даних.

EventHandler – це делегат, який надає нам платформа .NET. Для більшості подій достатньо делегатів, які визначені самою платформою, визначати свій делегат є необхідність тільки в окремих нестандартних випадках.

Аналогія: це як звернутися до маклера, для того щоб він підшукав нове приміщення, а не бігати самому за оголошеннями.

Третє, цей делегат EventHandler, замість того, щоб представити метод обробки, представив іншого делегата. До того ж цей делегат, навіть імені немає! Це так званий анонімний делегат, в таких делегатах замість параметрів передається просто блок коду.

Анонімний делегат використовується для економії часу і місця. Інакше нам довелось би в параметрах EventHandler, вказати назву методу , а потім написати код методу і все це заради однієї операції по присвоєнню властивості textBox1.Text значення.

Настав час випробувати своє творіння.

Перевірити працездатність програми, можна і не втілюючи в «залізо» наш термометр. На практиці, перш ніж паяти, я змоделював спрощений варіант термометра в Proteus (на схемі тільки найнеобхідніше для роботи програми мікроконтролера: без узгодження рівнів і т.п).   Proteus дозволяє працювати з реальними COM портами комп’ютера, тому я вивожу значення температури на COM2.       Наша програма на C# зчитує значення температури з COM1, а тому необхідно віртуально зв’язати обидва порти. Це можна зробити наприклад з допомогою тріальної Virtual Serial Port Driver, або безкоштовної Virtual Serial Ports Emulator. Я скористався тріальним варіантом, і хоча через два тижні безкоштовний період закінчився, порти так і залишилися зв’язаними і в мене досі так і не з’явилась потреба їх роз’єднувати, і навіть навпаки, досі активно і часто користуюсь їх з’єднаністю 🙂

Але не берусь стверджувати, чи так буде на всіх комп’ютерах, чи це особливість мого, тому мабуть варто віддати перевагу безкоштовній програмі, мінусом у неї є те, що вона безкоштовна лише у своєму 32-ох бітному варіанті.

Сирець для завантаження.

Проект термометра в Proteus.
Virtual Serial Ports Emulator