IOFireBug – první projekt

Cílem prvního projektu bude zejména seznámit se s principem, jakým je implementována komunikace mezi deskou Engine a počítačem, prostřednictvím kódu jazyka C#.

Návod bude popisovat implementaci následujících funkcí:

  • Digitální výstupy
  • Digitální vstupy
  • Analogové vstupy
  • Rotační enkodéry

Doporučený postup před začátkem psaní kódu je vyzkoušení požadovaných funkcí v prostředí naší konfigurační a testovací aplikace dostupné v sekci ke stažení. Omezíme tím případné hledání problému a ověříme funkčnost zapojení.

Implementace bude demonstrována ve vývojovém prostředí Microsoft Visual Studio 2015 v projektu typu WPF aplikace.

Prvním krokem po otevření Visual Studia je vytvoření nového projektu volbou „File -> New -> Project“, nebo odkazem na titulní stránce.

01

V dialogovém okně „New Project“ zvolíme položku WPF Application. Cílový .NET Framework necháme poslední aktuální, v době psaní návodu ve verzi 4.5.2. Dále zvolíme název projektu a jeho umístění. Potvrdíme tlačítkem „OK“.

02

 

Než se pustíme do psaní kódu, rovnou si přidáme do projektu reference na knihovny IOFireBug.

Poslední verze potřebných knihoven je možné získat v sekci ke stažení. Předmětem našeho zájmu jsou tyto knihovny:

  • IOFireBugEngine.dll – Hlavní knihovna IOFireBug
  • log4net.dll – Podpůrná knihovna pro diagnostiku a logování
  • Log4NetLibrary.dll – Podpůrná knihovna pro diagnostiku a logování

V nabídce „Solution Explorer“ zobrazíme, kliknutím pravým tlačítkem myši, dialogovou nabídku položky „References“ a zvolíme „Add Reference“. V levém menu vybereme „Browse“, pomocí dialogového okna vyhledáme stažené knihovny, všechny výše zmíněné označíme a přidáme tlačítkem „Add“. Zkontrolujeme, zda jsou zaškrtnuté potřebné knihovny a potvrdíme tlačítkem „OK“.

Všechny nově importované knihovny by se měly zobrazit v seznamu referencí.

03

 

Začneme jednoduchým grafickým rozhraním. Předmětem návodu není výuka jazyka XAML, tudíž nebudu zacházet do detailů tvorby GUI.

Prvním prvkem grafického rozhraní je dvojice tlačítek pro zahájení a ukončení komunikace s Engine deskou spolu s polem pro vložení sériového čísla. Abychom byli schopní graficky interpretovat stav vstupů a zadávat hodnoty výstupů, použijeme kombinaci „labelů“ a „checkboxů“ logicky oddělených pomocí „groupboxů“ do kategorií:

  • Digitální výstupy (8 výstupů)
  • Digitální vstupy (8 vstupů)
  • Analogové vstupy (8 vstupů)
  • Rotační enkodéry (4 vstupy)

Checkboxy“ jsou použity pro změnu stavu výstupů, tudíž jim zaregistrujeme event „Click“ a všem přiřadíme volání stejné metody, v našem případě „checkBoxDigitalOutput_Changed“.

Pole pro zadání sériového čísla sleduje svůj obsah a podbarvuje svoje pozadí dle správnosti formátu vloženého textu. Kontrola probíhá voláním metody „txbSerial_TextChanged“.

Pozn.: Kontrola umožňuje pouze ověřit správnost formátu sériového čísla, správnost zadaných hodnot není zaručena, kontrola není možná. Nesprávnost sériového čísla se projevuje nenavázáním komunikace.

Pro přehlednost jsem naimplementoval funkcionalitu, která na základě stisku tlačítek pro otevření a zavření komunikace nastavuje vlastnost „IsEnabled“ u všech „groupboxů“, aby byl na první pohled patrný stav komunikace. Tato vlastnost se projeví zašednutím položek v aplikaci. Kompletní podobu kódu XAML naleznete v příloze.

04

 

Přepneme do okna pro psaní C# kódu „MainWindows.xaml.cs“ buď pomocí nabídky „Solution Explorer“ nebo tlačítkem F7 z předešlého okna pro psaní XAMLu.

Přidáme příkaz „using IOFireBug“ a „using Windows.Threading“ do příslušné sekce, abychom si usnadnily budoucí definici jmenných prostorů.

Ještě před konstruktor třídy „MainWindow“ umístíme objekty „IOFireBugDeviceV1“ a „IOFireBugComm“.

Dále je definována konstanta „baundrate“, která bude později sloužit pro nastavení komunikační rychlosti. Stejně tak konstanta „address“, která definuje adresu desky Engine. Protože tyto položky budeme nastavovat na více místech, je vhodné je definovat konstantou.

Další globální proměnnou je „serial“, která obsahuje definici sériového čísla.

using System.Windows;
using IoFireBugEngine;
using System.Windows.Threading;
using System;
using System.Windows.Media;

namespace IOFireBug_sample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        IoFireBugDeviceV1 Device;
        IoFireBugComm Comm;

        const int baundrate = 115200;
        const int address = 1;

        byte[] serial = null;

        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Kód si rozdělíme do několika metod, které budeme postupně generovat:

  • IOFireBugInitialize – zahájení komunikace
  • IOFireBugClose – ukončení komunikace
  • GuiState – aktualizace grafického rozhraní

a event handlerů:

  • IOFireBugData_Changed – příchozí zpráva, změna na vstupech
  • checkBoxDigitalOutput_Changed – odchozí zpráva, požadavek na změnu výstupů

Inicializaci začneme psát do metody „IOFireBugInitialize“. Jako první inicializujeme objekt „IOFireBugDeviceV1“ s prázdným konstruktorem a nastavíme jeho vlastnosti:

  • Control.Addr

Tato vlastnost nastaví adresu Engine desky určenou DIP spínačem. Dle nastavení volíme adresu 1, přiřazením konstanty „address“.

  • Control.BinInputsFnc
  • Control.AnalogInputsFnc
  • Control.RotaryCountersFnc

Tyto vlastnosti určují periodu, s jakou se počítač dostává data od Engine o stavu I/O portů. Lze nastavit periodické vyčítání, ovšem velmi užitečným řešením je způsob „ON_CHANGE“, který, jak název napovídá, data odešle pouze při změně stavu. Z důvodu nevyhnutelného rušení u analogových vstupů tato možnost chybí, a je nutné nastavit časovou periodu. V našem případě nastavíme „ON_PER_50MS“. Nastavení je realizováno pomocí výběrového typu enum, který je následně přetypován na byte.

Device = new IoFireBugDeviceV1();
Device.Control.Addr = adress;

Device.Control.BinInputsFnc = (byte)IoFireBugConst.BIN_INPUT_FUNCTION.ON_CHANGE;
Device.Control.AnalogInputsFnc = (byte)IoFireBugConst.ADC_FUNCTION.ON_PER_50MS;
Device.Control.RotaryCountersFnc = (byte)IoFireBugConst.ROTARY_COUNTERS_FUNCTION.ON_CHANGE;

Konfigurací objektu „Device“ pokračujeme nastavením vlastností

  • Config.FtdiBaudrate
  • Config.BinInputsFilterLen
  • Config.BinInputsSamplePeriod

které zajistí rychlost komunikace a filtrování kmitů na vstupním pinu. Opět využijeme předem definované konstanty, tentokrát „baudrate“.

Device.Config.FtdiBaudrate = baundrate;
Device.Config.BinInputsFilterLen = 4;
Device.Config.BinInputsSamplePeriod = 4;

Samostatný oddíl tvoří konfigurace analogového vstupu, který vyžaduje další nastavení oproti vstupům digitálním. Detaily viz dokumentace.

Device.Config.AdcEnable = true;
Device.Config.AdcFreqHz = 1000000;
Device.Config.AdcInputFrom = 0;
Device.Config.AdcInputTo = 7;
Device.Config.AdcAverages = 1;
Device.Config.AdcRightZeros = 0;
Device.Config.AdcResolution = IoFireBugConst.ADC_RESOLUTION.ADC_12BIT;
Device.Config.AdcMultiplier = 1;

Dalším krokem je přiřazení sériového čísla. V podmínce je kontrola proti hodnotě null, která případně přeskočí zbytek metody.

if (serial == null)
{
    MessageBox.Show("Invalid serial number!");
    return;
}

Device.Control.Serial = serial;

Nyní inicializujeme objekt „IOFireBugComm“, předáme do jeho konstruktoru potřebná data a přidáme výše vytvořenou instanci „Device“.

Konstruktor požaduje identifikaci COM portu, rychlost přenosu a čas pro timeout komunikace.

Přidáme instanci „Device“ příkazem „add“ a následně spustíme komunikaci metodou Start.

Comm = new IoFireBugComm("COM3", baundrate, 200);
Comm.AddDevice(Device);
Comm.Start();

Protože byla zahájena komunikace, aktualizujeme grafické rozhraní vytvořením metody „GuiState“, kterou zavoláme při inicializaci.

private void GuiState(bool communicationOpen)
{
    grpBoxDigitalOutputs.IsEnabled = communicationOpen;
    grpBoxDigitalInputs.IsEnabled = communicationOpen;
    grpBoxAnalogInputs.IsEnabled = communicationOpen;
    grpRotaryEncoders.IsEnabled = communicationOpen;

    btnOpen.IsEnabled = !communicationOpen;
    btnClose.IsEnabled = communicationOpen;
}

Abychom byli schopni zachytit změnu stavu, je třeba zaregistrovat událost „Device.Data.PropertyChanged“. Po napsání += nám VS nabídne automatickou generaci pomocí tabulátoru, v našem případě pojmenovanou „IOFireBugData_Changed“.

Metoda „IOFireBugInitialize“ tedy končí následovně

Comm = new IoFireBugComm("COM3", baundrate, 200);
Comm.AddDevice(Device);
Comm.Start();
            
GuiState(true);

Device.Data.PropertyChanged += IoFireBugData_Changed;

Do vygenerované metody „IOFireBugData_Changed“ naimplementujeme podmínku, která kontroluje pomocí vlastnosti „PropertyName“, zda se změna stavu týká vstupu/výstupu, které nás aktuálně zajímají, a následně zobrazíme hodnotu grafického rozhraní dle příchozích dat.

Protože knihovna IOFireBugEngine pracuje ve vlastním vlákně, je nutné před přiřazením hodnot do grafického rozhraní použít konstrukci dispatcher, který ošetří mezivláknové volání.

První podmínka kontroluje, zda jsou inicializovány oba potřebné objekty. Bez této podmínky hrozí vyhození výjimky při zavření aplikace.

Další vnořené podmínky již kontrolují typ změny pomocí „e.PropertyName“ a následně změnu zobrazují v grafickém rozhraní. Typ změny může být

  • Inputs – digitální vstupy
  • AdcValues – analogové vstupy
  • RotaryCounters – rotační enkodéry
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
    if (Comm != null && Device != null)
    {
        if (e.PropertyName == "Inputs")
        {
            checkBoxDigitalInput1.IsChecked = Device.Data.Inputs[0];
            checkBoxDigitalInput2.IsChecked = Device.Data.Inputs[1];
            checkBoxDigitalInput3.IsChecked = Device.Data.Inputs[2];
            checkBoxDigitalInput4.IsChecked = Device.Data.Inputs[3];
            checkBoxDigitalInput5.IsChecked = Device.Data.Inputs[4];
            checkBoxDigitalInput6.IsChecked = Device.Data.Inputs[5];
            checkBoxDigitalInput7.IsChecked = Device.Data.Inputs[6];
            checkBoxDigitalInput8.IsChecked = Device.Data.Inputs[7];
        }

        if (e.PropertyName == "AdcValues")
        {
            lblAnalogInput9.Content = Device.Data.AdcValues[0];
            lblAnalogInput10.Content = Device.Data.AdcValues[1];
            lblAnalogInput11.Content = Device.Data.AdcValues[2];
            lblAnalogInput12.Content = Device.Data.AdcValues[3];
            lblAnalogInput13.Content = Device.Data.AdcValues[4];
            lblAnalogInput14.Content = Device.Data.AdcValues[5];
            lblAnalogInput15.Content = Device.Data.AdcValues[6];
            lblAnalogInput16.Content = Device.Data.AdcValues[7];
        }

        if (e.PropertyName == "RotaryCounters")
        {
            lblRotaryEncoder5.Content = Device.Data.RotaryCounters[0];
            lblRotaryEncoder6.Content = Device.Data.RotaryCounters[1];
            lblRotaryEncoder7.Content = Device.Data.RotaryCounters[2];
            lblRotaryEncoder8.Content = Device.Data.RotaryCounters[3];
        } 
    }
}));

Dále obsloužíme vygenerovanou metodu „checkBoxDigitalOutput_Changed“, která se volá při kliknutí na „checkboxy“. Implementace je realizována pomocí přiřazení pole typu bool do vlastnosti „GetDeviceData(adress).Outputs“, kde parametr požaduje adresu desky, přiřazená od konstanty „address“. Pole naplníme hodnotami „checkboxů“ z GUI.

private void checkBoxDigitalOutput_Changed(object sender, RoutedEventArgs e)
{
    Comm.GetDeviceData(adress).Outputs = new bool[] 
    {
        (bool)checkBoxDigitalOutput1.IsChecked,
        (bool)checkBoxDigitalOutput2.IsChecked,
        (bool)checkBoxDigitalOutput3.IsChecked,
        (bool)checkBoxDigitalOutput4.IsChecked,
        (bool)checkBoxDigitalOutput5.IsChecked,
        (bool)checkBoxDigitalOutput6.IsChecked,
        (bool)checkBoxDigitalOutput7.IsChecked,
        (bool)checkBoxDigitalOutput8.IsChecked,
    };
}

Je slušností při zastavení komunikace tlačítkem nebo při zavření aplikace zastavit přenos a uklidit po sobě. To zajišťuje metoda „IOFireBugClose“. Zároveň je zavolána metoda pro nastavení GUI do stavu „disabled“, předáním parametru false metodě „GuiState“.

private void IoFireBugClose()
{
    if (Comm != null)
    {
        Comm.Close();
        Comm = null;
    }

    if (Device != null)
    {
        Device.Stop();
        Device.Data.PropertyChanged -= IoFireBugData_Changed;
        Device = null;
    }

    GuiState(false);
}

Poslední přichází na řadu vygenerování metod pro obsluhu tlačítek a při zavření aplikace.

private void btnOpen_Click(object sender, RoutedEventArgs e)
{
    IoFireBugInitialize);
}

private void btnClose_Click(object sender, RoutedEventArgs e)
{
    IoFireBugClose();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    IoFireBugClose();
}

Kompletní kód aplikace je přiložen v příloze na konci dokumentu. Nyní stačí aplikaci přeložit, spustit, inicializovat tlačítkem, a sledovat stav GUI. Hodnoty vstupů a enkodérů jsou pro čtení, výstupy lze nastavit „checkboxem“.

06

Aplikace je tímto hotová a funkční. Základní funkcionalitu desky Engine máme tímto popsanou a je pouze na Vás, jak její schopnosti využijete.

 

Celý VS projekt je dostupný v sekci ke stažení.